diff options
1913 files changed, 48925 insertions, 66163 deletions
diff --git a/Android.bp b/Android.bp index e96c731f4567..cc754f21902a 100644 --- a/Android.bp +++ b/Android.bp @@ -62,12 +62,8 @@ license { "SPDX-license-identifier-Apache-2.0", "SPDX-license-identifier-BSD", "SPDX-license-identifier-CC-BY", - "SPDX-license-identifier-CPL-1.0", - "SPDX-license-identifier-GPL", - "SPDX-license-identifier-GPL-2.0", "SPDX-license-identifier-MIT", "SPDX-license-identifier-Unicode-DFS", - "SPDX-license-identifier-W3C", "legacy_unencumbered", ], license_text: [ @@ -81,6 +77,7 @@ filegroup { // Java/AIDL sources under frameworks/base ":framework-annotations", ":framework-blobstore-sources", + ":framework-bluetooth-sources", // TODO(b/214988855) : Remove once framework-bluetooth jar is ready ":framework-connectivity-tiramisu-sources", ":framework-core-sources", ":framework-drm-sources", @@ -93,7 +90,7 @@ filegroup { ":framework-mca-effect-sources", ":framework-mca-filterfw-sources", ":framework-mca-filterpacks-sources", - ":framework-media-sources", + ":framework-media-non-updatable-sources", ":framework-mms-sources", ":framework-omapi-sources", ":framework-opengl-sources", diff --git a/ApiDocs.bp b/ApiDocs.bp index 7d4a5e52df35..4aecc8fe4504 100644 --- a/ApiDocs.bp +++ b/ApiDocs.bp @@ -60,9 +60,9 @@ stubs_defaults { defaults: ["android-non-updatable-stubs-defaults"], srcs: [ // No longer part of the stubs, but are included in the docs. - "test-base/src/**/*.java", - "test-mock/src/**/*.java", - "test-runner/src/**/*.java", + ":android-test-base-sources", + ":android-test-mock-sources", + ":android-test-runner-sources", ], libs: framework_docs_only_libs, create_doc_stubs: true, @@ -74,11 +74,6 @@ stubs_defaults { srcs: [ ":android-non-updatable-stub-sources", - // Module sources - ":art.module.public.api{.public.stubs.source}", - ":conscrypt.module.public.api{.public.stubs.source}", - ":i18n.module.public.api{.public.stubs.source}", - // No longer part of the stubs, but are included in the docs. ":android-test-base-sources", ":android-test-mock-sources", @@ -116,6 +111,10 @@ stubs_defaults { name: "framework-doc-stubs-sources-default", defaults: ["framework-doc-stubs-default"], srcs: [ + ":art.module.public.api{.public.stubs.source}", + ":conscrypt.module.public.api{.public.stubs.source}", + ":i18n.module.public.api{.public.stubs.source}", + ":framework-appsearch-sources", ":framework-connectivity-sources", ":framework-connectivity-tiramisu-updatable-sources", @@ -160,26 +159,8 @@ droidstubs { droidstubs { name: "framework-doc-stubs", defaults: ["framework-doc-stubs-default"], + srcs: [":all-modules-public-stubs-source"], args: metalava_framework_docs_args, - srcs: [ - ":android.net.ipsec.ike{.public.stubs.source}", - ":framework-appsearch{.public.stubs.source}", - ":framework-connectivity{.public.stubs.source}", - ":framework-connectivity-tiramisu{.public.stubs.source}", - ":framework-graphics{.public.stubs.source}", - ":framework-media{.public.stubs.source}", - ":framework-mediaprovider{.public.stubs.source}", - ":framework-nearby{.public.stubs.source}", - ":framework-permission{.public.stubs.source}", - ":framework-permission-s{.public.stubs.source}", - ":framework-scheduling{.public.stubs.source}", - ":framework-sdkextensions{.public.stubs.source}", - ":framework-statsd{.public.stubs.source}", - ":framework-supplementalprocess{.public.stubs.source}", - ":framework-tethering{.public.stubs.source}", - ":framework-uwb{.public.stubs.source}", - ":framework-wifi{.public.stubs.source}", - ], aidl: { local_include_dirs: [ "apex/media/aidl/stable", @@ -1,4 +1,3 @@ third_party { - # would be NOTICE save for libs/usb/tests/accessorytest/f_accessory.h - license_type: RESTRICTED + license_type: RECIPROCAL } 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/apct-tests/perftests/packagemanager/Android.bp b/apct-tests/perftests/packagemanager/Android.bp index fc70219d2aeb..81cec9111b88 100644 --- a/apct-tests/perftests/packagemanager/Android.bp +++ b/apct-tests/perftests/packagemanager/Android.bp @@ -10,7 +10,10 @@ package { android_test { name: "PackageManagerPerfTests", - srcs: ["src/**/*.java"], + srcs: [ + "src/**/*.java", + "src/**/*.kt", + ], static_libs: [ "platform-compat-test-rules", @@ -21,6 +24,7 @@ android_test { "apct-perftests-utils", "collector-device-lib-platform", "cts-install-lib-java", + "services.core", ], libs: ["android.test.base"], diff --git a/apct-tests/perftests/core/src/android/os/PackageParsingPerfTest.kt b/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt index f9ddf9a9a59a..e873514f11a0 100644 --- a/apct-tests/perftests/core/src/android/os/PackageParsingPerfTest.kt +++ b/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt @@ -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. @@ -19,9 +19,6 @@ package android.os import android.content.pm.PackageParser import android.content.pm.PackageParserCacheHelper.ReadHelper import android.content.pm.PackageParserCacheHelper.WriteHelper -import android.content.pm.parsing.ParsingPackageImpl -import android.content.pm.parsing.ParsingPackageRead -import android.content.pm.parsing.ParsingPackageUtils import android.content.pm.parsing.result.ParseInput import android.content.pm.parsing.result.ParseTypeImpl import android.content.res.TypedArray @@ -29,6 +26,8 @@ import android.perftests.utils.BenchmarkState import android.perftests.utils.PerfStatusReporter import androidx.test.filters.LargeTest import com.android.internal.util.ConcurrentUtils +import com.android.server.pm.pkg.parsing.ParsingPackageImpl +import com.android.server.pm.pkg.parsing.ParsingPackageUtils import libcore.io.IoUtils import org.junit.Rule import org.junit.Test @@ -42,7 +41,7 @@ import java.util.concurrent.TimeUnit @LargeTest @RunWith(Parameterized::class) -class PackageParsingPerfTest { +public class PackageParsingPerfTest { companion object { private const val PARALLEL_QUEUE_CAPACITY = 10 @@ -196,8 +195,12 @@ class PackageParsingPerfTest { // For testing, just disable enforcement to avoid hooking up to compat framework ParseTypeImpl(ParseInput.Callback { _, _, _ -> false }) } - val parser = ParsingPackageUtils(false, null, null, emptyList(), - object : ParsingPackageUtils.Callback { + val parser = ParsingPackageUtils(false, + null, + null, + emptyList(), + object : + ParsingPackageUtils.Callback { override fun hasFeature(feature: String) = true override fun startParsingPackage( @@ -206,7 +209,12 @@ class PackageParsingPerfTest { path: String, manifestArray: TypedArray, isCoreApp: Boolean - ) = ParsingPackageImpl(packageName, baseApkPath, path, manifestArray) + ) = ParsingPackageImpl( + packageName, + baseApkPath, + path, + manifestArray + ) }) override fun parseImpl(file: File) = @@ -268,6 +276,7 @@ class PackageParsingPerfTest { * Re-implementation of the server side PackageCacher, as it's inaccessible here. */ class PackageCacher2(cacheDir: File) : PackageCacher<ParsingPackageImpl>(cacheDir) { - override fun fromParcel(parcel: Parcel) = ParsingPackageImpl(parcel) + override fun fromParcel(parcel: Parcel) = + ParsingPackageImpl(parcel) } } 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/framework/java/android/os/PowerExemptionManager.java b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java index 161a317b2a3c..9fb12277fa5e 100644 --- a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java +++ b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java @@ -258,6 +258,12 @@ public class PowerExemptionManager { * @hide */ public static final int REASON_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED = 207; + /** + * Broadcast {@link android.content.Intent#ACTION_REFRESH_SAFETY_SOURCES}. + * @hide + */ + public static final int REASON_ACTION_REFRESH_SAFETY_SOURCES = 208; + /* Reason code range 300-399 are reserved for other internal reasons */ /** * Device idle system allow list, including EXCEPT-IDLE @@ -398,6 +404,7 @@ public class PowerExemptionManager { REASON_TIME_CHANGED, REASON_LOCALE_CHANGED, REASON_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED, + REASON_ACTION_REFRESH_SAFETY_SOURCES, REASON_SYSTEM_ALLOW_LISTED, REASON_ALARM_MANAGER_ALARM_CLOCK, REASON_ALARM_MANAGER_WHILE_IDLE, @@ -681,6 +688,8 @@ public class PowerExemptionManager { return "LOCALE_CHANGED"; case REASON_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED: return "REASON_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED"; + case REASON_ACTION_REFRESH_SAFETY_SOURCES: + return "REASON_ACTION_REFRESH_SAFETY_SOURCES"; case REASON_SYSTEM_ALLOW_LISTED: return "SYSTEM_ALLOW_LISTED"; case REASON_ALARM_MANAGER_ALARM_CLOCK: 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..a22c2f6af35a 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -133,6 +133,7 @@ combined_apis { system_server_classpath: [ "service-media-s", "service-permission", + "service-supplementalprocess", ], } diff --git a/api/api.go b/api/api.go index 3b0e300c88f3..4b6ebc1947e9 100644 --- a/api/api.go +++ b/api/api.go @@ -81,6 +81,12 @@ type libraryProps struct { Visibility []string } +type fgProps struct { + Name *string + Srcs []string + Visibility []string +} + // Struct to pass parameters for the various merged [current|removed].txt file modules we create. type MergedTxtDefinition struct { // "current.txt" or "removed.txt" @@ -111,7 +117,7 @@ func createMergedTxt(ctx android.LoadHookContext, txt MergedTxtDefinition) { props.Tools = []string{"metalava"} props.Out = []string{filename} props.Cmd = proptools.StringPtr(metalavaCmd + "$(in) --api $(out)") - props.Srcs = createSrcs(txt.BaseTxt, txt.Modules, txt.ModuleTag) + props.Srcs = append([]string{txt.BaseTxt}, createSrcs(txt.Modules, txt.ModuleTag)...) props.Dists = []android.Dist{ { Targets: []string{"droidcore"}, @@ -134,7 +140,7 @@ func createMergedStubsSrcjar(ctx android.LoadHookContext, modules []string) { props.Tools = []string{"merge_zips"} props.Out = []string{"current.srcjar"} props.Cmd = proptools.StringPtr("$(location merge_zips) $(out) $(in)") - props.Srcs = createSrcs(":api-stubs-docs-non-updatable", modules, "{.public.stubs.source}") + props.Srcs = append([]string{":api-stubs-docs-non-updatable"}, createSrcs(modules, "{.public.stubs.source}")...) props.Visibility = []string{"//visibility:private"} // Used by make module in //development, mind ctx.CreateModule(genrule.GenRuleFactory, &props) } @@ -150,7 +156,7 @@ func createMergedAnnotations(ctx android.LoadHookContext, modules []string) { props.Out = []string{"annotations.zip"} props.Cmd = proptools.StringPtr("$(location merge_annotation_zips) $(genDir)/out $(in) && " + "$(location soong_zip) -o $(out) -C $(genDir)/out -D $(genDir)/out") - props.Srcs = createSrcs(":android-non-updatable-doc-stubs{.annotations.zip}", modules, "{.public.annotations.zip}") + props.Srcs = append([]string{":android-non-updatable-doc-stubs{.annotations.zip}"}, createSrcs(modules, "{.public.annotations.zip}")...) ctx.CreateModule(genrule.GenRuleFactory, &props) } @@ -172,7 +178,7 @@ func createFilteredApiVersions(ctx android.LoadHookContext, modules []string) { // Note: order matters: first parameter is the full api-versions.xml // after that the stubs files in any order // stubs files are all modules that export API surfaces EXCEPT ART - props.Srcs = createSrcs(":framework-doc-stubs{.api_versions.xml}", modules, ".stubs{.jar}") + props.Srcs = append([]string{":framework-doc-stubs{.api_versions.xml}"}, createSrcs(modules, ".stubs{.jar}")...) props.Dists = []android.Dist{{Targets: []string{"sdk"}}} ctx.CreateModule(genrule.GenRuleFactory, &props) } @@ -182,44 +188,18 @@ func createMergedModuleLibStubs(ctx android.LoadHookContext, modules []string) { modules = removeAll(modules, []string{art, conscrypt, i18n}) props := libraryProps{} props.Name = proptools.StringPtr("framework-updatable-stubs-module_libs_api") - props.Static_libs = appendStr(modules, ".stubs.module_lib") + props.Static_libs = transformArray(modules, "", ".stubs.module_lib") props.Sdk_version = proptools.StringPtr("module_current") props.Visibility = []string{"//frameworks/base"} ctx.CreateModule(java.LibraryFactory, &props) } -func appendStr(modules []string, s string) []string { - a := make([]string, 0, len(modules)) - for _, module := range modules { - a = append(a, module+s) - } - return a -} - -func createSrcs(base string, modules []string, tag string) []string { - a := make([]string, 0, len(modules)+1) - a = append(a, base) - for _, module := range modules { - a = append(a, ":"+module+tag) - } - return a -} - -func removeAll(s []string, vs []string) []string { - for _, v := range vs { - s = remove(s, v) - } - return s -} - -func remove(s []string, v string) []string { - s2 := make([]string, 0, len(s)) - for _, sv := range s { - if sv != v { - s2 = append(s2, sv) - } - } - return s2 +func createPublicStubsSourceFilegroup(ctx android.LoadHookContext, modules []string) { + props := fgProps{} + props.Name = proptools.StringPtr("all-modules-public-stubs-source") + props.Srcs = createSrcs(modules, "{.public.stubs.source}") + props.Visibility = []string{"//frameworks/base"} + ctx.CreateModule(android.FileGroupFactory, &props) } func createMergedTxts(ctx android.LoadHookContext, bootclasspath, system_server_classpath []string) { @@ -278,6 +258,8 @@ func (a *CombinedApis) createInternalModules(ctx android.LoadHookContext) { createMergedAnnotations(ctx, bootclasspath) createFilteredApiVersions(ctx, bootclasspath) + + createPublicStubsSourceFilegroup(ctx, bootclasspath) } func combinedApisModuleFactory() android.Module { @@ -287,3 +269,36 @@ func combinedApisModuleFactory() android.Module { android.AddLoadHook(module, func(ctx android.LoadHookContext) { module.createInternalModules(ctx) }) return module } + +// Various utility methods below. + +// Creates an array of ":<m><tag>" for each m in <modules>. +func createSrcs(modules []string, tag string) []string { + return transformArray(modules, ":", tag) +} + +// Creates an array of "<prefix><m><suffix>", for each m in <modules>. +func transformArray(modules []string, prefix, suffix string) []string { + a := make([]string, 0, len(modules)) + for _, module := range modules { + a = append(a, prefix+module+suffix) + } + return a +} + +func removeAll(s []string, vs []string) []string { + for _, v := range vs { + s = remove(s, v) + } + return s +} + +func remove(s []string, v string) []string { + s2 := make([]string, 0, len(s)) + for _, sv := range s { + if sv != v { + s2 = append(s2, sv) + } + } + return s2 +} diff --git a/cmds/sm/src/com/android/commands/sm/Sm.java b/cmds/sm/src/com/android/commands/sm/Sm.java index 260c8a47ea3c..b384e702ac4e 100644 --- a/cmds/sm/src/com/android/commands/sm/Sm.java +++ b/cmds/sm/src/com/android/commands/sm/Sm.java @@ -259,7 +259,8 @@ public final class Sm { public void runDisableAppDataIsolation() throws RemoteException { if (!SystemProperties.getBoolean( ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY, false)) { - throw new IllegalStateException("Storage app data isolation is not enabled."); + System.err.println("Storage app data isolation is not enabled."); + return; } final String pkgName = nextArg(); final int pid = Integer.parseInt(nextArg()); 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 e9749228b85c..78d4b4de5cc7 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 @@ -949,6 +952,7 @@ package android { field public static final int left = 16843181; // 0x10101ad 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 lineHeight = 16844159; // 0x101057f field public static final int lineSpacingExtra = 16843287; // 0x1010217 field public static final int lineSpacingMultiplier = 16843288; // 0x1010218 @@ -972,6 +976,7 @@ package android { field public static final int listSeparatorTextViewStyle = 16843272; // 0x1010208 field public static final int listViewStyle = 16842868; // 0x1010074 field public static final int listViewWhiteStyle = 16842869; // 0x1010075 + field public static final int localeConfig; field public static final int lockTaskMode = 16844013; // 0x10104ed field public static final int logo = 16843454; // 0x10102be field public static final int logoDescription = 16844009; // 0x10104e9 @@ -1130,6 +1135,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 @@ -1313,6 +1319,7 @@ package android { field public static final int shouldDisableView = 16843246; // 0x10101ee field public static final int shouldUseDefaultUnfoldTransition = 16844364; // 0x101064c field public static final int showAsAction = 16843481; // 0x10102d9 + field public static final int showBackground; field public static final int showClockAndComplications; field public static final int showDefault = 16843258; // 0x10101fa field public static final int showDividers = 16843561; // 0x1010329 @@ -3992,7 +3999,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(); @@ -4035,6 +4042,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); @@ -4913,7 +4921,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); @@ -4933,6 +4941,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(); @@ -5697,8 +5706,20 @@ package android.app { method @Deprecated public android.view.Window startActivity(String, android.content.Intent); } + public class LocaleConfig { + ctor public LocaleConfig(@NonNull android.content.Context); + method public int getStatus(); + method @Nullable public android.os.LocaleList getSupportedLocales(); + field public static final int STATUS_NOT_SPECIFIED = 1; // 0x1 + field public static final int STATUS_PARSING_FAILED = 2; // 0x2 + field public static final int STATUS_SUCCESS = 0; // 0x0 + field public static final String TAG_LOCALE = "locale"; + field public static final String TAG_LOCALE_CONFIG = "locale-config"; + } + public class LocaleManager { method @NonNull public android.os.LocaleList getApplicationLocales(); + method @NonNull @RequiresPermission(value="android.permission.READ_APP_SPECIFIC_LOCALES", conditional=true) public android.os.LocaleList getApplicationLocales(@NonNull String); method public void setApplicationLocales(@NonNull android.os.LocaleList); } @@ -7274,6 +7295,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 @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); @@ -7290,6 +7315,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); @@ -7330,6 +7356,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[]); @@ -7434,6 +7461,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); @@ -7482,6 +7510,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); @@ -7497,6 +7526,7 @@ package android.app.admin { field public static final String ACTION_CHECK_POLICY_COMPLIANCE = "android.app.action.CHECK_POLICY_COMPLIANCE"; field public static final String ACTION_DEVICE_ADMIN_SERVICE = "android.app.action.DEVICE_ADMIN_SERVICE"; field public static final String ACTION_DEVICE_OWNER_CHANGED = "android.app.action.DEVICE_OWNER_CHANGED"; + field public static final String ACTION_DEVICE_POLICY_RESOURCE_UPDATED = "android.app.action.DEVICE_POLICY_RESOURCE_UPDATED"; field public static final String ACTION_GET_PROVISIONING_MODE = "android.app.action.GET_PROVISIONING_MODE"; field public static final String ACTION_MANAGED_PROFILE_PROVISIONED = "android.app.action.MANAGED_PROFILE_PROVISIONED"; field public static final String ACTION_PROFILE_OWNER_CHANGED = "android.app.action.PROFILE_OWNER_CHANGED"; @@ -7532,6 +7562,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"; @@ -7545,6 +7576,7 @@ package android.app.admin { field @Deprecated public static final String EXTRA_PROVISIONING_EMAIL_ADDRESS = "android.app.extra.PROVISIONING_EMAIL_ADDRESS"; field public static final String EXTRA_PROVISIONING_IMEI = "android.app.extra.PROVISIONING_IMEI"; field public static final String EXTRA_PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION = "android.app.extra.PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION"; + field public static final String EXTRA_PROVISIONING_KEEP_SCREEN_ON = "android.app.extra.PROVISIONING_KEEP_SCREEN_ON"; field public static final String EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED = "android.app.extra.PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED"; field public static final String EXTRA_PROVISIONING_LOCALE = "android.app.extra.PROVISIONING_LOCALE"; field public static final String EXTRA_PROVISIONING_LOCAL_TIME = "android.app.extra.PROVISIONING_LOCAL_TIME"; @@ -7572,6 +7604,9 @@ package android.app.admin { field public static final String EXTRA_PROVISIONING_WIFI_SECURITY_TYPE = "android.app.extra.PROVISIONING_WIFI_SECURITY_TYPE"; field public static final String EXTRA_PROVISIONING_WIFI_SSID = "android.app.extra.PROVISIONING_WIFI_SSID"; 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 @@ -7646,6 +7681,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 @@ -7666,6 +7705,35 @@ package android.app.admin { method public void onApplicationUserDataCleared(String, boolean); } + public final class DevicePolicyResources { + ctor public DevicePolicyResources(); + } + + public static final class DevicePolicyResources.Drawable { + field public static final int INVALID_ID = -1; // 0xffffffff + field public static final int WORK_PROFILE_ICON = 1; // 0x1 + field public static final int WORK_PROFILE_ICON_BADGE = 0; // 0x0 + field public static final int WORK_PROFILE_OFF_ICON = 2; // 0x2 + field public static final int WORK_PROFILE_USER_ICON = 3; // 0x3 + } + + public static final class DevicePolicyResources.Drawable.Source { + field public static final int HOME_WIDGET = 2; // 0x2 + field public static final int LAUNCHER_OFF_BUTTON = 3; // 0x3 + field public static final int NOTIFICATION = 0; // 0x0 + field public static final int PROFILE_SWITCH_ANIMATION = 1; // 0x1 + field public static final int QUICK_SETTINGS = 4; // 0x4 + field public static final int STATUS_BAR = 5; // 0x5 + field public static final int UNDEFINED = -1; // 0xffffffff + } + + public static final class DevicePolicyResources.Drawable.Style { + field public static final int DEFAULT = -1; // 0xffffffff + field public static final int OUTLINE = 2; // 0x2 + field public static final int SOLID_COLORED = 0; // 0x0 + field public static final int SOLID_NOT_COLORED = 1; // 0x1 + } + public final class DnsEvent extends android.app.admin.NetworkEvent implements android.os.Parcelable { method public String getHostname(); method public java.util.List<java.net.InetAddress> getInetAddresses(); @@ -7804,6 +7872,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 { @@ -8796,11 +8876,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(); @@ -9209,6 +9290,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 } @@ -9663,6 +9745,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); @@ -9701,6 +9784,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 @@ -10774,7 +10858,7 @@ package android.content { method public boolean bindIsolatedService(@NonNull @RequiresPermission android.content.Intent, int, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.content.ServiceConnection); method public abstract boolean bindService(@RequiresPermission android.content.Intent, @NonNull android.content.ServiceConnection, int); method public boolean bindService(@NonNull @RequiresPermission android.content.Intent, int, @NonNull java.util.concurrent.Executor, @NonNull android.content.ServiceConnection); - method @RequiresPermission(anyOf={"android.permission.INTERACT_ACROSS_USERS", android.Manifest.permission.INTERACT_ACROSS_PROFILES}) public boolean bindServiceAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.content.ServiceConnection, int, @NonNull android.os.UserHandle); + method @RequiresPermission(anyOf={"android.permission.INTERACT_ACROSS_USERS", "android.permission.INTERACT_ACROSS_USERS_FULL", android.Manifest.permission.INTERACT_ACROSS_PROFILES}, conditional=true) public boolean bindServiceAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.content.ServiceConnection, int, @NonNull android.os.UserHandle); method @CheckResult(suggest="#enforceCallingOrSelfPermission(String,String)") public abstract int checkCallingOrSelfPermission(@NonNull String); method @CheckResult(suggest="#enforceCallingOrSelfUriPermission(Uri,int,String)") public abstract int checkCallingOrSelfUriPermission(android.net.Uri, int); method @NonNull public int[] checkCallingOrSelfUriPermissions(@NonNull java.util.List<android.net.Uri>, int); @@ -10876,10 +10960,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); @@ -11007,8 +11091,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"; @@ -13127,6 +13211,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 @@ -13419,6 +13504,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(); @@ -13433,6 +13519,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(); @@ -13457,6 +13544,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>); @@ -15230,6 +15318,11 @@ package android.graphics { public class BitmapShader extends android.graphics.Shader { ctor public BitmapShader(@NonNull android.graphics.Bitmap, @NonNull android.graphics.Shader.TileMode, @NonNull android.graphics.Shader.TileMode); + method public int getFilterMode(); + method public void setFilterMode(int); + field public static final int FILTER_MODE_DEFAULT = 0; // 0x0 + field public static final int FILTER_MODE_LINEAR = 2; // 0x2 + field public static final int FILTER_MODE_NEAREST = 1; // 0x1 } public enum BlendMode { @@ -16636,6 +16729,7 @@ package android.graphics { method public void setFloatUniform(@NonNull String, float, float, float); method public void setFloatUniform(@NonNull String, float, float, float, float); method public void setFloatUniform(@NonNull String, @NonNull float[]); + method public void setInputBuffer(@NonNull String, @NonNull android.graphics.BitmapShader); method public void setInputShader(@NonNull String, @NonNull android.graphics.Shader); method public void setIntUniform(@NonNull String, int); method public void setIntUniform(@NonNull String, int, int); @@ -17521,6 +17615,17 @@ package android.graphics.pdf { 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 void setLineBreakStyle(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 + } + public class LineBreaker { method @NonNull public android.graphics.text.LineBreaker.Result computeLineBreaks(@NonNull android.graphics.text.MeasuredText, @NonNull android.graphics.text.LineBreaker.ParagraphConstraints, @IntRange(from=0) int); field public static final int BREAK_STRATEGY_BALANCED = 2; // 0x2 @@ -17576,6 +17681,7 @@ package android.graphics.text { ctor public MeasuredText.Builder(@NonNull android.graphics.text.MeasuredText); method @NonNull public android.graphics.text.MeasuredText.Builder appendReplacementRun(@NonNull android.graphics.Paint, @IntRange(from=0) int, @FloatRange(from=0) @Px float); method @NonNull public android.graphics.text.MeasuredText.Builder appendStyleRun(@NonNull android.graphics.Paint, @IntRange(from=0) int, boolean); + method @NonNull public android.graphics.text.MeasuredText.Builder appendStyleRun(@NonNull android.graphics.Paint, @Nullable android.graphics.text.LineBreakConfig, @IntRange(from=0) int, boolean); method @NonNull public android.graphics.text.MeasuredText build(); method @Deprecated @NonNull public android.graphics.text.MeasuredText.Builder setComputeHyphenation(boolean); method @NonNull public android.graphics.text.MeasuredText.Builder setComputeHyphenation(int); @@ -17973,6 +18079,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 @@ -18016,6 +18123,7 @@ package android.hardware { field public static final String STRING_TYPE_GRAVITY = "android.sensor.gravity"; field public static final String STRING_TYPE_GYROSCOPE = "android.sensor.gyroscope"; field public static final String STRING_TYPE_GYROSCOPE_UNCALIBRATED = "android.sensor.gyroscope_uncalibrated"; + field public static final String STRING_TYPE_HEAD_TRACKER = "android.sensor.head_tracker"; field public static final String STRING_TYPE_HEART_BEAT = "android.sensor.heart_beat"; field public static final String STRING_TYPE_HEART_RATE = "android.sensor.heart_rate"; field public static final String STRING_TYPE_HINGE_ANGLE = "android.sensor.hinge_angle"; @@ -18046,6 +18154,7 @@ package android.hardware { field public static final int TYPE_GRAVITY = 9; // 0x9 field public static final int TYPE_GYROSCOPE = 4; // 0x4 field public static final int TYPE_GYROSCOPE_UNCALIBRATED = 16; // 0x10 + field public static final int TYPE_HEAD_TRACKER = 37; // 0x25 field public static final int TYPE_HEART_BEAT = 31; // 0x1f field public static final int TYPE_HEART_RATE = 21; // 0x15 field public static final int TYPE_HINGE_ANGLE = 36; // 0x24 @@ -18108,6 +18217,7 @@ package android.hardware { method public void onFlushCompleted(android.hardware.Sensor); method public void onSensorAdditionalInfo(android.hardware.SensorAdditionalInfo); method public void onSensorChanged(android.hardware.SensorEvent); + method public void onSensorDiscontinuity(@NonNull android.hardware.Sensor); } public interface SensorEventListener { @@ -18463,12 +18573,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; @@ -18476,6 +18588,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; @@ -18792,6 +18905,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 @@ -19145,6 +19259,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(); @@ -19191,8 +19324,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(); @@ -19246,12 +19381,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; @@ -19277,6 +19414,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 @@ -20996,6 +21134,7 @@ package android.media { method @NonNull public java.util.List<android.media.AudioPlaybackConfiguration> getActivePlaybackConfigurations(); method @NonNull public java.util.List<android.media.AudioRecordingConfiguration> getActiveRecordingConfigurations(); method public int getAllowedCapturePolicy(); + method @NonNull public java.util.List<android.media.AudioDeviceInfo> getAudioDevicesForAttributes(@NonNull android.media.AudioAttributes); method public int getAudioHwSyncForSession(int); method @NonNull public java.util.List<android.media.AudioDeviceInfo> getAvailableCommunicationDevices(); method @Nullable public android.media.AudioDeviceInfo getCommunicationDevice(); @@ -21967,14 +22106,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); } @@ -22386,6 +22541,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"; @@ -23160,6 +23316,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"; @@ -23177,6 +23334,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"; @@ -23237,12 +23396,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 { @@ -25605,6 +25770,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(); @@ -25621,6 +25787,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 @@ -25661,11 +25835,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 { @@ -26726,16 +26904,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 { @@ -31935,6 +32140,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(); @@ -32112,7 +32318,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); @@ -32122,7 +32328,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); @@ -32392,6 +32598,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(); @@ -36157,7 +36364,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"; @@ -36201,7 +36408,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"; @@ -39980,8 +40187,10 @@ package android.speech { ctor public RecognitionService(); method public final android.os.IBinder onBind(android.content.Intent); method protected abstract void onCancel(android.speech.RecognitionService.Callback); + method public void onCheckRecognitionSupport(@NonNull android.content.Intent, @NonNull android.speech.RecognitionService.SupportCallback); method protected abstract void onStartListening(android.content.Intent, android.speech.RecognitionService.Callback); method protected abstract void onStopListening(android.speech.RecognitionService.Callback); + method public void triggerModelDownload(@NonNull android.content.Intent); field public static final String SERVICE_INTERFACE = "android.speech.RecognitionService"; field public static final String SERVICE_META_DATA = "android.speech"; } @@ -39999,6 +40208,36 @@ package android.speech { method public void rmsChanged(float) throws android.os.RemoteException; } + public static class RecognitionService.SupportCallback { + method public void onError(int); + method public void onSupportResult(@NonNull android.speech.RecognitionSupport); + } + + public final class RecognitionSupport implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public java.util.List<java.lang.String> getInstalledLanguages(); + method @NonNull public java.util.List<java.lang.String> getPendingLanguages(); + method @NonNull public java.util.List<java.lang.String> getSupportedLanguages(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.speech.RecognitionSupport> CREATOR; + } + + public static final class RecognitionSupport.Builder { + ctor public RecognitionSupport.Builder(); + method @NonNull public android.speech.RecognitionSupport.Builder addInstalledLanguages(@NonNull String); + method @NonNull public android.speech.RecognitionSupport.Builder addPendingLanguages(@NonNull String); + method @NonNull public android.speech.RecognitionSupport.Builder addSupportedLanguages(@NonNull String); + method @NonNull public android.speech.RecognitionSupport build(); + method @NonNull public android.speech.RecognitionSupport.Builder setInstalledLanguages(@NonNull java.util.List<java.lang.String>); + method @NonNull public android.speech.RecognitionSupport.Builder setPendingLanguages(@NonNull java.util.List<java.lang.String>); + method @NonNull public android.speech.RecognitionSupport.Builder setSupportedLanguages(@NonNull java.util.List<java.lang.String>); + } + + public interface RecognitionSupportCallback { + method public void onError(int); + method public void onSupportResult(@NonNull android.speech.RecognitionSupport); + } + public class RecognizerIntent { method public static final android.content.Intent getVoiceDetailsIntent(android.content.Context); field public static final String ACTION_GET_LANGUAGE_DETAILS = "android.speech.action.GET_LANGUAGE_DETAILS"; @@ -40048,6 +40287,7 @@ package android.speech { public class SpeechRecognizer { method @MainThread public void cancel(); + method public void checkRecognitionSupport(@NonNull android.content.Intent, @NonNull android.speech.RecognitionSupportCallback); method @MainThread @NonNull public static android.speech.SpeechRecognizer createOnDeviceSpeechRecognizer(@NonNull android.content.Context); method @MainThread public static android.speech.SpeechRecognizer createSpeechRecognizer(android.content.Context); method @MainThread public static android.speech.SpeechRecognizer createSpeechRecognizer(android.content.Context, android.content.ComponentName); @@ -40057,8 +40297,10 @@ package android.speech { method @MainThread public void setRecognitionListener(android.speech.RecognitionListener); method @MainThread public void startListening(android.content.Intent); method @MainThread public void stopListening(); + method public void triggerModelDownload(@NonNull android.content.Intent); field public static final String CONFIDENCE_SCORES = "confidence_scores"; field public static final int ERROR_AUDIO = 3; // 0x3 + field public static final int ERROR_CANNOT_CHECK_SUPPORT = 14; // 0xe field public static final int ERROR_CLIENT = 5; // 0x5 field public static final int ERROR_INSUFFICIENT_PERMISSIONS = 9; // 0x9 field public static final int ERROR_LANGUAGE_NOT_SUPPORTED = 12; // 0xc @@ -41596,11 +41838,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"; @@ -41846,6 +42088,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"; @@ -41860,11 +42109,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"; @@ -42106,7 +42357,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"; @@ -42128,6 +42378,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 { @@ -43357,6 +43608,7 @@ package android.telephony { field public static final int RESULT_RIL_BLOCKED_DUE_TO_CALL = 123; // 0x7b field public static final int RESULT_RIL_CANCELLED = 119; // 0x77 field public static final int RESULT_RIL_ENCODING_ERR = 109; // 0x6d + field public static final int RESULT_RIL_GENERIC_ERROR = 124; // 0x7c field public static final int RESULT_RIL_INTERNAL_ERR = 113; // 0x71 field public static final int RESULT_RIL_INVALID_ARGUMENTS = 104; // 0x68 field public static final int RESULT_RIL_INVALID_MODEM_STATE = 115; // 0x73 @@ -44165,6 +44417,7 @@ package android.telephony.data { field public static final int TYPE_DEFAULT = 17; // 0x11 field public static final int TYPE_DUN = 8; // 0x8 field public static final int TYPE_EMERGENCY = 512; // 0x200 + field public static final int TYPE_ENTERPRISE = 16384; // 0x4000 field public static final int TYPE_FOTA = 32; // 0x20 field public static final int TYPE_HIPRI = 16; // 0x10 field public static final int TYPE_IA = 256; // 0x100 @@ -44361,7 +44614,7 @@ package android.telephony.euicc { method public boolean isEnabled(); method public boolean isSimPortAvailable(int); method public void startResolutionActivity(android.app.Activity, int, android.content.Intent, android.app.PendingIntent) throws android.content.IntentSender.SendIntentException; - method @Deprecated @RequiresPermission("android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS") public void switchToSubscription(int, android.app.PendingIntent); + method @RequiresPermission("android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS") public void switchToSubscription(int, android.app.PendingIntent); method @RequiresPermission("android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS") public void switchToSubscription(int, int, @NonNull android.app.PendingIntent); method @RequiresPermission("android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS") public void updateSubscriptionNickname(int, @Nullable String, @NonNull android.app.PendingIntent); field public static final String ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS = "android.telephony.euicc.action.MANAGE_EMBEDDED_SUBSCRIPTIONS"; @@ -44381,6 +44634,7 @@ package android.telephony.euicc { field public static final int ERROR_INSTALL_PROFILE = 10009; // 0x2719 field public static final int ERROR_INVALID_ACTIVATION_CODE = 10001; // 0x2711 field public static final int ERROR_INVALID_CONFIRMATION_CODE = 10002; // 0x2712 + field public static final int ERROR_INVALID_PORT = 10017; // 0x2721 field public static final int ERROR_INVALID_RESPONSE = 10015; // 0x271f field public static final int ERROR_NO_PROFILES_AVAILABLE = 10013; // 0x271d field public static final int ERROR_OPERATION_BUSY = 10016; // 0x2720 @@ -44510,6 +44764,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"; @@ -44760,6 +45015,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; } @@ -44800,6 +45072,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 { @@ -45375,6 +45668,7 @@ package android.text { public static final class PrecomputedText.Params { method public int getBreakStrategy(); method public int getHyphenationFrequency(); + method @Nullable public android.graphics.text.LineBreakConfig getLineBreakConfig(); method @NonNull public android.text.TextDirectionHeuristic getTextDirection(); method @NonNull public android.text.TextPaint getTextPaint(); } @@ -45385,6 +45679,7 @@ package android.text { method @NonNull public android.text.PrecomputedText.Params build(); method public android.text.PrecomputedText.Params.Builder setBreakStrategy(int); method public android.text.PrecomputedText.Params.Builder setHyphenationFrequency(int); + method @NonNull public android.text.PrecomputedText.Params.Builder setLineBreakConfig(@NonNull android.graphics.text.LineBreakConfig); method public android.text.PrecomputedText.Params.Builder setTextDirection(@NonNull android.text.TextDirectionHeuristic); } @@ -45545,6 +45840,7 @@ package android.text { method @NonNull public android.text.StaticLayout.Builder setIncludePad(boolean); method @NonNull public android.text.StaticLayout.Builder setIndents(@Nullable int[], @Nullable int[]); method @NonNull public android.text.StaticLayout.Builder setJustificationMode(int); + method @NonNull public android.text.StaticLayout.Builder setLineBreakConfig(@NonNull android.graphics.text.LineBreakConfig); method @NonNull public android.text.StaticLayout.Builder setLineSpacing(float, @FloatRange(from=0.0) float); method @NonNull public android.text.StaticLayout.Builder setMaxLines(@IntRange(from=0) int); method public android.text.StaticLayout.Builder setText(CharSequence); @@ -48020,15 +48316,33 @@ package android.view { public final class Choreographer { method public static android.view.Choreographer getInstance(); + method public void postExtendedFrameCallback(@NonNull android.view.Choreographer.ExtendedFrameCallback); method public void postFrameCallback(android.view.Choreographer.FrameCallback); method public void postFrameCallbackDelayed(android.view.Choreographer.FrameCallback, long); + method public void removeExtendedFrameCallback(@Nullable android.view.Choreographer.ExtendedFrameCallback); method public void removeFrameCallback(android.view.Choreographer.FrameCallback); } + public static interface Choreographer.ExtendedFrameCallback { + method public void onVsync(@NonNull android.view.Choreographer.FrameData); + } + public static interface Choreographer.FrameCallback { method public void doFrame(long); } + public static class Choreographer.FrameData { + method public long getFrameTimeNanos(); + method @NonNull public android.view.Choreographer.FrameTimeline[] getFrameTimelines(); + method @NonNull public android.view.Choreographer.FrameTimeline getPreferredFrameTimeline(); + } + + public static class Choreographer.FrameTimeline { + method public long getDeadlineNanos(); + method public long getExpectedPresentTimeNanos(); + method public long getVsyncId(); + } + public interface CollapsibleActionView { method public void onActionViewCollapsed(); method public void onActionViewExpanded(); @@ -48174,6 +48488,18 @@ package android.view { method @NonNull public android.graphics.Insets getWaterfallInsets(); } + public static final class DisplayCutout.Builder { + ctor public DisplayCutout.Builder(); + method @NonNull public android.view.DisplayCutout build(); + method @NonNull public android.view.DisplayCutout.Builder setBoundingRectBottom(@NonNull android.graphics.Rect); + method @NonNull public android.view.DisplayCutout.Builder setBoundingRectLeft(@NonNull android.graphics.Rect); + method @NonNull public android.view.DisplayCutout.Builder setBoundingRectRight(@NonNull android.graphics.Rect); + method @NonNull public android.view.DisplayCutout.Builder setBoundingRectTop(@NonNull android.graphics.Rect); + method @NonNull public android.view.DisplayCutout.Builder setCutoutPath(@NonNull android.graphics.Path); + method @NonNull public android.view.DisplayCutout.Builder setSafeInsets(@NonNull android.graphics.Insets); + method @NonNull public android.view.DisplayCutout.Builder setWaterfallInsets(@NonNull android.graphics.Insets); + } + public final class DragAndDropPermissions implements android.os.Parcelable { method public int describeContents(); method public void release(); @@ -48328,7 +48654,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 @@ -49304,6 +49630,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); } @@ -49727,7 +50069,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); @@ -49931,6 +50273,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(); @@ -49948,6 +50291,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(); @@ -50065,6 +50409,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(); @@ -50307,6 +50652,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); @@ -52371,6 +52718,8 @@ package android.view.accessibility { method @NonNull public android.view.accessibility.CaptioningManager.CaptionStyle getUserStyle(); method public boolean isCallCaptioningEnabled(); method public final boolean isEnabled(); + method public final boolean isSystemAudioCaptioningRequested(); + method public final boolean isSystemAudioCaptioningUiRequested(); method public void removeCaptioningChangeListener(@NonNull android.view.accessibility.CaptioningManager.CaptioningChangeListener); } @@ -52399,6 +52748,8 @@ package android.view.accessibility { method public void onEnabledChanged(boolean); method public void onFontScaleChanged(float); method public void onLocaleChanged(@Nullable java.util.Locale); + method public void onSystemAudioCaptioningChanged(boolean); + method public void onSystemAudioCaptioningUiChanged(boolean); method public void onUserStyleChanged(@NonNull android.view.accessibility.CaptioningManager.CaptionStyle); } @@ -52441,6 +52792,7 @@ package android.view.animation { method public int getRepeatCount(); method public int getRepeatMode(); method protected float getScaleFactor(); + method public boolean getShowBackground(); method public long getStartOffset(); method public long getStartTime(); method public boolean getTransformation(long, android.view.animation.Transformation); @@ -52466,6 +52818,7 @@ package android.view.animation { method public void setInterpolator(android.view.animation.Interpolator); method public void setRepeatCount(int); method public void setRepeatMode(int); + method public void setShowBackground(boolean); method public void setStartOffset(long); method public void setStartTime(long); method public void setZAdjustment(int); @@ -52971,6 +53324,7 @@ package android.view.inputmethod { method public int getCharacterBoundsFlags(int); method public CharSequence getComposingText(); method public int getComposingTextStart(); + method @Nullable public android.view.inputmethod.EditorBoundsInfo getEditorBoundsInfo(); method public float getInsertionMarkerBaseline(); method public float getInsertionMarkerBottom(); method public int getInsertionMarkerFlags(); @@ -52992,11 +53346,27 @@ package android.view.inputmethod { method public android.view.inputmethod.CursorAnchorInfo build(); method public void reset(); method public android.view.inputmethod.CursorAnchorInfo.Builder setComposingText(int, CharSequence); + method @NonNull public android.view.inputmethod.CursorAnchorInfo.Builder setEditorBoundsInfo(@Nullable android.view.inputmethod.EditorBoundsInfo); method public android.view.inputmethod.CursorAnchorInfo.Builder setInsertionMarkerLocation(float, float, float, float, int); method public android.view.inputmethod.CursorAnchorInfo.Builder setMatrix(android.graphics.Matrix); method public android.view.inputmethod.CursorAnchorInfo.Builder setSelectionRange(int, int); } + public final class EditorBoundsInfo implements android.os.Parcelable { + method public int describeContents(); + method @Nullable public android.graphics.RectF getEditorBounds(); + method @Nullable public android.graphics.RectF getHandwritingBounds(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.EditorBoundsInfo> CREATOR; + } + + public static final class EditorBoundsInfo.Builder { + ctor public EditorBoundsInfo.Builder(); + method @NonNull public android.view.inputmethod.EditorBoundsInfo build(); + method @NonNull public android.view.inputmethod.EditorBoundsInfo.Builder setEditorBounds(@Nullable android.graphics.RectF); + method @NonNull public android.view.inputmethod.EditorBoundsInfo.Builder setHandwritingBounds(@Nullable android.graphics.RectF); + } + public class EditorInfo implements android.text.InputType android.os.Parcelable { ctor public EditorInfo(); method public int describeContents(); @@ -55123,6 +55493,7 @@ package android.widget { method protected boolean isInFilterMode(); method public boolean isItemChecked(int); method public boolean isScrollingCacheEnabled(); + method public boolean isSelectedChildViewEnabled(); method public boolean isSmoothScrollbarEnabled(); method public boolean isStackFromBottom(); method public boolean isTextFilterEnabled(); @@ -55158,6 +55529,7 @@ package android.widget { method public void setRemoteViewsAdapter(android.content.Intent); method public void setScrollIndicators(android.view.View, android.view.View); method public void setScrollingCacheEnabled(boolean); + method public void setSelectedChildViewEnabled(boolean); method public void setSelectionFromTop(int, int); method public void setSelector(@DrawableRes int); method public void setSelector(android.graphics.drawable.Drawable); @@ -57570,6 +57942,7 @@ package android.widget { method public final android.text.Layout getLayout(); method public float getLetterSpacing(); method public int getLineBounds(int, android.graphics.Rect); + method @NonNull public android.graphics.text.LineBreakConfig getLineBreakConfig(); method public int getLineCount(); method public int getLineHeight(); method public float getLineSpacingExtra(); @@ -57697,6 +58070,7 @@ package android.widget { method public void setKeyListener(android.text.method.KeyListener); method public void setLastBaselineToBottomHeight(@IntRange(from=0) @Px int); method public void setLetterSpacing(float); + method public void setLineBreakConfig(@NonNull android.graphics.text.LineBreakConfig); method public void setLineHeight(@IntRange(from=0) @Px int); method public void setLineSpacing(float, float); method public void setLines(int); diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index c4aff2d00304..c0847a4f3fce 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); } @@ -71,7 +71,25 @@ 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 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); } } @@ -129,10 +147,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 } } @@ -249,8 +269,10 @@ package android.net { 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); } @@ -326,6 +348,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(); @@ -369,6 +395,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 } @@ -400,6 +430,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 17ea0858b7c0..ab2d3d50c2a8 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"; @@ -293,8 +299,10 @@ package android { field public static final String SET_ORIENTATION = "android.permission.SET_ORIENTATION"; field public static final String SET_POINTER_SPEED = "android.permission.SET_POINTER_SPEED"; field public static final String SET_SCREEN_COMPATIBILITY = "android.permission.SET_SCREEN_COMPATIBILITY"; + 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"; @@ -363,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 @@ -390,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 @@ -442,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(); @@ -749,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 { @@ -798,7 +818,6 @@ package android.app { } public class LocaleManager { - method @NonNull @RequiresPermission(android.Manifest.permission.READ_APP_SPECIFIC_LOCALES) public android.os.LocaleList getApplicationLocales(@NonNull String); method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public void setApplicationLocales(@NonNull String, @NonNull android.os.LocaleList); } @@ -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,14 +1010,28 @@ 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); } } package android.app.admin { + public final class DevicePolicyDrawableResource implements android.os.Parcelable { + ctor public DevicePolicyDrawableResource(@NonNull android.content.Context, int, int, int, @DrawableRes int); + ctor public DevicePolicyDrawableResource(@NonNull android.content.Context, int, int, @DrawableRes int); + method public int describeContents(); + method @DrawableRes public int getCallingPackageResourceId(); + method public int getDrawableId(); + method public int getDrawableSource(); + method public int getDrawableStyle(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.DevicePolicyDrawableResource> CREATOR; + } + public class DevicePolicyKeyguardService extends android.app.Service { ctor public DevicePolicyKeyguardService(); method @Nullable public void dismiss(); @@ -1014,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(); @@ -1025,24 +1066,29 @@ package android.app.admin { method @RequiresPermission("android.permission.NOTIFY_PENDING_SYSTEM_UPDATE") public void notifyPendingSystemUpdate(long, boolean); 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 @@ -1058,6 +1104,7 @@ package android.app.admin { field public static final int CODE_USER_HAS_PROFILE_OWNER = 2; // 0x2 field public static final int CODE_USER_NOT_RUNNING = 3; // 0x3 field public static final int CODE_USER_SETUP_COMPLETED = 4; // 0x4 + field public static final String EXTRA_FORCE_UPDATE_ROLE_HOLDER = "android.app.extra.FORCE_UPDATE_ROLE_HOLDER"; field public static final String EXTRA_PROFILE_OWNER_NAME = "android.app.extra.PROFILE_OWNER_NAME"; field @Deprecated public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI"; field @Deprecated public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL"; @@ -1069,6 +1116,7 @@ package android.app.admin { field public static final String EXTRA_PROVISIONING_SUPPORT_URL = "android.app.extra.PROVISIONING_SUPPORT_URL"; field public static final String EXTRA_PROVISIONING_TRIGGER = "android.app.extra.PROVISIONING_TRIGGER"; field public static final String EXTRA_RESTRICTION = "android.app.extra.RESTRICTION"; + field public static final String EXTRA_ROLE_HOLDER_STATE = "android.app.extra.ROLE_HOLDER_STATE"; field public static final int FLAG_SUPPORTED_MODES_DEVICE_OWNER = 4; // 0x4 field public static final int FLAG_SUPPORTED_MODES_ORGANIZATION_OWNED = 1; // 0x1 field public static final int FLAG_SUPPORTED_MODES_PERSONALLY_OWNED = 2; // 0x2 @@ -1084,6 +1132,7 @@ package android.app.admin { field public static final int RESULT_DEVICE_OWNER_SET = 123; // 0x7b field public static final int RESULT_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER_RECOVERABLE_ERROR = 1; // 0x1 field public static final int RESULT_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER_UNRECOVERABLE_ERROR = 2; // 0x2 + field public static final int RESULT_UPDATE_ROLE_HOLDER = 2; // 0x2 field public static final int RESULT_WORK_PROFILE_CREATED = 122; // 0x7a field public static final int STATE_USER_PROFILE_COMPLETE = 4; // 0x4 field public static final int STATE_USER_PROFILE_FINALIZED = 5; // 0x5 @@ -1093,6 +1142,19 @@ 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 INVALID_ID = "INVALID_ID"; + } + + 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(); @@ -1164,6 +1226,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 { @@ -1406,13 +1549,6 @@ package android.app.backup { } -package android.app.communal { - - public final class CommunalManager { - } - -} - package android.app.compat { public final class CompatChanges { @@ -2020,6 +2156,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); } @@ -2196,6 +2334,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); @@ -2266,6 +2405,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(); @@ -2283,7 +2426,7 @@ package android.bluetooth { 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"; @@ -2304,6 +2447,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 @@ -2345,6 +2497,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; @@ -2561,10 +2714,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>); } } @@ -2622,6 +2797,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"; @@ -2629,7 +2805,6 @@ package android.content { field public static final String BATTERY_STATS_SERVICE = "batterystats"; field @Deprecated public static final int BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS = 1048576; // 0x100000 field public static final int BIND_ALLOW_FOREGROUND_SERVICE_STARTS_FROM_BACKGROUND = 262144; // 0x40000 - field public static final String COMMUNAL_SERVICE = "communal"; field public static final String CONTENT_SUGGESTIONS_SERVICE = "content_suggestions"; field public static final String CONTEXTHUB_SERVICE = "contexthub"; field public static final String ETHERNET_SERVICE = "ethernet"; @@ -3619,6 +3794,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 } } @@ -3834,7 +4010,7 @@ package android.hardware.hdmi { } public class HdmiDeviceInfo implements android.os.Parcelable { - ctor public HdmiDeviceInfo(); + ctor @Deprecated public HdmiDeviceInfo(); method public int describeContents(); method public int getAdopterId(); method public int getDeviceId(); @@ -3855,6 +4031,7 @@ package android.hardware.hdmi { method public boolean isSourceType(); method public void writeToParcel(android.os.Parcel, int); field public static final int ADDR_INTERNAL = 0; // 0x0 + field public static final int ADDR_INVALID = -1; // 0xffffffff field @NonNull public static final android.os.Parcelable.Creator<android.hardware.hdmi.HdmiDeviceInfo> CREATOR; field public static final int DEVICE_AUDIO_SYSTEM = 5; // 0x5 field public static final int DEVICE_INACTIVE = -1; // 0xffffffff @@ -3868,6 +4045,7 @@ package android.hardware.hdmi { field public static final int PATH_INTERNAL = 0; // 0x0 field public static final int PATH_INVALID = 65535; // 0xffff field public static final int PORT_INVALID = -1; // 0xffffffff + field public static final int VENDOR_ID_UNKNOWN = 16777215; // 0xffffff } public final class HdmiHotplugEvent implements android.os.Parcelable { @@ -4052,6 +4230,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); @@ -5011,8 +5190,20 @@ 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 @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 } public final class UsbPortStatus implements android.os.Parcelable { @@ -5022,6 +5213,7 @@ package android.hardware.usb { method public int getCurrentPowerRole(); method public int getSupportedRoleCombinations(); 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; @@ -5652,6 +5844,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 @@ -5668,6 +5861,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); } @@ -5883,6 +6077,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 { @@ -6548,6 +6743,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); @@ -6988,7 +7184,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(); @@ -7688,6 +7884,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(); @@ -7730,6 +7927,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 @@ -8138,11 +8336,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; @@ -8166,6 +8365,17 @@ package android.net { public static class NetworkStats.Entry { ctor public NetworkStats.Entry(@Nullable String, int, int, int, int, int, int, long, long, long, long, long); + method public int getDefaultNetwork(); + method public int getMetered(); + method public long getOperations(); + method public int getRoaming(); + method public long getRxBytes(); + method public long getRxPackets(); + method public int getSet(); + method public int getTag(); + method public long getTxBytes(); + method public long getTxPackets(); + method public int getUid(); } @Deprecated public class RssiCurve implements android.os.Parcelable { @@ -8202,6 +8412,7 @@ package android.net { public class TrafficStats { method public static void setThreadStatsTagApp(); method public static void setThreadStatsTagBackup(); + method public static void setThreadStatsTagDownload(); method public static void setThreadStatsTagRestore(); field public static final int TAG_NETWORK_STACK_IMPERSONATION_RANGE_END = -113; // 0xffffff8f field public static final int TAG_NETWORK_STACK_IMPERSONATION_RANGE_START = -128; // 0xffffff80 @@ -9636,9 +9847,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); @@ -9846,6 +10057,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"; @@ -9888,6 +10100,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"; @@ -10116,6 +10329,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"; @@ -10416,6 +10630,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 { @@ -10854,9 +11080,11 @@ package android.service.games { public class GameService extends android.app.Service { ctor public GameService(); + method public final void createGameSession(@IntRange(from=0) int); method @Nullable public final android.os.IBinder onBind(@Nullable android.content.Intent); method public void onConnected(); method public void onDisconnected(); + method public void onGameStarted(@NonNull android.service.games.GameStartedEvent); field public static final String ACTION_GAME_SERVICE = "android.service.games.action.GAME_SERVICE"; field public static final String SERVICE_META_DATA = "android.game_service"; } @@ -10865,6 +11093,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 { @@ -10874,6 +11111,15 @@ package android.service.games { field public static final String ACTION_GAME_SESSION_SERVICE = "android.service.games.action.GAME_SESSION_SERVICE"; } + public final class GameStartedEvent implements android.os.Parcelable { + ctor public GameStartedEvent(@IntRange(from=0) int, @NonNull String); + method public int describeContents(); + method @NonNull public String getPackageName(); + method @IntRange(from=0) public int getTaskId(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.service.games.GameStartedEvent> CREATOR; + } + } package android.service.notification { @@ -10996,6 +11242,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(); @@ -12953,6 +13200,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"; @@ -13121,6 +13369,7 @@ package android.telephony { } public final class UiccSlotMapping implements android.os.Parcelable { + ctor public UiccSlotMapping(int, int, int); method public int describeContents(); method @IntRange(from=0) public int getLogicalSlotIndex(); method @IntRange(from=0) public int getPhysicalSlotIndex(); @@ -13178,6 +13427,7 @@ package android.telephony.data { field public static final String TYPE_DEFAULT_STRING = "default"; field public static final String TYPE_DUN_STRING = "dun"; field public static final String TYPE_EMERGENCY_STRING = "emergency"; + field public static final String TYPE_ENTERPRISE_STRING = "enterprise"; field public static final String TYPE_FOTA_STRING = "fota"; field public static final String TYPE_HIPRI_STRING = "hipri"; field public static final String TYPE_IA_STRING = "ia"; @@ -14038,14 +14288,21 @@ package android.telephony.ims { public class ImsService extends android.app.Service { ctor public ImsService(); - method public android.telephony.ims.feature.MmTelFeature createMmTelFeature(int); - method public android.telephony.ims.feature.RcsFeature createRcsFeature(int); - method public void disableIms(int); - method public void enableIms(int); - method public android.telephony.ims.stub.ImsConfigImplBase getConfig(int); + method @Nullable public android.telephony.ims.feature.MmTelFeature createEmergencyOnlyMmTelFeature(int); + method @Deprecated public android.telephony.ims.feature.MmTelFeature createMmTelFeature(int); + method @Nullable public android.telephony.ims.feature.MmTelFeature createMmTelFeatureForSubscription(int, int); + method @Deprecated public android.telephony.ims.feature.RcsFeature createRcsFeature(int); + method @Nullable public android.telephony.ims.feature.RcsFeature createRcsFeatureForSubscription(int, int); + method @Deprecated public void disableIms(int); + method public void disableImsForSubscription(int, int); + method @Deprecated public void enableIms(int); + method public void enableImsForSubscription(int, int); + method @Deprecated public android.telephony.ims.stub.ImsConfigImplBase getConfig(int); + method @NonNull public android.telephony.ims.stub.ImsConfigImplBase getConfigForSubscription(int, int); method @NonNull public java.util.concurrent.Executor getExecutor(); method public long getImsServiceCapabilities(); - method public android.telephony.ims.stub.ImsRegistrationImplBase getRegistration(int); + method @Deprecated public android.telephony.ims.stub.ImsRegistrationImplBase getRegistration(int); + method @NonNull public android.telephony.ims.stub.ImsRegistrationImplBase getRegistrationForSubscription(int, int); method @Nullable public android.telephony.ims.stub.SipTransportImplBase getSipTransport(int); method public final void onUpdateSupportedImsFeatures(android.telephony.ims.stub.ImsFeatureConfiguration) throws android.os.RemoteException; method public android.telephony.ims.stub.ImsFeatureConfiguration querySupportedImsFeatures(); @@ -14269,18 +14526,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); @@ -14713,9 +14968,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 } } @@ -14865,11 +15117,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 { @@ -15147,6 +15394,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 { @@ -15172,6 +15421,11 @@ package android.view.accessibility { method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public void unregisterSystemAction(int); } + public class CaptioningManager { + method @RequiresPermission(android.Manifest.permission.SET_SYSTEM_AUDIO_CAPTION) public final void setSystemAudioCaptioningRequested(boolean); + method @RequiresPermission(android.Manifest.permission.SET_SYSTEM_AUDIO_CAPTION) public final void setSystemAudioCaptioningUiRequested(boolean); + } + } package android.view.autofill { @@ -15729,3 +15983,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 f1b46248ef85..e97ef6c912f5 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -43,7 +43,6 @@ package android { field public static final String TEST_BIOMETRIC = "android.permission.TEST_BIOMETRIC"; field public static final String TEST_MANAGE_ROLLBACKS = "android.permission.TEST_MANAGE_ROLLBACKS"; field public static final String UPGRADE_RUNTIME_PERMISSIONS = "android.permission.UPGRADE_RUNTIME_PERMISSIONS"; - field public static final String WRITE_COMMUNAL_STATE = "android.permission.WRITE_COMMUNAL_STATE"; field public static final String WRITE_DEVICE_CONFIG = "android.permission.WRITE_DEVICE_CONFIG"; field @Deprecated public static final String WRITE_MEDIA_STORAGE = "android.permission.WRITE_MEDIA_STORAGE"; field public static final String WRITE_OBB = "android.permission.WRITE_OBB"; @@ -102,7 +101,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); } @@ -557,14 +556,6 @@ package android.app.blob { } -package android.app.communal { - - public final class CommunalManager { - method @RequiresPermission(android.Manifest.permission.WRITE_COMMUNAL_STATE) public void setCommunalViewShowing(boolean); - } - -} - package android.app.contentsuggestions { public final class ContentSuggestionsManager { @@ -1937,6 +1928,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 { @@ -2788,7 +2782,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/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/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/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index 324e1aea81e7..96487de5e119 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -71,6 +71,9 @@ public abstract class ActivityManagerInternal { } // Access modes for handleIncomingUser. + /** + * Allows access to a caller with {@link android.Manifest.permission#INTERACT_ACROSS_USERS}. + */ public static final int ALLOW_NON_FULL = 0; /** * Allows access to a caller with {@link android.Manifest.permission#INTERACT_ACROSS_USERS} @@ -78,13 +81,18 @@ public abstract class ActivityManagerInternal { * Otherwise, {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required. */ public static final int ALLOW_NON_FULL_IN_PROFILE = 1; + /** + * Allows access to a caller only if it has the full + * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL}. + */ public static final int ALLOW_FULL_ONLY = 2; /** * Allows access to a caller with {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES} - * or {@link android.Manifest.permission#INTERACT_ACROSS_USERS} if in the same profile group. - * Otherwise, {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required. + * if in the same profile group. + * Otherwise, {@link android.Manifest.permission#INTERACT_ACROSS_USERS} is required and suffices + * as in {@link #ALLOW_NON_FULL}. */ - public static final int ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE = 3; + public static final int ALLOW_PROFILES_OR_NON_FULL = 3; /** * Returns profile information in free form string in two separate strings. diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 7ac4bddbed57..3ddbe9e85240 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -105,6 +105,7 @@ 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; @@ -1646,7 +1647,7 @@ public final class ActivityThread extends ClientTransactionHandler @Override public void dumpCacheInfo(ParcelFileDescriptor pfd, String[] args) { - PropertyInvalidatedCache.dumpCacheInfo(pfd.getFileDescriptor(), args); + PropertyInvalidatedCache.dumpCacheInfo(pfd, args); IoUtils.closeQuietly(pfd); } @@ -6391,9 +6392,7 @@ public final class ActivityThread extends ClientTransactionHandler if (DEBUG_MEMORY_TRIM) Slog.v(TAG, "Trimming memory to level: " + level); if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) { - for (PropertyInvalidatedCache pic : PropertyInvalidatedCache.getActiveCaches()) { - pic.clear(); - } + PropertyInvalidatedCache.onTrimMemory(); } final ArrayList<ComponentCallbacks2> callbacks = @@ -6727,6 +6726,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); 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/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 49c75c49b2d7..7b55b6c0e458 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -62,6 +62,7 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageItemInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageParser; import android.content.pm.ParceledListSlice; import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; @@ -73,12 +74,6 @@ import android.content.pm.SuspendDialogInfo; import android.content.pm.VerifierDeviceIdentity; import android.content.pm.VersionedPackage; import android.content.pm.dex.ArtManager; -import android.content.pm.parsing.PackageInfoWithoutStateUtils; -import android.content.pm.parsing.ParsingPackage; -import android.content.pm.parsing.ParsingPackageUtils; -import android.content.pm.parsing.result.ParseInput; -import android.content.pm.parsing.result.ParseResult; -import android.content.pm.parsing.result.ParseTypeImpl; import android.content.pm.pkg.FrameworkPackageUserState; import android.content.res.Configuration; import android.content.res.Resources; @@ -2335,37 +2330,6 @@ public class ApplicationPackageManager extends PackageManager { return info.loadLabel(this); } - @Nullable - public PackageInfo getPackageArchiveInfo(@NonNull String archiveFilePath, int flags) { - return getPackageArchiveInfo(archiveFilePath, PackageInfoFlags.of(flags)); - } - - @Nullable - public PackageInfo getPackageArchiveInfo(@NonNull String archiveFilePath, - PackageInfoFlags flags) { - long flagsBits = flags.getValue(); - if ((flagsBits & (PackageManager.MATCH_DIRECT_BOOT_UNAWARE - | PackageManager.MATCH_DIRECT_BOOT_AWARE)) == 0) { - // Caller expressed no opinion about what encryption - // aware/unaware components they want to see, so match both - flagsBits |= PackageManager.MATCH_DIRECT_BOOT_AWARE - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE; - } - - boolean collectCertificates = (flagsBits & PackageManager.GET_SIGNATURES) != 0 - || (flagsBits & PackageManager.GET_SIGNING_CERTIFICATES) != 0; - - ParseInput input = ParseTypeImpl.forParsingWithoutPlatformCompat().reset(); - ParseResult<ParsingPackage> result = ParsingPackageUtils.parseDefault(input, - new File(archiveFilePath), 0, getPermissionManager().getSplitPermissions(), - collectCertificates); - if (result.isError()) { - return null; - } - return PackageInfoWithoutStateUtils.generate(result.getResult(), null, flagsBits, 0, 0, - null, FrameworkPackageUserState.DEFAULT, UserHandle.getCallingUserId()); - } - @Override public int installExistingPackage(String packageName) throws NameNotFoundException { return installExistingPackage(packageName, INSTALL_REASON_UNKNOWN); 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/communal/ICommunalManager.aidl b/core/java/android/app/GameModeInfo.aidl index df9d7cec05f7..3b13201c5d1b 100644 --- a/core/java/android/app/communal/ICommunalManager.aidl +++ b/core/java/android/app/GameModeInfo.aidl @@ -14,14 +14,9 @@ * limitations under the License. */ -package android.app.communal; +package android.app; /** - * System private API for talking with the communal manager service that handles communal mode - * state. - * * @hide */ -interface ICommunalManager { - oneway void setCommunalViewShowing(boolean isShowing); -}
\ No newline at end of file +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/LocaleConfig.java b/core/java/android/app/LocaleConfig.java new file mode 100644 index 000000000000..436eac37b4fb --- /dev/null +++ b/core/java/android/app/LocaleConfig.java @@ -0,0 +1,166 @@ +/* + * 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.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.os.LocaleList; +import android.util.AttributeSet; +import android.util.Slog; +import android.util.Xml; + +import com.android.internal.util.XmlUtils; + +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.HashSet; +import java.util.Set; + +/** + * The LocaleConfig of an application. + * Defined in an XML resource file with an {@code <locale-config>} element and + * referenced in the manifest via {@code android:localeConfig} on + * {@code <application>}. + * + * For more information, see TODO(b/214154050): add link to guide + * + * @attr ref android.R.styleable#LocaleConfig_Locale_name + * @attr ref android.R.styleable#AndroidManifestApplication_localeConfig + */ +public class LocaleConfig { + + private static final String TAG = "LocaleConfig"; + public static final String TAG_LOCALE_CONFIG = "locale-config"; + public static final String TAG_LOCALE = "locale"; + private LocaleList mLocales; + private int mStatus; + + /** + * succeeded reading the LocaleConfig structure stored in an XML file. + */ + public static final int STATUS_SUCCESS = 0; + /** + * No android:localeConfig tag on <application>. + */ + public static final int STATUS_NOT_SPECIFIED = 1; + /** + * Malformed input in the XML file where the LocaleConfig was stored. + */ + public static final int STATUS_PARSING_FAILED = 2; + + /** @hide */ + @IntDef(prefix = { "STATUS_" }, value = { + STATUS_SUCCESS, + STATUS_NOT_SPECIFIED, + STATUS_PARSING_FAILED + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Status{} + + /** + * Returns the LocaleConfig for the provided application context + * + * @param context the context of the application + * + * @see Context#createPackageContext(String, int). + */ + public LocaleConfig(@NonNull Context context) { + int resId = 0; + Resources res = context.getResources(); + try { + //Get the resource id + resId = new ApplicationInfo(context.getApplicationInfo()).getLocaleConfigRes(); + //Get the parser to read XML data + XmlResourceParser parser = res.getXml(resId); + parseLocaleConfig(parser, res); + } catch (Resources.NotFoundException e) { + Slog.w(TAG, "The resource file pointed to by the given resource ID isn't found.", e); + mStatus = STATUS_NOT_SPECIFIED; + } catch (XmlPullParserException | IOException e) { + Slog.w(TAG, "Failed to parse XML configuration from " + + res.getResourceEntryName(resId), e); + mStatus = STATUS_PARSING_FAILED; + } + } + + /** + * Parse the XML content and get the locales supported by the application + */ + private void parseLocaleConfig(XmlResourceParser parser, Resources res) + throws IOException, XmlPullParserException { + XmlUtils.beginDocument(parser, TAG_LOCALE_CONFIG); + int outerDepth = parser.getDepth(); + AttributeSet attrs = Xml.asAttributeSet(parser); + Set<String> localeNames = new HashSet<String>(); + while (XmlUtils.nextElementWithin(parser, outerDepth)) { + if (TAG_LOCALE.equals(parser.getName())) { + final TypedArray attributes = res.obtainAttributes( + attrs, com.android.internal.R.styleable.LocaleConfig_Locale); + String nameAttr = attributes.getString( + com.android.internal.R.styleable.LocaleConfig_Locale_name); + localeNames.add(nameAttr); + attributes.recycle(); + } else { + XmlUtils.skipCurrentTag(parser); + } + } + mStatus = STATUS_SUCCESS; + mLocales = LocaleList.forLanguageTags(String.join(",", localeNames)); + } + + /** + * Returns the locales supported by the specified application. + * + * <p><b>Note:</b> The locale format should follow the + * <a href="https://www.rfc-editor.org/rfc/bcp/bcp47.txt">IETF BCP47 regular expression</a> + * + * @return the {@link LocaleList} + */ + public @Nullable LocaleList getSupportedLocales() { + return mLocales; + } + + /** + * Get the status of reading the resource file where the LocaleConfig was stored. + * + * <p>Distinguish "the application didn't provide the resource file" from "the application + * provided malformed input" if {@link #getSupportedLocales()} returns {@code null}. + * + * @return {@code STATUS_SUCCESS} if the LocaleConfig structure existed in an XML file was + * successfully read, or {@code STATUS_NOT_SPECIFIED} if no android:localeConfig tag on + * <application> pointing to an XML file that stores the LocaleConfig, or + * {@code STATUS_PARSING_FAILED} if the application provided malformed input for the + * LocaleConfig structure. + * + * @see #STATUS_SUCCESS + * @see #STATUS_NOT_SPECIFIED + * @see #STATUS_PARSING_FAILED + * + */ + public @Status int getStatus() { + return mStatus; + } +} diff --git a/core/java/android/app/LocaleManager.java b/core/java/android/app/LocaleManager.java index 2aa7c8e60236..522dc845f57c 100644 --- a/core/java/android/app/LocaleManager.java +++ b/core/java/android/app/LocaleManager.java @@ -55,6 +55,9 @@ public class LocaleManager { * * <p>Pass a {@link LocaleList#getEmptyLocaleList()} to reset to the system locale. * + * <p><b>Note:</b> The set locales are persisted; they are backed up if the user has enabled + * Backup & Restore. + * * @param locales the desired locales for the calling app. */ @UserHandleAware @@ -67,6 +70,9 @@ public class LocaleManager { * * <p>Pass a {@link LocaleList#getEmptyLocaleList()} to reset to the system locale. * + * <p><b>Note:</b> The set locales are persisted; they are backed up if the user has enabled + * Backup & Restore. + * * @param appPackageName the package name of the app for which to set the locales. * @param locales the desired locales for the specified app. * @hide @@ -95,18 +101,20 @@ public class LocaleManager { } /** - * Returns the current UI locales for a specific app (described by package name). + * Returns the current UI locales for a specified app (described by package name). * * <p>Returns a {@link LocaleList#getEmptyLocaleList()} if no app-specific locales are set. * - * <b>Note:</b> Non-system apps should read Locale information via their in-process - * LocaleLists. + * <p>This API can be used by an app's installer + * (per {@link android.content.pm.InstallSourceInfo#getInstallingPackageName}) to retrieve + * the app's locales. + * All other cases require {@code android.Manifest.permission#READ_APP_SPECIFIC_LOCALES}. + * Apps should generally retrieve their own locales via their in-process LocaleLists, + * or by calling {@link #getApplicationLocales()}. * * @param appPackageName the package name of the app for which to retrieve the locales. - * @hide */ - @SystemApi - @RequiresPermission(Manifest.permission.READ_APP_SPECIFIC_LOCALES) + @RequiresPermission(value = Manifest.permission.READ_APP_SPECIFIC_LOCALES, conditional = true) @UserHandleAware @NonNull public LocaleList getApplicationLocales(@NonNull String appPackageName) { 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/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java index 978160c1c243..ec8d989c61d4 100644 --- a/core/java/android/app/PropertyInvalidatedCache.java +++ b/core/java/android/app/PropertyInvalidatedCache.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.os.ParcelFileDescriptor; import android.os.SystemClock; import android.os.SystemProperties; import android.text.TextUtils; @@ -29,7 +30,6 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FastPrintWriter; -import java.io.FileDescriptor; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; @@ -355,11 +355,12 @@ public abstract class PropertyInvalidatedCache<Query, Result> { private final int mMaxEntries; /** - * Make a new property invalidated cache. + * Make a new property invalidated cache. This constructor names the cache after the + * property name. New clients should prefer the constructor that takes an explicit + * cache name. * * @param maxEntries Maximum number of entries to cache; LRU discard - * @param propertyName Name of the system property holding the cache invalidation nonce - * Defaults the cache name to the property name. + * @param propertyName Name of the system property holding the cache invalidation nonce. */ public PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName) { this(maxEntries, propertyName, propertyName); @@ -418,7 +419,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> { * Enable or disable testing. The testing property map is cleared every time this * method is called. */ - @VisibleForTesting + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public static void setTestMode(boolean mode) { sTesting = mode; synchronized (sTestingPropertyMap) { @@ -426,12 +427,12 @@ public abstract class PropertyInvalidatedCache<Query, Result> { } } - /** - * Enable testing the specific cache key. Only keys in the map are subject to testing. - * There is no method to stop testing a property name. Just disable the test mode. - */ - @VisibleForTesting - public static void testPropertyName(String name) { + /** + * Enable testing the specific cache key. Only keys in the map are subject to testing. + * There is no method to stop testing a property name. Just disable the test mode. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public static void testPropertyName(@NonNull String name) { synchronized (sTestingPropertyMap) { sTestingPropertyMap.put(name, (long) NONCE_UNSET); } @@ -505,21 +506,23 @@ public abstract class PropertyInvalidatedCache<Query, Result> { * block. If this function returns null, the result of the cache query is null. There is no * "negative cache" in the query: we don't cache null results at all. */ - public abstract Result recompute(Query query); + public abstract @NonNull Result recompute(@NonNull Query query); /** * Return true if the query should bypass the cache. The default behavior is to * always use the cache but the method can be overridden for a specific class. */ - public boolean bypass(Query query) { + public boolean bypass(@NonNull Query query) { return false; } /** - * Determines if a pair of responses are considered equal. Used to determine whether - * a cache is inadvertently returning stale results when VERIFY is set to true. + * Determines if a pair of responses are considered equal. Used to determine whether a + * cache is inadvertently returning stale results when VERIFY is set to true. Some + * existing clients override this method, but it is now deprecated in favor of a valid + * equals() method on the Result class. */ - protected boolean resultEquals(Result cachedResult, Result fetchedResult) { + public boolean resultEquals(Result cachedResult, Result fetchedResult) { // If a service crashes and returns a null result, the cached value remains valid. if (fetchedResult != null) { return Objects.equals(cachedResult, fetchedResult); @@ -544,8 +547,11 @@ public abstract class PropertyInvalidatedCache<Query, Result> { } /** - * Disable the use of this cache in this process. + * Disable the use of this cache in this process. This method is using during + * testing. To disable a cache in normal code, use disableLocal(). + * @hide */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public final void disableInstance() { synchronized (mLock) { mDisabled = true; @@ -558,7 +564,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> { * using the key will be disabled now, and all future cache instances that use the key will be * disabled in their constructor. */ - public static final void disableLocal(@NonNull String name) { + private static final void disableLocal(@NonNull String name) { synchronized (sCorkLock) { sDisabledKeys.add(name); for (PropertyInvalidatedCache cache : sCaches.keySet()) { @@ -570,7 +576,21 @@ public abstract class PropertyInvalidatedCache<Query, Result> { } /** + * Stop disabling local caches with a particular name. Any caches that are currently + * disabled remain disabled (the "disabled" setting is sticky). However, new caches + * with this name will not be disabled. It is not an error if the cache name is not + * found in the list of disabled caches. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public final void clearDisableLocal() { + synchronized (sCorkLock) { + sDisabledKeys.remove(mCacheName); + } + } + + /** * Disable this cache in the current process, and all other caches that use the same + * name. This does not affect caches that have a different name but use the same * property. */ public final void disableLocal() { @@ -580,6 +600,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> { /** * Return whether the cache is disabled in this process. */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public final boolean isDisabledLocal() { return mDisabled || !sEnabled; } @@ -587,7 +608,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> { /** * Get a value from the cache or recompute it. */ - public Result query(Query query) { + public @NonNull Result query(@NonNull Query query) { // Let access to mDisabled race: it's atomic anyway. long currentNonce = (!isDisabledLocal()) ? getCurrentNonce() : NONCE_DISABLED; if (bypass(query)) { @@ -704,6 +725,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> { * the cache objects to invalidate all of the cache objects becomes confusing and you should * just use the static version of this function. */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public final void disableSystemWide() { disableSystemWide(mPropertyName); } @@ -714,7 +736,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> { * * @param name Name of the cache-key property to invalidate */ - public static void disableSystemWide(@NonNull String name) { + private static void disableSystemWide(@NonNull String name) { if (!sEnabled) { return; } @@ -784,8 +806,8 @@ public abstract class PropertyInvalidatedCache<Query, Result> { "invalidating cache [%s]: [%s] -> [%s]", name, nonce, Long.toString(newValue))); } - // TODO(dancol): add an atomic compare and exchange property set operation to avoid a - // small race with concurrent disable here. + // There is a small race with concurrent disables here. A compare-and-exchange + // property operation would be required to eliminate the race condition. setNonce(name, newValue); long invalidateCount = sInvalidates.getOrDefault(name, (long) 0); sInvalidates.put(name, ++invalidateCount); @@ -990,6 +1012,10 @@ public abstract class PropertyInvalidatedCache<Query, Result> { } } + /** + * Return the result generated by a given query to the cache, performing debugging checks when + * enabled. + */ private Result maybeCheckConsistency(Query query, Result proposedResult) { if (VERIFY) { Result resultToCompare = recompute(query); @@ -1007,24 +1033,27 @@ public abstract class PropertyInvalidatedCache<Query, Result> { } /** - * Return the name of the cache, to be used in debug messages. The - * method is public so clients can use it. + * Return the name of the cache, to be used in debug messages. */ - public String cacheName() { + private final @NonNull String cacheName() { return mCacheName; } /** - * Return the query as a string, to be used in debug messages. The - * method is public so clients can use it in external debug messages. + * Return the query as a string, to be used in debug messages. New clients should not + * override this, but should instead add the necessary toString() method to the Query + * class. */ - public String queryToString(Query query) { + protected @NonNull String queryToString(@NonNull Query query) { return Objects.toString(query); } /** - * Disable all caches in the local process. Once disabled it is not - * possible to re-enable caching in the current process. + * Disable all caches in the local process. This is primarily useful for testing when + * the test needs to bypass the cache or when the test is for a server, and the test + * process does not have privileges to write SystemProperties. Once disabled it is not + * possible to re-enable caching in the current process. If a client wants to + * temporarily disable caching, use the corking mechanism. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public static void disableForTestMode() { @@ -1044,7 +1073,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> { /** * Returns a list of caches alive at the current time. */ - public static ArrayList<PropertyInvalidatedCache> getActiveCaches() { + private static @NonNull ArrayList<PropertyInvalidatedCache> getActiveCaches() { synchronized (sCorkLock) { return new ArrayList<PropertyInvalidatedCache>(sCaches.keySet()); } @@ -1053,7 +1082,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> { /** * Returns a list of the active corks in a process. */ - public static ArrayList<Map.Entry<String, Integer>> getActiveCorks() { + private static @NonNull ArrayList<Map.Entry<String, Integer>> getActiveCorks() { synchronized (sCorkLock) { return new ArrayList<Map.Entry<String, Integer>>(sCorks.entrySet()); } @@ -1104,14 +1133,14 @@ public abstract class PropertyInvalidatedCache<Query, Result> { } /** - * Dumps contents of every cache in the process to the provided FileDescriptor. + * Dumps the contents of every cache in the process to the provided ParcelFileDescriptor. */ - public static void dumpCacheInfo(FileDescriptor fd, String[] args) { + public static void dumpCacheInfo(@NonNull ParcelFileDescriptor pfd, @NonNull String[] args) { ArrayList<PropertyInvalidatedCache> activeCaches; ArrayList<Map.Entry<String, Integer>> activeCorks; try ( - FileOutputStream fout = new FileOutputStream(fd); + FileOutputStream fout = new FileOutputStream(pfd.getFileDescriptor()); PrintWriter pw = new FastPrintWriter(fout); ) { if (!sEnabled) { @@ -1142,4 +1171,13 @@ public abstract class PropertyInvalidatedCache<Query, Result> { Log.e(TAG, "Failed to dump PropertyInvalidatedCache instances"); } } + + /** + * Trim memory by clearing all the caches. + */ + public static void onTrimMemory() { + for (PropertyInvalidatedCache pic : getActiveCaches()) { + pic.clear(); + } + } } 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/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 85ddff9a9311..f0208c621886 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -24,10 +24,10 @@ 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.communal.CommunalManager; -import android.app.communal.ICommunalManager; import android.app.contentsuggestions.ContentSuggestionsManager; import android.app.contentsuggestions.IContentSuggestionsManager; import android.app.job.JobSchedulerFrameworkInitializer; @@ -126,10 +126,11 @@ 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; import android.net.ConnectivityFrameworkInitializer; import android.net.ConnectivityFrameworkInitializerTiramisu; import android.net.EthernetManager; @@ -965,13 +966,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, @@ -1022,19 +1026,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>() { @@ -1519,20 +1525,17 @@ public final class SystemServiceRegistry { } }); - registerService(Context.COMMUNAL_SERVICE, CommunalManager.class, - new CachedServiceFetcher<CommunalManager>() { + registerService(Context.AMBIENT_CONTEXT_SERVICE, AmbientContextManager.class, + new CachedServiceFetcher<AmbientContextManager>() { @Override - public CommunalManager createService(ContextImpl ctx) { - if (!ctx.getPackageManager().hasSystemFeature( - PackageManager.FEATURE_COMMUNAL_MODE)) { - return null; - } - IBinder iBinder = - ServiceManager.getService(Context.COMMUNAL_SERVICE); - return iBinder != null ? new CommunalManager( - ICommunalManager.Stub.asInterface(iBinder)) : null; - } - }); + 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 { @@ -1554,6 +1557,7 @@ public final class SystemServiceRegistry { UwbFrameworkInitializer.registerServiceWrappers(); SafetyCenterFrameworkInitializer.registerServiceWrappers(); ConnectivityFrameworkInitializerTiramisu.registerServiceWrappers(); + NearbyFrameworkInitializer.registerServiceWrappers(); } finally { // If any of the above code throws, we're in a pretty bad shape and the process // will likely crash, but we'll reset it just in case there's an exception handler... 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/DevicePolicyDrawableResource.aidl b/core/java/android/app/admin/DevicePolicyDrawableResource.aidl new file mode 100644 index 000000000000..6b73d9815fe6 --- /dev/null +++ b/core/java/android/app/admin/DevicePolicyDrawableResource.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 DevicePolicyDrawableResource; diff --git a/core/java/android/app/admin/DevicePolicyDrawableResource.java b/core/java/android/app/admin/DevicePolicyDrawableResource.java new file mode 100644 index 000000000000..d32ff841f802 --- /dev/null +++ b/core/java/android/app/admin/DevicePolicyDrawableResource.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.app.admin; + +import android.annotation.DrawableRes; +import android.annotation.NonNull; +import android.annotation.Nullable; +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 drawable resource using + * {@link DevicePolicyManager#setDrawables}. + * + * @hide + */ +@SystemApi +public final class DevicePolicyDrawableResource implements Parcelable { + private final @DevicePolicyResources.UpdatableDrawableId int mDrawableId; + private final @DevicePolicyResources.UpdatableDrawableStyle int mDrawableStyle; + private final @DevicePolicyResources.UpdatableDrawableSource int mDrawableSource; + private final @DrawableRes int mCallingPackageResourceId; + @NonNull private ParcelableResource mResource; + + /** + * Creates an object containing the required information for updating an enterprise drawable + * resource using {@link DevicePolicyManager#setDrawables}. + * + * <p>It will be used to update the drawable defined by {@code drawableId} with style + * {@code drawableStyle} located in source {@code drawableSource} to the drawable with ID + * {@code callingPackageResourceId} in the calling package</p> + * + * @param drawableId The ID of the drawable to update. + * @param drawableStyle The style of the drawable to update. + * @param drawableSource The source of the drawable to update. + * @param callingPackageResourceId The ID of the drawable resource 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 DevicePolicyDrawableResource( + @NonNull Context context, + @DevicePolicyResources.UpdatableDrawableId int drawableId, + @DevicePolicyResources.UpdatableDrawableStyle int drawableStyle, + @DevicePolicyResources.UpdatableDrawableSource int drawableSource, + @DrawableRes int callingPackageResourceId) { + this(drawableId, drawableStyle, drawableSource, callingPackageResourceId, + new ParcelableResource(context, callingPackageResourceId, + ParcelableResource.RESOURCE_TYPE_DRAWABLE)); + } + + private DevicePolicyDrawableResource( + @DevicePolicyResources.UpdatableDrawableId int drawableId, + @DevicePolicyResources.UpdatableDrawableStyle int drawableStyle, + @DevicePolicyResources.UpdatableDrawableSource int drawableSource, + @DrawableRes int callingPackageResourceId, + @NonNull ParcelableResource resource) { + this.mDrawableId = drawableId; + this.mDrawableStyle = drawableStyle; + this.mDrawableSource = drawableSource; + this.mCallingPackageResourceId = callingPackageResourceId; + this.mResource = resource; + } + + /** + * Creates an object containing the required information for updating an enterprise drawable + * resource using {@link DevicePolicyManager#setDrawables}. + * <p>It will be used to update the drawable defined by {@code drawableId} with style + * {@code drawableStyle} to the drawable with ID {@code callingPackageResourceId} in the + * calling package</p> + * + * @param drawableId The ID of the drawable to update. + * @param drawableStyle The style of the drawable to update. + * @param callingPackageResourceId The ID of the drawable resource in the calling package to + * use as an updated resource. + * + * @throws IllegalStateException if the resource with ID + * {@code callingPackageResourceId} doesn't exist in the calling package. + */ + public DevicePolicyDrawableResource( + @NonNull Context context, + @DevicePolicyResources.UpdatableDrawableId int drawableId, + @DevicePolicyResources.UpdatableDrawableStyle int drawableStyle, + @DrawableRes int callingPackageResourceId) { + this(context, drawableId, drawableStyle, DevicePolicyResources.Drawable.Source.UNDEFINED, + callingPackageResourceId); + } + + /** + * Returns the ID of the drawable to update. + */ + @DevicePolicyResources.UpdatableDrawableId + public int getDrawableId() { + return mDrawableId; + } + + /** + * Returns the style of the drawable to update + */ + @DevicePolicyResources.UpdatableDrawableStyle + public int getDrawableStyle() { + return mDrawableStyle; + } + + /** + * Returns the source of the drawable to update. + */ + @DevicePolicyResources.UpdatableDrawableSource + public int getDrawableSource() { + return mDrawableSource; + } + + /** + * Returns the ID of the drawable resource in the calling package to use as an updated + * resource. + */ + @DrawableRes + public int getCallingPackageResourceId() { + return mCallingPackageResourceId; + } + + /** + * Returns the {@link ParcelableResource} of the drawable. + * + * @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; + DevicePolicyDrawableResource other = (DevicePolicyDrawableResource) o; + return mDrawableId == other.mDrawableId + && mDrawableStyle == other.mDrawableStyle + && mDrawableSource == other.mDrawableSource + && mCallingPackageResourceId == other.mCallingPackageResourceId + && mResource.equals(other.mResource); + } + + @Override + public int hashCode() { + return Objects.hash( + mDrawableId, mDrawableStyle, mDrawableSource, mCallingPackageResourceId, mResource); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mDrawableId); + dest.writeInt(mDrawableStyle); + dest.writeInt(mDrawableSource); + dest.writeInt(mCallingPackageResourceId); + dest.writeTypedObject(mResource, flags); + } + + public static final @NonNull Creator<DevicePolicyDrawableResource> CREATOR = + new Creator<DevicePolicyDrawableResource>() { + @Override + public DevicePolicyDrawableResource createFromParcel(Parcel in) { + int drawableId = in.readInt(); + int drawableStyle = in.readInt(); + int drawableSource = in.readInt(); + int callingPackageResourceId = in.readInt(); + ParcelableResource resource = in.readTypedObject(ParcelableResource.CREATOR); + + return new DevicePolicyDrawableResource( + drawableId, drawableStyle, drawableSource, callingPackageResourceId, + resource); + } + + @Override + public DevicePolicyDrawableResource[] newArray(int size) { + return new DevicePolicyDrawableResource[size]; + } + }; +} diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index a60de088bab2..e47c5098192e 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -16,6 +16,9 @@ package android.app.admin; +import static android.app.admin.DevicePolicyResources.Drawable.INVALID_ID; +import static android.app.admin.DevicePolicyResources.Drawable.Source.UNDEFINED; + import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; import android.Manifest.permission; @@ -52,7 +55,9 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ParceledListSlice; import android.content.pm.UserInfo; +import android.content.res.Resources; import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; import android.net.PrivateDnsConnectivityChecker; import android.net.ProxyInfo; import android.net.Uri; @@ -89,6 +94,7 @@ import android.telephony.data.ApnSetting; import android.text.TextUtils; import android.util.ArraySet; import android.util.DebugUtils; +import android.util.DisplayMetrics; import android.util.Log; import android.util.Pair; @@ -123,6 +129,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; @@ -480,6 +487,10 @@ public class DevicePolicyManager { * * <p>Device management role holders are required to have a handler for this intent action. * + * <p>If {@link #EXTRA_ROLE_HOLDER_STATE} is supplied to this intent, it is the responsibility + * of the role holder to restore its state from this extra. This is the same {@link Bundle} + * which the role holder returns alongside {@link #RESULT_UPDATE_ROLE_HOLDER}. + * * <p>A result code of {@link Activity#RESULT_OK} implies that managed profile provisioning * finished successfully. If it did not, a result code of {@link Activity#RESULT_CANCELED} * is used instead. @@ -527,6 +538,10 @@ public class DevicePolicyManager { * * <p>Device management role holders are required to have a handler for this intent action. * + * <p>If {@link #EXTRA_ROLE_HOLDER_STATE} is supplied to this intent, it is the responsibility + * of the role holder to restore its state from this extra. This is the same {@link Bundle} + * which the role holder returns alongside {@link #RESULT_UPDATE_ROLE_HOLDER}. + * * <p>The result codes can be either {@link #RESULT_WORK_PROFILE_CREATED}, {@link * #RESULT_DEVICE_OWNER_SET} or {@link Activity#RESULT_CANCELED} if provisioning failed. * @@ -566,6 +581,73 @@ public class DevicePolicyManager { "android.app.action.ROLE_HOLDER_PROVISION_FINALIZATION"; /** + * {@link Activity} result code which can be returned by {@link + * #ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE} and {@link + * #ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} to signal that an update + * to the role holder is required. + * + * <p>This result code must be accompanied by {@link #EXTRA_ROLE_HOLDER_STATE}. + * + * @hide + */ + @SystemApi + public static final int RESULT_UPDATE_ROLE_HOLDER = 2; + + /** + * A {@link Bundle} extra which describes the state of the role holder at the time when it + * returns {@link #RESULT_UPDATE_ROLE_HOLDER}. + * + * <p>After the update completes, the role holder's {@link + * #ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE} or {@link + * #ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} intent will be relaunched, + * which will contain this extra. It is the role holder's responsibility to restore its + * state from this extra. + * + * @hide + */ + @SystemApi + public static final String EXTRA_ROLE_HOLDER_STATE = "android.app.extra.ROLE_HOLDER_STATE"; + + /** + * A {@code boolean} extra which determines whether to force a role holder update, regardless + * of any internal conditions {@link #ACTION_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER} might have. + * + * <p>This extra can be provided to intents with action {@link + * #ACTION_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER}. + * + * @hide + */ + @SystemApi + public static final String EXTRA_FORCE_UPDATE_ROLE_HOLDER = + "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 @@ -1496,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. @@ -2829,6 +2983,20 @@ public class DevicePolicyManager { "android.app.extra.PROVISIONING_RETURN_BEFORE_POLICY_COMPLIANCE"; /** + * A {@code boolean} flag that indicates whether the screen should be on throughout the + * provisioning flow. + * + * <p>The default value is {@code false}. + * + * <p>This extra can either be passed as an extra to the {@link + * #ACTION_PROVISION_MANAGED_PROFILE} intent, or it can be returned by the + * admin app when performing the admin-integrated provisioning flow as a result of the + * {@link #ACTION_GET_PROVISIONING_MODE} activity. + */ + public static final String EXTRA_PROVISIONING_KEEP_SCREEN_ON = + "android.app.extra.PROVISIONING_KEEP_SCREEN_ON"; + + /** * Activity action: Starts the administrator to show policy compliance for the provisioning. * This action is used any time that the administrator has an opportunity to show policy * compliance before the end of setup wizard. This could happen as part of the admin-integrated @@ -2854,6 +3022,9 @@ public class DevicePolicyManager { * #RESULT_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER_UNRECOVERABLE_ERROR} if it encounters a problem * that will not be solved by relaunching it again. * + * <p>If this activity has additional internal conditions which are not met, it should return + * {@link #RESULT_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER_UNRECOVERABLE_ERROR}. + * * @hide */ @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_SETUP) @@ -2913,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 */ @@ -3181,6 +3400,45 @@ public class DevicePolicyManager { */ public static final int OPERATION_SAFETY_REASON_DRIVING_DISTRACTION = 1; + /** + * Broadcast action: notify system apps (e.g. settings, SysUI, etc) that the device management + * 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) + public static final String ACTION_DEVICE_POLICY_RESOURCE_UPDATED = + "android.app.action.DEVICE_POLICY_RESOURCE_UPDATED"; + + /** + * A boolean extra for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to indicate that a + * resource of type {@link Drawable} is being updated. + */ + public static final String EXTRA_RESOURCE_TYPE_DRAWABLE = + "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 + * 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"; + /** @hide */ @NonNull @TestApi @@ -14372,4 +14630,509 @@ public class DevicePolicyManager { public Intent createProvisioningIntentFromNfcIntent(@NonNull Intent nfcIntent) { return ProvisioningIntentHelper.createProvisioningIntentFromNfcIntent(nfcIntent); } + + /** + * 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 + * the combination of {@link DevicePolicyDrawableResource#getDrawableId()} and + * {@link DevicePolicyDrawableResource#getDrawableStyle()}, (see + * {@link DevicePolicyResources.Drawable} and {@link DevicePolicyResources.Drawable.Style}) to + * the drawable with ID {@link DevicePolicyDrawableResource#getCallingPackageResourceId()}, + * meaning any system UI surface calling {@link #getDrawable} + * with {@code drawableId} and {@code drawableStyle} will get the new resource after this API + * is called. + * + * <p>Otherwise, if {@link DevicePolicyDrawableResource#getDrawableSource()} is set (see + * {@link DevicePolicyResources.Drawable.Source}, it overrides any drawables that was set for + * the same {@code drawableId} and {@code drawableStyle} for the provided source. + * + * <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 #getDrawable} references the resource + * {@link DevicePolicyDrawableResource#getCallingPackageResourceId()} 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 setDrawables} even if you only make changes to the + * content of the resource with ID + * {@link DevicePolicyDrawableResource#getCallingPackageResourceId()} as the content might be + * cached and would need updating. + * </ul> + * + * @param drawables The list of {@link DevicePolicyDrawableResource} to update. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES) + public void setDrawables(@NonNull Set<DevicePolicyDrawableResource> drawables) { + if (mService != null) { + try { + mService.setDrawables(new ArrayList<>(drawables)); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Removes all updated drawables for the list of {@code drawableIds} (see + * {@link DevicePolicyResources.Drawable} that was previously set by calling + * {@link #setDrawables}, meaning any subsequent calls to {@link #getDrawable} for the provided + * IDs with any {@link DevicePolicyResources.Drawable.Style} and any + * {@link DevicePolicyResources.Drawable.Source} will return the default drawable from + * {@code defaultDrawableLoader}. + * + * <p>Sends a broadcast with action {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to + * registered receivers when a resource has been reset successfully. + * + * @param drawableIds The list of IDs to remove. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES) + public void resetDrawables(@NonNull int[] drawableIds) { + if (mService != null) { + try { + mService.resetDrawables(drawableIds); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Returns the appropriate updated drawable for the {@code drawableId} + * (see {@link DevicePolicyResources.Drawable}), with style {@code drawableStyle} + * (see {@link DevicePolicyResources.Drawable.Style}) if one was set using + * {@code setDrawables}, otherwise returns the drawable from {@code defaultDrawableLoader}. + * + * <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)}. + * + * <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 + * {@code setDrawables} to set the updated resource. + * + * @param drawableId The drawable ID to get the updated resource for. + * @param drawableStyle The drawable style to use. + * @param defaultDrawableLoader To get the default drawable if no updated drawable was set for + * the provided params. + */ + @NonNull + public Drawable getDrawable( + @DevicePolicyResources.UpdatableDrawableId int drawableId, + @DevicePolicyResources.UpdatableDrawableStyle int drawableStyle, + @NonNull Callable<Drawable> defaultDrawableLoader) { + return getDrawable(drawableId, drawableStyle, UNDEFINED, defaultDrawableLoader); + } + + /** + * Similar to {@link #getDrawable(int, int, Callable)}, but also accepts + * a {@code drawableSource} (see {@link DevicePolicyResources.Drawable.Source}) which + * 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. + * + * @param drawableId The drawable ID to get the updated resource for. + * @param drawableStyle The drawable style to use. + * @param drawableSource The source for the caller. + * @param defaultDrawableLoader To get the default drawable if no updated drawable was set for + * the provided params. + */ + @NonNull + public Drawable getDrawable( + @DevicePolicyResources.UpdatableDrawableId int drawableId, + @DevicePolicyResources.UpdatableDrawableStyle int drawableStyle, + @DevicePolicyResources.UpdatableDrawableSource int drawableSource, + @NonNull Callable<Drawable> defaultDrawableLoader) { + Objects.requireNonNull(defaultDrawableLoader, "defaultDrawableLoader can't be null"); + if (drawableId == INVALID_ID) { + return ParcelableResource.loadDefaultDrawable(defaultDrawableLoader); + } + if (mService != null) { + try { + ParcelableResource resource = mService.getDrawable( + drawableId, drawableStyle, drawableSource); + if (resource == null) { + return ParcelableResource.loadDefaultDrawable( + defaultDrawableLoader); + } + return resource.getDrawable( + mContext, + /* density= */ 0, + defaultDrawableLoader); + + } catch (RemoteException e) { + Log.e( + TAG, + "Error getting the updated drawable from DevicePolicyManagerService.", + e); + return ParcelableResource.loadDefaultDrawable(defaultDrawableLoader); + } + } + return ParcelableResource.loadDefaultDrawable(defaultDrawableLoader); + } + + /** + * 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. + * + * @param drawableId The drawable ID to get the updated resource for. + * @param drawableStyle The drawable style to use. + * @param density The desired screen density indicated by the resource as + * found in {@link DisplayMetrics}. A value of 0 means to use the + * density returned from {@link Resources#getConfiguration()}. + * @param defaultDrawableLoader To get the default drawable if no updated drawable was set for + * the provided params. + */ + @NonNull + public Drawable getDrawableForDensity( + @DevicePolicyResources.UpdatableDrawableId int drawableId, + @DevicePolicyResources.UpdatableDrawableStyle int drawableStyle, + int density, + @NonNull Callable<Drawable> defaultDrawableLoader) { + return getDrawableForDensity( + drawableId, + drawableStyle, + UNDEFINED, + density, + defaultDrawableLoader); + } + + /** + * 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. + * + * @param drawableId The drawable ID to get the updated resource for. + * @param drawableStyle The drawable style to use. + * @param drawableSource The source for the caller. + * @param density The desired screen density indicated by the resource as + * found in {@link DisplayMetrics}. A value of 0 means to use the + * density returned from {@link Resources#getConfiguration()}. + * @param defaultDrawableLoader To get the default drawable if no updated drawable was set for + * the provided params. + */ + @NonNull + public Drawable getDrawableForDensity( + @DevicePolicyResources.UpdatableDrawableId int drawableId, + @DevicePolicyResources.UpdatableDrawableStyle int drawableStyle, + @DevicePolicyResources.UpdatableDrawableSource int drawableSource, + int density, + @NonNull Callable<Drawable> defaultDrawableLoader) { + Objects.requireNonNull(defaultDrawableLoader, "defaultDrawableLoader can't be null"); + if (drawableId == INVALID_ID) { + return ParcelableResource.loadDefaultDrawable(defaultDrawableLoader); + } + if (mService != null) { + try { + ParcelableResource resource = mService.getDrawable( + drawableId, drawableStyle, drawableSource); + if (resource == null) { + return ParcelableResource.loadDefaultDrawable( + defaultDrawableLoader); + } + return resource.getDrawable(mContext, density, defaultDrawableLoader); + } catch (RemoteException e) { + Log.e( + TAG, + "Error getting the updated drawable from DevicePolicyManagerService.", + e); + return ParcelableResource.loadDefaultDrawable(defaultDrawableLoader); + } + } + 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.INVALID_ID)) { + 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.INVALID_ID)) { + 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 new file mode 100644 index 000000000000..e5d41a4a105a --- /dev/null +++ b/core/java/android/app/admin/DevicePolicyResources.java @@ -0,0 +1,1165 @@ +/* + * 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 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.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_PAUSED_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Launcher.WORK_PROFILE_PAUSE_BUTTON; +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; +import java.util.HashSet; +import java.util.Set; + +/** + * Class containing the required identifiers to update device management resources. + * + * <p>See {@link DevicePolicyManager#getDrawable} and + * {@code DevicePolicyManager#getString}. + */ +public final class DevicePolicyResources { + + /** + * Resource identifiers used to update device management-related system drawable resources. + * + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = { + Drawable.INVALID_ID, + Drawable.WORK_PROFILE_ICON_BADGE, + Drawable.WORK_PROFILE_ICON, + Drawable.WORK_PROFILE_OFF_ICON, + Drawable.WORK_PROFILE_USER_ICON + }) + public @interface UpdatableDrawableId {} + + /** + * Identifiers to specify the desired style for the updatable device management system + * resource. + * + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = { + Drawable.Style.SOLID_COLORED, + Drawable.Style.SOLID_NOT_COLORED, + Drawable.Style.OUTLINE, + }) + public @interface UpdatableDrawableStyle {} + + /** + * Identifiers to specify the location if the updatable device management system resource. + * + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = { + Drawable.Source.UNDEFINED, + Drawable.Source.NOTIFICATION, + Drawable.Source.PROFILE_SWITCH_ANIMATION, + Drawable.Source.HOME_WIDGET, + Drawable.Source.LAUNCHER_OFF_BUTTON, + Drawable.Source.QUICK_SETTINGS, + Drawable.Source.STATUS_BAR + }) + 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, 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 + }) + public @interface UpdatableStringId { + } + + /** + * Class containing the identifiers used to update device management-related system drawable. + */ + public static final class Drawable { + + private Drawable() { + } + + /** + * An ID for any drawable that can't be updated. + */ + public static final int INVALID_ID = -1; + + /** + * Specifically used to badge work profile app icons. + */ + public static final int WORK_PROFILE_ICON_BADGE = 0; + + /** + * General purpose work profile icon (i.e. generic icon badging). For badging app icons + * specifically, see {@link #WORK_PROFILE_ICON_BADGE}. + */ + public static final int WORK_PROFILE_ICON = 1; + + /** + * General purpose icon representing the work profile off state. + */ + public static final int WORK_PROFILE_OFF_ICON = 2; + + /** + * General purpose icon for the work profile user avatar. + */ + public static final int WORK_PROFILE_USER_ICON = 3; + + /** + * @hide + */ + public static final Set<Integer> UPDATABLE_DRAWABLE_IDS = buildDrawablesSet(); + + private static Set<Integer> buildDrawablesSet() { + Set<Integer> drawables = new HashSet<>(); + drawables.add(WORK_PROFILE_ICON_BADGE); + drawables.add(WORK_PROFILE_ICON); + drawables.add(WORK_PROFILE_OFF_ICON); + drawables.add(WORK_PROFILE_USER_ICON); + return drawables; + } + + /** + * Class containing the source identifiers used to update device management-related system + * drawable. + */ + public static final class Source { + + private Source() { + } + + /** + * A source identifier indicating that the updatable resource is used in a generic + * undefined location. + */ + public static final int UNDEFINED = -1; + + /** + * A source identifier indicating that the updatable drawable is used in notifications. + */ + public static final int NOTIFICATION = 0; + + /** + * A source identifier indicating that the updatable drawable is used in a cross + * profile switching animation. + */ + public static final int PROFILE_SWITCH_ANIMATION = 1; + + /** + * A source identifier indicating that the updatable drawable is used in a work + * profile home screen widget. + */ + public static final int HOME_WIDGET = 2; + + /** + * A source identifier indicating that the updatable drawable is used in the launcher + * turn off work button. + */ + public static final int LAUNCHER_OFF_BUTTON = 3; + + /** + * A source identifier indicating that the updatable drawable is used in quick settings. + */ + public static final int QUICK_SETTINGS = 4; + + /** + * A source identifier indicating that the updatable drawable is used in the status bar. + */ + public static final int STATUS_BAR = 5; + + /** + * @hide + */ + public static final Set<Integer> UPDATABLE_DRAWABLE_SOURCES = buildSourcesSet(); + + private static Set<Integer> buildSourcesSet() { + Set<Integer> sources = new HashSet<>(); + sources.add(UNDEFINED); + sources.add(NOTIFICATION); + sources.add(PROFILE_SWITCH_ANIMATION); + sources.add(HOME_WIDGET); + sources.add(LAUNCHER_OFF_BUTTON); + sources.add(QUICK_SETTINGS); + sources.add(STATUS_BAR); + return sources; + } + } + + /** + * Class containing the style identifiers used to update device management-related system + * drawable. + */ + @SuppressLint("StaticUtils") + public static final class Style { + + private Style() { + } + + /** + * A style identifier indicating that the updatable drawable should use the default + * style. + */ + public static final int DEFAULT = -1; + + /** + * A style identifier indicating that the updatable drawable has a solid color fill. + */ + public static final int SOLID_COLORED = 0; + + /** + * A style identifier indicating that the updatable drawable has a solid non-colored + * fill. + */ + public static final int SOLID_NOT_COLORED = 1; + + /** + * A style identifier indicating that the updatable drawable is an outline. + */ + public static final int OUTLINE = 2; + + /** + * @hide + */ + public static final Set<Integer> UPDATABLE_DRAWABLE_STYLES = buildStylesSet(); + + private static Set<Integer> buildStylesSet() { + Set<Integer> styles = new HashSet<>(); + styles.add(DEFAULT); + styles.add(SOLID_COLORED); + styles.add(SOLID_NOT_COLORED); + styles.add(OUTLINE); + return styles; + } + } + } + + /** + * 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 INVALID_ID = "INVALID_ID"; + + /** + * @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()); + 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; + } + } + } +} 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 b9fcdf537806..f663c17c7884 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -17,6 +17,9 @@ 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; import android.app.IServiceConnection; @@ -530,5 +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> 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.aidl b/core/java/android/app/admin/ParcelableResource.aidl new file mode 100644 index 000000000000..dd2b9751921d --- /dev/null +++ b/core/java/android/app/admin/ParcelableResource.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.app.admin; + +parcelable ParcelableResource; diff --git a/core/java/android/app/admin/ParcelableResource.java b/core/java/android/app/admin/ParcelableResource.java new file mode 100644 index 000000000000..dba362820b1d --- /dev/null +++ b/core/java/android/app/admin/ParcelableResource.java @@ -0,0 +1,381 @@ +/* + * 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 static java.util.Objects.requireNonNull; + +import android.annotation.AnyRes; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Slog; +import android.util.TypedXmlPullParser; +import android.util.TypedXmlSerializer; + +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; +import java.util.concurrent.Callable; + +/** + * Used to store the required information to load a resource that was updated using + * {@link DevicePolicyManager#setDrawables} and {@link DevicePolicyManager#setStrings}. + * + * @hide + */ +public final class ParcelableResource implements Parcelable { + + private static String TAG = "DevicePolicyManager"; + + private static final String ATTR_RESOURCE_ID = "resource-id"; + private static final String ATTR_PACKAGE_NAME = "package-name"; + private static final String ATTR_RESOURCE_NAME = "resource-name"; + 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_STRING + }) + public @interface ResourceType {} + + private final int mResourceId; + @NonNull private final String mPackageName; + @NonNull private final String mResourceName; + private final int mResourceType; + + /** + * + * Creates a {@code ParcelableDevicePolicyResource} for the given {@code resourceId} and + * verifies that it exists in the package of the given {@code context}. + * + * @param context for the package containing the {@code resourceId} to use as the updated + * resource + * @param resourceId of the resource to use as an updated resource + * @param resourceType see {@link ResourceType} + */ + 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; + this.mPackageName = context.getResources().getResourcePackageName(resourceId); + this.mResourceName = context.getResources().getResourceName(resourceId); + this.mResourceType = resourceType; + } + + /** + * Creates a {@code ParcelableDevicePolicyResource} with the given params, this DOES NOT make + * any verifications on whether the given {@code resourceId} actually exists. + */ + private ParcelableResource( + @AnyRes int resourceId, @NonNull String packageName, @NonNull String resourceName, + @ResourceType int resourceType) { + this.mResourceId = resourceId; + this.mPackageName = requireNonNull(packageName); + this.mResourceName = requireNonNull(resourceName); + this.mResourceType = resourceType; + } + + private static void verifyResourceExistsInCallingPackage( + Context context, @AnyRes int resourceId, @ResourceType int resourceType) + throws IllegalStateException, IllegalArgumentException { + switch (resourceType) { + case RESOURCE_TYPE_DRAWABLE: + if (!hasDrawableInCallingPackage(context, resourceId)) { + 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 ResourceType: " + resourceType); + } + } + + private static boolean hasDrawableInCallingPackage(Context context, @AnyRes int resourceId) { + try { + 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; + } + } + + public @AnyRes int getResourceId() { + return mResourceId; + } + + @NonNull + public String getPackageName() { + return mPackageName; + } + + @NonNull + public String getResourceName() { + return mResourceName; + } + + public int getResourceType() { + return mResourceType; + } + + /** + * Loads the drawable with id {@code mResourceId} from {@code mPackageName} using the provided + * {@code density} and {@link Resources.Theme} and {@link Resources#getConfiguration} of the + * provided {@code context}. + * + * <p>Returns the default drawable by calling the {@code defaultDrawableLoader} if the updated + * drawable was not found or could not be loaded.</p> + */ + @NonNull + public Drawable getDrawable( + Context context, + int density, + @NonNull Callable<Drawable> defaultDrawableLoader) { + // 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.getDrawableForDensity(mResourceId, density, context.getTheme()); + } catch (PackageManager.NameNotFoundException | RuntimeException e) { + Slog.e(TAG, "Unable to load drawable resource " + mResourceName, e); + return loadDefaultDrawable(defaultDrawableLoader); + } + } + + /** + * 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(); + ApplicationInfo ai = pm.getApplicationInfo( + mPackageName, + PackageManager.MATCH_UNINSTALLED_PACKAGES + | PackageManager.GET_SHARED_LIBRARY_FILES); + return pm.getResourcesForApplication(ai, context.getResources().getConfiguration()); + } + + private void verifyResourceName(Resources resources) throws IllegalStateException { + String name = resources.getResourceName(mResourceId); + if (!mResourceName.equals(name)) { + throw new IllegalStateException(String.format("Current resource name %s for resource id" + + " %d has changed from the previously stored resource name %s.", + name, mResourceId, mResourceName)); + } + } + + /** + * returns the {@link Drawable} loaded from calling {@code 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 { + 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 string: ", e); + } + } + + /** + * Writes the content of the current {@code ParcelableDevicePolicyResource} to the xml file + * specified by {@code xmlSerializer}. + */ + public void writeToXmlFile(TypedXmlSerializer xmlSerializer) throws IOException { + xmlSerializer.attributeInt(/* namespace= */ null, ATTR_RESOURCE_ID, mResourceId); + xmlSerializer.attribute(/* namespace= */ null, ATTR_PACKAGE_NAME, mPackageName); + xmlSerializer.attribute(/* namespace= */ null, ATTR_RESOURCE_NAME, mResourceName); + xmlSerializer.attributeInt(/* namespace= */ null, ATTR_RESOURCE_TYPE, mResourceType); + } + + /** + * Creates a new {@code ParcelableDevicePolicyResource} using the content of + * {@code xmlPullParser}. + */ + public static ParcelableResource createFromXml(TypedXmlPullParser xmlPullParser) + throws XmlPullParserException, IOException { + int resourceId = xmlPullParser.getAttributeInt(/* namespace= */ null, ATTR_RESOURCE_ID); + String packageName = xmlPullParser.getAttributeValue( + /* namespace= */ null, ATTR_PACKAGE_NAME); + String resourceName = xmlPullParser.getAttributeValue( + /* namespace= */ null, ATTR_RESOURCE_NAME); + int resourceType = xmlPullParser.getAttributeInt( + /* namespace= */ null, ATTR_RESOURCE_TYPE); + + return new ParcelableResource( + resourceId, packageName, resourceName, resourceType); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ParcelableResource other = (ParcelableResource) o; + return mResourceId == other.mResourceId + && mPackageName.equals(other.mPackageName) + && mResourceName.equals(other.mResourceName) + && mResourceType == other.mResourceType; + } + + @Override + public int hashCode() { + return Objects.hash(mResourceId, mPackageName, mResourceName, mResourceType); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mResourceId); + dest.writeString(mPackageName); + dest.writeString(mResourceName); + dest.writeInt(mResourceType); + } + + public static final @NonNull Creator<ParcelableResource> CREATOR = + new Creator<ParcelableResource>() { + @Override + public ParcelableResource createFromParcel(Parcel in) { + int resourceId = in.readInt(); + String packageName = in.readString(); + String resourceName = in.readString(); + int resourceType = in.readInt(); + + return new ParcelableResource( + resourceId, packageName, resourceName, resourceType); + } + + @Override + public ParcelableResource[] newArray(int size) { + return new ParcelableResource[size]; + } + }; +} 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/services/tests/apexsystemservices/services/src/com/android/server/testing/FakeApexSystemService2.java b/core/java/android/app/ambientcontext/IAmbientContextEventObserver.aidl index e83343b9c996..9032fe1ee045 100644 --- a/services/tests/apexsystemservices/services/src/com/android/server/testing/FakeApexSystemService2.java +++ b/core/java/android/app/ambientcontext/IAmbientContextEventObserver.aidl @@ -14,28 +14,17 @@ * limitations under the License. */ -package com.android.server.testing; +package android.app.ambientcontext; -import android.content.Context; -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.android.server.SystemService; +import android.app.PendingIntent; +import android.app.ambientcontext.AmbientContextEventRequest; /** - * A fake system service that just logs when it is started. + * Interface for an AmbientContextEventManager that provides access to AmbientContextEvents. + * + * @hide */ -public class FakeApexSystemService2 extends SystemService { - - private static final String TAG = "FakeApexSystemService"; - - public FakeApexSystemService2(@NonNull Context context) { - super(context); - } - - @Override - public void onStart() { - Log.d(TAG, "FakeApexSystemService2 onStart"); - } -} +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/communal/CommunalManager.java b/core/java/android/app/communal/CommunalManager.java deleted file mode 100644 index c7368ade2dcc..000000000000 --- a/core/java/android/app/communal/CommunalManager.java +++ /dev/null @@ -1,62 +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 android.app.communal; - -import android.Manifest; -import android.annotation.RequiresFeature; -import android.annotation.RequiresPermission; -import android.annotation.SystemApi; -import android.annotation.SystemService; -import android.annotation.TestApi; -import android.content.Context; -import android.content.pm.PackageManager; -import android.os.RemoteException; - -/** - * System private class for talking with the - * {@link com.android.server.communal.CommunalManagerService} that handles communal mode state. - * - * @hide - */ -@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) -@SystemService(Context.COMMUNAL_SERVICE) -@RequiresFeature(PackageManager.FEATURE_COMMUNAL_MODE) -public final class CommunalManager { - private final ICommunalManager mService; - - /** @hide */ - public CommunalManager(ICommunalManager service) { - mService = service; - } - - /** - * Updates whether or not the communal view is currently showing over the lockscreen. - * - * @param isShowing Whether communal view is showing. - * - * @hide - */ - @TestApi - @RequiresPermission(Manifest.permission.WRITE_COMMUNAL_STATE) - public void setCommunalViewShowing(boolean isShowing) { - try { - mService.setCommunalViewShowing(isShowing); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } -} diff --git a/core/java/android/app/communal/OWNERS b/core/java/android/app/communal/OWNERS deleted file mode 100644 index b02883da854a..000000000000 --- a/core/java/android/app/communal/OWNERS +++ /dev/null @@ -1,4 +0,0 @@ -brycelee@google.com -justinkoh@google.com -lusilva@google.com -xilei@google.com
\ No newline at end of file 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 9985cc02965b..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); @@ -36,5 +37,5 @@ interface ITrustManager { boolean isDeviceSecure(int userId); boolean isTrustUsuallyManaged(int userId); void unlockedByBiometricForUser(int userId, in BiometricSourceType source); - void clearAllBiometricRecognized(in BiometricSourceType target); + void clearAllBiometricRecognized(in BiometricSourceType target, int unlockedUser); } 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 177de835554b..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(); } @@ -217,9 +237,9 @@ public class TrustManager { * Clears authentication by the specified biometric type for all users. */ @RequiresPermission(Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE) - public void clearAllBiometricRecognized(BiometricSourceType source) { + public void clearAllBiometricRecognized(BiometricSourceType source, int unlockedUser) { try { - mService.clearAllBiometricRecognized(source); + mService.clearAllBiometricRecognized(source, unlockedUser); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -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/bluetooth/Attributable.java b/core/java/android/bluetooth/Attributable.java deleted file mode 100644 index d9acbe3eefb9..000000000000 --- a/core/java/android/bluetooth/Attributable.java +++ /dev/null @@ -1,55 +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 android.bluetooth; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.content.AttributionSource; - -import java.util.List; - -/** - * Marker interface for a class which can have an {@link AttributionSource} - * assigned to it; these are typically {@link android.os.Parcelable} classes - * which need to be updated after crossing Binder transaction boundaries. - * - * @hide - */ -public interface Attributable { - void setAttributionSource(@NonNull AttributionSource attributionSource); - - static @Nullable <T extends Attributable> T setAttributionSource( - @Nullable T attributable, - @NonNull AttributionSource attributionSource) { - if (attributable != null) { - attributable.setAttributionSource(attributionSource); - } - return attributable; - } - - static @Nullable <T extends Attributable> List<T> setAttributionSource( - @Nullable List<T> attributableList, - @NonNull AttributionSource attributionSource) { - if (attributableList != null) { - final int size = attributableList.size(); - for (int i = 0; i < size; i++) { - setAttributionSource(attributableList.get(i), attributionSource); - } - } - return attributableList; - } -} diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java deleted file mode 100644 index 8b9cec17a196..000000000000 --- a/core/java/android/bluetooth/BluetoothA2dp.java +++ /dev/null @@ -1,1149 +0,0 @@ -/* - * Copyright (C) 2008 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.bluetooth; - -import static android.bluetooth.BluetoothUtils.getSyncTimeout; - -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.RequiresNoPermission; -import android.annotation.RequiresPermission; -import android.annotation.SdkConstant; -import android.annotation.SdkConstant.SdkConstantType; -import android.annotation.SystemApi; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; -import android.compat.annotation.UnsupportedAppUsage; -import android.content.AttributionSource; -import android.content.Context; -import android.os.Build; -import android.os.IBinder; -import android.os.ParcelUuid; -import android.os.RemoteException; -import android.util.Log; - -import com.android.modules.utils.SynchronousResultReceiver; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeoutException; - - -/** - * This class provides the public APIs to control the Bluetooth A2DP - * profile. - * - * <p>BluetoothA2dp is a proxy object for controlling the Bluetooth A2DP - * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get - * the BluetoothA2dp proxy object. - * - * <p> Android only supports one connected Bluetooth A2dp device at a time. - * Each method is protected with its appropriate permission. - */ -public final class BluetoothA2dp implements BluetoothProfile { - private static final String TAG = "BluetoothA2dp"; - private static final boolean DBG = true; - private static final boolean VDBG = false; - - /** - * Intent used to broadcast the change in connection state of the A2DP - * profile. - * - * <p>This intent will have 3 extras: - * <ul> - * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> - * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> - * </ul> - * - * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of - * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, - * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_CONNECTION_STATE_CHANGED = - "android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED"; - - /** - * Intent used to broadcast the change in the Playing state of the A2DP - * profile. - * - * <p>This intent will have 3 extras: - * <ul> - * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> - * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> - * </ul> - * - * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of - * {@link #STATE_PLAYING}, {@link #STATE_NOT_PLAYING}, - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_PLAYING_STATE_CHANGED = - "android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED"; - - /** @hide */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_AVRCP_CONNECTION_STATE_CHANGED = - "android.bluetooth.a2dp.profile.action.AVRCP_CONNECTION_STATE_CHANGED"; - - /** - * Intent used to broadcast the selection of a connected device as active. - * - * <p>This intent will have one extra: - * <ul> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. It can - * be null if no device is active. </li> - * </ul> - * - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - @UnsupportedAppUsage(trackingBug = 171933273) - public static final String ACTION_ACTIVE_DEVICE_CHANGED = - "android.bluetooth.a2dp.profile.action.ACTIVE_DEVICE_CHANGED"; - - /** - * Intent used to broadcast the change in the Audio Codec state of the - * A2DP Source profile. - * - * <p>This intent will have 2 extras: - * <ul> - * <li> {@link BluetoothCodecStatus#EXTRA_CODEC_STATUS} - The codec status. </li> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device if the device is currently - * connected, otherwise it is not included.</li> - * </ul> - * - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - @UnsupportedAppUsage(trackingBug = 181103983) - public static final String ACTION_CODEC_CONFIG_CHANGED = - "android.bluetooth.a2dp.profile.action.CODEC_CONFIG_CHANGED"; - - /** - * A2DP sink device is streaming music. This state can be one of - * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of - * {@link #ACTION_PLAYING_STATE_CHANGED} intent. - */ - public static final int STATE_PLAYING = 10; - - /** - * A2DP sink device is NOT streaming music. This state can be one of - * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of - * {@link #ACTION_PLAYING_STATE_CHANGED} intent. - */ - public static final int STATE_NOT_PLAYING = 11; - - /** @hide */ - @IntDef(prefix = "OPTIONAL_CODECS_", value = { - OPTIONAL_CODECS_SUPPORT_UNKNOWN, - OPTIONAL_CODECS_NOT_SUPPORTED, - OPTIONAL_CODECS_SUPPORTED - }) - @Retention(RetentionPolicy.SOURCE) - public @interface OptionalCodecsSupportStatus {} - - /** - * We don't have a stored preference for whether or not the given A2DP sink device supports - * optional codecs. - * - * @hide - */ - @SystemApi - public static final int OPTIONAL_CODECS_SUPPORT_UNKNOWN = -1; - - /** - * The given A2DP sink device does not support optional codecs. - * - * @hide - */ - @SystemApi - public static final int OPTIONAL_CODECS_NOT_SUPPORTED = 0; - - /** - * The given A2DP sink device does support optional codecs. - * - * @hide - */ - @SystemApi - public static final int OPTIONAL_CODECS_SUPPORTED = 1; - - /** @hide */ - @IntDef(prefix = "OPTIONAL_CODECS_PREF_", value = { - OPTIONAL_CODECS_PREF_UNKNOWN, - OPTIONAL_CODECS_PREF_DISABLED, - OPTIONAL_CODECS_PREF_ENABLED - }) - @Retention(RetentionPolicy.SOURCE) - public @interface OptionalCodecsPreferenceStatus {} - - /** - * We don't have a stored preference for whether optional codecs should be enabled or - * disabled for the given A2DP device. - * - * @hide - */ - @SystemApi - public static final int OPTIONAL_CODECS_PREF_UNKNOWN = -1; - - /** - * Optional codecs should be disabled for the given A2DP device. - * - * @hide - */ - @SystemApi - public static final int OPTIONAL_CODECS_PREF_DISABLED = 0; - - /** - * Optional codecs should be enabled for the given A2DP device. - * - * @hide - */ - @SystemApi - public static final int OPTIONAL_CODECS_PREF_ENABLED = 1; - - /** @hide */ - @IntDef(prefix = "DYNAMIC_BUFFER_SUPPORT_", value = { - DYNAMIC_BUFFER_SUPPORT_NONE, - DYNAMIC_BUFFER_SUPPORT_A2DP_OFFLOAD, - DYNAMIC_BUFFER_SUPPORT_A2DP_SOFTWARE_ENCODING - }) - @Retention(RetentionPolicy.SOURCE) - public @interface Type {} - - /** - * Indicates the supported type of Dynamic Audio Buffer is not supported. - * - * @hide - */ - @SystemApi - public static final int DYNAMIC_BUFFER_SUPPORT_NONE = 0; - - /** - * Indicates the supported type of Dynamic Audio Buffer is A2DP offload. - * - * @hide - */ - @SystemApi - public static final int DYNAMIC_BUFFER_SUPPORT_A2DP_OFFLOAD = 1; - - /** - * Indicates the supported type of Dynamic Audio Buffer is A2DP software encoding. - * - * @hide - */ - @SystemApi - public static final int DYNAMIC_BUFFER_SUPPORT_A2DP_SOFTWARE_ENCODING = 2; - - private final BluetoothAdapter mAdapter; - private final AttributionSource mAttributionSource; - private final BluetoothProfileConnector<IBluetoothA2dp> mProfileConnector = - new BluetoothProfileConnector(this, BluetoothProfile.A2DP, "BluetoothA2dp", - IBluetoothA2dp.class.getName()) { - @Override - public IBluetoothA2dp getServiceInterface(IBinder service) { - return IBluetoothA2dp.Stub.asInterface(service); - } - }; - - /** - * Create a BluetoothA2dp proxy object for interacting with the local - * Bluetooth A2DP service. - */ - /* package */ BluetoothA2dp(Context context, ServiceListener listener, - BluetoothAdapter adapter) { - mAdapter = adapter; - mAttributionSource = adapter.getAttributionSource(); - mProfileConnector.connect(context, listener); - } - - @UnsupportedAppUsage - /*package*/ void close() { - mProfileConnector.disconnect(); - } - - private IBluetoothA2dp getService() { - return mProfileConnector.getService(); - } - - @Override - public void finalize() { - // The empty finalize needs to be kept or the - // cts signature tests would fail. - } - - /** - * Initiate connection to a profile of the remote Bluetooth device. - * - * <p> This API returns false in scenarios like the profile on the - * device is already connected or Bluetooth is not turned on. - * When this API returns true, it is guaranteed that - * connection state intent for the profile will be broadcasted with - * the state. Users can get the connection state of the profile - * from this intent. - * - * - * @param device Remote Bluetooth Device - * @return false on immediate error, true otherwise - * @hide - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @UnsupportedAppUsage - public boolean connect(BluetoothDevice device) { - if (DBG) log("connect(" + device + ")"); - final IBluetoothA2dp service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.connectWithAttribution(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Initiate disconnection from a profile - * - * <p> This API will return false in scenarios like the profile on the - * Bluetooth device is not in connected state etc. When this API returns, - * true, it is guaranteed that the connection state change - * intent will be broadcasted with the state. Users can get the - * disconnection state of the profile from this intent. - * - * <p> If the disconnection is initiated by a remote device, the state - * will transition from {@link #STATE_CONNECTED} to - * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the - * host (local) device the state will transition from - * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to - * state {@link #STATE_DISCONNECTED}. The transition to - * {@link #STATE_DISCONNECTING} can be used to distinguish between the - * two scenarios. - * - * - * @param device Remote Bluetooth Device - * @return false on immediate error, true otherwise - * @hide - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @UnsupportedAppUsage - public boolean disconnect(BluetoothDevice device) { - if (DBG) log("disconnect(" + device + ")"); - final IBluetoothA2dp service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.disconnectWithAttribution(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getConnectedDevices() { - if (VDBG) log("getConnectedDevices()"); - final IBluetoothA2dp service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getConnectedDevicesWithAttribution(mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { - if (VDBG) log("getDevicesMatchingStates()"); - final IBluetoothA2dp service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getDevicesMatchingConnectionStatesWithAttribution(states, - mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public @BtProfileState int getConnectionState(BluetoothDevice device) { - if (VDBG) log("getState(" + device + ")"); - final IBluetoothA2dp service = getService(); - final int defaultValue = BluetoothProfile.STATE_DISCONNECTED; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionStateWithAttribution(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Select a connected device as active. - * - * The active device selection is per profile. An active device's - * purpose is profile-specific. For example, A2DP audio streaming - * is to the active A2DP Sink device. If a remote device is not - * connected, it cannot be selected as active. - * - * <p> This API returns false in scenarios like the profile on the - * device is not connected or Bluetooth is not turned on. - * When this API returns true, it is guaranteed that the - * {@link #ACTION_ACTIVE_DEVICE_CHANGED} intent will be broadcasted - * with the active device. - * - * @param device the remote Bluetooth device. Could be null to clear - * the active device and stop streaming audio to a Bluetooth device. - * @return false on immediate error, true otherwise - * @hide - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @UnsupportedAppUsage(trackingBug = 171933273) - public boolean setActiveDevice(@Nullable BluetoothDevice device) { - if (DBG) log("setActiveDevice(" + device + ")"); - final IBluetoothA2dp service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && ((device == null) || isValidDevice(device))) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.setActiveDevice(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the connected device that is active. - * - * @return the connected device that is active or null if no device - * is active - * @hide - */ - @UnsupportedAppUsage(trackingBug = 171933273) - @Nullable - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothDevice getActiveDevice() { - if (VDBG) log("getActiveDevice()"); - final IBluetoothA2dp service = getService(); - final BluetoothDevice defaultValue = null; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<BluetoothDevice> recv = - new SynchronousResultReceiver(); - service.getActiveDevice(mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Set priority of the profile - * - * <p> The device should already be paired. - * Priority can be one of {@link #PRIORITY_ON} or {@link #PRIORITY_OFF} - * - * @param device Paired bluetooth device - * @param priority - * @return true if priority is set, false on error - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setPriority(BluetoothDevice device, int priority) { - if (DBG) log("setPriority(" + device + ", " + priority + ")"); - return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority)); - } - - /** - * Set connection policy of the profile - * - * <p> The device should already be paired. - * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED}, - * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Paired bluetooth device - * @param connectionPolicy is the connection policy to set to for this profile - * @return true if connectionPolicy is set, false on error - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setConnectionPolicy(@NonNull BluetoothDevice device, - @ConnectionPolicy int connectionPolicy) { - if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); - final IBluetoothA2dp service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device) - && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN - || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the priority of the profile. - * - * <p> The priority can be any of: - * {@link #PRIORITY_OFF}, {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED} - * - * @param device Bluetooth device - * @return priority of the device - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) - public int getPriority(BluetoothDevice device) { - if (VDBG) log("getPriority(" + device + ")"); - return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device)); - } - - /** - * Get the connection policy of the profile. - * - * <p> The connection policy can be any of: - * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, - * {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Bluetooth device - * @return connection policy of the device - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) { - if (VDBG) log("getConnectionPolicy(" + device + ")"); - final IBluetoothA2dp service = getService(); - final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionPolicy(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Checks if Avrcp device supports the absolute volume feature. - * - * @return true if device supports absolute volume - * @hide - */ - @RequiresNoPermission - public boolean isAvrcpAbsoluteVolumeSupported() { - if (DBG) Log.d(TAG, "isAvrcpAbsoluteVolumeSupported"); - final IBluetoothA2dp service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.isAvrcpAbsoluteVolumeSupported(recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Tells remote device to set an absolute volume. Only if absolute volume is supported - * - * @param volume Absolute volume to be set on AVRCP side - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public void setAvrcpAbsoluteVolume(int volume) { - if (DBG) Log.d(TAG, "setAvrcpAbsoluteVolume"); - final IBluetoothA2dp service = getService(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - service.setAvrcpAbsoluteVolume(volume, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - } - - /** - * Check if A2DP profile is streaming music. - * - * @param device BluetoothDevice device - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean isA2dpPlaying(BluetoothDevice device) { - if (DBG) log("isA2dpPlaying(" + device + ")"); - final IBluetoothA2dp service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.isA2dpPlaying(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * This function checks if the remote device is an AVCRP - * target and thus whether we should send volume keys - * changes or not. - * - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean shouldSendVolumeKeys(BluetoothDevice device) { - if (isEnabled() && isValidDevice(device)) { - ParcelUuid[] uuids = device.getUuids(); - if (uuids == null) return false; - - for (ParcelUuid uuid : uuids) { - if (uuid.equals(BluetoothUuid.AVRCP_TARGET)) { - return true; - } - } - } - return false; - } - - /** - * Gets the current codec status (configuration and capability). - * - * @param device the remote Bluetooth device. - * @return the current codec status - * @hide - */ - @UnsupportedAppUsage(trackingBug = 181103983) - @Nullable - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothCodecStatus getCodecStatus(@NonNull BluetoothDevice device) { - if (DBG) Log.d(TAG, "getCodecStatus(" + device + ")"); - verifyDeviceNotNull(device, "getCodecStatus"); - final IBluetoothA2dp service = getService(); - final BluetoothCodecStatus defaultValue = null; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<BluetoothCodecStatus> recv = - new SynchronousResultReceiver(); - service.getCodecStatus(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Sets the codec configuration preference. - * - * @param device the remote Bluetooth device. - * @param codecConfig the codec configuration preference - * @hide - */ - @UnsupportedAppUsage(trackingBug = 181103983) - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public void setCodecConfigPreference(@NonNull BluetoothDevice device, - @NonNull BluetoothCodecConfig codecConfig) { - if (DBG) Log.d(TAG, "setCodecConfigPreference(" + device + ")"); - verifyDeviceNotNull(device, "setCodecConfigPreference"); - if (codecConfig == null) { - Log.e(TAG, "setCodecConfigPreference: Codec config can't be null"); - throw new IllegalArgumentException("codecConfig cannot be null"); - } - final IBluetoothA2dp service = getService(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - service.setCodecConfigPreference(device, codecConfig, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - } - - /** - * Enables the optional codecs. - * - * @param device the remote Bluetooth device. - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public void enableOptionalCodecs(@NonNull BluetoothDevice device) { - if (DBG) Log.d(TAG, "enableOptionalCodecs(" + device + ")"); - verifyDeviceNotNull(device, "enableOptionalCodecs"); - enableDisableOptionalCodecs(device, true); - } - - /** - * Disables the optional codecs. - * - * @param device the remote Bluetooth device. - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public void disableOptionalCodecs(@NonNull BluetoothDevice device) { - if (DBG) Log.d(TAG, "disableOptionalCodecs(" + device + ")"); - verifyDeviceNotNull(device, "disableOptionalCodecs"); - enableDisableOptionalCodecs(device, false); - } - - /** - * Enables or disables the optional codecs. - * - * @param device the remote Bluetooth device. - * @param enable if true, enable the optional codecs, other disable them - */ - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - private void enableDisableOptionalCodecs(BluetoothDevice device, boolean enable) { - final IBluetoothA2dp service = getService(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - if (enable) { - service.enableOptionalCodecs(device, mAttributionSource); - } else { - service.disableOptionalCodecs(device, mAttributionSource); - } - } catch (RemoteException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - } - - /** - * Returns whether this device supports optional codecs. - * - * @param device The device to check - * @return one of OPTIONAL_CODECS_SUPPORT_UNKNOWN, OPTIONAL_CODECS_NOT_SUPPORTED, or - * OPTIONAL_CODECS_SUPPORTED. - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @OptionalCodecsSupportStatus - public int isOptionalCodecsSupported(@NonNull BluetoothDevice device) { - if (DBG) log("isOptionalCodecsSupported(" + device + ")"); - verifyDeviceNotNull(device, "isOptionalCodecsSupported"); - final IBluetoothA2dp service = getService(); - final int defaultValue = OPTIONAL_CODECS_SUPPORT_UNKNOWN; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.supportsOptionalCodecs(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Returns whether this device should have optional codecs enabled. - * - * @param device The device in question. - * @return one of OPTIONAL_CODECS_PREF_UNKNOWN, OPTIONAL_CODECS_PREF_ENABLED, or - * OPTIONAL_CODECS_PREF_DISABLED. - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @OptionalCodecsPreferenceStatus - public int isOptionalCodecsEnabled(@NonNull BluetoothDevice device) { - if (DBG) log("isOptionalCodecsEnabled(" + device + ")"); - verifyDeviceNotNull(device, "isOptionalCodecsEnabled"); - final IBluetoothA2dp service = getService(); - final int defaultValue = OPTIONAL_CODECS_PREF_UNKNOWN; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getOptionalCodecsEnabled(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Sets a persistent preference for whether a given device should have optional codecs enabled. - * - * @param device The device to set this preference for. - * @param value Whether the optional codecs should be enabled for this device. This should be - * one of OPTIONAL_CODECS_PREF_UNKNOWN, OPTIONAL_CODECS_PREF_ENABLED, or - * OPTIONAL_CODECS_PREF_DISABLED. - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public void setOptionalCodecsEnabled(@NonNull BluetoothDevice device, - @OptionalCodecsPreferenceStatus int value) { - if (DBG) log("setOptionalCodecsEnabled(" + device + ")"); - verifyDeviceNotNull(device, "setOptionalCodecsEnabled"); - if (value != BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN - && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED - && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED) { - Log.e(TAG, "Invalid value passed to setOptionalCodecsEnabled: " + value); - return; - } - final IBluetoothA2dp service = getService(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - service.setOptionalCodecsEnabled(device, value, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - } - - /** - * Get the supported type of the Dynamic Audio Buffer. - * <p>Possible return values are - * {@link #DYNAMIC_BUFFER_SUPPORT_NONE}, - * {@link #DYNAMIC_BUFFER_SUPPORT_A2DP_OFFLOAD}, - * {@link #DYNAMIC_BUFFER_SUPPORT_A2DP_SOFTWARE_ENCODING}. - * - * @return supported type of Dynamic Audio Buffer feature - * - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public @Type int getDynamicBufferSupport() { - if (VDBG) log("getDynamicBufferSupport()"); - final IBluetoothA2dp service = getService(); - final int defaultValue = DYNAMIC_BUFFER_SUPPORT_NONE; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getDynamicBufferSupport(mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Return the record of {@link BufferConstraints} object that - * has the default/maximum/minimum audio buffer. This can be used to inform what the controller - * has support for the audio buffer. - * - * @return a record with {@link BufferConstraints} or null if report is unavailable - * or unsupported - * - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public @Nullable BufferConstraints getBufferConstraints() { - if (VDBG) log("getBufferConstraints()"); - final IBluetoothA2dp service = getService(); - final BufferConstraints defaultValue = null; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<BufferConstraints> recv = - new SynchronousResultReceiver(); - service.getBufferConstraints(mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Set Dynamic Audio Buffer Size. - * - * @param codec audio codec - * @param value buffer millis - * @return true to indicate success, or false on immediate error - * - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setBufferLengthMillis(@BluetoothCodecConfig.SourceCodecType int codec, - int value) { - if (VDBG) log("setBufferLengthMillis(" + codec + ", " + value + ")"); - if (value < 0) { - Log.e(TAG, "Trying to set audio buffer length to a negative value: " + value); - return false; - } - final IBluetoothA2dp service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.setBufferLengthMillis(codec, value, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Helper for converting a state to a string. - * - * For debug use only - strings are not internationalized. - * - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) - public static String stateToString(int state) { - switch (state) { - case STATE_DISCONNECTED: - return "disconnected"; - case STATE_CONNECTING: - return "connecting"; - case STATE_CONNECTED: - return "connected"; - case STATE_DISCONNECTING: - return "disconnecting"; - case STATE_PLAYING: - return "playing"; - case STATE_NOT_PLAYING: - return "not playing"; - default: - return "<unknown state " + state + ">"; - } - } - - private boolean isEnabled() { - if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true; - return false; - } - - private void verifyDeviceNotNull(BluetoothDevice device, String methodName) { - if (device == null) { - Log.e(TAG, methodName + ": device param is null"); - throw new IllegalArgumentException("Device cannot be null"); - } - } - - private boolean isValidDevice(BluetoothDevice device) { - if (device == null) return false; - - if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; - return false; - } - - private static void log(String msg) { - Log.d(TAG, msg); - } -} diff --git a/core/java/android/bluetooth/BluetoothA2dpSink.java b/core/java/android/bluetooth/BluetoothA2dpSink.java deleted file mode 100755 index 59416818ceb3..000000000000 --- a/core/java/android/bluetooth/BluetoothA2dpSink.java +++ /dev/null @@ -1,516 +0,0 @@ -/* - * Copyright (C) 2014 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.bluetooth; - -import static android.bluetooth.BluetoothUtils.getSyncTimeout; - -import android.Manifest; -import android.annotation.NonNull; -import android.annotation.RequiresPermission; -import android.annotation.SdkConstant; -import android.annotation.SdkConstant.SdkConstantType; -import android.annotation.SuppressLint; -import android.annotation.SystemApi; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; -import android.compat.annotation.UnsupportedAppUsage; -import android.content.AttributionSource; -import android.content.Context; -import android.os.Build; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.Log; - -import com.android.modules.utils.SynchronousResultReceiver; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeoutException; - -/** - * This class provides the public APIs to control the Bluetooth A2DP Sink - * profile. - * - * <p>BluetoothA2dpSink is a proxy object for controlling the Bluetooth A2DP Sink - * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get - * the BluetoothA2dpSink proxy object. - * - * @hide - */ -@SystemApi -public final class BluetoothA2dpSink implements BluetoothProfile { - private static final String TAG = "BluetoothA2dpSink"; - private static final boolean DBG = true; - private static final boolean VDBG = false; - - /** - * Intent used to broadcast the change in connection state of the A2DP Sink - * profile. - * - * <p>This intent will have 3 extras: - * <ul> - * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> - * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> - * </ul> - * - * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of - * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, - * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. - * - * @hide - */ - @SystemApi - @SuppressLint("ActionValue") - @RequiresBluetoothConnectPermission - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_CONNECTION_STATE_CHANGED = - "android.bluetooth.a2dp-sink.profile.action.CONNECTION_STATE_CHANGED"; - - private final BluetoothAdapter mAdapter; - private final AttributionSource mAttributionSource; - private final BluetoothProfileConnector<IBluetoothA2dpSink> mProfileConnector = - new BluetoothProfileConnector(this, BluetoothProfile.A2DP_SINK, - "BluetoothA2dpSink", IBluetoothA2dpSink.class.getName()) { - @Override - public IBluetoothA2dpSink getServiceInterface(IBinder service) { - return IBluetoothA2dpSink.Stub.asInterface(service); - } - }; - - /** - * Create a BluetoothA2dp proxy object for interacting with the local - * Bluetooth A2DP service. - */ - /* package */ BluetoothA2dpSink(Context context, ServiceListener listener, - BluetoothAdapter adapter) { - mAdapter = adapter; - mAttributionSource = adapter.getAttributionSource(); - mProfileConnector.connect(context, listener); - } - - /*package*/ void close() { - mProfileConnector.disconnect(); - } - - private IBluetoothA2dpSink getService() { - return mProfileConnector.getService(); - } - - @Override - public void finalize() { - close(); - } - - /** - * Initiate connection to a profile of the remote bluetooth device. - * - * <p> Currently, the system supports only 1 connection to the - * A2DP profile. The API will automatically disconnect connected - * devices before connecting. - * - * <p> This API returns false in scenarios like the profile on the - * device is already connected or Bluetooth is not turned on. - * When this API returns true, it is guaranteed that - * connection state intent for the profile will be broadcasted with - * the state. Users can get the connection state of the profile - * from this intent. - * - * @param device Remote Bluetooth Device - * @return false on immediate error, true otherwise - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean connect(BluetoothDevice device) { - if (DBG) log("connect(" + device + ")"); - final IBluetoothA2dpSink service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.connect(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Initiate disconnection from a profile - * - * <p> This API will return false in scenarios like the profile on the - * Bluetooth device is not in connected state etc. When this API returns, - * true, it is guaranteed that the connection state change - * intent will be broadcasted with the state. Users can get the - * disconnection state of the profile from this intent. - * - * <p> If the disconnection is initiated by a remote device, the state - * will transition from {@link #STATE_CONNECTED} to - * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the - * host (local) device the state will transition from - * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to - * state {@link #STATE_DISCONNECTED}. The transition to - * {@link #STATE_DISCONNECTING} can be used to distinguish between the - * two scenarios. - * - * @param device Remote Bluetooth Device - * @return false on immediate error, true otherwise - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean disconnect(BluetoothDevice device) { - if (DBG) log("disconnect(" + device + ")"); - final IBluetoothA2dpSink service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.disconnect(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - * - * @hide - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getConnectedDevices() { - if (VDBG) log("getConnectedDevices()"); - final IBluetoothA2dpSink service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getConnectedDevices(mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - * - * @hide - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { - if (VDBG) log("getDevicesMatchingStates()"); - final IBluetoothA2dpSink service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - * - * @hide - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public int getConnectionState(BluetoothDevice device) { - if (VDBG) log("getConnectionState(" + device + ")"); - final IBluetoothA2dpSink service = getService(); - final int defaultValue = BluetoothProfile.STATE_DISCONNECTED; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionState(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the current audio configuration for the A2DP source device, - * or null if the device has no audio configuration - * - * @param device Remote bluetooth device. - * @return audio configuration for the device, or null - * - * {@see BluetoothAudioConfig} - * - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothAudioConfig getAudioConfig(BluetoothDevice device) { - if (VDBG) log("getAudioConfig(" + device + ")"); - final IBluetoothA2dpSink service = getService(); - final BluetoothAudioConfig defaultValue = null; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<BluetoothAudioConfig> recv = - new SynchronousResultReceiver(); - service.getAudioConfig(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Set priority of the profile - * - * <p> The device should already be paired. - * Priority can be one of {@link #PRIORITY_ON} or {@link #PRIORITY_OFF} - * - * @param device Paired bluetooth device - * @param priority - * @return true if priority is set, false on error - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setPriority(BluetoothDevice device, int priority) { - if (DBG) log("setPriority(" + device + ", " + priority + ")"); - return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority)); - } - - /** - * Set connection policy of the profile - * - * <p> The device should already be paired. - * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED}, - * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Paired bluetooth device - * @param connectionPolicy is the connection policy to set to for this profile - * @return true if connectionPolicy is set, false on error - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED - }) - public boolean setConnectionPolicy(@NonNull BluetoothDevice device, - @ConnectionPolicy int connectionPolicy) { - if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); - final IBluetoothA2dpSink service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device) - && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN - || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the priority of the profile. - * - * <p> The priority can be any of: - * {@link #PRIORITY_OFF}, {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED} - * - * @param device Bluetooth device - * @return priority of the device - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public int getPriority(BluetoothDevice device) { - if (VDBG) log("getPriority(" + device + ")"); - return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device)); - } - - /** - * Get the connection policy of the profile. - * - * <p> The connection policy can be any of: - * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, - * {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Bluetooth device - * @return connection policy of the device - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) { - if (VDBG) log("getConnectionPolicy(" + device + ")"); - final IBluetoothA2dpSink service = getService(); - final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionPolicy(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Check if audio is playing on the bluetooth device (A2DP profile is streaming music). - * - * @param device BluetoothDevice device - * @return true if audio is playing (A2dp is streaming music), false otherwise - * - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean isAudioPlaying(@NonNull BluetoothDevice device) { - if (VDBG) log("isAudioPlaying(" + device + ")"); - final IBluetoothA2dpSink service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.isA2dpPlaying(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Helper for converting a state to a string. - * - * For debug use only - strings are not internationalized. - * - * @hide - */ - public static String stateToString(int state) { - switch (state) { - case STATE_DISCONNECTED: - return "disconnected"; - case STATE_CONNECTING: - return "connecting"; - case STATE_CONNECTED: - return "connected"; - case STATE_DISCONNECTING: - return "disconnecting"; - case BluetoothA2dp.STATE_PLAYING: - return "playing"; - case BluetoothA2dp.STATE_NOT_PLAYING: - return "not playing"; - default: - return "<unknown state " + state + ">"; - } - } - - private boolean isEnabled() { - return mAdapter.getState() == BluetoothAdapter.STATE_ON; - } - - private static boolean isValidDevice(BluetoothDevice device) { - return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); - } - - private static void log(String msg) { - Log.d(TAG, msg); - } -} diff --git a/core/java/android/bluetooth/BluetoothActivityEnergyInfo.java b/core/java/android/bluetooth/BluetoothActivityEnergyInfo.java deleted file mode 100644 index c17a7b4b3dfd..000000000000 --- a/core/java/android/bluetooth/BluetoothActivityEnergyInfo.java +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright (C) 2014 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.bluetooth; - -import android.annotation.ElapsedRealtimeLong; -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.SystemApi; -import android.os.Parcel; -import android.os.Parcelable; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.Collections; -import java.util.List; - -/** - * Record of energy and activity information from controller and - * underlying bt stack state.Timestamp the record with system - * time. - * - * @hide - */ -@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) -public final class BluetoothActivityEnergyInfo implements Parcelable { - private final long mTimestamp; - private int mBluetoothStackState; - private long mControllerTxTimeMs; - private long mControllerRxTimeMs; - private long mControllerIdleTimeMs; - private long mControllerEnergyUsed; - private List<UidTraffic> mUidTraffic; - - /** @hide */ - @IntDef(prefix = { "BT_STACK_STATE_" }, value = { - BT_STACK_STATE_INVALID, - BT_STACK_STATE_STATE_ACTIVE, - BT_STACK_STATE_STATE_SCANNING, - BT_STACK_STATE_STATE_IDLE - }) - @Retention(RetentionPolicy.SOURCE) - public @interface BluetoothStackState {} - - public static final int BT_STACK_STATE_INVALID = 0; - public static final int BT_STACK_STATE_STATE_ACTIVE = 1; - public static final int BT_STACK_STATE_STATE_SCANNING = 2; - public static final int BT_STACK_STATE_STATE_IDLE = 3; - - /** @hide */ - public BluetoothActivityEnergyInfo(long timestamp, int stackState, - long txTime, long rxTime, long idleTime, long energyUsed) { - mTimestamp = timestamp; - mBluetoothStackState = stackState; - mControllerTxTimeMs = txTime; - mControllerRxTimeMs = rxTime; - mControllerIdleTimeMs = idleTime; - mControllerEnergyUsed = energyUsed; - } - - /** @hide */ - private BluetoothActivityEnergyInfo(Parcel in) { - mTimestamp = in.readLong(); - mBluetoothStackState = in.readInt(); - mControllerTxTimeMs = in.readLong(); - mControllerRxTimeMs = in.readLong(); - mControllerIdleTimeMs = in.readLong(); - mControllerEnergyUsed = in.readLong(); - mUidTraffic = in.createTypedArrayList(UidTraffic.CREATOR); - } - - /** @hide */ - @Override - public String toString() { - return "BluetoothActivityEnergyInfo{" - + " mTimestamp=" + mTimestamp - + " mBluetoothStackState=" + mBluetoothStackState - + " mControllerTxTimeMs=" + mControllerTxTimeMs - + " mControllerRxTimeMs=" + mControllerRxTimeMs - + " mControllerIdleTimeMs=" + mControllerIdleTimeMs - + " mControllerEnergyUsed=" + mControllerEnergyUsed - + " mUidTraffic=" + mUidTraffic - + " }"; - } - - public static final @NonNull Parcelable.Creator<BluetoothActivityEnergyInfo> CREATOR = - new Parcelable.Creator<BluetoothActivityEnergyInfo>() { - public BluetoothActivityEnergyInfo createFromParcel(Parcel in) { - return new BluetoothActivityEnergyInfo(in); - } - - public BluetoothActivityEnergyInfo[] newArray(int size) { - return new BluetoothActivityEnergyInfo[size]; - } - }; - - /** @hide */ - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeLong(mTimestamp); - out.writeInt(mBluetoothStackState); - out.writeLong(mControllerTxTimeMs); - out.writeLong(mControllerRxTimeMs); - out.writeLong(mControllerIdleTimeMs); - out.writeLong(mControllerEnergyUsed); - out.writeTypedList(mUidTraffic); - } - - /** @hide */ - @Override - public int describeContents() { - return 0; - } - - /** - * Get the Bluetooth stack state associated with the energy info. - * - * @return one of {@link #BluetoothStackState} states - */ - @BluetoothStackState - public int getBluetoothStackState() { - return mBluetoothStackState; - } - - /** - * @return tx time in ms - */ - public long getControllerTxTimeMillis() { - return mControllerTxTimeMs; - } - - /** - * @return rx time in ms - */ - public long getControllerRxTimeMillis() { - return mControllerRxTimeMs; - } - - /** - * @return idle time in ms - */ - public long getControllerIdleTimeMillis() { - return mControllerIdleTimeMs; - } - - /** - * Get the product of current (mA), voltage (V), and time (ms). - * - * @return energy used - */ - public long getControllerEnergyUsed() { - return mControllerEnergyUsed; - } - - /** - * @return timestamp (real time elapsed in milliseconds since boot) of record creation - */ - public @ElapsedRealtimeLong long getTimestampMillis() { - return mTimestamp; - } - - /** - * Get the {@link List} of each application {@link android.bluetooth.UidTraffic}. - * - * @return current {@link List} of {@link android.bluetooth.UidTraffic} - */ - public @NonNull List<UidTraffic> getUidTraffic() { - if (mUidTraffic == null) { - return Collections.emptyList(); - } - return mUidTraffic; - } - - /** @hide */ - public void setUidTraffic(List<UidTraffic> traffic) { - mUidTraffic = traffic; - } - - /** - * @return true if the record Tx time, Rx time, and Idle time are more than 0. - */ - public boolean isValid() { - return ((mControllerTxTimeMs >= 0) && (mControllerRxTimeMs >= 0) - && (mControllerIdleTimeMs >= 0)); - } -} diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java deleted file mode 100644 index 661291c6f1e2..000000000000 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ /dev/null @@ -1,4413 +0,0 @@ -/* - * Copyright 2009-2016 The Android Open Source Project - * Copyright 2015 Samsung LSI - * - * 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.bluetooth; - -import static java.util.Objects.requireNonNull; - -import android.annotation.CallbackExecutor; -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.RequiresNoPermission; -import android.annotation.RequiresPermission; -import android.annotation.SdkConstant; -import android.annotation.SdkConstant.SdkConstantType; -import android.annotation.SuppressLint; -import android.annotation.SystemApi; //import android.app.PropertyInvalidatedCache; -import android.bluetooth.BluetoothDevice.Transport; -import android.bluetooth.BluetoothProfile.ConnectionPolicy; -import android.bluetooth.annotations.RequiresBluetoothAdvertisePermission; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.bluetooth.annotations.RequiresBluetoothLocationPermission; -import android.bluetooth.annotations.RequiresBluetoothScanPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; -import android.bluetooth.le.BluetoothLeAdvertiser; -import android.bluetooth.le.BluetoothLeScanner; -import android.bluetooth.le.PeriodicAdvertisingManager; -import android.bluetooth.le.ScanCallback; -import android.bluetooth.le.ScanFilter; -import android.bluetooth.le.ScanRecord; -import android.bluetooth.le.ScanResult; -import android.bluetooth.le.ScanSettings; -import android.compat.annotation.UnsupportedAppUsage; -import android.content.AttributionSource; -import android.content.Context; -import android.os.Binder; -import android.os.Build; -import android.os.IBinder; -import android.os.ParcelUuid; -import android.os.RemoteException; -import android.os.ResultReceiver; -import android.os.ServiceManager; -import android.sysprop.BluetoothProperties; -import android.util.Log; -import android.util.Pair; - -import com.android.internal.annotations.GuardedBy; - -import java.io.IOException; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.time.Duration; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.UUID; -import java.util.WeakHashMap; -import java.util.concurrent.Executor; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -/** - * Represents the local device Bluetooth adapter. The {@link BluetoothAdapter} - * lets you perform fundamental Bluetooth tasks, such as initiate - * device discovery, query a list of bonded (paired) devices, - * instantiate a {@link BluetoothDevice} using a known MAC address, and create - * a {@link BluetoothServerSocket} to listen for connection requests from other - * devices, and start a scan for Bluetooth LE devices. - * - * <p>To get a {@link BluetoothAdapter} representing the local Bluetooth - * adapter, call the {@link BluetoothManager#getAdapter} function on {@link BluetoothManager}. - * On JELLY_BEAN_MR1 and below you will need to use the static {@link #getDefaultAdapter} - * method instead. - * </p><p> - * Fundamentally, this is your starting point for all - * Bluetooth actions. Once you have the local adapter, you can get a set of - * {@link BluetoothDevice} objects representing all paired devices with - * {@link #getBondedDevices()}; start device discovery with - * {@link #startDiscovery()}; or create a {@link BluetoothServerSocket} to - * listen for incoming RFComm connection requests with {@link - * #listenUsingRfcommWithServiceRecord(String, UUID)}; listen for incoming L2CAP Connection-oriented - * Channels (CoC) connection requests with {@link #listenUsingL2capChannel()}; or start a scan for - * Bluetooth LE devices with {@link #startLeScan(LeScanCallback callback)}. - * </p> - * <p>This class is thread safe.</p> - * <div class="special reference"> - * <h3>Developer Guides</h3> - * <p> - * For more information about using Bluetooth, read the <a href= - * "{@docRoot}guide/topics/connectivity/bluetooth.html">Bluetooth</a> developer - * guide. - * </p> - * </div> - * - * {@see BluetoothDevice} - * {@see BluetoothServerSocket} - */ -public final class BluetoothAdapter { - private static final String TAG = "BluetoothAdapter"; - private static final String DESCRIPTOR = "android.bluetooth.BluetoothAdapter"; - private static final boolean DBG = true; - private static final boolean VDBG = false; - - /** - * Default MAC address reported to a client that does not have the - * android.permission.LOCAL_MAC_ADDRESS permission. - * - * @hide - */ - public static final String DEFAULT_MAC_ADDRESS = "02:00:00:00:00:00"; - - /** - * Sentinel error value for this class. Guaranteed to not equal any other - * integer constant in this class. Provided as a convenience for functions - * that require a sentinel error value, for example: - * <p><code>Intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, - * BluetoothAdapter.ERROR)</code> - */ - public static final int ERROR = Integer.MIN_VALUE; - - /** - * Broadcast Action: The state of the local Bluetooth adapter has been - * changed. - * <p>For example, Bluetooth has been turned on or off. - * <p>Always contains the extra fields {@link #EXTRA_STATE} and {@link - * #EXTRA_PREVIOUS_STATE} containing the new and old states - * respectively. - */ - @RequiresLegacyBluetoothPermission - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String - ACTION_STATE_CHANGED = "android.bluetooth.adapter.action.STATE_CHANGED"; - - /** - * Used as an int extra field in {@link #ACTION_STATE_CHANGED} - * intents to request the current power state. Possible values are: - * {@link #STATE_OFF}, - * {@link #STATE_TURNING_ON}, - * {@link #STATE_ON}, - * {@link #STATE_TURNING_OFF}, - */ - public static final String EXTRA_STATE = "android.bluetooth.adapter.extra.STATE"; - /** - * Used as an int extra field in {@link #ACTION_STATE_CHANGED} - * intents to request the previous power state. Possible values are: - * {@link #STATE_OFF}, - * {@link #STATE_TURNING_ON}, - * {@link #STATE_ON}, - * {@link #STATE_TURNING_OFF} - */ - public static final String EXTRA_PREVIOUS_STATE = - "android.bluetooth.adapter.extra.PREVIOUS_STATE"; - - /** @hide */ - @IntDef(prefix = { "STATE_" }, value = { - STATE_OFF, - STATE_TURNING_ON, - STATE_ON, - STATE_TURNING_OFF, - STATE_BLE_TURNING_ON, - STATE_BLE_ON, - STATE_BLE_TURNING_OFF - }) - @Retention(RetentionPolicy.SOURCE) - public @interface AdapterState {} - - /** - * Indicates the local Bluetooth adapter is off. - */ - public static final int STATE_OFF = 10; - /** - * Indicates the local Bluetooth adapter is turning on. However local - * clients should wait for {@link #STATE_ON} before attempting to - * use the adapter. - */ - public static final int STATE_TURNING_ON = 11; - /** - * Indicates the local Bluetooth adapter is on, and ready for use. - */ - public static final int STATE_ON = 12; - /** - * Indicates the local Bluetooth adapter is turning off. Local clients - * should immediately attempt graceful disconnection of any remote links. - */ - public static final int STATE_TURNING_OFF = 13; - - /** - * Indicates the local Bluetooth adapter is turning Bluetooth LE mode on. - * - * @hide - */ - public static final int STATE_BLE_TURNING_ON = 14; - - /** - * Indicates the local Bluetooth adapter is in LE only mode. - * - * @hide - */ - public static final int STATE_BLE_ON = 15; - - /** - * Indicates the local Bluetooth adapter is turning off LE only mode. - * - * @hide - */ - public static final int STATE_BLE_TURNING_OFF = 16; - - /** - * UUID of the GATT Read Characteristics for LE_PSM value. - * - * @hide - */ - public static final UUID LE_PSM_CHARACTERISTIC_UUID = - UUID.fromString("2d410339-82b6-42aa-b34e-e2e01df8cc1a"); - - /** - * Human-readable string helper for AdapterState - * - * @hide - */ - public static String nameForState(@AdapterState int state) { - switch (state) { - case STATE_OFF: - return "OFF"; - case STATE_TURNING_ON: - return "TURNING_ON"; - case STATE_ON: - return "ON"; - case STATE_TURNING_OFF: - return "TURNING_OFF"; - case STATE_BLE_TURNING_ON: - return "BLE_TURNING_ON"; - case STATE_BLE_ON: - return "BLE_ON"; - case STATE_BLE_TURNING_OFF: - return "BLE_TURNING_OFF"; - default: - return "?!?!? (" + state + ")"; - } - } - - /** - * Activity Action: Show a system activity that requests discoverable mode. - * This activity will also request the user to turn on Bluetooth if it - * is not currently enabled. - * <p>Discoverable mode is equivalent to {@link - * #SCAN_MODE_CONNECTABLE_DISCOVERABLE}. It allows remote devices to see - * this Bluetooth adapter when they perform a discovery. - * <p>For privacy, Android is not discoverable by default. - * <p>The sender of this Intent can optionally use extra field {@link - * #EXTRA_DISCOVERABLE_DURATION} to request the duration of - * discoverability. Currently the default duration is 120 seconds, and - * maximum duration is capped at 300 seconds for each request. - * <p>Notification of the result of this activity is posted using the - * {@link android.app.Activity#onActivityResult} callback. The - * <code>resultCode</code> - * will be the duration (in seconds) of discoverability or - * {@link android.app.Activity#RESULT_CANCELED} if the user rejected - * discoverability or an error has occurred. - * <p>Applications can also listen for {@link #ACTION_SCAN_MODE_CHANGED} - * for global notification whenever the scan mode changes. For example, an - * application can be notified when the device has ended discoverability. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothAdvertisePermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) - @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String - ACTION_REQUEST_DISCOVERABLE = "android.bluetooth.adapter.action.REQUEST_DISCOVERABLE"; - - /** - * Used as an optional int extra field in {@link - * #ACTION_REQUEST_DISCOVERABLE} intents to request a specific duration - * for discoverability in seconds. The current default is 120 seconds, and - * requests over 300 seconds will be capped. These values could change. - */ - public static final String EXTRA_DISCOVERABLE_DURATION = - "android.bluetooth.adapter.extra.DISCOVERABLE_DURATION"; - - /** - * Activity Action: Show a system activity that allows the user to turn on - * Bluetooth. - * <p>This system activity will return once Bluetooth has completed turning - * on, or the user has decided not to turn Bluetooth on. - * <p>Notification of the result of this activity is posted using the - * {@link android.app.Activity#onActivityResult} callback. The - * <code>resultCode</code> - * will be {@link android.app.Activity#RESULT_OK} if Bluetooth has been - * turned on or {@link android.app.Activity#RESULT_CANCELED} if the user - * has rejected the request or an error has occurred. - * <p>Applications can also listen for {@link #ACTION_STATE_CHANGED} - * for global notification whenever Bluetooth is turned on or off. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String - ACTION_REQUEST_ENABLE = "android.bluetooth.adapter.action.REQUEST_ENABLE"; - - /** - * Activity Action: Show a system activity that allows the user to turn off - * Bluetooth. This is used only if permission review is enabled which is for - * apps targeting API less than 23 require a permission review before any of - * the app's components can run. - * <p>This system activity will return once Bluetooth has completed turning - * off, or the user has decided not to turn Bluetooth off. - * <p>Notification of the result of this activity is posted using the - * {@link android.app.Activity#onActivityResult} callback. The - * <code>resultCode</code> - * will be {@link android.app.Activity#RESULT_OK} if Bluetooth has been - * turned off or {@link android.app.Activity#RESULT_CANCELED} if the user - * has rejected the request or an error has occurred. - * <p>Applications can also listen for {@link #ACTION_STATE_CHANGED} - * for global notification whenever Bluetooth is turned on or off. - * - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String - ACTION_REQUEST_DISABLE = "android.bluetooth.adapter.action.REQUEST_DISABLE"; - - /** - * Activity Action: Show a system activity that allows user to enable BLE scans even when - * Bluetooth is turned off.<p> - * - * Notification of result of this activity is posted using - * {@link android.app.Activity#onActivityResult}. The <code>resultCode</code> will be - * {@link android.app.Activity#RESULT_OK} if BLE scan always available setting is turned on or - * {@link android.app.Activity#RESULT_CANCELED} if the user has rejected the request or an - * error occurred. - * - * @hide - */ - @SystemApi - @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) - public static final String ACTION_REQUEST_BLE_SCAN_ALWAYS_AVAILABLE = - "android.bluetooth.adapter.action.REQUEST_BLE_SCAN_ALWAYS_AVAILABLE"; - - /** - * Broadcast Action: Indicates the Bluetooth scan mode of the local Adapter - * has changed. - * <p>Always contains the extra fields {@link #EXTRA_SCAN_MODE} and {@link - * #EXTRA_PREVIOUS_SCAN_MODE} containing the new and old scan modes - * respectively. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothScanPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String - ACTION_SCAN_MODE_CHANGED = "android.bluetooth.adapter.action.SCAN_MODE_CHANGED"; - - /** - * Used as an int extra field in {@link #ACTION_SCAN_MODE_CHANGED} - * intents to request the current scan mode. Possible values are: - * {@link #SCAN_MODE_NONE}, - * {@link #SCAN_MODE_CONNECTABLE}, - * {@link #SCAN_MODE_CONNECTABLE_DISCOVERABLE}, - */ - public static final String EXTRA_SCAN_MODE = "android.bluetooth.adapter.extra.SCAN_MODE"; - /** - * Used as an int extra field in {@link #ACTION_SCAN_MODE_CHANGED} - * intents to request the previous scan mode. Possible values are: - * {@link #SCAN_MODE_NONE}, - * {@link #SCAN_MODE_CONNECTABLE}, - * {@link #SCAN_MODE_CONNECTABLE_DISCOVERABLE}, - */ - public static final String EXTRA_PREVIOUS_SCAN_MODE = - "android.bluetooth.adapter.extra.PREVIOUS_SCAN_MODE"; - - /** @hide */ - @IntDef(prefix = { "SCAN_" }, value = { - SCAN_MODE_NONE, - SCAN_MODE_CONNECTABLE, - SCAN_MODE_CONNECTABLE_DISCOVERABLE - }) - @Retention(RetentionPolicy.SOURCE) - public @interface ScanMode {} - - /** @hide */ - @IntDef(value = { - BluetoothStatusCodes.SUCCESS, - BluetoothStatusCodes.ERROR_UNKNOWN, - BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED, - BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_SCAN_PERMISSION - }) - @Retention(RetentionPolicy.SOURCE) - public @interface ScanModeStatusCode {} - - /** - * Indicates that both inquiry scan and page scan are disabled on the local - * Bluetooth adapter. Therefore this device is neither discoverable - * nor connectable from remote Bluetooth devices. - */ - public static final int SCAN_MODE_NONE = 20; - /** - * Indicates that inquiry scan is disabled, but page scan is enabled on the - * local Bluetooth adapter. Therefore this device is not discoverable from - * remote Bluetooth devices, but is connectable from remote devices that - * have previously discovered this device. - */ - public static final int SCAN_MODE_CONNECTABLE = 21; - /** - * Indicates that both inquiry scan and page scan are enabled on the local - * Bluetooth adapter. Therefore this device is both discoverable and - * connectable from remote Bluetooth devices. - */ - public static final int SCAN_MODE_CONNECTABLE_DISCOVERABLE = 23; - - /** - * Device only has a display. - * - * @hide - */ - public static final int IO_CAPABILITY_OUT = 0; - - /** - * Device has a display and the ability to input Yes/No. - * - * @hide - */ - public static final int IO_CAPABILITY_IO = 1; - - /** - * Device only has a keyboard for entry but no display. - * - * @hide - */ - public static final int IO_CAPABILITY_IN = 2; - - /** - * Device has no Input or Output capability. - * - * @hide - */ - public static final int IO_CAPABILITY_NONE = 3; - - /** - * Device has a display and a full keyboard. - * - * @hide - */ - public static final int IO_CAPABILITY_KBDISP = 4; - - /** - * Maximum range value for Input/Output capabilities. - * - * <p>This should be updated when adding a new Input/Output capability. Other code - * like validation depends on this being accurate. - * - * @hide - */ - public static final int IO_CAPABILITY_MAX = 5; - - /** - * The Input/Output capability of the device is unknown. - * - * @hide - */ - public static final int IO_CAPABILITY_UNKNOWN = 255; - - /** @hide */ - @IntDef({IO_CAPABILITY_OUT, IO_CAPABILITY_IO, IO_CAPABILITY_IN, IO_CAPABILITY_NONE, - IO_CAPABILITY_KBDISP}) - @Retention(RetentionPolicy.SOURCE) - public @interface IoCapability {} - - /** @hide */ - @IntDef(prefix = "ACTIVE_DEVICE_", value = {ACTIVE_DEVICE_AUDIO, - ACTIVE_DEVICE_PHONE_CALL, ACTIVE_DEVICE_ALL}) - @Retention(RetentionPolicy.SOURCE) - public @interface ActiveDeviceUse {} - - /** - * Use the specified device for audio (a2dp and hearing aid profile) - * - * @hide - */ - @SystemApi - public static final int ACTIVE_DEVICE_AUDIO = 0; - - /** - * Use the specified device for phone calls (headset profile and hearing - * aid profile) - * - * @hide - */ - @SystemApi - public static final int ACTIVE_DEVICE_PHONE_CALL = 1; - - /** - * Use the specified device for a2dp, hearing aid profile, and headset profile - * - * @hide - */ - @SystemApi - public static final int ACTIVE_DEVICE_ALL = 2; - - /** @hide */ - @IntDef({BluetoothProfile.HEADSET, BluetoothProfile.A2DP, - BluetoothProfile.HEARING_AID}) - @Retention(RetentionPolicy.SOURCE) - public @interface ActiveDeviceProfile {} - - /** - * Broadcast Action: The local Bluetooth adapter has started the remote - * device discovery process. - * <p>This usually involves an inquiry scan of about 12 seconds, followed - * by a page scan of each new device to retrieve its Bluetooth name. - * <p>Register for {@link BluetoothDevice#ACTION_FOUND} to be notified as - * remote Bluetooth devices are found. - * <p>Device discovery is a heavyweight procedure. New connections to - * remote Bluetooth devices should not be attempted while discovery is in - * progress, and existing connections will experience limited bandwidth - * and high latency. Use {@link #cancelDiscovery()} to cancel an ongoing - * discovery. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothScanPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String - ACTION_DISCOVERY_STARTED = "android.bluetooth.adapter.action.DISCOVERY_STARTED"; - /** - * Broadcast Action: The local Bluetooth adapter has finished the device - * discovery process. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothScanPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String - ACTION_DISCOVERY_FINISHED = "android.bluetooth.adapter.action.DISCOVERY_FINISHED"; - - /** - * Broadcast Action: The local Bluetooth adapter has changed its friendly - * Bluetooth name. - * <p>This name is visible to remote Bluetooth devices. - * <p>Always contains the extra field {@link #EXTRA_LOCAL_NAME} containing - * the name. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String - ACTION_LOCAL_NAME_CHANGED = "android.bluetooth.adapter.action.LOCAL_NAME_CHANGED"; - /** - * Used as a String extra field in {@link #ACTION_LOCAL_NAME_CHANGED} - * intents to request the local Bluetooth name. - */ - public static final String EXTRA_LOCAL_NAME = "android.bluetooth.adapter.extra.LOCAL_NAME"; - - /** - * Intent used to broadcast the change in connection state of the local - * Bluetooth adapter to a profile of the remote device. When the adapter is - * not connected to any profiles of any remote devices and it attempts a - * connection to a profile this intent will be sent. Once connected, this intent - * will not be sent for any more connection attempts to any profiles of any - * remote device. When the adapter disconnects from the last profile its - * connected to of any remote device, this intent will be sent. - * - * <p> This intent is useful for applications that are only concerned about - * whether the local adapter is connected to any profile of any device and - * are not really concerned about which profile. For example, an application - * which displays an icon to display whether Bluetooth is connected or not - * can use this intent. - * - * <p>This intent will have 3 extras: - * {@link #EXTRA_CONNECTION_STATE} - The current connection state. - * {@link #EXTRA_PREVIOUS_CONNECTION_STATE}- The previous connection state. - * {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. - * - * {@link #EXTRA_CONNECTION_STATE} or {@link #EXTRA_PREVIOUS_CONNECTION_STATE} - * can be any of {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, - * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String - ACTION_CONNECTION_STATE_CHANGED = - "android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED"; - - /** - * Extra used by {@link #ACTION_CONNECTION_STATE_CHANGED} - * - * This extra represents the current connection state. - */ - public static final String EXTRA_CONNECTION_STATE = - "android.bluetooth.adapter.extra.CONNECTION_STATE"; - - /** - * Extra used by {@link #ACTION_CONNECTION_STATE_CHANGED} - * - * This extra represents the previous connection state. - */ - public static final String EXTRA_PREVIOUS_CONNECTION_STATE = - "android.bluetooth.adapter.extra.PREVIOUS_CONNECTION_STATE"; - - /** - * Broadcast Action: The Bluetooth adapter state has changed in LE only mode. - * - * @hide - */ - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - @SystemApi public static final String ACTION_BLE_STATE_CHANGED = - "android.bluetooth.adapter.action.BLE_STATE_CHANGED"; - - /** - * Intent used to broadcast the change in the Bluetooth address - * of the local Bluetooth adapter. - * <p>Always contains the extra field {@link - * #EXTRA_BLUETOOTH_ADDRESS} containing the Bluetooth address. - * - * Note: only system level processes are allowed to send this - * defined broadcast. - * - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_BLUETOOTH_ADDRESS_CHANGED = - "android.bluetooth.adapter.action.BLUETOOTH_ADDRESS_CHANGED"; - - /** - * Used as a String extra field in {@link - * #ACTION_BLUETOOTH_ADDRESS_CHANGED} intent to store the local - * Bluetooth address. - * - * @hide - */ - public static final String EXTRA_BLUETOOTH_ADDRESS = - "android.bluetooth.adapter.extra.BLUETOOTH_ADDRESS"; - - /** - * Broadcast Action: The notifys Bluetooth ACL connected event. This will be - * by BLE Always on enabled application to know the ACL_CONNECTED event - * when Bluetooth state in STATE_BLE_ON. This denotes GATT connection - * as Bluetooth LE is the only feature available in STATE_BLE_ON - * - * This is counterpart of {@link BluetoothDevice#ACTION_ACL_CONNECTED} which - * works in Bluetooth state STATE_ON - * - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_BLE_ACL_CONNECTED = - "android.bluetooth.adapter.action.BLE_ACL_CONNECTED"; - - /** - * Broadcast Action: The notifys Bluetooth ACL connected event. This will be - * by BLE Always on enabled application to know the ACL_DISCONNECTED event - * when Bluetooth state in STATE_BLE_ON. This denotes GATT disconnection as Bluetooth - * LE is the only feature available in STATE_BLE_ON - * - * This is counterpart of {@link BluetoothDevice#ACTION_ACL_DISCONNECTED} which - * works in Bluetooth state STATE_ON - * - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_BLE_ACL_DISCONNECTED = - "android.bluetooth.adapter.action.BLE_ACL_DISCONNECTED"; - - /** The profile is in disconnected state */ - public static final int STATE_DISCONNECTED = - 0; //BluetoothProtoEnums.CONNECTION_STATE_DISCONNECTED; - /** The profile is in connecting state */ - public static final int STATE_CONNECTING = 1; //BluetoothProtoEnums.CONNECTION_STATE_CONNECTING; - /** The profile is in connected state */ - public static final int STATE_CONNECTED = 2; //BluetoothProtoEnums.CONNECTION_STATE_CONNECTED; - /** The profile is in disconnecting state */ - public static final int STATE_DISCONNECTING = - 3; //BluetoothProtoEnums.CONNECTION_STATE_DISCONNECTING; - - /** @hide */ - public static final String BLUETOOTH_MANAGER_SERVICE = "bluetooth_manager"; - private final IBinder mToken; - - - /** - * When creating a ServerSocket using listenUsingRfcommOn() or - * listenUsingL2capOn() use SOCKET_CHANNEL_AUTO_STATIC to create - * a ServerSocket that auto assigns a channel number to the first - * bluetooth socket. - * The channel number assigned to this first Bluetooth Socket will - * be stored in the ServerSocket, and reused for subsequent Bluetooth - * sockets. - * - * @hide - */ - public static final int SOCKET_CHANNEL_AUTO_STATIC_NO_SDP = -2; - - - private static final int ADDRESS_LENGTH = 17; - - /** - * Lazily initialized singleton. Guaranteed final after first object - * constructed. - */ - private static BluetoothAdapter sAdapter; - - private BluetoothLeScanner mBluetoothLeScanner; - private BluetoothLeAdvertiser mBluetoothLeAdvertiser; - private PeriodicAdvertisingManager mPeriodicAdvertisingManager; - - private final IBluetoothManager mManagerService; - private final AttributionSource mAttributionSource; - - // Yeah, keeping both mService and sService isn't pretty, but it's too late - // in the current release for a major refactoring, so we leave them both - // intact until this can be cleaned up in a future release - - @UnsupportedAppUsage - @GuardedBy("mServiceLock") - private IBluetooth mService; - private final ReentrantReadWriteLock mServiceLock = new ReentrantReadWriteLock(); - - @GuardedBy("sServiceLock") - private static boolean sServiceRegistered; - @GuardedBy("sServiceLock") - private static IBluetooth sService; - private static final Object sServiceLock = new Object(); - - private final Object mLock = new Object(); - private final Map<LeScanCallback, ScanCallback> mLeScanClients; - private final Map<BluetoothDevice, List<Pair<OnMetadataChangedListener, Executor>>> - mMetadataListeners = new HashMap<>(); - private final Map<BluetoothConnectionCallback, Executor> - mBluetoothConnectionCallbackExecutorMap = new HashMap<>(); - - /** - * Bluetooth metadata listener. Overrides the default BluetoothMetadataListener - * implementation. - */ - @SuppressLint("AndroidFrameworkBluetoothPermission") - private final IBluetoothMetadataListener mBluetoothMetadataListener = - new IBluetoothMetadataListener.Stub() { - @Override - public void onMetadataChanged(BluetoothDevice device, int key, byte[] value) { - Attributable.setAttributionSource(device, mAttributionSource); - synchronized (mMetadataListeners) { - if (mMetadataListeners.containsKey(device)) { - List<Pair<OnMetadataChangedListener, Executor>> list = - mMetadataListeners.get(device); - for (Pair<OnMetadataChangedListener, Executor> pair : list) { - OnMetadataChangedListener listener = pair.first; - Executor executor = pair.second; - executor.execute(() -> { - listener.onMetadataChanged(device, key, value); - }); - } - } - } - return; - } - }; - - /** - * Get a handle to the default local Bluetooth adapter. - * <p> - * Currently Android only supports one Bluetooth adapter, but the API could - * be extended to support more. This will always return the default adapter. - * </p> - * - * @return the default local adapter, or null if Bluetooth is not supported - * on this hardware platform - * @deprecated this method will continue to work, but developers are - * strongly encouraged to migrate to using - * {@link BluetoothManager#getAdapter()}, since that approach - * enables support for {@link Context#createAttributionContext}. - */ - @Deprecated - @RequiresNoPermission - public static synchronized BluetoothAdapter getDefaultAdapter() { - if (sAdapter == null) { - sAdapter = createAdapter(AttributionSource.myAttributionSource()); - } - return sAdapter; - } - - /** {@hide} */ - public static BluetoothAdapter createAdapter(AttributionSource attributionSource) { - IBinder binder = ServiceManager.getService(BLUETOOTH_MANAGER_SERVICE); - if (binder != null) { - return new BluetoothAdapter(IBluetoothManager.Stub.asInterface(binder), - attributionSource); - } else { - Log.e(TAG, "Bluetooth binder is null"); - return null; - } - } - - /** - * Use {@link #getDefaultAdapter} to get the BluetoothAdapter instance. - */ - BluetoothAdapter(IBluetoothManager managerService, AttributionSource attributionSource) { - mManagerService = Objects.requireNonNull(managerService); - mAttributionSource = Objects.requireNonNull(attributionSource); - synchronized (mServiceLock.writeLock()) { - mService = getBluetoothService(mManagerCallback); - } - mLeScanClients = new HashMap<LeScanCallback, ScanCallback>(); - mToken = new Binder(DESCRIPTOR); - } - - /** - * Get a {@link BluetoothDevice} object for the given Bluetooth hardware - * address. - * <p>Valid Bluetooth hardware addresses must be upper case, in a format - * such as "00:11:22:33:AA:BB". The helper {@link #checkBluetoothAddress} is - * available to validate a Bluetooth address. - * <p>A {@link BluetoothDevice} will always be returned for a valid - * hardware address, even if this adapter has never seen that device. - * - * @param address valid Bluetooth MAC address - * @throws IllegalArgumentException if address is invalid - */ - @RequiresNoPermission - public BluetoothDevice getRemoteDevice(String address) { - final BluetoothDevice res = new BluetoothDevice(address); - res.setAttributionSource(mAttributionSource); - return res; - } - - /** - * Get a {@link BluetoothDevice} object for the given Bluetooth hardware - * address. - * <p>Valid Bluetooth hardware addresses must be 6 bytes. This method - * expects the address in network byte order (MSB first). - * <p>A {@link BluetoothDevice} will always be returned for a valid - * hardware address, even if this adapter has never seen that device. - * - * @param address Bluetooth MAC address (6 bytes) - * @throws IllegalArgumentException if address is invalid - */ - @RequiresNoPermission - public BluetoothDevice getRemoteDevice(byte[] address) { - if (address == null || address.length != 6) { - throw new IllegalArgumentException("Bluetooth address must have 6 bytes"); - } - final BluetoothDevice res = new BluetoothDevice( - String.format(Locale.US, "%02X:%02X:%02X:%02X:%02X:%02X", address[0], address[1], - address[2], address[3], address[4], address[5])); - res.setAttributionSource(mAttributionSource); - return res; - } - - /** - * Returns a {@link BluetoothLeAdvertiser} object for Bluetooth LE Advertising operations. - * Will return null if Bluetooth is turned off or if Bluetooth LE Advertising is not - * supported on this device. - * <p> - * Use {@link #isMultipleAdvertisementSupported()} to check whether LE Advertising is supported - * on this device before calling this method. - */ - @RequiresNoPermission - public BluetoothLeAdvertiser getBluetoothLeAdvertiser() { - if (!getLeAccess()) { - return null; - } - synchronized (mLock) { - if (mBluetoothLeAdvertiser == null) { - mBluetoothLeAdvertiser = new BluetoothLeAdvertiser(this); - } - return mBluetoothLeAdvertiser; - } - } - - /** - * Returns a {@link PeriodicAdvertisingManager} object for Bluetooth LE Periodic Advertising - * operations. Will return null if Bluetooth is turned off or if Bluetooth LE Periodic - * Advertising is not supported on this device. - * <p> - * Use {@link #isLePeriodicAdvertisingSupported()} to check whether LE Periodic Advertising is - * supported on this device before calling this method. - * - * @hide - */ - @RequiresNoPermission - public PeriodicAdvertisingManager getPeriodicAdvertisingManager() { - if (!getLeAccess()) { - return null; - } - - if (!isLePeriodicAdvertisingSupported()) { - return null; - } - - synchronized (mLock) { - if (mPeriodicAdvertisingManager == null) { - mPeriodicAdvertisingManager = new PeriodicAdvertisingManager(this); - } - return mPeriodicAdvertisingManager; - } - } - - /** - * Returns a {@link BluetoothLeScanner} object for Bluetooth LE scan operations. - */ - @RequiresNoPermission - public BluetoothLeScanner getBluetoothLeScanner() { - if (!getLeAccess()) { - return null; - } - synchronized (mLock) { - if (mBluetoothLeScanner == null) { - mBluetoothLeScanner = new BluetoothLeScanner(this); - } - return mBluetoothLeScanner; - } - } - - /** - * Return true if Bluetooth is currently enabled and ready for use. - * <p>Equivalent to: - * <code>getBluetoothState() == STATE_ON</code> - * - * @return true if the local adapter is turned on - */ - @RequiresLegacyBluetoothPermission - @RequiresNoPermission - public boolean isEnabled() { - return getState() == BluetoothAdapter.STATE_ON; - } - - /** - * Return true if Bluetooth LE(Always BLE On feature) is currently - * enabled and ready for use - * <p>This returns true if current state is either STATE_ON or STATE_BLE_ON - * - * @return true if the local Bluetooth LE adapter is turned on - * @hide - */ - @SystemApi - @RequiresNoPermission - public boolean isLeEnabled() { - final int state = getLeState(); - if (DBG) { - Log.d(TAG, "isLeEnabled(): " + BluetoothAdapter.nameForState(state)); - } - return (state == BluetoothAdapter.STATE_ON - || state == BluetoothAdapter.STATE_BLE_ON - || state == BluetoothAdapter.STATE_TURNING_ON - || state == BluetoothAdapter.STATE_TURNING_OFF); - } - - /** - * Turns off Bluetooth LE which was earlier turned on by calling enableBLE(). - * - * <p> If the internal Adapter state is STATE_BLE_ON, this would trigger the transition - * to STATE_OFF and completely shut-down Bluetooth - * - * <p> If the Adapter state is STATE_ON, This would unregister the existance of - * special Bluetooth LE application and hence the further turning off of Bluetooth - * from UI would ensure the complete turn-off of Bluetooth rather than staying back - * BLE only state - * - * <p>This is an asynchronous call: it will return immediately, and - * clients should listen for {@link #ACTION_BLE_STATE_CHANGED} - * to be notified of subsequent adapter state changes If this call returns - * true, then the adapter state will immediately transition from {@link - * #STATE_ON} to {@link #STATE_TURNING_OFF}, and some time - * later transition to either {@link #STATE_BLE_ON} or {@link - * #STATE_OFF} based on the existance of the further Always BLE ON enabled applications - * If this call returns false then there was an - * immediate problem that will prevent the QAdapter from being turned off - - * such as the QAadapter already being turned off. - * - * @return true to indicate success, or false on immediate error - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean disableBLE() { - if (!isBleScanAlwaysAvailable()) { - return false; - } - try { - return mManagerService.disableBle(mAttributionSource, mToken); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return false; - } - - /** - * Applications who want to only use Bluetooth Low Energy (BLE) can call enableBLE. - * - * enableBLE registers the existence of an app using only LE functions. - * - * enableBLE may enable Bluetooth to an LE only mode so that an app can use - * LE related features (BluetoothGatt or BluetoothGattServer classes) - * - * If the user disables Bluetooth while an app is registered to use LE only features, - * Bluetooth will remain on in LE only mode for the app. - * - * When Bluetooth is in LE only mode, it is not shown as ON to the UI. - * - * <p>This is an asynchronous call: it returns immediately, and - * clients should listen for {@link #ACTION_BLE_STATE_CHANGED} - * to be notified of adapter state changes. - * - * If this call returns * true, then the adapter state is either in a mode where - * LE is available, or will transition from {@link #STATE_OFF} to {@link #STATE_BLE_TURNING_ON}, - * and some time later transition to either {@link #STATE_OFF} or {@link #STATE_BLE_ON}. - * - * If this call returns false then there was an immediate problem that prevents the - * adapter from being turned on - such as Airplane mode. - * - * {@link #ACTION_BLE_STATE_CHANGED} returns the Bluetooth Adapter's various - * states, It includes all the classic Bluetooth Adapter states along with - * internal BLE only states - * - * @return true to indicate Bluetooth LE will be available, or false on immediate error - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean enableBLE() { - if (!isBleScanAlwaysAvailable()) { - return false; - } - try { - return mManagerService.enableBle(mAttributionSource, mToken); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - - return false; - } - - /* - private static final String BLUETOOTH_GET_STATE_CACHE_PROPERTY = "cache_key.bluetooth.get_state"; - - private final PropertyInvalidatedCache<Void, Integer> mBluetoothGetStateCache = - new PropertyInvalidatedCache<Void, Integer>( - 8, BLUETOOTH_GET_STATE_CACHE_PROPERTY) { - @Override - @SuppressLint("AndroidFrameworkRequiresPermission") - public Integer recompute(Void query) { - try { - return mService.getState(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - }; - */ - - /** @hide */ - /* - @RequiresNoPermission - public void disableBluetoothGetStateCache() { - mBluetoothGetStateCache.disableLocal(); - } - */ - - /** @hide */ - /* - public static void invalidateBluetoothGetStateCache() { - PropertyInvalidatedCache.invalidateCache(BLUETOOTH_GET_STATE_CACHE_PROPERTY); - } - */ - - /** - * Fetch the current bluetooth state. If the service is down, return - * OFF. - */ - @AdapterState - private int getStateInternal() { - int state = BluetoothAdapter.STATE_OFF; - try { - mServiceLock.readLock().lock(); - if (mService != null) { - //state = mBluetoothGetStateCache.query(null); - state = mService.getState(); - } - } catch (RemoteException e) { - Log.e(TAG, "", e); - e.rethrowFromSystemServer(); - } finally { - mServiceLock.readLock().unlock(); - } - return state; - } - - /** - * Get the current state of the local Bluetooth adapter. - * <p>Possible return values are - * {@link #STATE_OFF}, - * {@link #STATE_TURNING_ON}, - * {@link #STATE_ON}, - * {@link #STATE_TURNING_OFF}. - * - * @return current state of Bluetooth adapter - */ - @RequiresLegacyBluetoothPermission - @RequiresNoPermission - @AdapterState - public int getState() { - int state = getStateInternal(); - - // Consider all internal states as OFF - if (state == BluetoothAdapter.STATE_BLE_ON || state == BluetoothAdapter.STATE_BLE_TURNING_ON - || state == BluetoothAdapter.STATE_BLE_TURNING_OFF) { - if (VDBG) { - Log.d(TAG, "Consider " + BluetoothAdapter.nameForState(state) + " state as OFF"); - } - state = BluetoothAdapter.STATE_OFF; - } - if (VDBG) { - Log.d(TAG, "" + hashCode() + ": getState(). Returning " + BluetoothAdapter.nameForState( - state)); - } - return state; - } - - /** - * Get the current state of the local Bluetooth adapter - * <p>This returns current internal state of Adapter including LE ON/OFF - * - * <p>Possible return values are - * {@link #STATE_OFF}, - * {@link #STATE_BLE_TURNING_ON}, - * {@link #STATE_BLE_ON}, - * {@link #STATE_TURNING_ON}, - * {@link #STATE_ON}, - * {@link #STATE_TURNING_OFF}, - * {@link #STATE_BLE_TURNING_OFF}. - * - * @return current state of Bluetooth adapter - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresNoPermission - @AdapterState - @UnsupportedAppUsage(publicAlternatives = "Use {@link #getState()} instead to determine " - + "whether you can use BLE & BT classic.") - public int getLeState() { - int state = getStateInternal(); - - if (VDBG) { - Log.d(TAG, "getLeState() returning " + BluetoothAdapter.nameForState(state)); - } - return state; - } - - boolean getLeAccess() { - if (getLeState() == STATE_ON) { - return true; - } else if (getLeState() == STATE_BLE_ON) { - return true; // TODO: FILTER SYSTEM APPS HERE <-- - } - - return false; - } - - /** - * Turn on the local Bluetooth adapter—do not use without explicit - * user action to turn on Bluetooth. - * <p>This powers on the underlying Bluetooth hardware, and starts all - * Bluetooth system services. - * <p class="caution"><strong>Bluetooth should never be enabled without - * direct user consent</strong>. If you want to turn on Bluetooth in order - * to create a wireless connection, you should use the {@link - * #ACTION_REQUEST_ENABLE} Intent, which will raise a dialog that requests - * user permission to turn on Bluetooth. The {@link #enable()} method is - * provided only for applications that include a user interface for changing - * system settings, such as a "power manager" app.</p> - * <p>This is an asynchronous call: it will return immediately, and - * clients should listen for {@link #ACTION_STATE_CHANGED} - * to be notified of subsequent adapter state changes. If this call returns - * true, then the adapter state will immediately transition from {@link - * #STATE_OFF} to {@link #STATE_TURNING_ON}, and some time - * later transition to either {@link #STATE_OFF} or {@link - * #STATE_ON}. If this call returns false then there was an - * immediate problem that will prevent the adapter from being turned on - - * such as Airplane mode, or the adapter is already turned on. - * - * @return true to indicate adapter startup has begun, or false on immediate error - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean enable() { - if (isEnabled()) { - if (DBG) { - Log.d(TAG, "enable(): BT already enabled!"); - } - return true; - } - try { - return mManagerService.enable(mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return false; - } - - /** - * Turn off the local Bluetooth adapter—do not use without explicit - * user action to turn off Bluetooth. - * <p>This gracefully shuts down all Bluetooth connections, stops Bluetooth - * system services, and powers down the underlying Bluetooth hardware. - * <p class="caution"><strong>Bluetooth should never be disabled without - * direct user consent</strong>. The {@link #disable()} method is - * provided only for applications that include a user interface for changing - * system settings, such as a "power manager" app.</p> - * <p>This is an asynchronous call: it will return immediately, and - * clients should listen for {@link #ACTION_STATE_CHANGED} - * to be notified of subsequent adapter state changes. If this call returns - * true, then the adapter state will immediately transition from {@link - * #STATE_ON} to {@link #STATE_TURNING_OFF}, and some time - * later transition to either {@link #STATE_OFF} or {@link - * #STATE_ON}. If this call returns false then there was an - * immediate problem that will prevent the adapter from being turned off - - * such as the adapter already being turned off. - * - * @return true to indicate adapter shutdown has begun, or false on immediate error - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean disable() { - try { - return mManagerService.disable(mAttributionSource, true); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return false; - } - - /** - * Turn off the local Bluetooth adapter and don't persist the setting. - * - * @param persist Indicate whether the off state should be persisted following the next reboot - * @return true to indicate adapter shutdown has begun, or false on immediate error - * @hide - */ - @SystemApi - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean disable(boolean persist) { - - try { - return mManagerService.disable(mAttributionSource, persist); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return false; - } - - /** - * Returns the hardware address of the local Bluetooth adapter. - * <p>For example, "00:11:22:AA:BB:CC". - * - * @return Bluetooth hardware address as string - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.LOCAL_MAC_ADDRESS, - }) - public String getAddress() { - try { - return mManagerService.getAddress(mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return null; - } - - /** - * Get the friendly Bluetooth name of the local Bluetooth adapter. - * <p>This name is visible to remote Bluetooth devices. - * - * @return the Bluetooth name, or null on error - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public String getName() { - try { - return mManagerService.getName(mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return null; - } - - /** {@hide} */ - @RequiresBluetoothAdvertisePermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) - public int getNameLengthForAdvertise() { - try { - return mService.getNameLengthForAdvertise(mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return -1; - } - - /** - * Factory reset bluetooth settings. - * - * @return true to indicate that the config file was successfully cleared - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean factoryReset() { - try { - mServiceLock.readLock().lock(); - if (mService != null && mService.factoryReset(mAttributionSource) - && mManagerService != null - && mManagerService.onFactoryReset(mAttributionSource)) { - return true; - } - Log.e(TAG, "factoryReset(): Setting persist.bluetooth.factoryreset to retry later"); - BluetoothProperties.factory_reset(true); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } finally { - mServiceLock.readLock().unlock(); - } - return false; - } - - /** - * Get the UUIDs supported by the local Bluetooth adapter. - * - * @return the UUIDs supported by the local Bluetooth Adapter. - * @hide - */ - @UnsupportedAppUsage - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public @Nullable ParcelUuid[] getUuids() { - if (getState() != STATE_ON) { - return null; - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.getUuids(mAttributionSource); - } - } catch (RemoteException e) { - Log.e(TAG, "", e); - } finally { - mServiceLock.readLock().unlock(); - } - return null; - } - - /** - * Set the friendly Bluetooth name of the local Bluetooth adapter. - * <p>This name is visible to remote Bluetooth devices. - * <p>Valid Bluetooth names are a maximum of 248 bytes using UTF-8 - * encoding, although many remote devices can only display the first - * 40 characters, and some may be limited to just 20. - * <p>If Bluetooth state is not {@link #STATE_ON}, this API - * will return false. After turning on Bluetooth, - * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON} - * to get the updated value. - * - * @param name a valid Bluetooth name - * @return true if the name was set, false otherwise - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean setName(String name) { - if (getState() != STATE_ON) { - return false; - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.setName(name, mAttributionSource); - } - } catch (RemoteException e) { - Log.e(TAG, "", e); - } finally { - mServiceLock.readLock().unlock(); - } - return false; - } - - /** - * Returns the {@link BluetoothClass} Bluetooth Class of Device (CoD) of the local Bluetooth - * adapter. - * - * @return {@link BluetoothClass} Bluetooth CoD of local Bluetooth device. - * - * @hide - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothClass getBluetoothClass() { - if (getState() != STATE_ON) { - return null; - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.getBluetoothClass(mAttributionSource); - } - } catch (RemoteException e) { - Log.e(TAG, "", e); - } finally { - mServiceLock.readLock().unlock(); - } - return null; - } - - /** - * Sets the {@link BluetoothClass} Bluetooth Class of Device (CoD) of the local Bluetooth - * adapter. - * - * <p>Note: This value persists across system reboot. - * - * @param bluetoothClass {@link BluetoothClass} to set the local Bluetooth adapter to. - * @return true if successful, false if unsuccessful. - * - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setBluetoothClass(BluetoothClass bluetoothClass) { - if (getState() != STATE_ON) { - return false; - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.setBluetoothClass(bluetoothClass, mAttributionSource); - } - } catch (RemoteException e) { - Log.e(TAG, "", e); - } finally { - mServiceLock.readLock().unlock(); - } - return false; - } - - /** - * Returns the Input/Output capability of the device for classic Bluetooth. - * - * @return Input/Output capability of the device. One of {@link #IO_CAPABILITY_OUT}, - * {@link #IO_CAPABILITY_IO}, {@link #IO_CAPABILITY_IN}, {@link #IO_CAPABILITY_NONE}, - * {@link #IO_CAPABILITY_KBDISP} or {@link #IO_CAPABILITY_UNKNOWN}. - * - * @hide - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @IoCapability - public int getIoCapability() { - if (getState() != STATE_ON) return BluetoothAdapter.IO_CAPABILITY_UNKNOWN; - try { - mServiceLock.readLock().lock(); - if (mService != null) return mService.getIoCapability(mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, e.getMessage(), e); - } finally { - mServiceLock.readLock().unlock(); - } - return BluetoothAdapter.IO_CAPABILITY_UNKNOWN; - } - - /** - * Sets the Input/Output capability of the device for classic Bluetooth. - * - * <p>Changing the Input/Output capability of a device only takes effect on restarting the - * Bluetooth stack. You would need to restart the stack using {@link BluetoothAdapter#disable()} - * and {@link BluetoothAdapter#enable()} to see the changes. - * - * @param capability Input/Output capability of the device. One of {@link #IO_CAPABILITY_OUT}, - * {@link #IO_CAPABILITY_IO}, {@link #IO_CAPABILITY_IN}, - * {@link #IO_CAPABILITY_NONE} or {@link #IO_CAPABILITY_KBDISP}. - * - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setIoCapability(@IoCapability int capability) { - if (getState() != STATE_ON) return false; - try { - mServiceLock.readLock().lock(); - if (mService != null) return mService.setIoCapability(capability, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, e.getMessage(), e); - } finally { - mServiceLock.readLock().unlock(); - } - return false; - } - - /** - * Returns the Input/Output capability of the device for BLE operations. - * - * @return Input/Output capability of the device. One of {@link #IO_CAPABILITY_OUT}, - * {@link #IO_CAPABILITY_IO}, {@link #IO_CAPABILITY_IN}, {@link #IO_CAPABILITY_NONE}, - * {@link #IO_CAPABILITY_KBDISP} or {@link #IO_CAPABILITY_UNKNOWN}. - * - * @hide - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @IoCapability - public int getLeIoCapability() { - if (getState() != STATE_ON) return BluetoothAdapter.IO_CAPABILITY_UNKNOWN; - try { - mServiceLock.readLock().lock(); - if (mService != null) return mService.getLeIoCapability(mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, e.getMessage(), e); - } finally { - mServiceLock.readLock().unlock(); - } - return BluetoothAdapter.IO_CAPABILITY_UNKNOWN; - } - - /** - * Sets the Input/Output capability of the device for BLE operations. - * - * <p>Changing the Input/Output capability of a device only takes effect on restarting the - * Bluetooth stack. You would need to restart the stack using {@link BluetoothAdapter#disable()} - * and {@link BluetoothAdapter#enable()} to see the changes. - * - * @param capability Input/Output capability of the device. One of {@link #IO_CAPABILITY_OUT}, - * {@link #IO_CAPABILITY_IO}, {@link #IO_CAPABILITY_IN}, - * {@link #IO_CAPABILITY_NONE} or {@link #IO_CAPABILITY_KBDISP}. - * - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setLeIoCapability(@IoCapability int capability) { - if (getState() != STATE_ON) return false; - try { - mServiceLock.readLock().lock(); - if (mService != null) return mService.setLeIoCapability(capability, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, e.getMessage(), e); - } finally { - mServiceLock.readLock().unlock(); - } - return false; - } - - /** - * Get the current Bluetooth scan mode of the local Bluetooth adapter. - * <p>The Bluetooth scan mode determines if the local adapter is - * connectable and/or discoverable from remote Bluetooth devices. - * <p>Possible values are: - * {@link #SCAN_MODE_NONE}, - * {@link #SCAN_MODE_CONNECTABLE}, - * {@link #SCAN_MODE_CONNECTABLE_DISCOVERABLE}. - * <p>If Bluetooth state is not {@link #STATE_ON}, this API - * will return {@link #SCAN_MODE_NONE}. After turning on Bluetooth, - * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON} - * to get the updated value. - * - * @return scan mode - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothScanPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - @ScanMode - public int getScanMode() { - if (getState() != STATE_ON) { - return SCAN_MODE_NONE; - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.getScanMode(mAttributionSource); - } - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } finally { - mServiceLock.readLock().unlock(); - } - return SCAN_MODE_NONE; - } - - /** - * Set the local Bluetooth adapter connectablility and discoverability. - * <p>If the scan mode is set to {@link #SCAN_MODE_CONNECTABLE_DISCOVERABLE}, - * it will change to {@link #SCAN_MODE_CONNECTABLE} after the discoverable timeout. - * The discoverable timeout can be set with {@link #setDiscoverableTimeout} and - * checked with {@link #getDiscoverableTimeout}. By default, the timeout is usually - * 120 seconds on phones which is enough for a remote device to initiate and complete - * its discovery process. - * <p>Applications cannot set the scan mode. They should use - * {@link #ACTION_REQUEST_DISCOVERABLE} instead. - * - * @param mode represents the desired state of the local device scan mode - * - * @return status code indicating whether the scan mode was successfully set - * @hide - */ - @SystemApi - @RequiresBluetoothScanPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_SCAN, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - @ScanModeStatusCode - public int setScanMode(@ScanMode int mode) { - if (getState() != STATE_ON) { - return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED; - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.setScanMode(mode, mAttributionSource); - } - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } finally { - mServiceLock.readLock().unlock(); - } - return BluetoothStatusCodes.ERROR_UNKNOWN; - } - - /** - * Get the timeout duration of the {@link #SCAN_MODE_CONNECTABLE_DISCOVERABLE}. - * - * @return the duration of the discoverable timeout or null if an error has occurred - */ - @RequiresBluetoothScanPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - public @Nullable Duration getDiscoverableTimeout() { - if (getState() != STATE_ON) { - return null; - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - long timeout = mService.getDiscoverableTimeout(mAttributionSource); - return (timeout == -1) ? null : Duration.ofSeconds(timeout); - } - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } finally { - mServiceLock.readLock().unlock(); - } - return null; - } - - /** - * Set the total time the Bluetooth local adapter will stay discoverable when - * {@link #setScanMode} is called with {@link #SCAN_MODE_CONNECTABLE_DISCOVERABLE} mode. - * After this timeout, the scan mode will fallback to {@link #SCAN_MODE_CONNECTABLE}. - * <p>If <code>timeout</code> is set to 0, no timeout will occur and the scan mode will - * be persisted until a subsequent call to {@link #setScanMode}. - * - * @param timeout represents the total duration the local Bluetooth adapter will remain - * discoverable, or no timeout if set to 0 - * @return whether the timeout was successfully set - * @throws IllegalArgumentException if <code>timeout</code> duration in seconds is more - * than {@link Integer#MAX_VALUE} - * @hide - */ - @SystemApi - @RequiresBluetoothScanPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_SCAN, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - @ScanModeStatusCode - public int setDiscoverableTimeout(@NonNull Duration timeout) { - if (getState() != STATE_ON) { - return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED; - } - if (timeout.toSeconds() > Integer.MAX_VALUE) { - throw new IllegalArgumentException("Timeout in seconds must be less or equal to " - + Integer.MAX_VALUE); - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.setDiscoverableTimeout(timeout.toSeconds(), mAttributionSource); - } - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } finally { - mServiceLock.readLock().unlock(); - } - return BluetoothStatusCodes.ERROR_UNKNOWN; - } - - /** - * Get the end time of the latest remote device discovery process. - * - * @return the latest time that the bluetooth adapter was/will be in discovery mode, in - * milliseconds since the epoch. This time can be in the future if {@link #startDiscovery()} has - * been called recently. - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public long getDiscoveryEndMillis() { - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.getDiscoveryEndMillis(mAttributionSource); - } - } catch (RemoteException e) { - Log.e(TAG, "", e); - } finally { - mServiceLock.readLock().unlock(); - } - return -1; - } - - /** - * Start the remote device discovery process. - * <p>The discovery process usually involves an inquiry scan of about 12 - * seconds, followed by a page scan of each new device to retrieve its - * Bluetooth name. - * <p>This is an asynchronous call, it will return immediately. Register - * for {@link #ACTION_DISCOVERY_STARTED} and {@link - * #ACTION_DISCOVERY_FINISHED} intents to determine exactly when the - * discovery starts and completes. Register for {@link - * BluetoothDevice#ACTION_FOUND} to be notified as remote Bluetooth devices - * are found. - * <p>Device discovery is a heavyweight procedure. New connections to - * remote Bluetooth devices should not be attempted while discovery is in - * progress, and existing connections will experience limited bandwidth - * and high latency. Use {@link #cancelDiscovery()} to cancel an ongoing - * discovery. Discovery is not managed by the Activity, - * but is run as a system service, so an application should always call - * {@link BluetoothAdapter#cancelDiscovery()} even if it - * did not directly request a discovery, just to be sure. - * <p>Device discovery will only find remote devices that are currently - * <i>discoverable</i> (inquiry scan enabled). Many Bluetooth devices are - * not discoverable by default, and need to be entered into a special mode. - * <p>If Bluetooth state is not {@link #STATE_ON}, this API - * will return false. After turning on Bluetooth, wait for {@link #ACTION_STATE_CHANGED} - * with {@link #STATE_ON} to get the updated value. - * <p>If a device is currently bonding, this request will be queued and executed once that - * device has finished bonding. If a request is already queued, this request will be ignored. - * - * @return true on success, false on error - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothScanPermission - @RequiresBluetoothLocationPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - public boolean startDiscovery() { - if (getState() != STATE_ON) { - return false; - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.startDiscovery(mAttributionSource); - } - } catch (RemoteException e) { - Log.e(TAG, "", e); - } finally { - mServiceLock.readLock().unlock(); - } - return false; - } - - /** - * Cancel the current device discovery process. - * <p>Because discovery is a heavyweight procedure for the Bluetooth - * adapter, this method should always be called before attempting to connect - * to a remote device with {@link - * android.bluetooth.BluetoothSocket#connect()}. Discovery is not managed by - * the Activity, but is run as a system service, so an application should - * always call cancel discovery even if it did not directly request a - * discovery, just to be sure. - * <p>If Bluetooth state is not {@link #STATE_ON}, this API - * will return false. After turning on Bluetooth, - * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON} - * to get the updated value. - * - * @return true on success, false on error - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothScanPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - public boolean cancelDiscovery() { - if (getState() != STATE_ON) { - return false; - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.cancelDiscovery(mAttributionSource); - } - } catch (RemoteException e) { - Log.e(TAG, "", e); - } finally { - mServiceLock.readLock().unlock(); - } - return false; - } - - /** - * Return true if the local Bluetooth adapter is currently in the device - * discovery process. - * <p>Device discovery is a heavyweight procedure. New connections to - * remote Bluetooth devices should not be attempted while discovery is in - * progress, and existing connections will experience limited bandwidth - * and high latency. Use {@link #cancelDiscovery()} to cancel an ongoing - * discovery. - * <p>Applications can also register for {@link #ACTION_DISCOVERY_STARTED} - * or {@link #ACTION_DISCOVERY_FINISHED} to be notified when discovery - * starts or completes. - * <p>If Bluetooth state is not {@link #STATE_ON}, this API - * will return false. After turning on Bluetooth, - * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON} - * to get the updated value. - * - * @return true if discovering - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothScanPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - public boolean isDiscovering() { - if (getState() != STATE_ON) { - return false; - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.isDiscovering(mAttributionSource); - } - } catch (RemoteException e) { - Log.e(TAG, "", e); - } finally { - mServiceLock.readLock().unlock(); - } - return false; - } - - /** - * Removes the active device for the grouping of @ActiveDeviceUse specified - * - * @param profiles represents the purpose for which we are setting this as the active device. - * Possible values are: - * {@link BluetoothAdapter#ACTIVE_DEVICE_AUDIO}, - * {@link BluetoothAdapter#ACTIVE_DEVICE_PHONE_CALL}, - * {@link BluetoothAdapter#ACTIVE_DEVICE_ALL} - * @return false on immediate error, true otherwise - * @throws IllegalArgumentException if device is null or profiles is not one of - * {@link ActiveDeviceUse} - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - android.Manifest.permission.MODIFY_PHONE_STATE, - }) - public boolean removeActiveDevice(@ActiveDeviceUse int profiles) { - if (profiles != ACTIVE_DEVICE_AUDIO && profiles != ACTIVE_DEVICE_PHONE_CALL - && profiles != ACTIVE_DEVICE_ALL) { - Log.e(TAG, "Invalid profiles param value in removeActiveDevice"); - throw new IllegalArgumentException("Profiles must be one of " - + "BluetoothAdapter.ACTIVE_DEVICE_AUDIO, " - + "BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL, or " - + "BluetoothAdapter.ACTIVE_DEVICE_ALL"); - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - if (DBG) Log.d(TAG, "removeActiveDevice, profiles: " + profiles); - return mService.removeActiveDevice(profiles, mAttributionSource); - } - } catch (RemoteException e) { - Log.e(TAG, "", e); - } finally { - mServiceLock.readLock().unlock(); - } - - return false; - } - - /** - * Sets device as the active devices for the profiles passed into the function - * - * @param device is the remote bluetooth device - * @param profiles represents the purpose for which we are setting this as the active device. - * Possible values are: - * {@link BluetoothAdapter#ACTIVE_DEVICE_AUDIO}, - * {@link BluetoothAdapter#ACTIVE_DEVICE_PHONE_CALL}, - * {@link BluetoothAdapter#ACTIVE_DEVICE_ALL} - * @return false on immediate error, true otherwise - * @throws IllegalArgumentException if device is null or profiles is not one of - * {@link ActiveDeviceUse} - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - android.Manifest.permission.MODIFY_PHONE_STATE, - }) - public boolean setActiveDevice(@NonNull BluetoothDevice device, - @ActiveDeviceUse int profiles) { - if (device == null) { - Log.e(TAG, "setActiveDevice: Null device passed as parameter"); - throw new IllegalArgumentException("device cannot be null"); - } - if (profiles != ACTIVE_DEVICE_AUDIO && profiles != ACTIVE_DEVICE_PHONE_CALL - && profiles != ACTIVE_DEVICE_ALL) { - Log.e(TAG, "Invalid profiles param value in setActiveDevice"); - throw new IllegalArgumentException("Profiles must be one of " - + "BluetoothAdapter.ACTIVE_DEVICE_AUDIO, " - + "BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL, or " - + "BluetoothAdapter.ACTIVE_DEVICE_ALL"); - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - if (DBG) { - Log.d(TAG, "setActiveDevice, device: " + device + ", profiles: " + profiles); - } - return mService.setActiveDevice(device, profiles, mAttributionSource); - } - } catch (RemoteException e) { - Log.e(TAG, "", e); - } finally { - mServiceLock.readLock().unlock(); - } - - return false; - } - - /** - * Get the active devices for the BluetoothProfile specified - * - * @param profile is the profile from which we want the active devices. - * Possible values are: - * {@link BluetoothProfile#HEADSET}, - * {@link BluetoothProfile#A2DP}, - * {@link BluetoothProfile#HEARING_AID} - * {@link BluetoothProfile#LE_AUDIO} - * @return A list of active bluetooth devices - * @throws IllegalArgumentException If profile is not one of {@link ActiveDeviceProfile} - * @hide - */ - @SystemApi - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public @NonNull List<BluetoothDevice> getActiveDevices(@ActiveDeviceProfile int profile) { - if (profile != BluetoothProfile.HEADSET - && profile != BluetoothProfile.A2DP - && profile != BluetoothProfile.HEARING_AID - && profile != BluetoothProfile.LE_AUDIO) { - Log.e(TAG, "Invalid profile param value in getActiveDevices"); - throw new IllegalArgumentException("Profiles must be one of " - + "BluetoothProfile.A2DP, " - + "BluetoothProfile.HEARING_AID, or" - + "BluetoothProfile.HEARING_AID" - + "BluetoothProfile.LE_AUDIO"); - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - if (DBG) { - Log.d(TAG, "getActiveDevices(profile= " - + BluetoothProfile.getProfileName(profile) + ")"); - } - return mService.getActiveDevices(profile, mAttributionSource); - } - } catch (RemoteException e) { - Log.e(TAG, "", e); - } finally { - mServiceLock.readLock().unlock(); - } - - return new ArrayList<>(); - } - - /** - * Return true if the multi advertisement is supported by the chipset - * - * @return true if Multiple Advertisement feature is supported - */ - @RequiresLegacyBluetoothPermission - @RequiresNoPermission - public boolean isMultipleAdvertisementSupported() { - if (getState() != STATE_ON) { - return false; - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.isMultiAdvertisementSupported(); - } - } catch (RemoteException e) { - Log.e(TAG, "failed to get isMultipleAdvertisementSupported, error: ", e); - } finally { - mServiceLock.readLock().unlock(); - } - return false; - } - - /** - * Returns {@code true} if BLE scan is always available, {@code false} otherwise. <p> - * - * If this returns {@code true}, application can issue {@link BluetoothLeScanner#startScan} and - * fetch scan results even when Bluetooth is turned off.<p> - * - * To change this setting, use {@link #ACTION_REQUEST_BLE_SCAN_ALWAYS_AVAILABLE}. - * - * @hide - */ - @SystemApi - @RequiresNoPermission - public boolean isBleScanAlwaysAvailable() { - try { - return mManagerService.isBleScanAlwaysAvailable(); - } catch (RemoteException e) { - Log.e(TAG, "remote exception when calling isBleScanAlwaysAvailable", e); - return false; - } - } - - /* - private static final String BLUETOOTH_FILTERING_CACHE_PROPERTY = - "cache_key.bluetooth.is_offloaded_filtering_supported"; - private final PropertyInvalidatedCache<Void, Boolean> mBluetoothFilteringCache = - new PropertyInvalidatedCache<Void, Boolean>( - 8, BLUETOOTH_FILTERING_CACHE_PROPERTY) { - @Override - @SuppressLint("AndroidFrameworkRequiresPermission") - public Boolean recompute(Void query) { - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.isOffloadedFilteringSupported(); - } - } catch (RemoteException e) { - Log.e(TAG, "failed to get isOffloadedFilteringSupported, error: ", e); - } finally { - mServiceLock.readLock().unlock(); - } - return false; - - } - }; - */ - - /** @hide */ - /* - @RequiresNoPermission - public void disableIsOffloadedFilteringSupportedCache() { - mBluetoothFilteringCache.disableLocal(); - } - */ - - /** @hide */ - /* - public static void invalidateIsOffloadedFilteringSupportedCache() { - PropertyInvalidatedCache.invalidateCache(BLUETOOTH_FILTERING_CACHE_PROPERTY); - } - */ - - /** - * Return true if offloaded filters are supported - * - * @return true if chipset supports on-chip filtering - */ - @RequiresLegacyBluetoothPermission - @RequiresNoPermission - public boolean isOffloadedFilteringSupported() { - if (!getLeAccess()) { - return false; - } - //return mBluetoothFilteringCache.query(null); - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.isOffloadedFilteringSupported(); - } - } catch (RemoteException e) { - Log.e(TAG, "failed to get isOffloadedFilteringSupported, error: ", e); - } finally { - mServiceLock.readLock().unlock(); - } - return false; - } - - /** - * Return true if offloaded scan batching is supported - * - * @return true if chipset supports on-chip scan batching - */ - @RequiresLegacyBluetoothPermission - @RequiresNoPermission - public boolean isOffloadedScanBatchingSupported() { - if (!getLeAccess()) { - return false; - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.isOffloadedScanBatchingSupported(); - } - } catch (RemoteException e) { - Log.e(TAG, "failed to get isOffloadedScanBatchingSupported, error: ", e); - } finally { - mServiceLock.readLock().unlock(); - } - return false; - } - - /** - * Return true if LE 2M PHY feature is supported. - * - * @return true if chipset supports LE 2M PHY feature - */ - @RequiresLegacyBluetoothPermission - @RequiresNoPermission - public boolean isLe2MPhySupported() { - if (!getLeAccess()) { - return false; - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.isLe2MPhySupported(); - } - } catch (RemoteException e) { - Log.e(TAG, "failed to get isExtendedAdvertisingSupported, error: ", e); - } finally { - mServiceLock.readLock().unlock(); - } - return false; - } - - /** - * Return true if LE Coded PHY feature is supported. - * - * @return true if chipset supports LE Coded PHY feature - */ - @RequiresLegacyBluetoothPermission - @RequiresNoPermission - public boolean isLeCodedPhySupported() { - if (!getLeAccess()) { - return false; - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.isLeCodedPhySupported(); - } - } catch (RemoteException e) { - Log.e(TAG, "failed to get isLeCodedPhySupported, error: ", e); - } finally { - mServiceLock.readLock().unlock(); - } - return false; - } - - /** - * Return true if LE Extended Advertising feature is supported. - * - * @return true if chipset supports LE Extended Advertising feature - */ - @RequiresLegacyBluetoothPermission - @RequiresNoPermission - public boolean isLeExtendedAdvertisingSupported() { - if (!getLeAccess()) { - return false; - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.isLeExtendedAdvertisingSupported(); - } - } catch (RemoteException e) { - Log.e(TAG, "failed to get isLeExtendedAdvertisingSupported, error: ", e); - } finally { - mServiceLock.readLock().unlock(); - } - return false; - } - - /** - * Return true if LE Periodic Advertising feature is supported. - * - * @return true if chipset supports LE Periodic Advertising feature - */ - @RequiresLegacyBluetoothPermission - @RequiresNoPermission - public boolean isLePeriodicAdvertisingSupported() { - if (!getLeAccess()) { - return false; - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.isLePeriodicAdvertisingSupported(); - } - } catch (RemoteException e) { - Log.e(TAG, "failed to get isLePeriodicAdvertisingSupported, error: ", e); - } finally { - mServiceLock.readLock().unlock(); - } - return false; - } - - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(value = { - BluetoothStatusCodes.FEATURE_SUPPORTED, - BluetoothStatusCodes.ERROR_UNKNOWN, - BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED, - BluetoothStatusCodes.FEATURE_NOT_SUPPORTED, - }) - public @interface LeFeatureReturnValues {} - - /** - * Returns {@link BluetoothStatusCodes#FEATURE_SUPPORTED} if the LE audio feature is - * supported, {@link BluetoothStatusCodes#FEATURE_NOT_SUPPORTED} if the feature is not - * supported, or an error code. - * - * @return whether the LE audio is supported - */ - @RequiresNoPermission - public @LeFeatureReturnValues int isLeAudioSupported() { - if (!getLeAccess()) { - return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED; - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.isLeAudioSupported(); - } - } catch (RemoteException e) { - e.rethrowFromSystemServer(); - } finally { - mServiceLock.readLock().unlock(); - } - return BluetoothStatusCodes.ERROR_UNKNOWN; - } - - /** - * Returns {@link BluetoothStatusCodes#FEATURE_SUPPORTED} if LE Periodic Advertising Sync - * Transfer Sender feature is supported, - * {@link BluetoothStatusCodes#FEATURE_NOT_SUPPORTED} if the feature is not supported, or - * an error code - * - * @return whether the chipset supports the LE Periodic Advertising Sync Transfer Sender feature - */ - @RequiresNoPermission - public @LeFeatureReturnValues int isLePeriodicAdvertisingSyncTransferSenderSupported() { - if (!getLeAccess()) { - return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED; - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.isLePeriodicAdvertisingSyncTransferSenderSupported(); - } - } catch (RemoteException e) { - e.rethrowFromSystemServer(); - } finally { - mServiceLock.readLock().unlock(); - } - return BluetoothStatusCodes.ERROR_UNKNOWN; - } - - /** - * Return the maximum LE advertising data length in bytes, - * if LE Extended Advertising feature is supported, 0 otherwise. - * - * @return the maximum LE advertising data length. - */ - @RequiresLegacyBluetoothPermission - @RequiresNoPermission - public int getLeMaximumAdvertisingDataLength() { - if (!getLeAccess()) { - return 0; - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.getLeMaximumAdvertisingDataLength(); - } - } catch (RemoteException e) { - Log.e(TAG, "failed to get getLeMaximumAdvertisingDataLength, error: ", e); - } finally { - mServiceLock.readLock().unlock(); - } - return 0; - } - - /** - * Return true if Hearing Aid Profile is supported. - * - * @return true if phone supports Hearing Aid Profile - */ - @RequiresNoPermission - private boolean isHearingAidProfileSupported() { - try { - return mManagerService.isHearingAidProfileSupported(); - } catch (RemoteException e) { - Log.e(TAG, "remote exception when calling isHearingAidProfileSupported", e); - return false; - } - } - - /** - * Get the maximum number of connected audio devices. - * - * @return the maximum number of connected audio devices - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public int getMaxConnectedAudioDevices() { - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.getMaxConnectedAudioDevices(mAttributionSource); - } - } catch (RemoteException e) { - Log.e(TAG, "failed to get getMaxConnectedAudioDevices, error: ", e); - } finally { - mServiceLock.readLock().unlock(); - } - return 1; - } - - /** - * Return true if hardware has entries available for matching beacons - * - * @return true if there are hw entries available for matching beacons - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean isHardwareTrackingFiltersAvailable() { - if (!getLeAccess()) { - return false; - } - try { - IBluetoothGatt iGatt = mManagerService.getBluetoothGatt(); - if (iGatt == null) { - // BLE is not supported - return false; - } - return (iGatt.numHwTrackFiltersAvailable(mAttributionSource) != 0); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return false; - } - - /** - * Request the record of {@link BluetoothActivityEnergyInfo} object that - * has the activity and energy info. This can be used to ascertain what - * the controller has been up to, since the last sample. - * - * A null value for the activity info object may be sent if the bluetooth service is - * unreachable or the device does not support reporting such information. - * - * @param result The callback to which to send the activity info. - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public void requestControllerActivityEnergyInfo(ResultReceiver result) { - try { - mServiceLock.readLock().lock(); - if (mService != null) { - mService.requestActivityInfo(result, mAttributionSource); - result = null; - } - } catch (RemoteException e) { - Log.e(TAG, "getControllerActivityEnergyInfoCallback: " + e); - } finally { - mServiceLock.readLock().unlock(); - if (result != null) { - // Only send an immediate result if we failed. - result.send(0, null); - } - } - } - - /** - * Fetches a list of the most recently connected bluetooth devices ordered by how recently they - * were connected with most recently first and least recently last - * - * @return {@link List} of bonded {@link BluetoothDevice} ordered by how recently they were - * connected - * - * @hide - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public @NonNull List<BluetoothDevice> getMostRecentlyConnectedDevices() { - if (getState() != STATE_ON) { - return new ArrayList<>(); - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return Attributable.setAttributionSource( - mService.getMostRecentlyConnectedDevices(mAttributionSource), - mAttributionSource); - } - } catch (RemoteException e) { - Log.e(TAG, "", e); - } finally { - mServiceLock.readLock().unlock(); - } - return new ArrayList<>(); - } - - /** - * Return the set of {@link BluetoothDevice} objects that are bonded - * (paired) to the local adapter. - * <p>If Bluetooth state is not {@link #STATE_ON}, this API - * will return an empty set. After turning on Bluetooth, - * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON} - * to get the updated value. - * - * @return unmodifiable set of {@link BluetoothDevice}, or null on error - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public Set<BluetoothDevice> getBondedDevices() { - if (getState() != STATE_ON) { - return toDeviceSet(Arrays.asList()); - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return toDeviceSet(Attributable.setAttributionSource( - Arrays.asList(mService.getBondedDevices(mAttributionSource)), - mAttributionSource)); - } - return toDeviceSet(Arrays.asList()); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } finally { - mServiceLock.readLock().unlock(); - } - return null; - } - - /** - * Gets the currently supported profiles by the adapter. - * - * <p> This can be used to check whether a profile is supported before attempting - * to connect to its respective proxy. - * - * @return a list of integers indicating the ids of supported profiles as defined in {@link - * BluetoothProfile}. - * @hide - */ - @RequiresNoPermission - public @NonNull List<Integer> getSupportedProfiles() { - final ArrayList<Integer> supportedProfiles = new ArrayList<Integer>(); - - try { - synchronized (mManagerCallback) { - if (mService != null) { - final long supportedProfilesBitMask = mService.getSupportedProfiles(); - - for (int i = 0; i <= BluetoothProfile.MAX_PROFILE_ID; i++) { - if ((supportedProfilesBitMask & (1 << i)) != 0) { - supportedProfiles.add(i); - } - } - } else { - // Bluetooth is disabled. Just fill in known supported Profiles - if (isHearingAidProfileSupported()) { - supportedProfiles.add(BluetoothProfile.HEARING_AID); - } - } - } - } catch (RemoteException e) { - Log.e(TAG, "getSupportedProfiles:", e); - } - return supportedProfiles; - } - - /* - private static final String BLUETOOTH_GET_ADAPTER_CONNECTION_STATE_CACHE_PROPERTY = - "cache_key.bluetooth.get_adapter_connection_state"; - private final PropertyInvalidatedCache<Void, Integer> - mBluetoothGetAdapterConnectionStateCache = - new PropertyInvalidatedCache<Void, Integer> ( - 8, BLUETOOTH_GET_ADAPTER_CONNECTION_STATE_CACHE_PROPERTY) { - @Override - @SuppressLint("AndroidFrameworkRequiresPermission") - public Integer recompute(Void query) { - try { - return mService.getAdapterConnectionState(); - } catch (RemoteException e) { - throw e.rethrowAsRuntimeException(); - } - } - }; - */ - - /** @hide */ - /* - @RequiresNoPermission - public void disableGetAdapterConnectionStateCache() { - mBluetoothGetAdapterConnectionStateCache.disableLocal(); - } - */ - - /** @hide */ - /* - public static void invalidateGetAdapterConnectionStateCache() { - PropertyInvalidatedCache.invalidateCache( - BLUETOOTH_GET_ADAPTER_CONNECTION_STATE_CACHE_PROPERTY); - } - */ - - /** - * Get the current connection state of the local Bluetooth adapter. - * This can be used to check whether the local Bluetooth adapter is connected - * to any profile of any other remote Bluetooth Device. - * - * <p> Use this function along with {@link #ACTION_CONNECTION_STATE_CHANGED} - * intent to get the connection state of the adapter. - * - * @return One of {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTED}, {@link - * #STATE_CONNECTING} or {@link #STATE_DISCONNECTED} - * @hide - */ - @UnsupportedAppUsage - @RequiresLegacyBluetoothPermission - @RequiresNoPermission - public int getConnectionState() { - if (getState() != STATE_ON) { - return BluetoothAdapter.STATE_DISCONNECTED; - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.getAdapterConnectionState(); - } - //return mBluetoothGetAdapterConnectionStateCache.query(null); - } catch (RemoteException e) { - Log.e(TAG, "failed to getConnectionState, error: ", e); - } finally { - mServiceLock.readLock().unlock(); - } - return BluetoothAdapter.STATE_DISCONNECTED; - } - - /* - private static final String BLUETOOTH_PROFILE_CACHE_PROPERTY = - "cache_key.bluetooth.get_profile_connection_state"; - private final PropertyInvalidatedCache<Integer, Integer> - mGetProfileConnectionStateCache = - new PropertyInvalidatedCache<Integer, Integer>( - 8, BLUETOOTH_PROFILE_CACHE_PROPERTY) { - @Override - @SuppressLint("AndroidFrameworkRequiresPermission") - public Integer recompute(Integer query) { - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.getProfileConnectionState(query); - } - } catch (RemoteException e) { - Log.e(TAG, "getProfileConnectionState:", e); - } finally { - mServiceLock.readLock().unlock(); - } - return BluetoothProfile.STATE_DISCONNECTED; - } - @Override - public String queryToString(Integer query) { - return String.format("getProfileConnectionState(profile=\"%d\")", - query); - } - }; - */ - - /** @hide */ - /* - @RequiresNoPermission - public void disableGetProfileConnectionStateCache() { - mGetProfileConnectionStateCache.disableLocal(); - } - */ - - /** @hide */ - /* - public static void invalidateGetProfileConnectionStateCache() { - PropertyInvalidatedCache.invalidateCache(BLUETOOTH_PROFILE_CACHE_PROPERTY); - } - */ - - /** - * Get the current connection state of a profile. - * This function can be used to check whether the local Bluetooth adapter - * is connected to any remote device for a specific profile. - * Profile can be one of {@link BluetoothProfile#HEADSET}, {@link BluetoothProfile#A2DP}. - * - * <p> Return value can be one of - * {@link BluetoothProfile#STATE_DISCONNECTED}, - * {@link BluetoothProfile#STATE_CONNECTING}, - * {@link BluetoothProfile#STATE_CONNECTED}, - * {@link BluetoothProfile#STATE_DISCONNECTING} - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SuppressLint("AndroidFrameworkRequiresPermission") - public int getProfileConnectionState(int profile) { - if (getState() != STATE_ON) { - return BluetoothProfile.STATE_DISCONNECTED; - } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - mService.getProfileConnectionState(profile); - } - //return mGetProfileConnectionStateCache.query(new Integer(profile)); - } catch (RemoteException e) { - Log.e(TAG, "failed to getProfileConnectionState, error: ", e); - } finally { - mServiceLock.readLock().unlock(); - } - return BluetoothProfile.STATE_DISCONNECTED; - } - - /** - * Create a listening, secure RFCOMM Bluetooth socket. - * <p>A remote device connecting to this socket will be authenticated and - * communication on this socket will be encrypted. - * <p>Use {@link BluetoothServerSocket#accept} to retrieve incoming - * connections from a listening {@link BluetoothServerSocket}. - * <p>Valid RFCOMM channels are in range 1 to 30. - * - * @param channel RFCOMM channel to listen on - * @return a listening RFCOMM BluetoothServerSocket - * @throws IOException on error, for example Bluetooth not available, or insufficient - * permissions, or channel in use. - * @hide - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothServerSocket listenUsingRfcommOn(int channel) throws IOException { - return listenUsingRfcommOn(channel, false, false); - } - - /** - * Create a listening, secure RFCOMM Bluetooth socket. - * <p>A remote device connecting to this socket will be authenticated and - * communication on this socket will be encrypted. - * <p>Use {@link BluetoothServerSocket#accept} to retrieve incoming - * connections from a listening {@link BluetoothServerSocket}. - * <p>Valid RFCOMM channels are in range 1 to 30. - * <p>To auto assign a channel without creating a SDP record use - * {@link #SOCKET_CHANNEL_AUTO_STATIC_NO_SDP} as channel number. - * - * @param channel RFCOMM channel to listen on - * @param mitm enforce person-in-the-middle protection for authentication. - * @param min16DigitPin enforce a pin key length og minimum 16 digit for sec mode 2 - * connections. - * @return a listening RFCOMM BluetoothServerSocket - * @throws IOException on error, for example Bluetooth not available, or insufficient - * permissions, or channel in use. - * @hide - */ - @UnsupportedAppUsage - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothServerSocket listenUsingRfcommOn(int channel, boolean mitm, - boolean min16DigitPin) throws IOException { - BluetoothServerSocket socket = - new BluetoothServerSocket(BluetoothSocket.TYPE_RFCOMM, true, true, channel, mitm, - min16DigitPin); - int errno = socket.mSocket.bindListen(); - if (channel == SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) { - socket.setChannel(socket.mSocket.getPort()); - } - if (errno != 0) { - //TODO(BT): Throw the same exception error code - // that the previous code was using. - //socket.mSocket.throwErrnoNative(errno); - throw new IOException("Error: " + errno); - } - return socket; - } - - /** - * Create a listening, secure RFCOMM Bluetooth socket with Service Record. - * <p>A remote device connecting to this socket will be authenticated and - * communication on this socket will be encrypted. - * <p>Use {@link BluetoothServerSocket#accept} to retrieve incoming - * connections from a listening {@link BluetoothServerSocket}. - * <p>The system will assign an unused RFCOMM channel to listen on. - * <p>The system will also register a Service Discovery - * Protocol (SDP) record with the local SDP server containing the specified - * UUID, service name, and auto-assigned channel. Remote Bluetooth devices - * can use the same UUID to query our SDP server and discover which channel - * to connect to. This SDP record will be removed when this socket is - * closed, or if this application closes unexpectedly. - * <p>Use {@link BluetoothDevice#createRfcommSocketToServiceRecord} to - * connect to this socket from another device using the same {@link UUID}. - * - * @param name service name for SDP record - * @param uuid uuid for SDP record - * @return a listening RFCOMM BluetoothServerSocket - * @throws IOException on error, for example Bluetooth not available, or insufficient - * permissions, or channel in use. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothServerSocket listenUsingRfcommWithServiceRecord(String name, UUID uuid) - throws IOException { - return createNewRfcommSocketAndRecord(name, uuid, true, true); - } - - /** - * Create a listening, insecure RFCOMM Bluetooth socket with Service Record. - * <p>The link key is not required to be authenticated, i.e the communication may be - * vulnerable to Person In the Middle attacks. For Bluetooth 2.1 devices, - * the link will be encrypted, as encryption is mandatory. - * For legacy devices (pre Bluetooth 2.1 devices) the link will not - * be encrypted. Use {@link #listenUsingRfcommWithServiceRecord}, if an - * encrypted and authenticated communication channel is desired. - * <p>Use {@link BluetoothServerSocket#accept} to retrieve incoming - * connections from a listening {@link BluetoothServerSocket}. - * <p>The system will assign an unused RFCOMM channel to listen on. - * <p>The system will also register a Service Discovery - * Protocol (SDP) record with the local SDP server containing the specified - * UUID, service name, and auto-assigned channel. Remote Bluetooth devices - * can use the same UUID to query our SDP server and discover which channel - * to connect to. This SDP record will be removed when this socket is - * closed, or if this application closes unexpectedly. - * <p>Use {@link BluetoothDevice#createRfcommSocketToServiceRecord} to - * connect to this socket from another device using the same {@link UUID}. - * - * @param name service name for SDP record - * @param uuid uuid for SDP record - * @return a listening RFCOMM BluetoothServerSocket - * @throws IOException on error, for example Bluetooth not available, or insufficient - * permissions, or channel in use. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothServerSocket listenUsingInsecureRfcommWithServiceRecord(String name, UUID uuid) - throws IOException { - return createNewRfcommSocketAndRecord(name, uuid, false, false); - } - - /** - * Create a listening, encrypted, - * RFCOMM Bluetooth socket with Service Record. - * <p>The link will be encrypted, but the link key is not required to be authenticated - * i.e the communication is vulnerable to Person In the Middle attacks. Use - * {@link #listenUsingRfcommWithServiceRecord}, to ensure an authenticated link key. - * <p> Use this socket if authentication of link key is not possible. - * For example, for Bluetooth 2.1 devices, if any of the devices does not have - * an input and output capability or just has the ability to display a numeric key, - * a secure socket connection is not possible and this socket can be used. - * Use {@link #listenUsingInsecureRfcommWithServiceRecord}, if encryption is not required. - * For Bluetooth 2.1 devices, the link will be encrypted, as encryption is mandatory. - * For more details, refer to the Security Model section 5.2 (vol 3) of - * Bluetooth Core Specification version 2.1 + EDR. - * <p>Use {@link BluetoothServerSocket#accept} to retrieve incoming - * connections from a listening {@link BluetoothServerSocket}. - * <p>The system will assign an unused RFCOMM channel to listen on. - * <p>The system will also register a Service Discovery - * Protocol (SDP) record with the local SDP server containing the specified - * UUID, service name, and auto-assigned channel. Remote Bluetooth devices - * can use the same UUID to query our SDP server and discover which channel - * to connect to. This SDP record will be removed when this socket is - * closed, or if this application closes unexpectedly. - * <p>Use {@link BluetoothDevice#createRfcommSocketToServiceRecord} to - * connect to this socket from another device using the same {@link UUID}. - * - * @param name service name for SDP record - * @param uuid uuid for SDP record - * @return a listening RFCOMM BluetoothServerSocket - * @throws IOException on error, for example Bluetooth not available, or insufficient - * permissions, or channel in use. - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothServerSocket listenUsingEncryptedRfcommWithServiceRecord(String name, UUID uuid) - throws IOException { - return createNewRfcommSocketAndRecord(name, uuid, false, true); - } - - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - private BluetoothServerSocket createNewRfcommSocketAndRecord(String name, UUID uuid, - boolean auth, boolean encrypt) throws IOException { - BluetoothServerSocket socket; - socket = new BluetoothServerSocket(BluetoothSocket.TYPE_RFCOMM, auth, encrypt, - new ParcelUuid(uuid)); - socket.setServiceName(name); - int errno = socket.mSocket.bindListen(); - if (errno != 0) { - //TODO(BT): Throw the same exception error code - // that the previous code was using. - //socket.mSocket.throwErrnoNative(errno); - throw new IOException("Error: " + errno); - } - return socket; - } - - /** - * Construct an unencrypted, unauthenticated, RFCOMM server socket. - * Call #accept to retrieve connections to this socket. - * - * @return An RFCOMM BluetoothServerSocket - * @throws IOException On error, for example Bluetooth not available, or insufficient - * permissions. - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothServerSocket listenUsingInsecureRfcommOn(int port) throws IOException { - BluetoothServerSocket socket = - new BluetoothServerSocket(BluetoothSocket.TYPE_RFCOMM, false, false, port); - int errno = socket.mSocket.bindListen(); - if (port == SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) { - socket.setChannel(socket.mSocket.getPort()); - } - if (errno != 0) { - //TODO(BT): Throw the same exception error code - // that the previous code was using. - //socket.mSocket.throwErrnoNative(errno); - throw new IOException("Error: " + errno); - } - return socket; - } - - /** - * Construct an encrypted, authenticated, L2CAP server socket. - * Call #accept to retrieve connections to this socket. - * <p>To auto assign a port without creating a SDP record use - * {@link #SOCKET_CHANNEL_AUTO_STATIC_NO_SDP} as port number. - * - * @param port the PSM to listen on - * @param mitm enforce person-in-the-middle protection for authentication. - * @param min16DigitPin enforce a pin key length og minimum 16 digit for sec mode 2 - * connections. - * @return An L2CAP BluetoothServerSocket - * @throws IOException On error, for example Bluetooth not available, or insufficient - * permissions. - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothServerSocket listenUsingL2capOn(int port, boolean mitm, boolean min16DigitPin) - throws IOException { - BluetoothServerSocket socket = - new BluetoothServerSocket(BluetoothSocket.TYPE_L2CAP, true, true, port, mitm, - min16DigitPin); - int errno = socket.mSocket.bindListen(); - if (port == SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) { - int assignedChannel = socket.mSocket.getPort(); - if (DBG) Log.d(TAG, "listenUsingL2capOn: set assigned channel to " + assignedChannel); - socket.setChannel(assignedChannel); - } - if (errno != 0) { - //TODO(BT): Throw the same exception error code - // that the previous code was using. - //socket.mSocket.throwErrnoNative(errno); - throw new IOException("Error: " + errno); - } - return socket; - } - - /** - * Construct an encrypted, authenticated, L2CAP server socket. - * Call #accept to retrieve connections to this socket. - * <p>To auto assign a port without creating a SDP record use - * {@link #SOCKET_CHANNEL_AUTO_STATIC_NO_SDP} as port number. - * - * @param port the PSM to listen on - * @return An L2CAP BluetoothServerSocket - * @throws IOException On error, for example Bluetooth not available, or insufficient - * permissions. - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothServerSocket listenUsingL2capOn(int port) throws IOException { - return listenUsingL2capOn(port, false, false); - } - - /** - * Construct an insecure L2CAP server socket. - * Call #accept to retrieve connections to this socket. - * <p>To auto assign a port without creating a SDP record use - * {@link #SOCKET_CHANNEL_AUTO_STATIC_NO_SDP} as port number. - * - * @param port the PSM to listen on - * @return An L2CAP BluetoothServerSocket - * @throws IOException On error, for example Bluetooth not available, or insufficient - * permissions. - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothServerSocket listenUsingInsecureL2capOn(int port) throws IOException { - Log.d(TAG, "listenUsingInsecureL2capOn: port=" + port); - BluetoothServerSocket socket = - new BluetoothServerSocket(BluetoothSocket.TYPE_L2CAP, false, false, port, false, - false); - int errno = socket.mSocket.bindListen(); - if (port == SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) { - int assignedChannel = socket.mSocket.getPort(); - if (DBG) { - Log.d(TAG, "listenUsingInsecureL2capOn: set assigned channel to " - + assignedChannel); - } - socket.setChannel(assignedChannel); - } - if (errno != 0) { - //TODO(BT): Throw the same exception error code - // that the previous code was using. - //socket.mSocket.throwErrnoNative(errno); - throw new IOException("Error: " + errno); - } - return socket; - - } - - /** - * Read the local Out of Band Pairing Data - * - * @return Pair<byte[], byte[]> of Hash and Randomizer - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SuppressLint("AndroidFrameworkRequiresPermission") - public Pair<byte[], byte[]> readOutOfBandData() { - return null; - } - - /** - * Get the profile proxy object associated with the profile. - * - * <p>Profile can be one of {@link BluetoothProfile#HEADSET}, {@link BluetoothProfile#A2DP}, - * {@link BluetoothProfile#GATT}, {@link BluetoothProfile#HEARING_AID}, or {@link - * BluetoothProfile#GATT_SERVER}. Clients must implement {@link - * BluetoothProfile.ServiceListener} to get notified of the connection status and to get the - * proxy object. - * - * @param context Context of the application - * @param listener The service Listener for connection callbacks. - * @param profile The Bluetooth profile; either {@link BluetoothProfile#HEADSET}, - * {@link BluetoothProfile#A2DP}, {@link BluetoothProfile#GATT}, {@link - * BluetoothProfile#HEARING_AID} or {@link BluetoothProfile#GATT_SERVER}. - * @return true on success, false on error - */ - @SuppressLint({ - "AndroidFrameworkRequiresPermission", - "AndroidFrameworkBluetoothPermission" - }) - public boolean getProfileProxy(Context context, BluetoothProfile.ServiceListener listener, - int profile) { - if (context == null || listener == null) { - return false; - } - - if (profile == BluetoothProfile.HEADSET) { - BluetoothHeadset headset = new BluetoothHeadset(context, listener, this); - return true; - } else if (profile == BluetoothProfile.A2DP) { - BluetoothA2dp a2dp = new BluetoothA2dp(context, listener, this); - return true; - } else if (profile == BluetoothProfile.A2DP_SINK) { - BluetoothA2dpSink a2dpSink = new BluetoothA2dpSink(context, listener, this); - return true; - } else if (profile == BluetoothProfile.AVRCP_CONTROLLER) { - BluetoothAvrcpController avrcp = new BluetoothAvrcpController(context, listener, this); - return true; - } else if (profile == BluetoothProfile.HID_HOST) { - BluetoothHidHost iDev = new BluetoothHidHost(context, listener, this); - return true; - } else if (profile == BluetoothProfile.PAN) { - BluetoothPan pan = new BluetoothPan(context, listener, this); - return true; - } else if (profile == BluetoothProfile.PBAP) { - BluetoothPbap pbap = new BluetoothPbap(context, listener, this); - return true; - } else if (profile == BluetoothProfile.HEALTH) { - Log.e(TAG, "getProfileProxy(): BluetoothHealth is deprecated"); - return false; - } else if (profile == BluetoothProfile.MAP) { - BluetoothMap map = new BluetoothMap(context, listener, this); - return true; - } else if (profile == BluetoothProfile.HEADSET_CLIENT) { - BluetoothHeadsetClient headsetClient = - new BluetoothHeadsetClient(context, listener, this); - return true; - } else if (profile == BluetoothProfile.SAP) { - BluetoothSap sap = new BluetoothSap(context, listener, this); - return true; - } else if (profile == BluetoothProfile.PBAP_CLIENT) { - BluetoothPbapClient pbapClient = new BluetoothPbapClient(context, listener, this); - return true; - } else if (profile == BluetoothProfile.MAP_CLIENT) { - BluetoothMapClient mapClient = new BluetoothMapClient(context, listener, this); - return true; - } else if (profile == BluetoothProfile.HID_DEVICE) { - BluetoothHidDevice hidDevice = new BluetoothHidDevice(context, listener, this); - return true; - } else if (profile == BluetoothProfile.HEARING_AID) { - if (isHearingAidProfileSupported()) { - BluetoothHearingAid hearingAid = new BluetoothHearingAid(context, listener, this); - return true; - } - return false; - } else if (profile == BluetoothProfile.LE_AUDIO) { - BluetoothLeAudio leAudio = new BluetoothLeAudio(context, listener, this); - return true; - } else if (profile == BluetoothProfile.VOLUME_CONTROL) { - BluetoothVolumeControl vcs = new BluetoothVolumeControl(context, listener, this); - return true; - } else if (profile == BluetoothProfile.CSIP_SET_COORDINATOR) { - BluetoothCsipSetCoordinator csipSetCoordinator = - new BluetoothCsipSetCoordinator(context, listener, this); - return true; - } else if (profile == BluetoothProfile.LE_CALL_CONTROL) { - BluetoothLeCallControl tbs = new BluetoothLeCallControl(context, listener); - return true; - } else { - return false; - } - } - - /** - * Close the connection of the profile proxy to the Service. - * - * <p> Clients should call this when they are no longer using - * the proxy obtained from {@link #getProfileProxy}. - * Profile can be one of {@link BluetoothProfile#HEADSET} or {@link BluetoothProfile#A2DP} - * - * @param profile - * @param proxy Profile proxy object - */ - @SuppressLint({ - "AndroidFrameworkRequiresPermission", - "AndroidFrameworkBluetoothPermission" - }) - public void closeProfileProxy(int profile, BluetoothProfile proxy) { - if (proxy == null) { - return; - } - - switch (profile) { - case BluetoothProfile.HEADSET: - BluetoothHeadset headset = (BluetoothHeadset) proxy; - headset.close(); - break; - case BluetoothProfile.A2DP: - BluetoothA2dp a2dp = (BluetoothA2dp) proxy; - a2dp.close(); - break; - case BluetoothProfile.A2DP_SINK: - BluetoothA2dpSink a2dpSink = (BluetoothA2dpSink) proxy; - a2dpSink.close(); - break; - case BluetoothProfile.AVRCP_CONTROLLER: - BluetoothAvrcpController avrcp = (BluetoothAvrcpController) proxy; - avrcp.close(); - break; - case BluetoothProfile.HID_HOST: - BluetoothHidHost iDev = (BluetoothHidHost) proxy; - iDev.close(); - break; - case BluetoothProfile.PAN: - BluetoothPan pan = (BluetoothPan) proxy; - pan.close(); - break; - case BluetoothProfile.PBAP: - BluetoothPbap pbap = (BluetoothPbap) proxy; - pbap.close(); - break; - case BluetoothProfile.GATT: - BluetoothGatt gatt = (BluetoothGatt) proxy; - gatt.close(); - break; - case BluetoothProfile.GATT_SERVER: - BluetoothGattServer gattServer = (BluetoothGattServer) proxy; - gattServer.close(); - break; - case BluetoothProfile.MAP: - BluetoothMap map = (BluetoothMap) proxy; - map.close(); - break; - case BluetoothProfile.HEADSET_CLIENT: - BluetoothHeadsetClient headsetClient = (BluetoothHeadsetClient) proxy; - headsetClient.close(); - break; - case BluetoothProfile.SAP: - BluetoothSap sap = (BluetoothSap) proxy; - sap.close(); - break; - case BluetoothProfile.PBAP_CLIENT: - BluetoothPbapClient pbapClient = (BluetoothPbapClient) proxy; - pbapClient.close(); - break; - case BluetoothProfile.MAP_CLIENT: - BluetoothMapClient mapClient = (BluetoothMapClient) proxy; - mapClient.close(); - break; - case BluetoothProfile.HID_DEVICE: - BluetoothHidDevice hidDevice = (BluetoothHidDevice) proxy; - hidDevice.close(); - break; - case BluetoothProfile.HEARING_AID: - BluetoothHearingAid hearingAid = (BluetoothHearingAid) proxy; - hearingAid.close(); - break; - case BluetoothProfile.LE_AUDIO: - BluetoothLeAudio leAudio = (BluetoothLeAudio) proxy; - leAudio.close(); - break; - case BluetoothProfile.VOLUME_CONTROL: - BluetoothVolumeControl vcs = (BluetoothVolumeControl) proxy; - vcs.close(); - break; - case BluetoothProfile.CSIP_SET_COORDINATOR: - BluetoothCsipSetCoordinator csipSetCoordinator = - (BluetoothCsipSetCoordinator) proxy; - csipSetCoordinator.close(); - break; - case BluetoothProfile.LE_CALL_CONTROL: - BluetoothLeCallControl tbs = (BluetoothLeCallControl) proxy; - tbs.close(); - break; - } - } - - private static final IBluetoothManagerCallback sManagerCallback = - new IBluetoothManagerCallback.Stub() { - public void onBluetoothServiceUp(IBluetooth bluetoothService) { - if (DBG) { - Log.d(TAG, "onBluetoothServiceUp: " + bluetoothService); - } - - synchronized (sServiceLock) { - sService = bluetoothService; - for (IBluetoothManagerCallback cb : sProxyServiceStateCallbacks.keySet()) { - try { - if (cb != null) { - cb.onBluetoothServiceUp(bluetoothService); - } else { - Log.d(TAG, "onBluetoothServiceUp: cb is null!"); - } - } catch (Exception e) { - Log.e(TAG, "", e); - } - } - } - } - - public void onBluetoothServiceDown() { - if (DBG) { - Log.d(TAG, "onBluetoothServiceDown"); - } - - synchronized (sServiceLock) { - sService = null; - for (IBluetoothManagerCallback cb : sProxyServiceStateCallbacks.keySet()) { - try { - if (cb != null) { - cb.onBluetoothServiceDown(); - } else { - Log.d(TAG, "onBluetoothServiceDown: cb is null!"); - } - } catch (Exception e) { - Log.e(TAG, "", e); - } - } - } - } - - public void onBrEdrDown() { - if (VDBG) { - Log.i(TAG, "onBrEdrDown"); - } - - synchronized (sServiceLock) { - for (IBluetoothManagerCallback cb : sProxyServiceStateCallbacks.keySet()) { - try { - if (cb != null) { - cb.onBrEdrDown(); - } else { - Log.d(TAG, "onBrEdrDown: cb is null!"); - } - } catch (Exception e) { - Log.e(TAG, "", e); - } - } - } - } - }; - - private final IBluetoothManagerCallback mManagerCallback = - new IBluetoothManagerCallback.Stub() { - public void onBluetoothServiceUp(IBluetooth bluetoothService) { - synchronized (mServiceLock.writeLock()) { - mService = bluetoothService; - } - synchronized (mMetadataListeners) { - mMetadataListeners.forEach((device, pair) -> { - try { - mService.registerMetadataListener(mBluetoothMetadataListener, - device, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "Failed to register metadata listener", e); - } - }); - } - synchronized (mBluetoothConnectionCallbackExecutorMap) { - if (!mBluetoothConnectionCallbackExecutorMap.isEmpty()) { - try { - mService.registerBluetoothConnectionCallback(mConnectionCallback, - mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "onBluetoothServiceUp: Failed to register bluetooth" - + "connection callback", e); - } - } - } - } - - public void onBluetoothServiceDown() { - synchronized (mServiceLock.writeLock()) { - mService = null; - if (mLeScanClients != null) { - mLeScanClients.clear(); - } - if (mBluetoothLeAdvertiser != null) { - mBluetoothLeAdvertiser.cleanup(); - } - if (mBluetoothLeScanner != null) { - mBluetoothLeScanner.cleanup(); - } - } - } - - public void onBrEdrDown() { - } - }; - - /** - * Enable the Bluetooth Adapter, but don't auto-connect devices - * and don't persist state. Only for use by system applications. - * - * @hide - */ - @SystemApi - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean enableNoAutoConnect() { - if (isEnabled()) { - if (DBG) { - Log.d(TAG, "enableNoAutoConnect(): BT already enabled!"); - } - return true; - } - try { - return mManagerService.enableNoAutoConnect(mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return false; - } - - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(value = { - BluetoothStatusCodes.ERROR_UNKNOWN, - BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED, - BluetoothStatusCodes.ERROR_ANOTHER_ACTIVE_OOB_REQUEST, - }) - public @interface OobError {} - - /** - * Provides callback methods for receiving {@link OobData} from the host stack, as well as an - * error interface in order to allow the caller to determine next steps based on the {@code - * ErrorCode}. - * - * @hide - */ - @SystemApi - public interface OobDataCallback { - /** - * Handles the {@link OobData} received from the host stack. - * - * @param transport - whether the {@link OobData} is generated for LE or Classic. - * @param oobData - data generated in the host stack(LE) or controller (Classic) - */ - void onOobData(@Transport int transport, @NonNull OobData oobData); - - /** - * Provides feedback when things don't go as expected. - * - * @param errorCode - the code describing the type of error that occurred. - */ - void onError(@OobError int errorCode); - } - - /** - * Wraps an AIDL interface around an {@link OobDataCallback} interface. - * - * @see {@link IBluetoothOobDataCallback} for interface definition. - * - * @hide - */ - public class WrappedOobDataCallback extends IBluetoothOobDataCallback.Stub { - private final OobDataCallback mCallback; - private final Executor mExecutor; - - /** - * @param callback - object to receive {@link OobData} must be a non null argument - * - * @throws NullPointerException if the callback is null. - */ - WrappedOobDataCallback(@NonNull OobDataCallback callback, - @NonNull @CallbackExecutor Executor executor) { - requireNonNull(callback); - requireNonNull(executor); - mCallback = callback; - mExecutor = executor; - } - /** - * Wrapper function to relay to the {@link OobDataCallback#onOobData} - * - * @param transport - whether the {@link OobData} is generated for LE or Classic. - * @param oobData - data generated in the host stack(LE) or controller (Classic) - * - * @hide - */ - public void onOobData(@Transport int transport, @NonNull OobData oobData) { - mExecutor.execute(new Runnable() { - public void run() { - mCallback.onOobData(transport, oobData); - } - }); - } - /** - * Wrapper function to relay to the {@link OobDataCallback#onError} - * - * @param errorCode - the code descibing the type of error that occurred. - * - * @hide - */ - public void onError(@OobError int errorCode) { - mExecutor.execute(new Runnable() { - public void run() { - mCallback.onError(errorCode); - } - }); - } - } - - /** - * Fetches a secret data value that can be used for a secure and simple pairing experience. - * - * <p>This is the Local Out of Band data the comes from the - * - * <p>This secret is the local Out of Band data. This data is used to securely and quickly - * pair two devices with minimal user interaction. - * - * <p>For example, this secret can be transferred to a remote device out of band (meaning any - * other way besides using bluetooth). Once the remote device finds this device using the - * information given in the data, such as the PUBLIC ADDRESS, the remote device could then - * connect to this device using this secret when the pairing sequenece asks for the secret. - * This device will respond by automatically accepting the pairing due to the secret being so - * trustworthy. - * - * @param transport - provide type of transport (e.g. LE or Classic). - * @param callback - target object to receive the {@link OobData} value. - * - * @throws NullPointerException if callback is null. - * @throws IllegalArgumentException if the transport is not valid. - * - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public void generateLocalOobData(@Transport int transport, - @NonNull @CallbackExecutor Executor executor, @NonNull OobDataCallback callback) { - if (transport != BluetoothDevice.TRANSPORT_BREDR && transport - != BluetoothDevice.TRANSPORT_LE) { - throw new IllegalArgumentException("Invalid transport '" + transport + "'!"); - } - requireNonNull(callback); - if (!isEnabled()) { - Log.w(TAG, "generateLocalOobData(): Adapter isn't enabled!"); - callback.onError(BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED); - } else { - try { - mService.generateLocalOobData(transport, new WrappedOobDataCallback(callback, - executor), mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - } - - /** - * Enable control of the Bluetooth Adapter for a single application. - * - * <p>Some applications need to use Bluetooth for short periods of time to - * transfer data but don't want all the associated implications like - * automatic connection to headsets etc. - * - * <p> Multiple applications can call this. This is reference counted and - * Bluetooth disabled only when no one else is using it. There will be no UI - * shown to the user while bluetooth is being enabled. Any user action will - * override this call. For example, if user wants Bluetooth on and the last - * user of this API wanted to disable Bluetooth, Bluetooth will not be - * turned off. - * - * <p> This API is only meant to be used by internal applications. Third - * party applications but use {@link #enable} and {@link #disable} APIs. - * - * <p> If this API returns true, it means the callback will be called. - * The callback will be called with the current state of Bluetooth. - * If the state is not what was requested, an internal error would be the - * reason. If Bluetooth is already on and if this function is called to turn - * it on, the api will return true and a callback will be called. - * - * @param on True for on, false for off. - * @param callback The callback to notify changes to the state. - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SuppressLint("AndroidFrameworkRequiresPermission") - public boolean changeApplicationBluetoothState(boolean on, - BluetoothStateChangeCallback callback) { - return false; - } - - /** - * @hide - */ - public interface BluetoothStateChangeCallback { - /** - * @hide - */ - void onBluetoothStateChange(boolean on); - } - - /** - * @hide - */ - public class StateChangeCallbackWrapper extends IBluetoothStateChangeCallback.Stub { - private BluetoothStateChangeCallback mCallback; - - StateChangeCallbackWrapper(BluetoothStateChangeCallback callback) { - mCallback = callback; - } - - @Override - public void onBluetoothStateChange(boolean on) { - mCallback.onBluetoothStateChange(on); - } - } - - private Set<BluetoothDevice> toDeviceSet(List<BluetoothDevice> devices) { - Set<BluetoothDevice> deviceSet = new HashSet<BluetoothDevice>(devices); - return Collections.unmodifiableSet(deviceSet); - } - - protected void finalize() throws Throwable { - try { - removeServiceStateCallback(mManagerCallback); - } finally { - super.finalize(); - } - } - - /** - * Validate a String Bluetooth address, such as "00:43:A8:23:10:F0" - * <p>Alphabetic characters must be uppercase to be valid. - * - * @param address Bluetooth address as string - * @return true if the address is valid, false otherwise - */ - public static boolean checkBluetoothAddress(String address) { - if (address == null || address.length() != ADDRESS_LENGTH) { - return false; - } - for (int i = 0; i < ADDRESS_LENGTH; i++) { - char c = address.charAt(i); - switch (i % 3) { - case 0: - case 1: - if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F')) { - // hex character, OK - break; - } - return false; - case 2: - if (c == ':') { - break; // OK - } - return false; - } - } - return true; - } - - /** - * Determines whether a String Bluetooth address, such as "F0:43:A8:23:10:00" - * is a RANDOM STATIC address. - * - * RANDOM STATIC: (addr & 0xC0) == 0xC0 - * RANDOM RESOLVABLE: (addr & 0xC0) == 0x40 - * RANDOM non-RESOLVABLE: (addr & 0xC0) == 0x00 - * - * @param address Bluetooth address as string - * @return true if the 2 Most Significant Bits of the address equals 0xC0. - * - * @hide - */ - public static boolean isAddressRandomStatic(@NonNull String address) { - requireNonNull(address); - return checkBluetoothAddress(address) - && (Integer.parseInt(address.split(":")[0], 16) & 0xC0) == 0xC0; - } - - /** {@hide} */ - @UnsupportedAppUsage - @RequiresNoPermission - public IBluetoothManager getBluetoothManager() { - return mManagerService; - } - - /** {@hide} */ - @RequiresNoPermission - public AttributionSource getAttributionSource() { - return mAttributionSource; - } - - @GuardedBy("sServiceLock") - private static final WeakHashMap<IBluetoothManagerCallback, Void> sProxyServiceStateCallbacks = - new WeakHashMap<>(); - - /*package*/ IBluetooth getBluetoothService() { - synchronized (sServiceLock) { - if (sProxyServiceStateCallbacks.isEmpty()) { - throw new IllegalStateException( - "Anonymous service access requires at least one lifecycle in process"); - } - return sService; - } - } - - @UnsupportedAppUsage - /*package*/ IBluetooth getBluetoothService(IBluetoothManagerCallback cb) { - Objects.requireNonNull(cb); - synchronized (sServiceLock) { - sProxyServiceStateCallbacks.put(cb, null); - registerOrUnregisterAdapterLocked(); - return sService; - } - } - - /*package*/ void removeServiceStateCallback(IBluetoothManagerCallback cb) { - Objects.requireNonNull(cb); - synchronized (sServiceLock) { - sProxyServiceStateCallbacks.remove(cb); - registerOrUnregisterAdapterLocked(); - } - } - - /** - * Handle registering (or unregistering) a single process-wide - * {@link IBluetoothManagerCallback} based on the presence of local - * {@link #sProxyServiceStateCallbacks} clients. - */ - @GuardedBy("sServiceLock") - private void registerOrUnregisterAdapterLocked() { - final boolean isRegistered = sServiceRegistered; - final boolean wantRegistered = !sProxyServiceStateCallbacks.isEmpty(); - - if (isRegistered != wantRegistered) { - if (wantRegistered) { - try { - sService = mManagerService.registerAdapter(sManagerCallback); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } else { - try { - mManagerService.unregisterAdapter(sManagerCallback); - sService = null; - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - sServiceRegistered = wantRegistered; - } - } - - /** - * Callback interface used to deliver LE scan results. - * - * @see #startLeScan(LeScanCallback) - * @see #startLeScan(UUID[], LeScanCallback) - */ - public interface LeScanCallback { - /** - * Callback reporting an LE device found during a device scan initiated - * by the {@link BluetoothAdapter#startLeScan} function. - * - * @param device Identifies the remote device - * @param rssi The RSSI value for the remote device as reported by the Bluetooth hardware. 0 - * if no RSSI value is available. - * @param scanRecord The content of the advertisement record offered by the remote device. - */ - void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord); - } - - /** - * Register a callback to receive events whenever the bluetooth stack goes down and back up, - * e.g. in the event the bluetooth is turned off/on via settings. - * - * If the bluetooth stack is currently up, there will not be an initial callback call. - * You can use the return value as an indication of this being the case. - * - * Callbacks will be delivered on a binder thread. - * - * @return whether bluetooth is already up currently - * - * @hide - */ - public boolean registerServiceLifecycleCallback(ServiceLifecycleCallback callback) { - return getBluetoothService(callback.mRemote) != null; - } - - /** - * Unregister a callback registered via {@link #registerServiceLifecycleCallback} - * - * @hide - */ - public void unregisterServiceLifecycleCallback(ServiceLifecycleCallback callback) { - removeServiceStateCallback(callback.mRemote); - } - - /** - * A callback for {@link #registerServiceLifecycleCallback} - * - * @hide - */ - public abstract static class ServiceLifecycleCallback { - - /** Called when the bluetooth stack is up */ - public abstract void onBluetoothServiceUp(); - - /** Called when the bluetooth stack is down */ - public abstract void onBluetoothServiceDown(); - - IBluetoothManagerCallback mRemote = new IBluetoothManagerCallback.Stub() { - @Override - public void onBluetoothServiceUp(IBluetooth bluetoothService) { - ServiceLifecycleCallback.this.onBluetoothServiceUp(); - } - - @Override - public void onBluetoothServiceDown() { - ServiceLifecycleCallback.this.onBluetoothServiceDown(); - } - - @Override - public void onBrEdrDown() {} - }; - } - - /** - * Starts a scan for Bluetooth LE devices. - * - * <p>Results of the scan are reported using the - * {@link LeScanCallback#onLeScan} callback. - * - * @param callback the callback LE scan results are delivered - * @return true, if the scan was started successfully - * @deprecated use {@link BluetoothLeScanner#startScan(List, ScanSettings, ScanCallback)} - * instead. - */ - @Deprecated - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothScanPermission - @RequiresBluetoothLocationPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - public boolean startLeScan(LeScanCallback callback) { - return startLeScan(null, callback); - } - - /** - * Starts a scan for Bluetooth LE devices, looking for devices that - * advertise given services. - * - * <p>Devices which advertise all specified services are reported using the - * {@link LeScanCallback#onLeScan} callback. - * - * @param serviceUuids Array of services to look for - * @param callback the callback LE scan results are delivered - * @return true, if the scan was started successfully - * @deprecated use {@link BluetoothLeScanner#startScan(List, ScanSettings, ScanCallback)} - * instead. - */ - @Deprecated - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothScanPermission - @RequiresBluetoothLocationPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - public boolean startLeScan(final UUID[] serviceUuids, final LeScanCallback callback) { - if (DBG) { - Log.d(TAG, "startLeScan(): " + Arrays.toString(serviceUuids)); - } - if (callback == null) { - if (DBG) { - Log.e(TAG, "startLeScan: null callback"); - } - return false; - } - BluetoothLeScanner scanner = getBluetoothLeScanner(); - if (scanner == null) { - if (DBG) { - Log.e(TAG, "startLeScan: cannot get BluetoothLeScanner"); - } - return false; - } - - synchronized (mLeScanClients) { - if (mLeScanClients.containsKey(callback)) { - if (DBG) { - Log.e(TAG, "LE Scan has already started"); - } - return false; - } - - try { - IBluetoothGatt iGatt = mManagerService.getBluetoothGatt(); - if (iGatt == null) { - // BLE is not supported - return false; - } - - @SuppressLint("AndroidFrameworkBluetoothPermission") - ScanCallback scanCallback = new ScanCallback() { - @Override - public void onScanResult(int callbackType, ScanResult result) { - if (callbackType != ScanSettings.CALLBACK_TYPE_ALL_MATCHES) { - // Should not happen. - Log.e(TAG, "LE Scan has already started"); - return; - } - ScanRecord scanRecord = result.getScanRecord(); - if (scanRecord == null) { - return; - } - if (serviceUuids != null) { - List<ParcelUuid> uuids = new ArrayList<ParcelUuid>(); - for (UUID uuid : serviceUuids) { - uuids.add(new ParcelUuid(uuid)); - } - List<ParcelUuid> scanServiceUuids = scanRecord.getServiceUuids(); - if (scanServiceUuids == null || !scanServiceUuids.containsAll(uuids)) { - if (DBG) { - Log.d(TAG, "uuids does not match"); - } - return; - } - } - callback.onLeScan(result.getDevice(), result.getRssi(), - scanRecord.getBytes()); - } - }; - ScanSettings settings = new ScanSettings.Builder().setCallbackType( - ScanSettings.CALLBACK_TYPE_ALL_MATCHES) - .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) - .build(); - - List<ScanFilter> filters = new ArrayList<ScanFilter>(); - if (serviceUuids != null && serviceUuids.length > 0) { - // Note scan filter does not support matching an UUID array so we put one - // UUID to hardware and match the whole array in callback. - ScanFilter filter = - new ScanFilter.Builder().setServiceUuid(new ParcelUuid(serviceUuids[0])) - .build(); - filters.add(filter); - } - scanner.startScan(filters, settings, scanCallback); - - mLeScanClients.put(callback, scanCallback); - return true; - - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - return false; - } - - /** - * Stops an ongoing Bluetooth LE device scan. - * - * @param callback used to identify which scan to stop must be the same handle used to start the - * scan - * @deprecated Use {@link BluetoothLeScanner#stopScan(ScanCallback)} instead. - */ - @Deprecated - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothScanPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - public void stopLeScan(LeScanCallback callback) { - if (DBG) { - Log.d(TAG, "stopLeScan()"); - } - BluetoothLeScanner scanner = getBluetoothLeScanner(); - if (scanner == null) { - return; - } - synchronized (mLeScanClients) { - ScanCallback scanCallback = mLeScanClients.remove(callback); - if (scanCallback == null) { - if (DBG) { - Log.d(TAG, "scan not started yet"); - } - return; - } - scanner.stopScan(scanCallback); - } - } - - /** - * Create a secure L2CAP Connection-oriented Channel (CoC) {@link BluetoothServerSocket} and - * assign a dynamic protocol/service multiplexer (PSM) value. This socket can be used to listen - * for incoming connections. The supported Bluetooth transport is LE only. - * <p>A remote device connecting to this socket will be authenticated and communication on this - * socket will be encrypted. - * <p>Use {@link BluetoothServerSocket#accept} to retrieve incoming connections from a listening - * {@link BluetoothServerSocket}. - * <p>The system will assign a dynamic PSM value. This PSM value can be read from the {@link - * BluetoothServerSocket#getPsm()} and this value will be released when this server socket is - * closed, Bluetooth is turned off, or the application exits unexpectedly. - * <p>The mechanism of disclosing the assigned dynamic PSM value to the initiating peer is - * defined and performed by the application. - * <p>Use {@link BluetoothDevice#createL2capChannel(int)} to connect to this server - * socket from another Android device that is given the PSM value. - * - * @return an L2CAP CoC BluetoothServerSocket - * @throws IOException on error, for example Bluetooth not available, or insufficient - * permissions, or unable to start this CoC - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public @NonNull BluetoothServerSocket listenUsingL2capChannel() - throws IOException { - BluetoothServerSocket socket = - new BluetoothServerSocket(BluetoothSocket.TYPE_L2CAP_LE, true, true, - SOCKET_CHANNEL_AUTO_STATIC_NO_SDP, false, false); - int errno = socket.mSocket.bindListen(); - if (errno != 0) { - throw new IOException("Error: " + errno); - } - - int assignedPsm = socket.mSocket.getPort(); - if (assignedPsm == 0) { - throw new IOException("Error: Unable to assign PSM value"); - } - if (DBG) { - Log.d(TAG, "listenUsingL2capChannel: set assigned PSM to " - + assignedPsm); - } - socket.setChannel(assignedPsm); - - return socket; - } - - /** - * Create an insecure L2CAP Connection-oriented Channel (CoC) {@link BluetoothServerSocket} and - * assign a dynamic PSM value. This socket can be used to listen for incoming connections. The - * supported Bluetooth transport is LE only. - * <p>The link key is not required to be authenticated, i.e the communication may be vulnerable - * to person-in-the-middle attacks. Use {@link #listenUsingL2capChannel}, if an encrypted and - * authenticated communication channel is desired. - * <p>Use {@link BluetoothServerSocket#accept} to retrieve incoming connections from a listening - * {@link BluetoothServerSocket}. - * <p>The system will assign a dynamic protocol/service multiplexer (PSM) value. This PSM value - * can be read from the {@link BluetoothServerSocket#getPsm()} and this value will be released - * when this server socket is closed, Bluetooth is turned off, or the application exits - * unexpectedly. - * <p>The mechanism of disclosing the assigned dynamic PSM value to the initiating peer is - * defined and performed by the application. - * <p>Use {@link BluetoothDevice#createInsecureL2capChannel(int)} to connect to this server - * socket from another Android device that is given the PSM value. - * - * @return an L2CAP CoC BluetoothServerSocket - * @throws IOException on error, for example Bluetooth not available, or insufficient - * permissions, or unable to start this CoC - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public @NonNull BluetoothServerSocket listenUsingInsecureL2capChannel() - throws IOException { - BluetoothServerSocket socket = - new BluetoothServerSocket(BluetoothSocket.TYPE_L2CAP_LE, false, false, - SOCKET_CHANNEL_AUTO_STATIC_NO_SDP, false, false); - int errno = socket.mSocket.bindListen(); - if (errno != 0) { - throw new IOException("Error: " + errno); - } - - int assignedPsm = socket.mSocket.getPort(); - if (assignedPsm == 0) { - throw new IOException("Error: Unable to assign PSM value"); - } - if (DBG) { - Log.d(TAG, "listenUsingInsecureL2capChannel: set assigned PSM to " - + assignedPsm); - } - socket.setChannel(assignedPsm); - - return socket; - } - - /** - * Register a {@link #OnMetadataChangedListener} to receive update about metadata - * changes for this {@link BluetoothDevice}. - * Registration must be done when Bluetooth is ON and will last until - * {@link #removeOnMetadataChangedListener(BluetoothDevice)} is called, even when Bluetooth - * restarted in the middle. - * All input parameters should not be null or {@link NullPointerException} will be triggered. - * The same {@link BluetoothDevice} and {@link #OnMetadataChangedListener} pair can only be - * registered once, double registration would cause {@link IllegalArgumentException}. - * - * @param device {@link BluetoothDevice} that will be registered - * @param executor the executor for listener callback - * @param listener {@link #OnMetadataChangedListener} that will receive asynchronous callbacks - * @return true on success, false on error - * @throws NullPointerException If one of {@code listener}, {@code device} or {@code executor} - * is null. - * @throws IllegalArgumentException The same {@link #OnMetadataChangedListener} and - * {@link BluetoothDevice} are registered twice. - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean addOnMetadataChangedListener(@NonNull BluetoothDevice device, - @NonNull Executor executor, @NonNull OnMetadataChangedListener listener) { - if (DBG) Log.d(TAG, "addOnMetadataChangedListener()"); - - final IBluetooth service = mService; - if (service == null) { - Log.e(TAG, "Bluetooth is not enabled. Cannot register metadata listener"); - return false; - } - if (listener == null) { - throw new NullPointerException("listener is null"); - } - if (device == null) { - throw new NullPointerException("device is null"); - } - if (executor == null) { - throw new NullPointerException("executor is null"); - } - - synchronized (mMetadataListeners) { - List<Pair<OnMetadataChangedListener, Executor>> listenerList = - mMetadataListeners.get(device); - if (listenerList == null) { - // Create new listener/executor list for registeration - listenerList = new ArrayList<>(); - mMetadataListeners.put(device, listenerList); - } else { - // Check whether this device was already registed by the lisenter - if (listenerList.stream().anyMatch((pair) -> (pair.first.equals(listener)))) { - throw new IllegalArgumentException("listener was already regestered" - + " for the device"); - } - } - - Pair<OnMetadataChangedListener, Executor> listenerPair = new Pair(listener, executor); - listenerList.add(listenerPair); - - boolean ret = false; - try { - ret = service.registerMetadataListener(mBluetoothMetadataListener, device, - mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "registerMetadataListener fail", e); - } finally { - if (!ret) { - // Remove listener registered earlier when fail. - listenerList.remove(listenerPair); - if (listenerList.isEmpty()) { - // Remove the device if its listener list is empty - mMetadataListeners.remove(device); - } - } - } - return ret; - } - } - - /** - * Unregister a {@link #OnMetadataChangedListener} from a registered {@link BluetoothDevice}. - * Unregistration can be done when Bluetooth is either ON or OFF. - * {@link #addOnMetadataChangedListener(OnMetadataChangedListener, BluetoothDevice, Executor)} - * must be called before unregisteration. - * - * @param device {@link BluetoothDevice} that will be unregistered. It - * should not be null or {@link NullPointerException} will be triggered. - * @param listener {@link OnMetadataChangedListener} that will be unregistered. It - * should not be null or {@link NullPointerException} will be triggered. - * @return true on success, false on error - * @throws NullPointerException If {@code listener} or {@code device} is null. - * @throws IllegalArgumentException If {@code device} has not been registered before. - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean removeOnMetadataChangedListener(@NonNull BluetoothDevice device, - @NonNull OnMetadataChangedListener listener) { - if (DBG) Log.d(TAG, "removeOnMetadataChangedListener()"); - if (device == null) { - throw new NullPointerException("device is null"); - } - if (listener == null) { - throw new NullPointerException("listener is null"); - } - - synchronized (mMetadataListeners) { - if (!mMetadataListeners.containsKey(device)) { - throw new IllegalArgumentException("device was not registered"); - } - // Remove issued listener from the registered device - mMetadataListeners.get(device).removeIf((pair) -> (pair.first.equals(listener))); - - if (mMetadataListeners.get(device).isEmpty()) { - // Unregister to Bluetooth service if all listeners are removed from - // the registered device - mMetadataListeners.remove(device); - final IBluetooth service = mService; - if (service == null) { - // Bluetooth is OFF, do nothing to Bluetooth service. - return true; - } - try { - return service.unregisterMetadataListener(device, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "unregisterMetadataListener fail", e); - return false; - } - } - } - return true; - } - - /** - * This interface is used to implement {@link BluetoothAdapter} metadata listener. - * @hide - */ - @SystemApi - public interface OnMetadataChangedListener { - /** - * Callback triggered if the metadata of {@link BluetoothDevice} registered in - * {@link #addOnMetadataChangedListener}. - * - * @param device changed {@link BluetoothDevice}. - * @param key changed metadata key, one of BluetoothDevice.METADATA_*. - * @param value the new value of metadata as byte array. - */ - void onMetadataChanged(@NonNull BluetoothDevice device, int key, - @Nullable byte[] value); - } - - @SuppressLint("AndroidFrameworkBluetoothPermission") - private final IBluetoothConnectionCallback mConnectionCallback = - new IBluetoothConnectionCallback.Stub() { - @Override - public void onDeviceConnected(BluetoothDevice device) { - Attributable.setAttributionSource(device, mAttributionSource); - for (Map.Entry<BluetoothConnectionCallback, Executor> callbackExecutorEntry: - mBluetoothConnectionCallbackExecutorMap.entrySet()) { - BluetoothConnectionCallback callback = callbackExecutorEntry.getKey(); - Executor executor = callbackExecutorEntry.getValue(); - executor.execute(() -> callback.onDeviceConnected(device)); - } - } - - @Override - public void onDeviceDisconnected(BluetoothDevice device, int hciReason) { - Attributable.setAttributionSource(device, mAttributionSource); - for (Map.Entry<BluetoothConnectionCallback, Executor> callbackExecutorEntry: - mBluetoothConnectionCallbackExecutorMap.entrySet()) { - BluetoothConnectionCallback callback = callbackExecutorEntry.getKey(); - Executor executor = callbackExecutorEntry.getValue(); - executor.execute(() -> callback.onDeviceDisconnected(device, hciReason)); - } - } - }; - - /** - * Registers the BluetoothConnectionCallback to receive callback events when a bluetooth device - * (classic or low energy) is connected or disconnected. - * - * @param executor is the callback executor - * @param callback is the connection callback you wish to register - * @return true if the callback was registered successfully, false otherwise - * @throws IllegalArgumentException if the callback is already registered - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean registerBluetoothConnectionCallback(@NonNull @CallbackExecutor Executor executor, - @NonNull BluetoothConnectionCallback callback) { - if (DBG) Log.d(TAG, "registerBluetoothConnectionCallback()"); - if (callback == null) { - return false; - } - - synchronized (mBluetoothConnectionCallbackExecutorMap) { - // If the callback map is empty, we register the service-to-app callback - if (mBluetoothConnectionCallbackExecutorMap.isEmpty()) { - try { - mServiceLock.readLock().lock(); - if (mService != null) { - if (!mService.registerBluetoothConnectionCallback(mConnectionCallback, - mAttributionSource)) { - return false; - } - } - } catch (RemoteException e) { - Log.e(TAG, "", e); - mBluetoothConnectionCallbackExecutorMap.remove(callback); - } finally { - mServiceLock.readLock().unlock(); - } - } - - // Adds the passed in callback to our map of callbacks to executors - if (mBluetoothConnectionCallbackExecutorMap.containsKey(callback)) { - throw new IllegalArgumentException("This callback has already been registered"); - } - mBluetoothConnectionCallbackExecutorMap.put(callback, executor); - } - - return true; - } - - /** - * Unregisters the BluetoothConnectionCallback that was previously registered by the application - * - * @param callback is the connection callback you wish to unregister - * @return true if the callback was unregistered successfully, false otherwise - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean unregisterBluetoothConnectionCallback( - @NonNull BluetoothConnectionCallback callback) { - if (DBG) Log.d(TAG, "unregisterBluetoothConnectionCallback()"); - if (callback == null) { - return false; - } - - synchronized (mBluetoothConnectionCallbackExecutorMap) { - if (mBluetoothConnectionCallbackExecutorMap.remove(callback) != null) { - return false; - } - } - - if (!mBluetoothConnectionCallbackExecutorMap.isEmpty()) { - return true; - } - - // If the callback map is empty, we unregister the service-to-app callback - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.unregisterBluetoothConnectionCallback(mConnectionCallback, - mAttributionSource); - } - } catch (RemoteException e) { - Log.e(TAG, "", e); - } finally { - mServiceLock.readLock().unlock(); - } - - return false; - } - - /** - * This abstract class is used to implement callbacks for when a bluetooth classic or Bluetooth - * Low Energy (BLE) device is either connected or disconnected. - * - * @hide - */ - public abstract static class BluetoothConnectionCallback { - /** - * Callback triggered when a bluetooth device (classic or BLE) is connected - * @param device is the connected bluetooth device - */ - public void onDeviceConnected(BluetoothDevice device) {} - - /** - * Callback triggered when a bluetooth device (classic or BLE) is disconnected - * @param device is the disconnected bluetooth device - * @param reason is the disconnect reason - */ - public void onDeviceDisconnected(BluetoothDevice device, @DisconnectReason int reason) {} - - /** - * @hide - */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(prefix = { "REASON_" }, value = { - BluetoothStatusCodes.ERROR_UNKNOWN, - BluetoothStatusCodes.ERROR_DISCONNECT_REASON_LOCAL_REQUEST, - BluetoothStatusCodes.ERROR_DISCONNECT_REASON_REMOTE_REQUEST, - BluetoothStatusCodes.ERROR_DISCONNECT_REASON_LOCAL, - BluetoothStatusCodes.ERROR_DISCONNECT_REASON_REMOTE, - BluetoothStatusCodes.ERROR_DISCONNECT_REASON_TIMEOUT, - BluetoothStatusCodes.ERROR_DISCONNECT_REASON_SECURITY, - BluetoothStatusCodes.ERROR_DISCONNECT_REASON_SYSTEM_POLICY, - BluetoothStatusCodes.ERROR_DISCONNECT_REASON_RESOURCE_LIMIT_REACHED, - BluetoothStatusCodes.ERROR_DISCONNECT_REASON_CONNECTION_ALREADY_EXISTS, - BluetoothStatusCodes.ERROR_DISCONNECT_REASON_BAD_PARAMETERS}) - public @interface DisconnectReason {} - - /** - * Returns human-readable strings corresponding to {@link DisconnectReason}. - */ - public static String disconnectReasonText(@DisconnectReason int reason) { - switch (reason) { - case BluetoothStatusCodes.ERROR_UNKNOWN: - return "Reason unknown"; - case BluetoothStatusCodes.ERROR_DISCONNECT_REASON_LOCAL_REQUEST: - return "Local request"; - case BluetoothStatusCodes.ERROR_DISCONNECT_REASON_REMOTE_REQUEST: - return "Remote request"; - case BluetoothStatusCodes.ERROR_DISCONNECT_REASON_LOCAL: - return "Local error"; - case BluetoothStatusCodes.ERROR_DISCONNECT_REASON_REMOTE: - return "Remote error"; - case BluetoothStatusCodes.ERROR_DISCONNECT_REASON_TIMEOUT: - return "Timeout"; - case BluetoothStatusCodes.ERROR_DISCONNECT_REASON_SECURITY: - return "Security"; - case BluetoothStatusCodes.ERROR_DISCONNECT_REASON_SYSTEM_POLICY: - return "System policy"; - case BluetoothStatusCodes.ERROR_DISCONNECT_REASON_RESOURCE_LIMIT_REACHED: - return "Resource constrained"; - case BluetoothStatusCodes.ERROR_DISCONNECT_REASON_CONNECTION_ALREADY_EXISTS: - return "Connection already exists"; - case BluetoothStatusCodes.ERROR_DISCONNECT_REASON_BAD_PARAMETERS: - return "Bad parameters"; - default: - return "Unrecognized disconnect reason: " + reason; - } - } - } - - /** - * Converts old constant of priority to the new for connection policy - * - * @param priority is the priority to convert to connection policy - * @return the equivalent connection policy constant to the priority - * - * @hide - */ - public static @ConnectionPolicy int priorityToConnectionPolicy(int priority) { - switch(priority) { - case BluetoothProfile.PRIORITY_AUTO_CONNECT: - return BluetoothProfile.CONNECTION_POLICY_ALLOWED; - case BluetoothProfile.PRIORITY_ON: - return BluetoothProfile.CONNECTION_POLICY_ALLOWED; - case BluetoothProfile.PRIORITY_OFF: - return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; - case BluetoothProfile.PRIORITY_UNDEFINED: - return BluetoothProfile.CONNECTION_POLICY_UNKNOWN; - default: - Log.e(TAG, "setPriority: Invalid priority: " + priority); - return BluetoothProfile.CONNECTION_POLICY_UNKNOWN; - } - } - - /** - * Converts new constant of connection policy to the old for priority - * - * @param connectionPolicy is the connection policy to convert to priority - * @return the equivalent priority constant to the connectionPolicy - * - * @hide - */ - public static int connectionPolicyToPriority(@ConnectionPolicy int connectionPolicy) { - switch(connectionPolicy) { - case BluetoothProfile.CONNECTION_POLICY_ALLOWED: - return BluetoothProfile.PRIORITY_ON; - case BluetoothProfile.CONNECTION_POLICY_FORBIDDEN: - return BluetoothProfile.PRIORITY_OFF; - case BluetoothProfile.CONNECTION_POLICY_UNKNOWN: - return BluetoothProfile.PRIORITY_UNDEFINED; - } - return BluetoothProfile.PRIORITY_UNDEFINED; - } -} diff --git a/core/java/android/bluetooth/BluetoothAssignedNumbers.java b/core/java/android/bluetooth/BluetoothAssignedNumbers.java deleted file mode 100644 index 41a34e061845..000000000000 --- a/core/java/android/bluetooth/BluetoothAssignedNumbers.java +++ /dev/null @@ -1,1171 +0,0 @@ -/* - * Copyright (C) 2010 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.bluetooth; - -/** - * Bluetooth Assigned Numbers. - * <p> - * For now we only include Company ID values. - * - * @see <a href="https://www.bluetooth.org/technical/assignednumbers/identifiers.htm"> The Official - * Bluetooth SIG Member Website | Company Identifiers</a> - */ -public class BluetoothAssignedNumbers { - - // Bluetooth SIG Company ID values - /* - * Ericsson Technology Licensing. - */ - public static final int ERICSSON_TECHNOLOGY = 0x0000; - - /* - * Nokia Mobile Phones. - */ - public static final int NOKIA_MOBILE_PHONES = 0x0001; - - /* - * Intel Corp. - */ - public static final int INTEL = 0x0002; - - /* - * IBM Corp. - */ - public static final int IBM = 0x0003; - - /* - * Toshiba Corp. - */ - public static final int TOSHIBA = 0x0004; - - /* - * 3Com. - */ - public static final int THREECOM = 0x0005; - - /* - * Microsoft. - */ - public static final int MICROSOFT = 0x0006; - - /* - * Lucent. - */ - public static final int LUCENT = 0x0007; - - /* - * Motorola. - */ - public static final int MOTOROLA = 0x0008; - - /* - * Infineon Technologies AG. - */ - public static final int INFINEON_TECHNOLOGIES = 0x0009; - - /* - * Cambridge Silicon Radio. - */ - public static final int CAMBRIDGE_SILICON_RADIO = 0x000A; - - /* - * Silicon Wave. - */ - public static final int SILICON_WAVE = 0x000B; - - /* - * Digianswer A/S. - */ - public static final int DIGIANSWER = 0x000C; - - /* - * Texas Instruments Inc. - */ - public static final int TEXAS_INSTRUMENTS = 0x000D; - - /* - * Parthus Technologies Inc. - */ - public static final int PARTHUS_TECHNOLOGIES = 0x000E; - - /* - * Broadcom Corporation. - */ - public static final int BROADCOM = 0x000F; - - /* - * Mitel Semiconductor. - */ - public static final int MITEL_SEMICONDUCTOR = 0x0010; - - /* - * Widcomm, Inc. - */ - public static final int WIDCOMM = 0x0011; - - /* - * Zeevo, Inc. - */ - public static final int ZEEVO = 0x0012; - - /* - * Atmel Corporation. - */ - public static final int ATMEL = 0x0013; - - /* - * Mitsubishi Electric Corporation. - */ - public static final int MITSUBISHI_ELECTRIC = 0x0014; - - /* - * RTX Telecom A/S. - */ - public static final int RTX_TELECOM = 0x0015; - - /* - * KC Technology Inc. - */ - public static final int KC_TECHNOLOGY = 0x0016; - - /* - * Newlogic. - */ - public static final int NEWLOGIC = 0x0017; - - /* - * Transilica, Inc. - */ - public static final int TRANSILICA = 0x0018; - - /* - * Rohde & Schwarz GmbH & Co. KG. - */ - public static final int ROHDE_AND_SCHWARZ = 0x0019; - - /* - * TTPCom Limited. - */ - public static final int TTPCOM = 0x001A; - - /* - * Signia Technologies, Inc. - */ - public static final int SIGNIA_TECHNOLOGIES = 0x001B; - - /* - * Conexant Systems Inc. - */ - public static final int CONEXANT_SYSTEMS = 0x001C; - - /* - * Qualcomm. - */ - public static final int QUALCOMM = 0x001D; - - /* - * Inventel. - */ - public static final int INVENTEL = 0x001E; - - /* - * AVM Berlin. - */ - public static final int AVM_BERLIN = 0x001F; - - /* - * BandSpeed, Inc. - */ - public static final int BANDSPEED = 0x0020; - - /* - * Mansella Ltd. - */ - public static final int MANSELLA = 0x0021; - - /* - * NEC Corporation. - */ - public static final int NEC = 0x0022; - - /* - * WavePlus Technology Co., Ltd. - */ - public static final int WAVEPLUS_TECHNOLOGY = 0x0023; - - /* - * Alcatel. - */ - public static final int ALCATEL = 0x0024; - - /* - * Philips Semiconductors. - */ - public static final int PHILIPS_SEMICONDUCTORS = 0x0025; - - /* - * C Technologies. - */ - public static final int C_TECHNOLOGIES = 0x0026; - - /* - * Open Interface. - */ - public static final int OPEN_INTERFACE = 0x0027; - - /* - * R F Micro Devices. - */ - public static final int RF_MICRO_DEVICES = 0x0028; - - /* - * Hitachi Ltd. - */ - public static final int HITACHI = 0x0029; - - /* - * Symbol Technologies, Inc. - */ - public static final int SYMBOL_TECHNOLOGIES = 0x002A; - - /* - * Tenovis. - */ - public static final int TENOVIS = 0x002B; - - /* - * Macronix International Co. Ltd. - */ - public static final int MACRONIX = 0x002C; - - /* - * GCT Semiconductor. - */ - public static final int GCT_SEMICONDUCTOR = 0x002D; - - /* - * Norwood Systems. - */ - public static final int NORWOOD_SYSTEMS = 0x002E; - - /* - * MewTel Technology Inc. - */ - public static final int MEWTEL_TECHNOLOGY = 0x002F; - - /* - * ST Microelectronics. - */ - public static final int ST_MICROELECTRONICS = 0x0030; - - /* - * Synopsys. - */ - public static final int SYNOPSYS = 0x0031; - - /* - * Red-M (Communications) Ltd. - */ - public static final int RED_M = 0x0032; - - /* - * Commil Ltd. - */ - public static final int COMMIL = 0x0033; - - /* - * Computer Access Technology Corporation (CATC). - */ - public static final int CATC = 0x0034; - - /* - * Eclipse (HQ Espana) S.L. - */ - public static final int ECLIPSE = 0x0035; - - /* - * Renesas Technology Corp. - */ - public static final int RENESAS_TECHNOLOGY = 0x0036; - - /* - * Mobilian Corporation. - */ - public static final int MOBILIAN_CORPORATION = 0x0037; - - /* - * Terax. - */ - public static final int TERAX = 0x0038; - - /* - * Integrated System Solution Corp. - */ - public static final int INTEGRATED_SYSTEM_SOLUTION = 0x0039; - - /* - * Matsushita Electric Industrial Co., Ltd. - */ - public static final int MATSUSHITA_ELECTRIC = 0x003A; - - /* - * Gennum Corporation. - */ - public static final int GENNUM = 0x003B; - - /* - * Research In Motion. - */ - public static final int RESEARCH_IN_MOTION = 0x003C; - - /* - * IPextreme, Inc. - */ - public static final int IPEXTREME = 0x003D; - - /* - * Systems and Chips, Inc. - */ - public static final int SYSTEMS_AND_CHIPS = 0x003E; - - /* - * Bluetooth SIG, Inc. - */ - public static final int BLUETOOTH_SIG = 0x003F; - - /* - * Seiko Epson Corporation. - */ - public static final int SEIKO_EPSON = 0x0040; - - /* - * Integrated Silicon Solution Taiwan, Inc. - */ - public static final int INTEGRATED_SILICON_SOLUTION = 0x0041; - - /* - * CONWISE Technology Corporation Ltd. - */ - public static final int CONWISE_TECHNOLOGY = 0x0042; - - /* - * PARROT SA. - */ - public static final int PARROT = 0x0043; - - /* - * Socket Mobile. - */ - public static final int SOCKET_MOBILE = 0x0044; - - /* - * Atheros Communications, Inc. - */ - public static final int ATHEROS_COMMUNICATIONS = 0x0045; - - /* - * MediaTek, Inc. - */ - public static final int MEDIATEK = 0x0046; - - /* - * Bluegiga. - */ - public static final int BLUEGIGA = 0x0047; - - /* - * Marvell Technology Group Ltd. - */ - public static final int MARVELL = 0x0048; - - /* - * 3DSP Corporation. - */ - public static final int THREE_DSP = 0x0049; - - /* - * Accel Semiconductor Ltd. - */ - public static final int ACCEL_SEMICONDUCTOR = 0x004A; - - /* - * Continental Automotive Systems. - */ - public static final int CONTINENTAL_AUTOMOTIVE = 0x004B; - - /* - * Apple, Inc. - */ - public static final int APPLE = 0x004C; - - /* - * Staccato Communications, Inc. - */ - public static final int STACCATO_COMMUNICATIONS = 0x004D; - - /* - * Avago Technologies. - */ - public static final int AVAGO = 0x004E; - - /* - * APT Licensing Ltd. - */ - public static final int APT_LICENSING = 0x004F; - - /* - * SiRF Technology, Inc. - */ - public static final int SIRF_TECHNOLOGY = 0x0050; - - /* - * Tzero Technologies, Inc. - */ - public static final int TZERO_TECHNOLOGIES = 0x0051; - - /* - * J&M Corporation. - */ - public static final int J_AND_M = 0x0052; - - /* - * Free2move AB. - */ - public static final int FREE2MOVE = 0x0053; - - /* - * 3DiJoy Corporation. - */ - public static final int THREE_DIJOY = 0x0054; - - /* - * Plantronics, Inc. - */ - public static final int PLANTRONICS = 0x0055; - - /* - * Sony Ericsson Mobile Communications. - */ - public static final int SONY_ERICSSON = 0x0056; - - /* - * Harman International Industries, Inc. - */ - public static final int HARMAN_INTERNATIONAL = 0x0057; - - /* - * Vizio, Inc. - */ - public static final int VIZIO = 0x0058; - - /* - * Nordic Semiconductor ASA. - */ - public static final int NORDIC_SEMICONDUCTOR = 0x0059; - - /* - * EM Microelectronic-Marin SA. - */ - public static final int EM_MICROELECTRONIC_MARIN = 0x005A; - - /* - * Ralink Technology Corporation. - */ - public static final int RALINK_TECHNOLOGY = 0x005B; - - /* - * Belkin International, Inc. - */ - public static final int BELKIN_INTERNATIONAL = 0x005C; - - /* - * Realtek Semiconductor Corporation. - */ - public static final int REALTEK_SEMICONDUCTOR = 0x005D; - - /* - * Stonestreet One, LLC. - */ - public static final int STONESTREET_ONE = 0x005E; - - /* - * Wicentric, Inc. - */ - public static final int WICENTRIC = 0x005F; - - /* - * RivieraWaves S.A.S. - */ - public static final int RIVIERAWAVES = 0x0060; - - /* - * RDA Microelectronics. - */ - public static final int RDA_MICROELECTRONICS = 0x0061; - - /* - * Gibson Guitars. - */ - public static final int GIBSON_GUITARS = 0x0062; - - /* - * MiCommand Inc. - */ - public static final int MICOMMAND = 0x0063; - - /* - * Band XI International, LLC. - */ - public static final int BAND_XI_INTERNATIONAL = 0x0064; - - /* - * Hewlett-Packard Company. - */ - public static final int HEWLETT_PACKARD = 0x0065; - - /* - * 9Solutions Oy. - */ - public static final int NINE_SOLUTIONS = 0x0066; - - /* - * GN Netcom A/S. - */ - public static final int GN_NETCOM = 0x0067; - - /* - * General Motors. - */ - public static final int GENERAL_MOTORS = 0x0068; - - /* - * A&D Engineering, Inc. - */ - public static final int A_AND_D_ENGINEERING = 0x0069; - - /* - * MindTree Ltd. - */ - public static final int MINDTREE = 0x006A; - - /* - * Polar Electro OY. - */ - public static final int POLAR_ELECTRO = 0x006B; - - /* - * Beautiful Enterprise Co., Ltd. - */ - public static final int BEAUTIFUL_ENTERPRISE = 0x006C; - - /* - * BriarTek, Inc. - */ - public static final int BRIARTEK = 0x006D; - - /* - * Summit Data Communications, Inc. - */ - public static final int SUMMIT_DATA_COMMUNICATIONS = 0x006E; - - /* - * Sound ID. - */ - public static final int SOUND_ID = 0x006F; - - /* - * Monster, LLC. - */ - public static final int MONSTER = 0x0070; - - /* - * connectBlue AB. - */ - public static final int CONNECTBLUE = 0x0071; - - /* - * ShangHai Super Smart Electronics Co. Ltd. - */ - public static final int SHANGHAI_SUPER_SMART_ELECTRONICS = 0x0072; - - /* - * Group Sense Ltd. - */ - public static final int GROUP_SENSE = 0x0073; - - /* - * Zomm, LLC. - */ - public static final int ZOMM = 0x0074; - - /* - * Samsung Electronics Co. Ltd. - */ - public static final int SAMSUNG_ELECTRONICS = 0x0075; - - /* - * Creative Technology Ltd. - */ - public static final int CREATIVE_TECHNOLOGY = 0x0076; - - /* - * Laird Technologies. - */ - public static final int LAIRD_TECHNOLOGIES = 0x0077; - - /* - * Nike, Inc. - */ - public static final int NIKE = 0x0078; - - /* - * lesswire AG. - */ - public static final int LESSWIRE = 0x0079; - - /* - * MStar Semiconductor, Inc. - */ - public static final int MSTAR_SEMICONDUCTOR = 0x007A; - - /* - * Hanlynn Technologies. - */ - public static final int HANLYNN_TECHNOLOGIES = 0x007B; - - /* - * A & R Cambridge. - */ - public static final int A_AND_R_CAMBRIDGE = 0x007C; - - /* - * Seers Technology Co. Ltd. - */ - public static final int SEERS_TECHNOLOGY = 0x007D; - - /* - * Sports Tracking Technologies Ltd. - */ - public static final int SPORTS_TRACKING_TECHNOLOGIES = 0x007E; - - /* - * Autonet Mobile. - */ - public static final int AUTONET_MOBILE = 0x007F; - - /* - * DeLorme Publishing Company, Inc. - */ - public static final int DELORME_PUBLISHING_COMPANY = 0x0080; - - /* - * WuXi Vimicro. - */ - public static final int WUXI_VIMICRO = 0x0081; - - /* - * Sennheiser Communications A/S. - */ - public static final int SENNHEISER_COMMUNICATIONS = 0x0082; - - /* - * TimeKeeping Systems, Inc. - */ - public static final int TIMEKEEPING_SYSTEMS = 0x0083; - - /* - * Ludus Helsinki Ltd. - */ - public static final int LUDUS_HELSINKI = 0x0084; - - /* - * BlueRadios, Inc. - */ - public static final int BLUERADIOS = 0x0085; - - /* - * equinox AG. - */ - public static final int EQUINOX_AG = 0x0086; - - /* - * Garmin International, Inc. - */ - public static final int GARMIN_INTERNATIONAL = 0x0087; - - /* - * Ecotest. - */ - public static final int ECOTEST = 0x0088; - - /* - * GN ReSound A/S. - */ - public static final int GN_RESOUND = 0x0089; - - /* - * Jawbone. - */ - public static final int JAWBONE = 0x008A; - - /* - * Topcorn Positioning Systems, LLC. - */ - public static final int TOPCORN_POSITIONING_SYSTEMS = 0x008B; - - /* - * Qualcomm Labs, Inc. - */ - public static final int QUALCOMM_LABS = 0x008C; - - /* - * Zscan Software. - */ - public static final int ZSCAN_SOFTWARE = 0x008D; - - /* - * Quintic Corp. - */ - public static final int QUINTIC = 0x008E; - - /* - * Stollman E+V GmbH. - */ - public static final int STOLLMAN_E_PLUS_V = 0x008F; - - /* - * Funai Electric Co., Ltd. - */ - public static final int FUNAI_ELECTRIC = 0x0090; - - /* - * Advanced PANMOBIL Systems GmbH & Co. KG. - */ - public static final int ADVANCED_PANMOBIL_SYSTEMS = 0x0091; - - /* - * ThinkOptics, Inc. - */ - public static final int THINKOPTICS = 0x0092; - - /* - * Universal Electronics, Inc. - */ - public static final int UNIVERSAL_ELECTRONICS = 0x0093; - - /* - * Airoha Technology Corp. - */ - public static final int AIROHA_TECHNOLOGY = 0x0094; - - /* - * NEC Lighting, Ltd. - */ - public static final int NEC_LIGHTING = 0x0095; - - /* - * ODM Technology, Inc. - */ - public static final int ODM_TECHNOLOGY = 0x0096; - - /* - * Bluetrek Technologies Limited. - */ - public static final int BLUETREK_TECHNOLOGIES = 0x0097; - - /* - * zer01.tv GmbH. - */ - public static final int ZER01_TV = 0x0098; - - /* - * i.Tech Dynamic Global Distribution Ltd. - */ - public static final int I_TECH_DYNAMIC_GLOBAL_DISTRIBUTION = 0x0099; - - /* - * Alpwise. - */ - public static final int ALPWISE = 0x009A; - - /* - * Jiangsu Toppower Automotive Electronics Co., Ltd. - */ - public static final int JIANGSU_TOPPOWER_AUTOMOTIVE_ELECTRONICS = 0x009B; - - /* - * Colorfy, Inc. - */ - public static final int COLORFY = 0x009C; - - /* - * Geoforce Inc. - */ - public static final int GEOFORCE = 0x009D; - - /* - * Bose Corporation. - */ - public static final int BOSE = 0x009E; - - /* - * Suunto Oy. - */ - public static final int SUUNTO = 0x009F; - - /* - * Kensington Computer Products Group. - */ - public static final int KENSINGTON_COMPUTER_PRODUCTS_GROUP = 0x00A0; - - /* - * SR-Medizinelektronik. - */ - public static final int SR_MEDIZINELEKTRONIK = 0x00A1; - - /* - * Vertu Corporation Limited. - */ - public static final int VERTU = 0x00A2; - - /* - * Meta Watch Ltd. - */ - public static final int META_WATCH = 0x00A3; - - /* - * LINAK A/S. - */ - public static final int LINAK = 0x00A4; - - /* - * OTL Dynamics LLC. - */ - public static final int OTL_DYNAMICS = 0x00A5; - - /* - * Panda Ocean Inc. - */ - public static final int PANDA_OCEAN = 0x00A6; - - /* - * Visteon Corporation. - */ - public static final int VISTEON = 0x00A7; - - /* - * ARP Devices Limited. - */ - public static final int ARP_DEVICES = 0x00A8; - - /* - * Magneti Marelli S.p.A. - */ - public static final int MAGNETI_MARELLI = 0x00A9; - - /* - * CAEN RFID srl. - */ - public static final int CAEN_RFID = 0x00AA; - - /* - * Ingenieur-Systemgruppe Zahn GmbH. - */ - public static final int INGENIEUR_SYSTEMGRUPPE_ZAHN = 0x00AB; - - /* - * Green Throttle Games. - */ - public static final int GREEN_THROTTLE_GAMES = 0x00AC; - - /* - * Peter Systemtechnik GmbH. - */ - public static final int PETER_SYSTEMTECHNIK = 0x00AD; - - /* - * Omegawave Oy. - */ - public static final int OMEGAWAVE = 0x00AE; - - /* - * Cinetix. - */ - public static final int CINETIX = 0x00AF; - - /* - * Passif Semiconductor Corp. - */ - public static final int PASSIF_SEMICONDUCTOR = 0x00B0; - - /* - * Saris Cycling Group, Inc. - */ - public static final int SARIS_CYCLING_GROUP = 0x00B1; - - /* - * Bekey A/S. - */ - public static final int BEKEY = 0x00B2; - - /* - * Clarinox Technologies Pty. Ltd. - */ - public static final int CLARINOX_TECHNOLOGIES = 0x00B3; - - /* - * BDE Technology Co., Ltd. - */ - public static final int BDE_TECHNOLOGY = 0x00B4; - - /* - * Swirl Networks. - */ - public static final int SWIRL_NETWORKS = 0x00B5; - - /* - * Meso international. - */ - public static final int MESO_INTERNATIONAL = 0x00B6; - - /* - * TreLab Ltd. - */ - public static final int TRELAB = 0x00B7; - - /* - * Qualcomm Innovation Center, Inc. (QuIC). - */ - public static final int QUALCOMM_INNOVATION_CENTER = 0x00B8; - - /* - * Johnson Controls, Inc. - */ - public static final int JOHNSON_CONTROLS = 0x00B9; - - /* - * Starkey Laboratories Inc. - */ - public static final int STARKEY_LABORATORIES = 0x00BA; - - /* - * S-Power Electronics Limited. - */ - public static final int S_POWER_ELECTRONICS = 0x00BB; - - /* - * Ace Sensor Inc. - */ - public static final int ACE_SENSOR = 0x00BC; - - /* - * Aplix Corporation. - */ - public static final int APLIX = 0x00BD; - - /* - * AAMP of America. - */ - public static final int AAMP_OF_AMERICA = 0x00BE; - - /* - * Stalmart Technology Limited. - */ - public static final int STALMART_TECHNOLOGY = 0x00BF; - - /* - * AMICCOM Electronics Corporation. - */ - public static final int AMICCOM_ELECTRONICS = 0x00C0; - - /* - * Shenzhen Excelsecu Data Technology Co.,Ltd. - */ - public static final int SHENZHEN_EXCELSECU_DATA_TECHNOLOGY = 0x00C1; - - /* - * Geneq Inc. - */ - public static final int GENEQ = 0x00C2; - - /* - * adidas AG. - */ - public static final int ADIDAS = 0x00C3; - - /* - * LG Electronics. - */ - public static final int LG_ELECTRONICS = 0x00C4; - - /* - * Onset Computer Corporation. - */ - public static final int ONSET_COMPUTER = 0x00C5; - - /* - * Selfly BV. - */ - public static final int SELFLY = 0x00C6; - - /* - * Quuppa Oy. - */ - public static final int QUUPPA = 0x00C7; - - /* - * GeLo Inc. - */ - public static final int GELO = 0x00C8; - - /* - * Evluma. - */ - public static final int EVLUMA = 0x00C9; - - /* - * MC10. - */ - public static final int MC10 = 0x00CA; - - /* - * Binauric SE. - */ - public static final int BINAURIC = 0x00CB; - - /* - * Beats Electronics. - */ - public static final int BEATS_ELECTRONICS = 0x00CC; - - /* - * Microchip Technology Inc. - */ - public static final int MICROCHIP_TECHNOLOGY = 0x00CD; - - /* - * Elgato Systems GmbH. - */ - public static final int ELGATO_SYSTEMS = 0x00CE; - - /* - * ARCHOS SA. - */ - public static final int ARCHOS = 0x00CF; - - /* - * Dexcom, Inc. - */ - public static final int DEXCOM = 0x00D0; - - /* - * Polar Electro Europe B.V. - */ - public static final int POLAR_ELECTRO_EUROPE = 0x00D1; - - /* - * Dialog Semiconductor B.V. - */ - public static final int DIALOG_SEMICONDUCTOR = 0x00D2; - - /* - * Taixingbang Technology (HK) Co,. LTD. - */ - public static final int TAIXINGBANG_TECHNOLOGY = 0x00D3; - - /* - * Kawantech. - */ - public static final int KAWANTECH = 0x00D4; - - /* - * Austco Communication Systems. - */ - public static final int AUSTCO_COMMUNICATION_SYSTEMS = 0x00D5; - - /* - * Timex Group USA, Inc. - */ - public static final int TIMEX_GROUP_USA = 0x00D6; - - /* - * Qualcomm Technologies, Inc. - */ - public static final int QUALCOMM_TECHNOLOGIES = 0x00D7; - - /* - * Qualcomm Connected Experiences, Inc. - */ - public static final int QUALCOMM_CONNECTED_EXPERIENCES = 0x00D8; - - /* - * Voyetra Turtle Beach. - */ - public static final int VOYETRA_TURTLE_BEACH = 0x00D9; - - /* - * txtr GmbH. - */ - public static final int TXTR = 0x00DA; - - /* - * Biosentronics. - */ - public static final int BIOSENTRONICS = 0x00DB; - - /* - * Procter & Gamble. - */ - public static final int PROCTER_AND_GAMBLE = 0x00DC; - - /* - * Hosiden Corporation. - */ - public static final int HOSIDEN = 0x00DD; - - /* - * Muzik LLC. - */ - public static final int MUZIK = 0x00DE; - - /* - * Misfit Wearables Corp. - */ - public static final int MISFIT_WEARABLES = 0x00DF; - - /* - * Google. - */ - public static final int GOOGLE = 0x00E0; - - /* - * Danlers Ltd. - */ - public static final int DANLERS = 0x00E1; - - /* - * Semilink Inc. - */ - public static final int SEMILINK = 0x00E2; - - /* - * You can't instantiate one of these. - */ - private BluetoothAssignedNumbers() { - } - -} diff --git a/core/java/android/bluetooth/BluetoothAudioConfig.java b/core/java/android/bluetooth/BluetoothAudioConfig.java deleted file mode 100644 index 4c8b8c11fbc2..000000000000 --- a/core/java/android/bluetooth/BluetoothAudioConfig.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (C) 2009 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.bluetooth; - -import android.annotation.Nullable; -import android.os.Parcel; -import android.os.Parcelable; - -/** - * Represents the audio configuration for a Bluetooth A2DP source device. - * - * {@see BluetoothA2dpSink} - * - * {@hide} - */ -public final class BluetoothAudioConfig implements Parcelable { - - private final int mSampleRate; - private final int mChannelConfig; - private final int mAudioFormat; - - public BluetoothAudioConfig(int sampleRate, int channelConfig, int audioFormat) { - mSampleRate = sampleRate; - mChannelConfig = channelConfig; - mAudioFormat = audioFormat; - } - - @Override - public boolean equals(@Nullable Object o) { - if (o instanceof BluetoothAudioConfig) { - BluetoothAudioConfig bac = (BluetoothAudioConfig) o; - return (bac.mSampleRate == mSampleRate && bac.mChannelConfig == mChannelConfig - && bac.mAudioFormat == mAudioFormat); - } - return false; - } - - @Override - public int hashCode() { - return mSampleRate | (mChannelConfig << 24) | (mAudioFormat << 28); - } - - @Override - public String toString() { - return "{mSampleRate:" + mSampleRate + ",mChannelConfig:" + mChannelConfig - + ",mAudioFormat:" + mAudioFormat + "}"; - } - - @Override - public int describeContents() { - return 0; - } - - public static final @android.annotation.NonNull Parcelable.Creator<BluetoothAudioConfig> CREATOR = - new Parcelable.Creator<BluetoothAudioConfig>() { - public BluetoothAudioConfig createFromParcel(Parcel in) { - int sampleRate = in.readInt(); - int channelConfig = in.readInt(); - int audioFormat = in.readInt(); - return new BluetoothAudioConfig(sampleRate, channelConfig, audioFormat); - } - - public BluetoothAudioConfig[] newArray(int size) { - return new BluetoothAudioConfig[size]; - } - }; - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeInt(mSampleRate); - out.writeInt(mChannelConfig); - out.writeInt(mAudioFormat); - } - - /** - * Returns the sample rate in samples per second - * - * @return sample rate - */ - public int getSampleRate() { - return mSampleRate; - } - - /** - * Returns the channel configuration (either {@link android.media.AudioFormat#CHANNEL_IN_MONO} - * or {@link android.media.AudioFormat#CHANNEL_IN_STEREO}) - * - * @return channel configuration - */ - public int getChannelConfig() { - return mChannelConfig; - } - - /** - * Returns the channel audio format (either {@link android.media.AudioFormat#ENCODING_PCM_16BIT} - * or {@link android.media.AudioFormat#ENCODING_PCM_8BIT} - * - * @return audio format - */ - public int getAudioFormat() { - return mAudioFormat; - } -} diff --git a/core/java/android/bluetooth/BluetoothAvrcp.java b/core/java/android/bluetooth/BluetoothAvrcp.java deleted file mode 100644 index 1a4c75906482..000000000000 --- a/core/java/android/bluetooth/BluetoothAvrcp.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2014 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.bluetooth; - -/** - * This class contains constants for Bluetooth AVRCP profile. - * - * {@hide} - */ -public final class BluetoothAvrcp { - - /* - * State flags for Passthrough commands - */ - public static final int PASSTHROUGH_STATE_PRESS = 0; - public static final int PASSTHROUGH_STATE_RELEASE = 1; - - /* - * Operation IDs for Passthrough commands - */ - public static final int PASSTHROUGH_ID_SELECT = 0x00; /* select */ - public static final int PASSTHROUGH_ID_UP = 0x01; /* up */ - public static final int PASSTHROUGH_ID_DOWN = 0x02; /* down */ - public static final int PASSTHROUGH_ID_LEFT = 0x03; /* left */ - public static final int PASSTHROUGH_ID_RIGHT = 0x04; /* right */ - public static final int PASSTHROUGH_ID_RIGHT_UP = 0x05; /* right-up */ - public static final int PASSTHROUGH_ID_RIGHT_DOWN = 0x06; /* right-down */ - public static final int PASSTHROUGH_ID_LEFT_UP = 0x07; /* left-up */ - public static final int PASSTHROUGH_ID_LEFT_DOWN = 0x08; /* left-down */ - public static final int PASSTHROUGH_ID_ROOT_MENU = 0x09; /* root menu */ - public static final int PASSTHROUGH_ID_SETUP_MENU = 0x0A; /* setup menu */ - public static final int PASSTHROUGH_ID_CONT_MENU = 0x0B; /* contents menu */ - public static final int PASSTHROUGH_ID_FAV_MENU = 0x0C; /* favorite menu */ - public static final int PASSTHROUGH_ID_EXIT = 0x0D; /* exit */ - public static final int PASSTHROUGH_ID_0 = 0x20; /* 0 */ - public static final int PASSTHROUGH_ID_1 = 0x21; /* 1 */ - public static final int PASSTHROUGH_ID_2 = 0x22; /* 2 */ - public static final int PASSTHROUGH_ID_3 = 0x23; /* 3 */ - public static final int PASSTHROUGH_ID_4 = 0x24; /* 4 */ - public static final int PASSTHROUGH_ID_5 = 0x25; /* 5 */ - public static final int PASSTHROUGH_ID_6 = 0x26; /* 6 */ - public static final int PASSTHROUGH_ID_7 = 0x27; /* 7 */ - public static final int PASSTHROUGH_ID_8 = 0x28; /* 8 */ - public static final int PASSTHROUGH_ID_9 = 0x29; /* 9 */ - public static final int PASSTHROUGH_ID_DOT = 0x2A; /* dot */ - public static final int PASSTHROUGH_ID_ENTER = 0x2B; /* enter */ - public static final int PASSTHROUGH_ID_CLEAR = 0x2C; /* clear */ - public static final int PASSTHROUGH_ID_CHAN_UP = 0x30; /* channel up */ - public static final int PASSTHROUGH_ID_CHAN_DOWN = 0x31; /* channel down */ - public static final int PASSTHROUGH_ID_PREV_CHAN = 0x32; /* previous channel */ - public static final int PASSTHROUGH_ID_SOUND_SEL = 0x33; /* sound select */ - public static final int PASSTHROUGH_ID_INPUT_SEL = 0x34; /* input select */ - public static final int PASSTHROUGH_ID_DISP_INFO = 0x35; /* display information */ - public static final int PASSTHROUGH_ID_HELP = 0x36; /* help */ - public static final int PASSTHROUGH_ID_PAGE_UP = 0x37; /* page up */ - public static final int PASSTHROUGH_ID_PAGE_DOWN = 0x38; /* page down */ - public static final int PASSTHROUGH_ID_POWER = 0x40; /* power */ - public static final int PASSTHROUGH_ID_VOL_UP = 0x41; /* volume up */ - public static final int PASSTHROUGH_ID_VOL_DOWN = 0x42; /* volume down */ - public static final int PASSTHROUGH_ID_MUTE = 0x43; /* mute */ - public static final int PASSTHROUGH_ID_PLAY = 0x44; /* play */ - public static final int PASSTHROUGH_ID_STOP = 0x45; /* stop */ - public static final int PASSTHROUGH_ID_PAUSE = 0x46; /* pause */ - public static final int PASSTHROUGH_ID_RECORD = 0x47; /* record */ - public static final int PASSTHROUGH_ID_REWIND = 0x48; /* rewind */ - public static final int PASSTHROUGH_ID_FAST_FOR = 0x49; /* fast forward */ - public static final int PASSTHROUGH_ID_EJECT = 0x4A; /* eject */ - public static final int PASSTHROUGH_ID_FORWARD = 0x4B; /* forward */ - public static final int PASSTHROUGH_ID_BACKWARD = 0x4C; /* backward */ - public static final int PASSTHROUGH_ID_ANGLE = 0x50; /* angle */ - public static final int PASSTHROUGH_ID_SUBPICT = 0x51; /* subpicture */ - public static final int PASSTHROUGH_ID_F1 = 0x71; /* F1 */ - public static final int PASSTHROUGH_ID_F2 = 0x72; /* F2 */ - public static final int PASSTHROUGH_ID_F3 = 0x73; /* F3 */ - public static final int PASSTHROUGH_ID_F4 = 0x74; /* F4 */ - public static final int PASSTHROUGH_ID_F5 = 0x75; /* F5 */ - public static final int PASSTHROUGH_ID_VENDOR = 0x7E; /* vendor unique */ - public static final int PASSTHROUGH_KEYPRESSED_RELEASE = 0x80; -} diff --git a/core/java/android/bluetooth/BluetoothAvrcpController.java b/core/java/android/bluetooth/BluetoothAvrcpController.java deleted file mode 100644 index 81fc3e11e9e1..000000000000 --- a/core/java/android/bluetooth/BluetoothAvrcpController.java +++ /dev/null @@ -1,298 +0,0 @@ -/* - * Copyright (C) 2014 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.bluetooth; - -import static android.bluetooth.BluetoothUtils.getSyncTimeout; - -import android.annotation.RequiresPermission; -import android.annotation.SdkConstant; -import android.annotation.SdkConstant.SdkConstantType; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; -import android.content.AttributionSource; -import android.content.Context; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.Log; - -import com.android.modules.utils.SynchronousResultReceiver; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeoutException; - -/** - * This class provides the public APIs to control the Bluetooth AVRCP Controller. It currently - * supports player information, playback support and track metadata. - * - * <p>BluetoothAvrcpController is a proxy object for controlling the Bluetooth AVRCP - * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get - * the BluetoothAvrcpController proxy object. - * - * {@hide} - */ -public final class BluetoothAvrcpController implements BluetoothProfile { - private static final String TAG = "BluetoothAvrcpController"; - private static final boolean DBG = false; - private static final boolean VDBG = false; - - /** - * Intent used to broadcast the change in connection state of the AVRCP Controller - * profile. - * - * <p>This intent will have 3 extras: - * <ul> - * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> - * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> - * </ul> - * - * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of - * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, - * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_CONNECTION_STATE_CHANGED = - "android.bluetooth.avrcp-controller.profile.action.CONNECTION_STATE_CHANGED"; - - /** - * Intent used to broadcast the change in player application setting state on AVRCP AG. - * - * <p>This intent will have the following extras: - * <ul> - * <li> {@link #EXTRA_PLAYER_SETTING} - {@link BluetoothAvrcpPlayerSettings} containing the - * most recent player setting. </li> - * </ul> - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_PLAYER_SETTING = - "android.bluetooth.avrcp-controller.profile.action.PLAYER_SETTING"; - - public static final String EXTRA_PLAYER_SETTING = - "android.bluetooth.avrcp-controller.profile.extra.PLAYER_SETTING"; - - private final BluetoothAdapter mAdapter; - private final AttributionSource mAttributionSource; - private final BluetoothProfileConnector<IBluetoothAvrcpController> mProfileConnector = - new BluetoothProfileConnector(this, BluetoothProfile.AVRCP_CONTROLLER, - "BluetoothAvrcpController", IBluetoothAvrcpController.class.getName()) { - @Override - public IBluetoothAvrcpController getServiceInterface(IBinder service) { - return IBluetoothAvrcpController.Stub.asInterface(service); - } - }; - - /** - * Create a BluetoothAvrcpController proxy object for interacting with the local - * Bluetooth AVRCP service. - */ - /* package */ BluetoothAvrcpController(Context context, ServiceListener listener, - BluetoothAdapter adapter) { - mAdapter = adapter; - mAttributionSource = adapter.getAttributionSource(); - mProfileConnector.connect(context, listener); - } - - /*package*/ void close() { - mProfileConnector.disconnect(); - } - - private IBluetoothAvrcpController getService() { - return mProfileConnector.getService(); - } - - @Override - public void finalize() { - close(); - } - - /** - * {@inheritDoc} - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getConnectedDevices() { - if (VDBG) log("getConnectedDevices()"); - final IBluetoothAvrcpController service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getConnectedDevices(mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { - if (VDBG) log("getDevicesMatchingStates()"); - final IBluetoothAvrcpController service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public int getConnectionState(BluetoothDevice device) { - if (VDBG) log("getState(" + device + ")"); - final IBluetoothAvrcpController service = getService(); - final int defaultValue = BluetoothProfile.STATE_DISCONNECTED; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionState(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Gets the player application settings. - * - * @return the {@link BluetoothAvrcpPlayerSettings} or {@link null} if there is an error. - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothAvrcpPlayerSettings getPlayerSettings(BluetoothDevice device) { - if (DBG) Log.d(TAG, "getPlayerSettings"); - BluetoothAvrcpPlayerSettings settings = null; - final IBluetoothAvrcpController service = getService(); - final BluetoothAvrcpPlayerSettings defaultValue = null; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<BluetoothAvrcpPlayerSettings> recv = - new SynchronousResultReceiver(); - service.getPlayerSettings(device, mAttributionSource, recv); - settings = recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Sets the player app setting for current player. - * returns true in case setting is supported by remote, false otherwise - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings plAppSetting) { - if (DBG) Log.d(TAG, "setPlayerApplicationSetting"); - final IBluetoothAvrcpController service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.setPlayerApplicationSetting(plAppSetting, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Send Group Navigation Command to Remote. - * possible keycode values: next_grp, previous_grp defined above - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public void sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState) { - Log.d(TAG, "sendGroupNavigationCmd dev = " + device + " key " + keyCode + " State = " - + keyState); - final IBluetoothAvrcpController service = getService(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver recv = new SynchronousResultReceiver(); - service.sendGroupNavigationCmd(device, keyCode, keyState, mAttributionSource, recv); - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null); - return; - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - } - - private boolean isEnabled() { - return mAdapter.getState() == BluetoothAdapter.STATE_ON; - } - - private static boolean isValidDevice(BluetoothDevice device) { - return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); - } - - private static void log(String msg) { - Log.d(TAG, msg); - } -} diff --git a/core/java/android/bluetooth/BluetoothAvrcpPlayerSettings.java b/core/java/android/bluetooth/BluetoothAvrcpPlayerSettings.java deleted file mode 100644 index 30aea1abf73c..000000000000 --- a/core/java/android/bluetooth/BluetoothAvrcpPlayerSettings.java +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright (C) 2015 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.bluetooth; - -import android.os.Parcel; -import android.os.Parcelable; -import android.util.Log; - -import java.util.HashMap; -import java.util.Map; - -/** - * Class used to identify settings associated with the player on AG. - * - * {@hide} - */ -public final class BluetoothAvrcpPlayerSettings implements Parcelable { - public static final String TAG = "BluetoothAvrcpPlayerSettings"; - - /** - * Equalizer setting. - */ - public static final int SETTING_EQUALIZER = 0x01; - - /** - * Repeat setting. - */ - public static final int SETTING_REPEAT = 0x02; - - /** - * Shuffle setting. - */ - public static final int SETTING_SHUFFLE = 0x04; - - /** - * Scan mode setting. - */ - public static final int SETTING_SCAN = 0x08; - - /** - * Invalid state. - * - * Used for returning error codes. - */ - public static final int STATE_INVALID = -1; - - /** - * OFF state. - * - * Denotes a general OFF state. Applies to all settings. - */ - public static final int STATE_OFF = 0x00; - - /** - * ON state. - * - * Applies to {@link SETTING_EQUALIZER}. - */ - public static final int STATE_ON = 0x01; - - /** - * Single track repeat. - * - * Applies only to {@link SETTING_REPEAT}. - */ - public static final int STATE_SINGLE_TRACK = 0x02; - - /** - * All track repeat/shuffle. - * - * Applies to {@link #SETTING_REPEAT}, {@link #SETTING_SHUFFLE} and {@link #SETTING_SCAN}. - */ - public static final int STATE_ALL_TRACK = 0x03; - - /** - * Group repeat/shuffle. - * - * Applies to {@link #SETTING_REPEAT}, {@link #SETTING_SHUFFLE} and {@link #SETTING_SCAN}. - */ - public static final int STATE_GROUP = 0x04; - - /** - * List of supported settings ORed. - */ - private int mSettings; - - /** - * Hash map of current capability values. - */ - private Map<Integer, Integer> mSettingsValue = new HashMap<Integer, Integer>(); - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeInt(mSettings); - out.writeInt(mSettingsValue.size()); - for (int k : mSettingsValue.keySet()) { - out.writeInt(k); - out.writeInt(mSettingsValue.get(k)); - } - } - - public static final @android.annotation.NonNull Parcelable.Creator<BluetoothAvrcpPlayerSettings> CREATOR = - new Parcelable.Creator<BluetoothAvrcpPlayerSettings>() { - public BluetoothAvrcpPlayerSettings createFromParcel(Parcel in) { - return new BluetoothAvrcpPlayerSettings(in); - } - - public BluetoothAvrcpPlayerSettings[] newArray(int size) { - return new BluetoothAvrcpPlayerSettings[size]; - } - }; - - private BluetoothAvrcpPlayerSettings(Parcel in) { - mSettings = in.readInt(); - int numSettings = in.readInt(); - for (int i = 0; i < numSettings; i++) { - mSettingsValue.put(in.readInt(), in.readInt()); - } - } - - /** - * Create a new player settings object. - * - * @param settings a ORed value of SETTINGS_* defined above. - */ - public BluetoothAvrcpPlayerSettings(int settings) { - mSettings = settings; - } - - /** - * Get the supported settings. - * - * @return int ORed value of supported settings. - */ - public int getSettings() { - return mSettings; - } - - /** - * Add a setting value. - * - * The setting must be part of possible settings in {@link getSettings()}. - * - * @param setting setting config. - * @param value value for the setting. - * @throws IllegalStateException if the setting is not supported. - */ - public void addSettingValue(int setting, int value) { - if ((setting & mSettings) == 0) { - Log.e(TAG, "Setting not supported: " + setting + " " + mSettings); - throw new IllegalStateException("Setting not supported: " + setting); - } - mSettingsValue.put(setting, value); - } - - /** - * Get a setting value. - * - * The setting must be part of possible settings in {@link getSettings()}. - * - * @param setting setting config. - * @return value value for the setting. - * @throws IllegalStateException if the setting is not supported. - */ - public int getSettingValue(int setting) { - if ((setting & mSettings) == 0) { - Log.e(TAG, "Setting not supported: " + setting + " " + mSettings); - throw new IllegalStateException("Setting not supported: " + setting); - } - Integer i = mSettingsValue.get(setting); - if (i == null) return -1; - return i; - } -} diff --git a/core/java/android/bluetooth/BluetoothClass.java b/core/java/android/bluetooth/BluetoothClass.java deleted file mode 100755 index a3c45d0276ca..000000000000 --- a/core/java/android/bluetooth/BluetoothClass.java +++ /dev/null @@ -1,445 +0,0 @@ -/* - * Copyright (C) 2008 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.bluetooth; - -import android.annotation.Nullable; -import android.annotation.SystemApi; -import android.annotation.TestApi; -import android.compat.annotation.UnsupportedAppUsage; -import android.os.Build; -import android.os.Parcel; -import android.os.Parcelable; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Arrays; - -/** - * Represents a Bluetooth class, which describes general characteristics - * and capabilities of a device. For example, a Bluetooth class will - * specify the general device type such as a phone, a computer, or - * headset, and whether it's capable of services such as audio or telephony. - * - * <p>Every Bluetooth class is composed of zero or more service classes, and - * exactly one device class. The device class is further broken down into major - * and minor device class components. - * - * <p>{@link BluetoothClass} is useful as a hint to roughly describe a device - * (for example to show an icon in the UI), but does not reliably describe which - * Bluetooth profiles or services are actually supported by a device. Accurate - * service discovery is done through SDP requests, which are automatically - * performed when creating an RFCOMM socket with {@link - * BluetoothDevice#createRfcommSocketToServiceRecord} and {@link - * BluetoothAdapter#listenUsingRfcommWithServiceRecord}</p> - * - * <p>Use {@link BluetoothDevice#getBluetoothClass} to retrieve the class for - * a remote device. - * - * <!-- - * The Bluetooth class is a 32 bit field. The format of these bits is defined at - * http://www.bluetooth.org/Technical/AssignedNumbers/baseband.htm - * (login required). This class contains that 32 bit field, and provides - * constants and methods to determine which Service Class(es) and Device Class - * are encoded in that field. - * --> - */ -public final class BluetoothClass implements Parcelable { - /** - * Legacy error value. Applications should use null instead. - * - * @hide - */ - public static final int ERROR = 0xFF000000; - - private final int mClass; - - /** @hide */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) - public BluetoothClass(int classInt) { - mClass = classInt; - } - - @Override - public boolean equals(@Nullable Object o) { - if (o instanceof BluetoothClass) { - return mClass == ((BluetoothClass) o).mClass; - } - return false; - } - - @Override - public int hashCode() { - return mClass; - } - - @Override - public String toString() { - return Integer.toHexString(mClass); - } - - @Override - public int describeContents() { - return 0; - } - - public static final @android.annotation.NonNull Parcelable.Creator<BluetoothClass> CREATOR = - new Parcelable.Creator<BluetoothClass>() { - public BluetoothClass createFromParcel(Parcel in) { - return new BluetoothClass(in.readInt()); - } - - public BluetoothClass[] newArray(int size) { - return new BluetoothClass[size]; - } - }; - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeInt(mClass); - } - - /** - * Defines all service class constants. - * <p>Each {@link BluetoothClass} encodes zero or more service classes. - */ - public static final class Service { - private static final int BITMASK = 0xFFE000; - - public static final int LIMITED_DISCOVERABILITY = 0x002000; - public static final int LE_AUDIO = 0x004000; - public static final int POSITIONING = 0x010000; - public static final int NETWORKING = 0x020000; - public static final int RENDER = 0x040000; - public static final int CAPTURE = 0x080000; - public static final int OBJECT_TRANSFER = 0x100000; - public static final int AUDIO = 0x200000; - public static final int TELEPHONY = 0x400000; - public static final int INFORMATION = 0x800000; - } - - /** - * Return true if the specified service class is supported by this - * {@link BluetoothClass}. - * <p>Valid service classes are the public constants in - * {@link BluetoothClass.Service}. For example, {@link - * BluetoothClass.Service#AUDIO}. - * - * @param service valid service class - * @return true if the service class is supported - */ - public boolean hasService(int service) { - return ((mClass & Service.BITMASK & service) != 0); - } - - /** - * Defines all device class constants. - * <p>Each {@link BluetoothClass} encodes exactly one device class, with - * major and minor components. - * <p>The constants in {@link - * BluetoothClass.Device} represent a combination of major and minor - * device components (the complete device class). The constants in {@link - * BluetoothClass.Device.Major} represent only major device classes. - * <p>See {@link BluetoothClass.Service} for service class constants. - */ - public static class Device { - private static final int BITMASK = 0x1FFC; - - /** - * Defines all major device class constants. - * <p>See {@link BluetoothClass.Device} for minor classes. - */ - public static class Major { - private static final int BITMASK = 0x1F00; - - public static final int MISC = 0x0000; - public static final int COMPUTER = 0x0100; - public static final int PHONE = 0x0200; - public static final int NETWORKING = 0x0300; - public static final int AUDIO_VIDEO = 0x0400; - public static final int PERIPHERAL = 0x0500; - public static final int IMAGING = 0x0600; - public static final int WEARABLE = 0x0700; - public static final int TOY = 0x0800; - public static final int HEALTH = 0x0900; - public static final int UNCATEGORIZED = 0x1F00; - } - - // Devices in the COMPUTER major class - public static final int COMPUTER_UNCATEGORIZED = 0x0100; - public static final int COMPUTER_DESKTOP = 0x0104; - public static final int COMPUTER_SERVER = 0x0108; - public static final int COMPUTER_LAPTOP = 0x010C; - public static final int COMPUTER_HANDHELD_PC_PDA = 0x0110; - public static final int COMPUTER_PALM_SIZE_PC_PDA = 0x0114; - public static final int COMPUTER_WEARABLE = 0x0118; - - // Devices in the PHONE major class - public static final int PHONE_UNCATEGORIZED = 0x0200; - public static final int PHONE_CELLULAR = 0x0204; - public static final int PHONE_CORDLESS = 0x0208; - public static final int PHONE_SMART = 0x020C; - public static final int PHONE_MODEM_OR_GATEWAY = 0x0210; - public static final int PHONE_ISDN = 0x0214; - - // Minor classes for the AUDIO_VIDEO major class - public static final int AUDIO_VIDEO_UNCATEGORIZED = 0x0400; - public static final int AUDIO_VIDEO_WEARABLE_HEADSET = 0x0404; - public static final int AUDIO_VIDEO_HANDSFREE = 0x0408; - //public static final int AUDIO_VIDEO_RESERVED = 0x040C; - public static final int AUDIO_VIDEO_MICROPHONE = 0x0410; - public static final int AUDIO_VIDEO_LOUDSPEAKER = 0x0414; - public static final int AUDIO_VIDEO_HEADPHONES = 0x0418; - public static final int AUDIO_VIDEO_PORTABLE_AUDIO = 0x041C; - public static final int AUDIO_VIDEO_CAR_AUDIO = 0x0420; - public static final int AUDIO_VIDEO_SET_TOP_BOX = 0x0424; - public static final int AUDIO_VIDEO_HIFI_AUDIO = 0x0428; - public static final int AUDIO_VIDEO_VCR = 0x042C; - public static final int AUDIO_VIDEO_VIDEO_CAMERA = 0x0430; - public static final int AUDIO_VIDEO_CAMCORDER = 0x0434; - public static final int AUDIO_VIDEO_VIDEO_MONITOR = 0x0438; - public static final int AUDIO_VIDEO_VIDEO_DISPLAY_AND_LOUDSPEAKER = 0x043C; - public static final int AUDIO_VIDEO_VIDEO_CONFERENCING = 0x0440; - //public static final int AUDIO_VIDEO_RESERVED = 0x0444; - public static final int AUDIO_VIDEO_VIDEO_GAMING_TOY = 0x0448; - - // Devices in the WEARABLE major class - public static final int WEARABLE_UNCATEGORIZED = 0x0700; - public static final int WEARABLE_WRIST_WATCH = 0x0704; - public static final int WEARABLE_PAGER = 0x0708; - public static final int WEARABLE_JACKET = 0x070C; - public static final int WEARABLE_HELMET = 0x0710; - public static final int WEARABLE_GLASSES = 0x0714; - - // Devices in the TOY major class - public static final int TOY_UNCATEGORIZED = 0x0800; - public static final int TOY_ROBOT = 0x0804; - public static final int TOY_VEHICLE = 0x0808; - public static final int TOY_DOLL_ACTION_FIGURE = 0x080C; - public static final int TOY_CONTROLLER = 0x0810; - public static final int TOY_GAME = 0x0814; - - // Devices in the HEALTH major class - public static final int HEALTH_UNCATEGORIZED = 0x0900; - public static final int HEALTH_BLOOD_PRESSURE = 0x0904; - public static final int HEALTH_THERMOMETER = 0x0908; - public static final int HEALTH_WEIGHING = 0x090C; - public static final int HEALTH_GLUCOSE = 0x0910; - public static final int HEALTH_PULSE_OXIMETER = 0x0914; - public static final int HEALTH_PULSE_RATE = 0x0918; - public static final int HEALTH_DATA_DISPLAY = 0x091C; - - // Devices in PERIPHERAL major class - /** - * @hide - */ - public static final int PERIPHERAL_NON_KEYBOARD_NON_POINTING = 0x0500; - /** - * @hide - */ - public static final int PERIPHERAL_KEYBOARD = 0x0540; - /** - * @hide - */ - public static final int PERIPHERAL_POINTING = 0x0580; - /** - * @hide - */ - public static final int PERIPHERAL_KEYBOARD_POINTING = 0x05C0; - } - - /** - * Return the major device class component of this {@link BluetoothClass}. - * <p>Values returned from this function can be compared with the - * public constants in {@link BluetoothClass.Device.Major} to determine - * which major class is encoded in this Bluetooth class. - * - * @return major device class component - */ - public int getMajorDeviceClass() { - return (mClass & Device.Major.BITMASK); - } - - /** - * Return the (major and minor) device class component of this - * {@link BluetoothClass}. - * <p>Values returned from this function can be compared with the - * public constants in {@link BluetoothClass.Device} to determine which - * device class is encoded in this Bluetooth class. - * - * @return device class component - */ - public int getDeviceClass() { - return (mClass & Device.BITMASK); - } - - /** - * Return the Bluetooth Class of Device (CoD) value including the - * {@link BluetoothClass.Service}, {@link BluetoothClass.Device.Major} and - * minor device fields. - * - * <p>This value is an integer representation of Bluetooth CoD as in - * Bluetooth specification. - * - * @see <a href="Bluetooth CoD">https://www.bluetooth.com/specifications/assigned-numbers/baseband</a> - * - * @hide - */ - @TestApi - public int getClassOfDevice() { - return mClass; - } - - /** - * Return the Bluetooth Class of Device (CoD) value including the - * {@link BluetoothClass.Service}, {@link BluetoothClass.Device.Major} and - * minor device fields. - * - * <p>This value is a byte array representation of Bluetooth CoD as in - * Bluetooth specification. - * - * <p>Bluetooth COD information is 3 bytes, but stored as an int. Hence the - * MSB is useless and needs to be thrown away. The lower 3 bytes are - * converted into a byte array MSB to LSB. Hence, using BIG_ENDIAN. - * - * @see <a href="Bluetooth CoD">https://www.bluetooth.com/specifications/assigned-numbers/baseband</a> - * - * @hide - */ - public byte[] getClassOfDeviceBytes() { - byte[] bytes = ByteBuffer.allocate(4) - .order(ByteOrder.BIG_ENDIAN) - .putInt(mClass) - .array(); - - // Discard the top byte - return Arrays.copyOfRange(bytes, 1, bytes.length); - } - - public static final int PROFILE_HEADSET = 0; - - public static final int PROFILE_A2DP = 1; - - /** @hide */ - @SystemApi - public static final int PROFILE_OPP = 2; - - public static final int PROFILE_HID = 3; - - /** @hide */ - @SystemApi - public static final int PROFILE_PANU = 4; - - /** @hide */ - @SystemApi - public static final int PROFILE_NAP = 5; - - /** @hide */ - @SystemApi - public static final int PROFILE_A2DP_SINK = 6; - - /** - * Check class bits for possible bluetooth profile support. - * This is a simple heuristic that tries to guess if a device with the - * given class bits might support specified profile. It is not accurate for all - * devices. It tries to err on the side of false positives. - * - * @param profile the profile to be checked - * @return whether this device supports specified profile - */ - public boolean doesClassMatch(int profile) { - if (profile == PROFILE_A2DP) { - if (hasService(Service.RENDER)) { - return true; - } - // By the A2DP spec, sinks must indicate the RENDER service. - // However we found some that do not (Chordette). So lets also - // match on some other class bits. - switch (getDeviceClass()) { - case Device.AUDIO_VIDEO_HIFI_AUDIO: - case Device.AUDIO_VIDEO_HEADPHONES: - case Device.AUDIO_VIDEO_LOUDSPEAKER: - case Device.AUDIO_VIDEO_CAR_AUDIO: - return true; - default: - return false; - } - } else if (profile == PROFILE_A2DP_SINK) { - if (hasService(Service.CAPTURE)) { - return true; - } - // By the A2DP spec, srcs must indicate the CAPTURE service. - // However if some device that do not, we try to - // match on some other class bits. - switch (getDeviceClass()) { - case Device.AUDIO_VIDEO_HIFI_AUDIO: - case Device.AUDIO_VIDEO_SET_TOP_BOX: - case Device.AUDIO_VIDEO_VCR: - return true; - default: - return false; - } - } else if (profile == PROFILE_HEADSET) { - // The render service class is required by the spec for HFP, so is a - // pretty good signal - if (hasService(Service.RENDER)) { - return true; - } - // Just in case they forgot the render service class - switch (getDeviceClass()) { - case Device.AUDIO_VIDEO_HANDSFREE: - case Device.AUDIO_VIDEO_WEARABLE_HEADSET: - case Device.AUDIO_VIDEO_CAR_AUDIO: - return true; - default: - return false; - } - } else if (profile == PROFILE_OPP) { - if (hasService(Service.OBJECT_TRANSFER)) { - return true; - } - - switch (getDeviceClass()) { - case Device.COMPUTER_UNCATEGORIZED: - case Device.COMPUTER_DESKTOP: - case Device.COMPUTER_SERVER: - case Device.COMPUTER_LAPTOP: - case Device.COMPUTER_HANDHELD_PC_PDA: - case Device.COMPUTER_PALM_SIZE_PC_PDA: - case Device.COMPUTER_WEARABLE: - case Device.PHONE_UNCATEGORIZED: - case Device.PHONE_CELLULAR: - case Device.PHONE_CORDLESS: - case Device.PHONE_SMART: - case Device.PHONE_MODEM_OR_GATEWAY: - case Device.PHONE_ISDN: - return true; - default: - return false; - } - } else if (profile == PROFILE_HID) { - return getMajorDeviceClass() == Device.Major.PERIPHERAL; - } else if (profile == PROFILE_PANU || profile == PROFILE_NAP) { - // No good way to distinguish between the two, based on class bits. - if (hasService(Service.NETWORKING)) { - return true; - } - return getMajorDeviceClass() == Device.Major.NETWORKING; - } else { - return false; - } - } -} diff --git a/core/java/android/bluetooth/BluetoothCodecConfig.java b/core/java/android/bluetooth/BluetoothCodecConfig.java deleted file mode 100644 index 9a4151adffc7..000000000000 --- a/core/java/android/bluetooth/BluetoothCodecConfig.java +++ /dev/null @@ -1,807 +0,0 @@ -/* - * Copyright (C) 2016 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.bluetooth; - -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.compat.annotation.UnsupportedAppUsage; -import android.os.Parcel; -import android.os.Parcelable; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.Objects; - -/** - * Represents the codec configuration for a Bluetooth A2DP source device. - * <p>Contains the source codec type, the codec priority, the codec sample - * rate, the codec bits per sample, and the codec channel mode. - * <p>The source codec type values are the same as those supported by the - * device hardware. - * - * {@see BluetoothA2dp} - */ -public final class BluetoothCodecConfig implements Parcelable { - /** @hide */ - @IntDef(prefix = "SOURCE_CODEC_TYPE_", value = { - SOURCE_CODEC_TYPE_SBC, - SOURCE_CODEC_TYPE_AAC, - SOURCE_CODEC_TYPE_APTX, - SOURCE_CODEC_TYPE_APTX_HD, - SOURCE_CODEC_TYPE_LDAC, - SOURCE_CODEC_TYPE_INVALID - }) - @Retention(RetentionPolicy.SOURCE) - public @interface SourceCodecType {} - - /** - * Source codec type SBC. This is the mandatory source codec - * type. - */ - public static final int SOURCE_CODEC_TYPE_SBC = 0; - - /** - * Source codec type AAC. - */ - public static final int SOURCE_CODEC_TYPE_AAC = 1; - - /** - * Source codec type APTX. - */ - public static final int SOURCE_CODEC_TYPE_APTX = 2; - - /** - * Source codec type APTX HD. - */ - public static final int SOURCE_CODEC_TYPE_APTX_HD = 3; - - /** - * Source codec type LDAC. - */ - public static final int SOURCE_CODEC_TYPE_LDAC = 4; - - /** - * Source codec type invalid. This is the default value used for codec - * type. - */ - public static final int SOURCE_CODEC_TYPE_INVALID = 1000 * 1000; - - /** - * Represents the count of valid source codec types. Can be accessed via - * {@link #getMaxCodecType}. - */ - private static final int SOURCE_CODEC_TYPE_MAX = 5; - - /** @hide */ - @IntDef(prefix = "CODEC_PRIORITY_", value = { - CODEC_PRIORITY_DISABLED, - CODEC_PRIORITY_DEFAULT, - CODEC_PRIORITY_HIGHEST - }) - @Retention(RetentionPolicy.SOURCE) - public @interface CodecPriority {} - - /** - * Codec priority disabled. - * Used to indicate that this codec is disabled and should not be used. - */ - public static final int CODEC_PRIORITY_DISABLED = -1; - - /** - * Codec priority default. - * Default value used for codec priority. - */ - public static final int CODEC_PRIORITY_DEFAULT = 0; - - /** - * Codec priority highest. - * Used to indicate the highest priority a codec can have. - */ - public static final int CODEC_PRIORITY_HIGHEST = 1000 * 1000; - - /** @hide */ - @IntDef(prefix = "SAMPLE_RATE_", value = { - SAMPLE_RATE_NONE, - SAMPLE_RATE_44100, - SAMPLE_RATE_48000, - SAMPLE_RATE_88200, - SAMPLE_RATE_96000, - SAMPLE_RATE_176400, - SAMPLE_RATE_192000 - }) - @Retention(RetentionPolicy.SOURCE) - public @interface SampleRate {} - - /** - * Codec sample rate 0 Hz. Default value used for - * codec sample rate. - */ - public static final int SAMPLE_RATE_NONE = 0; - - /** - * Codec sample rate 44100 Hz. - */ - public static final int SAMPLE_RATE_44100 = 0x1 << 0; - - /** - * Codec sample rate 48000 Hz. - */ - public static final int SAMPLE_RATE_48000 = 0x1 << 1; - - /** - * Codec sample rate 88200 Hz. - */ - public static final int SAMPLE_RATE_88200 = 0x1 << 2; - - /** - * Codec sample rate 96000 Hz. - */ - public static final int SAMPLE_RATE_96000 = 0x1 << 3; - - /** - * Codec sample rate 176400 Hz. - */ - public static final int SAMPLE_RATE_176400 = 0x1 << 4; - - /** - * Codec sample rate 192000 Hz. - */ - public static final int SAMPLE_RATE_192000 = 0x1 << 5; - - /** @hide */ - @IntDef(prefix = "BITS_PER_SAMPLE_", value = { - BITS_PER_SAMPLE_NONE, - BITS_PER_SAMPLE_16, - BITS_PER_SAMPLE_24, - BITS_PER_SAMPLE_32 - }) - @Retention(RetentionPolicy.SOURCE) - public @interface BitsPerSample {} - - /** - * Codec bits per sample 0. Default value of the codec - * bits per sample. - */ - public static final int BITS_PER_SAMPLE_NONE = 0; - - /** - * Codec bits per sample 16. - */ - public static final int BITS_PER_SAMPLE_16 = 0x1 << 0; - - /** - * Codec bits per sample 24. - */ - public static final int BITS_PER_SAMPLE_24 = 0x1 << 1; - - /** - * Codec bits per sample 32. - */ - public static final int BITS_PER_SAMPLE_32 = 0x1 << 2; - - /** @hide */ - @IntDef(prefix = "CHANNEL_MODE_", value = { - CHANNEL_MODE_NONE, - CHANNEL_MODE_MONO, - CHANNEL_MODE_STEREO - }) - @Retention(RetentionPolicy.SOURCE) - public @interface ChannelMode {} - - /** - * Codec channel mode NONE. Default value of the - * codec channel mode. - */ - public static final int CHANNEL_MODE_NONE = 0; - - /** - * Codec channel mode MONO. - */ - public static final int CHANNEL_MODE_MONO = 0x1 << 0; - - /** - * Codec channel mode STEREO. - */ - public static final int CHANNEL_MODE_STEREO = 0x1 << 1; - - private final @SourceCodecType int mCodecType; - private @CodecPriority int mCodecPriority; - private final @SampleRate int mSampleRate; - private final @BitsPerSample int mBitsPerSample; - private final @ChannelMode int mChannelMode; - private final long mCodecSpecific1; - private final long mCodecSpecific2; - private final long mCodecSpecific3; - private final long mCodecSpecific4; - - /** - * Creates a new BluetoothCodecConfig. - * - * @param codecType the source codec type - * @param codecPriority the priority of this codec - * @param sampleRate the codec sample rate - * @param bitsPerSample the bits per sample of this codec - * @param channelMode the channel mode of this codec - * @param codecSpecific1 the specific value 1 - * @param codecSpecific2 the specific value 2 - * @param codecSpecific3 the specific value 3 - * @param codecSpecific4 the specific value 4 - * values to 0. - * @hide - */ - @UnsupportedAppUsage - public BluetoothCodecConfig(@SourceCodecType int codecType, @CodecPriority int codecPriority, - @SampleRate int sampleRate, @BitsPerSample int bitsPerSample, - @ChannelMode int channelMode, long codecSpecific1, - long codecSpecific2, long codecSpecific3, - long codecSpecific4) { - mCodecType = codecType; - mCodecPriority = codecPriority; - mSampleRate = sampleRate; - mBitsPerSample = bitsPerSample; - mChannelMode = channelMode; - mCodecSpecific1 = codecSpecific1; - mCodecSpecific2 = codecSpecific2; - mCodecSpecific3 = codecSpecific3; - mCodecSpecific4 = codecSpecific4; - } - - /** - * Creates a new BluetoothCodecConfig. - * <p> By default, the codec priority will be set - * to {@link BluetoothCodecConfig#CODEC_PRIORITY_DEFAULT}, the sample rate to - * {@link BluetoothCodecConfig#SAMPLE_RATE_NONE}, the bits per sample to - * {@link BluetoothCodecConfig#BITS_PER_SAMPLE_NONE}, the channel mode to - * {@link BluetoothCodecConfig#CHANNEL_MODE_NONE}, and all the codec specific - * values to 0. - * - * @param codecType the source codec type - */ - public BluetoothCodecConfig(@SourceCodecType int codecType) { - this(codecType, BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_NONE, - BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, - BluetoothCodecConfig.CHANNEL_MODE_NONE, 0, 0, 0, 0); - } - - private BluetoothCodecConfig(Parcel in) { - mCodecType = in.readInt(); - mCodecPriority = in.readInt(); - mSampleRate = in.readInt(); - mBitsPerSample = in.readInt(); - mChannelMode = in.readInt(); - mCodecSpecific1 = in.readLong(); - mCodecSpecific2 = in.readLong(); - mCodecSpecific3 = in.readLong(); - mCodecSpecific4 = in.readLong(); - } - - @Override - public boolean equals(@Nullable Object o) { - if (o instanceof BluetoothCodecConfig) { - BluetoothCodecConfig other = (BluetoothCodecConfig) o; - return (other.mCodecType == mCodecType - && other.mCodecPriority == mCodecPriority - && other.mSampleRate == mSampleRate - && other.mBitsPerSample == mBitsPerSample - && other.mChannelMode == mChannelMode - && other.mCodecSpecific1 == mCodecSpecific1 - && other.mCodecSpecific2 == mCodecSpecific2 - && other.mCodecSpecific3 == mCodecSpecific3 - && other.mCodecSpecific4 == mCodecSpecific4); - } - return false; - } - - /** - * Returns a hash representation of this BluetoothCodecConfig - * based on all the config values. - */ - @Override - public int hashCode() { - return Objects.hash(mCodecType, mCodecPriority, mSampleRate, - mBitsPerSample, mChannelMode, mCodecSpecific1, - mCodecSpecific2, mCodecSpecific3, mCodecSpecific4); - } - - /** - * Adds capability string to an existing string. - * - * @param prevStr the previous string with the capabilities. Can be a {@code null} pointer - * @param capStr the capability string to append to prevStr argument - * @return the result string in the form "prevStr|capStr" - */ - private static String appendCapabilityToString(@Nullable String prevStr, - @NonNull String capStr) { - if (prevStr == null) { - return capStr; - } - return prevStr + "|" + capStr; - } - - /** - * Returns a {@link String} that describes each BluetoothCodecConfig parameter - * current value. - */ - @Override - public String toString() { - String sampleRateStr = null; - if (mSampleRate == SAMPLE_RATE_NONE) { - sampleRateStr = appendCapabilityToString(sampleRateStr, "NONE"); - } - if ((mSampleRate & SAMPLE_RATE_44100) != 0) { - sampleRateStr = appendCapabilityToString(sampleRateStr, "44100"); - } - if ((mSampleRate & SAMPLE_RATE_48000) != 0) { - sampleRateStr = appendCapabilityToString(sampleRateStr, "48000"); - } - if ((mSampleRate & SAMPLE_RATE_88200) != 0) { - sampleRateStr = appendCapabilityToString(sampleRateStr, "88200"); - } - if ((mSampleRate & SAMPLE_RATE_96000) != 0) { - sampleRateStr = appendCapabilityToString(sampleRateStr, "96000"); - } - if ((mSampleRate & SAMPLE_RATE_176400) != 0) { - sampleRateStr = appendCapabilityToString(sampleRateStr, "176400"); - } - if ((mSampleRate & SAMPLE_RATE_192000) != 0) { - sampleRateStr = appendCapabilityToString(sampleRateStr, "192000"); - } - - String bitsPerSampleStr = null; - if (mBitsPerSample == BITS_PER_SAMPLE_NONE) { - bitsPerSampleStr = appendCapabilityToString(bitsPerSampleStr, "NONE"); - } - if ((mBitsPerSample & BITS_PER_SAMPLE_16) != 0) { - bitsPerSampleStr = appendCapabilityToString(bitsPerSampleStr, "16"); - } - if ((mBitsPerSample & BITS_PER_SAMPLE_24) != 0) { - bitsPerSampleStr = appendCapabilityToString(bitsPerSampleStr, "24"); - } - if ((mBitsPerSample & BITS_PER_SAMPLE_32) != 0) { - bitsPerSampleStr = appendCapabilityToString(bitsPerSampleStr, "32"); - } - - String channelModeStr = null; - if (mChannelMode == CHANNEL_MODE_NONE) { - channelModeStr = appendCapabilityToString(channelModeStr, "NONE"); - } - if ((mChannelMode & CHANNEL_MODE_MONO) != 0) { - channelModeStr = appendCapabilityToString(channelModeStr, "MONO"); - } - if ((mChannelMode & CHANNEL_MODE_STEREO) != 0) { - channelModeStr = appendCapabilityToString(channelModeStr, "STEREO"); - } - - return "{codecName:" + getCodecName() - + ",mCodecType:" + mCodecType - + ",mCodecPriority:" + mCodecPriority - + ",mSampleRate:" + String.format("0x%x", mSampleRate) - + "(" + sampleRateStr + ")" - + ",mBitsPerSample:" + String.format("0x%x", mBitsPerSample) - + "(" + bitsPerSampleStr + ")" - + ",mChannelMode:" + String.format("0x%x", mChannelMode) - + "(" + channelModeStr + ")" - + ",mCodecSpecific1:" + mCodecSpecific1 - + ",mCodecSpecific2:" + mCodecSpecific2 - + ",mCodecSpecific3:" + mCodecSpecific3 - + ",mCodecSpecific4:" + mCodecSpecific4 + "}"; - } - - /** - * @return 0 - * @hide - */ - @Override - public int describeContents() { - return 0; - } - - public static final @android.annotation.NonNull Parcelable.Creator<BluetoothCodecConfig> CREATOR = - new Parcelable.Creator<BluetoothCodecConfig>() { - public BluetoothCodecConfig createFromParcel(Parcel in) { - return new BluetoothCodecConfig(in); - } - - public BluetoothCodecConfig[] newArray(int size) { - return new BluetoothCodecConfig[size]; - } - }; - - /** - * Flattens the object to a parcel - * - * @param out The Parcel in which the object should be written - * @param flags Additional flags about how the object should be written - * - * @hide - */ - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeInt(mCodecType); - out.writeInt(mCodecPriority); - out.writeInt(mSampleRate); - out.writeInt(mBitsPerSample); - out.writeInt(mChannelMode); - out.writeLong(mCodecSpecific1); - out.writeLong(mCodecSpecific2); - out.writeLong(mCodecSpecific3); - out.writeLong(mCodecSpecific4); - } - - /** - * Returns the codec name converted to {@link String}. - * @hide - */ - public @NonNull String getCodecName() { - switch (mCodecType) { - case SOURCE_CODEC_TYPE_SBC: - return "SBC"; - case SOURCE_CODEC_TYPE_AAC: - return "AAC"; - case SOURCE_CODEC_TYPE_APTX: - return "aptX"; - case SOURCE_CODEC_TYPE_APTX_HD: - return "aptX HD"; - case SOURCE_CODEC_TYPE_LDAC: - return "LDAC"; - case SOURCE_CODEC_TYPE_INVALID: - return "INVALID CODEC"; - default: - break; - } - return "UNKNOWN CODEC(" + mCodecType + ")"; - } - - /** - * Returns the source codec type of this config. - */ - public @SourceCodecType int getCodecType() { - return mCodecType; - } - - /** - * Returns the valid codec types count. - */ - public static int getMaxCodecType() { - return SOURCE_CODEC_TYPE_MAX; - } - - /** - * Checks whether the codec is mandatory. - * <p> The actual mandatory codec type for Android Bluetooth audio is SBC. - * See {@link #SOURCE_CODEC_TYPE_SBC}. - * - * @return {@code true} if the codec is mandatory, {@code false} otherwise - * @hide - */ - public boolean isMandatoryCodec() { - return mCodecType == SOURCE_CODEC_TYPE_SBC; - } - - /** - * Returns the codec selection priority. - * <p>The codec selection priority is relative to other codecs: larger value - * means higher priority. - */ - public @CodecPriority int getCodecPriority() { - return mCodecPriority; - } - - /** - * Sets the codec selection priority. - * <p>The codec selection priority is relative to other codecs: larger value - * means higher priority. - * - * @param codecPriority the priority this codec should have - * @hide - */ - public void setCodecPriority(@CodecPriority int codecPriority) { - mCodecPriority = codecPriority; - } - - /** - * Returns the codec sample rate. The value can be a bitmask with all - * supported sample rates. - */ - public @SampleRate int getSampleRate() { - return mSampleRate; - } - - /** - * Returns the codec bits per sample. The value can be a bitmask with all - * bits per sample supported. - */ - public @BitsPerSample int getBitsPerSample() { - return mBitsPerSample; - } - - /** - * Returns the codec channel mode. The value can be a bitmask with all - * supported channel modes. - */ - public @ChannelMode int getChannelMode() { - return mChannelMode; - } - - /** - * Returns the codec specific value1. - */ - public long getCodecSpecific1() { - return mCodecSpecific1; - } - - /** - * Returns the codec specific value2. - */ - public long getCodecSpecific2() { - return mCodecSpecific2; - } - - /** - * Returns the codec specific value3. - */ - public long getCodecSpecific3() { - return mCodecSpecific3; - } - - /** - * Returns the codec specific value4. - */ - public long getCodecSpecific4() { - return mCodecSpecific4; - } - - /** - * Checks whether a value set presented by a bitmask has zero or single bit - * - * @param valueSet the value set presented by a bitmask - * @return {@code true} if the valueSet contains zero or single bit, {@code false} otherwise - * @hide - */ - private static boolean hasSingleBit(int valueSet) { - return (valueSet == 0 || (valueSet & (valueSet - 1)) == 0); - } - - /** - * Returns whether the object contains none or single sample rate. - * @hide - */ - public boolean hasSingleSampleRate() { - return hasSingleBit(mSampleRate); - } - - /** - * Returns whether the object contains none or single bits per sample. - * @hide - */ - public boolean hasSingleBitsPerSample() { - return hasSingleBit(mBitsPerSample); - } - - /** - * Returns whether the object contains none or single channel mode. - * @hide - */ - public boolean hasSingleChannelMode() { - return hasSingleBit(mChannelMode); - } - - /** - * Checks whether the audio feeding parameters are the same. - * - * @param other the codec config to compare against - * @return {@code true} if the audio feeding parameters are same, {@code false} otherwise - * @hide - */ - public boolean sameAudioFeedingParameters(BluetoothCodecConfig other) { - return (other != null && other.mSampleRate == mSampleRate - && other.mBitsPerSample == mBitsPerSample - && other.mChannelMode == mChannelMode); - } - - /** - * Checks whether another codec config has the similar feeding parameters. - * Any parameters with NONE value will be considered to be a wildcard matching. - * - * @param other the codec config to compare against - * @return {@code true} if the audio feeding parameters are similar, {@code false} otherwise - * @hide - */ - public boolean similarCodecFeedingParameters(BluetoothCodecConfig other) { - if (other == null || mCodecType != other.mCodecType) { - return false; - } - int sampleRate = other.mSampleRate; - if (mSampleRate == SAMPLE_RATE_NONE - || sampleRate == SAMPLE_RATE_NONE) { - sampleRate = mSampleRate; - } - int bitsPerSample = other.mBitsPerSample; - if (mBitsPerSample == BITS_PER_SAMPLE_NONE - || bitsPerSample == BITS_PER_SAMPLE_NONE) { - bitsPerSample = mBitsPerSample; - } - int channelMode = other.mChannelMode; - if (mChannelMode == CHANNEL_MODE_NONE - || channelMode == CHANNEL_MODE_NONE) { - channelMode = mChannelMode; - } - return sameAudioFeedingParameters(new BluetoothCodecConfig( - mCodecType, /* priority */ 0, sampleRate, bitsPerSample, channelMode, - /* specific1 */ 0, /* specific2 */ 0, /* specific3 */ 0, - /* specific4 */ 0)); - } - - /** - * Checks whether the codec specific parameters are the same. - * <p> Currently, only AAC VBR and LDAC Playback Quality on CodecSpecific1 - * are compared. - * - * @param other the codec config to compare against - * @return {@code true} if the codec specific parameters are the same, {@code false} otherwise - * @hide - */ - public boolean sameCodecSpecificParameters(BluetoothCodecConfig other) { - if (other == null && mCodecType != other.mCodecType) { - return false; - } - switch (mCodecType) { - case SOURCE_CODEC_TYPE_AAC: - case SOURCE_CODEC_TYPE_LDAC: - if (mCodecSpecific1 != other.mCodecSpecific1) { - return false; - } - default: - return true; - } - } - - /** - * Builder for {@link BluetoothCodecConfig}. - * <p> By default, the codec type will be set to - * {@link BluetoothCodecConfig#SOURCE_CODEC_TYPE_INVALID}, the codec priority - * to {@link BluetoothCodecConfig#CODEC_PRIORITY_DEFAULT}, the sample rate to - * {@link BluetoothCodecConfig#SAMPLE_RATE_NONE}, the bits per sample to - * {@link BluetoothCodecConfig#BITS_PER_SAMPLE_NONE}, the channel mode to - * {@link BluetoothCodecConfig#CHANNEL_MODE_NONE}, and all the codec specific - * values to 0. - */ - public static final class Builder { - private int mCodecType = BluetoothCodecConfig.SOURCE_CODEC_TYPE_INVALID; - private int mCodecPriority = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT; - private int mSampleRate = BluetoothCodecConfig.SAMPLE_RATE_NONE; - private int mBitsPerSample = BluetoothCodecConfig.BITS_PER_SAMPLE_NONE; - private int mChannelMode = BluetoothCodecConfig.CHANNEL_MODE_NONE; - private long mCodecSpecific1 = 0; - private long mCodecSpecific2 = 0; - private long mCodecSpecific3 = 0; - private long mCodecSpecific4 = 0; - - /** - * Set codec type for Bluetooth codec config. - * - * @param codecType of this codec - * @return the same Builder instance - */ - public @NonNull Builder setCodecType(@SourceCodecType int codecType) { - mCodecType = codecType; - return this; - } - - /** - * Set codec priority for Bluetooth codec config. - * - * @param codecPriority of this codec - * @return the same Builder instance - */ - public @NonNull Builder setCodecPriority(@CodecPriority int codecPriority) { - mCodecPriority = codecPriority; - return this; - } - - /** - * Set sample rate for Bluetooth codec config. - * - * @param sampleRate of this codec - * @return the same Builder instance - */ - public @NonNull Builder setSampleRate(@SampleRate int sampleRate) { - mSampleRate = sampleRate; - return this; - } - - /** - * Set the bits per sample for Bluetooth codec config. - * - * @param bitsPerSample of this codec - * @return the same Builder instance - */ - public @NonNull Builder setBitsPerSample(@BitsPerSample int bitsPerSample) { - mBitsPerSample = bitsPerSample; - return this; - } - - /** - * Set the channel mode for Bluetooth codec config. - * - * @param channelMode of this codec - * @return the same Builder instance - */ - public @NonNull Builder setChannelMode(@ChannelMode int channelMode) { - mChannelMode = channelMode; - return this; - } - - /** - * Set the first codec specific values for Bluetooth codec config. - * - * @param codecSpecific1 codec specific value or 0 if default - * @return the same Builder instance - */ - public @NonNull Builder setCodecSpecific1(long codecSpecific1) { - mCodecSpecific1 = codecSpecific1; - return this; - } - - /** - * Set the second codec specific values for Bluetooth codec config. - * - * @param codecSpecific2 codec specific value or 0 if default - * @return the same Builder instance - */ - public @NonNull Builder setCodecSpecific2(long codecSpecific2) { - mCodecSpecific2 = codecSpecific2; - return this; - } - - /** - * Set the third codec specific values for Bluetooth codec config. - * - * @param codecSpecific3 codec specific value or 0 if default - * @return the same Builder instance - */ - public @NonNull Builder setCodecSpecific3(long codecSpecific3) { - mCodecSpecific3 = codecSpecific3; - return this; - } - - /** - * Set the fourth codec specific values for Bluetooth codec config. - * - * @param codecSpecific4 codec specific value or 0 if default - * @return the same Builder instance - */ - public @NonNull Builder setCodecSpecific4(long codecSpecific4) { - mCodecSpecific4 = codecSpecific4; - return this; - } - - /** - * Build {@link BluetoothCodecConfig}. - * @return new BluetoothCodecConfig built - */ - public @NonNull BluetoothCodecConfig build() { - return new BluetoothCodecConfig(mCodecType, mCodecPriority, - mSampleRate, mBitsPerSample, - mChannelMode, mCodecSpecific1, - mCodecSpecific2, mCodecSpecific3, - mCodecSpecific4); - } - } -} diff --git a/core/java/android/bluetooth/BluetoothCodecStatus.java b/core/java/android/bluetooth/BluetoothCodecStatus.java deleted file mode 100644 index 02606feb3b3f..000000000000 --- a/core/java/android/bluetooth/BluetoothCodecStatus.java +++ /dev/null @@ -1,208 +0,0 @@ -/* - * 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 android.bluetooth; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.os.Parcel; -import android.os.Parcelable; - -import java.util.Collections; -import java.util.List; -import java.util.Objects; - -/** - * Represents the codec status (configuration and capability) for a Bluetooth - * A2DP source device. - * - * {@see BluetoothA2dp} - */ -public final class BluetoothCodecStatus implements Parcelable { - /** - * Extra for the codec configuration intents of the individual profiles. - * - * This extra represents the current codec status of the A2DP - * profile. - */ - public static final String EXTRA_CODEC_STATUS = - "android.bluetooth.extra.CODEC_STATUS"; - - private final @Nullable BluetoothCodecConfig mCodecConfig; - private final @Nullable List<BluetoothCodecConfig> mCodecsLocalCapabilities; - private final @Nullable List<BluetoothCodecConfig> mCodecsSelectableCapabilities; - - public BluetoothCodecStatus(@Nullable BluetoothCodecConfig codecConfig, - @Nullable List<BluetoothCodecConfig> codecsLocalCapabilities, - @Nullable List<BluetoothCodecConfig> codecsSelectableCapabilities) { - mCodecConfig = codecConfig; - mCodecsLocalCapabilities = codecsLocalCapabilities; - mCodecsSelectableCapabilities = codecsSelectableCapabilities; - } - - private BluetoothCodecStatus(Parcel in) { - mCodecConfig = in.readTypedObject(BluetoothCodecConfig.CREATOR); - mCodecsLocalCapabilities = in.createTypedArrayList(BluetoothCodecConfig.CREATOR); - mCodecsSelectableCapabilities = in.createTypedArrayList(BluetoothCodecConfig.CREATOR); - } - - @Override - public boolean equals(@Nullable Object o) { - if (o instanceof BluetoothCodecStatus) { - BluetoothCodecStatus other = (BluetoothCodecStatus) o; - return (Objects.equals(other.mCodecConfig, mCodecConfig) - && sameCapabilities(other.mCodecsLocalCapabilities, mCodecsLocalCapabilities) - && sameCapabilities(other.mCodecsSelectableCapabilities, - mCodecsSelectableCapabilities)); - } - return false; - } - - /** - * Checks whether two lists of capabilities contain same capabilities. - * The order of the capabilities in each list is ignored. - * - * @param c1 the first list of capabilities to compare - * @param c2 the second list of capabilities to compare - * @return {@code true} if both lists contain same capabilities - */ - private static boolean sameCapabilities(@Nullable List<BluetoothCodecConfig> c1, - @Nullable List<BluetoothCodecConfig> c2) { - if (c1 == null) { - return (c2 == null); - } - if (c2 == null) { - return false; - } - if (c1.size() != c2.size()) { - return false; - } - return c1.containsAll(c2); - } - - /** - * Checks whether the codec config matches the selectable capabilities. - * Any parameters of the codec config with NONE value will be considered a wildcard matching. - * - * @param codecConfig the codec config to compare against - * @return {@code true} if the codec config matches, {@code false} otherwise - */ - public boolean isCodecConfigSelectable(@Nullable BluetoothCodecConfig codecConfig) { - if (codecConfig == null || !codecConfig.hasSingleSampleRate() - || !codecConfig.hasSingleBitsPerSample() || !codecConfig.hasSingleChannelMode()) { - return false; - } - for (BluetoothCodecConfig selectableConfig : mCodecsSelectableCapabilities) { - if (codecConfig.getCodecType() != selectableConfig.getCodecType()) { - continue; - } - int sampleRate = codecConfig.getSampleRate(); - if ((sampleRate & selectableConfig.getSampleRate()) == 0 - && sampleRate != BluetoothCodecConfig.SAMPLE_RATE_NONE) { - continue; - } - int bitsPerSample = codecConfig.getBitsPerSample(); - if ((bitsPerSample & selectableConfig.getBitsPerSample()) == 0 - && bitsPerSample != BluetoothCodecConfig.BITS_PER_SAMPLE_NONE) { - continue; - } - int channelMode = codecConfig.getChannelMode(); - if ((channelMode & selectableConfig.getChannelMode()) == 0 - && channelMode != BluetoothCodecConfig.CHANNEL_MODE_NONE) { - continue; - } - return true; - } - return false; - } - - /** - * Returns a hash based on the codec config and local capabilities. - */ - @Override - public int hashCode() { - return Objects.hash(mCodecConfig, mCodecsLocalCapabilities, - mCodecsLocalCapabilities); - } - - /** - * Returns a {@link String} that describes each BluetoothCodecStatus parameter - * current value. - */ - @Override - public String toString() { - return "{mCodecConfig:" + mCodecConfig - + ",mCodecsLocalCapabilities:" + mCodecsLocalCapabilities - + ",mCodecsSelectableCapabilities:" + mCodecsSelectableCapabilities - + "}"; - } - - /** - * @return 0 - * @hide - */ - @Override - public int describeContents() { - return 0; - } - - public static final @android.annotation.NonNull Parcelable.Creator<BluetoothCodecStatus> CREATOR = - new Parcelable.Creator<BluetoothCodecStatus>() { - public BluetoothCodecStatus createFromParcel(Parcel in) { - return new BluetoothCodecStatus(in); - } - - public BluetoothCodecStatus[] newArray(int size) { - return new BluetoothCodecStatus[size]; - } - }; - - /** - * Flattens the object to a parcel. - * - * @param out The Parcel in which the object should be written - * @param flags Additional flags about how the object should be written - */ - @Override - public void writeToParcel(@NonNull Parcel out, int flags) { - out.writeTypedObject(mCodecConfig, 0); - out.writeTypedList(mCodecsLocalCapabilities); - out.writeTypedList(mCodecsSelectableCapabilities); - } - - /** - * Returns the current codec configuration. - */ - public @Nullable BluetoothCodecConfig getCodecConfig() { - return mCodecConfig; - } - - /** - * Returns the codecs local capabilities. - */ - public @NonNull List<BluetoothCodecConfig> getCodecsLocalCapabilities() { - return (mCodecsLocalCapabilities == null) - ? Collections.emptyList() : mCodecsLocalCapabilities; - } - - /** - * Returns the codecs selectable capabilities. - */ - public @NonNull List<BluetoothCodecConfig> getCodecsSelectableCapabilities() { - return (mCodecsSelectableCapabilities == null) - ? Collections.emptyList() : mCodecsSelectableCapabilities; - } -} diff --git a/core/java/android/bluetooth/BluetoothCsipSetCoordinator.java b/core/java/android/bluetooth/BluetoothCsipSetCoordinator.java deleted file mode 100644 index ba57ec472a6e..000000000000 --- a/core/java/android/bluetooth/BluetoothCsipSetCoordinator.java +++ /dev/null @@ -1,555 +0,0 @@ -/* - * Copyright 2021 HIMSA II K/S - www.himsa.com. - * Represented by EHIMA - www.ehima.com - * - * 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.bluetooth; - -import static android.bluetooth.BluetoothUtils.getSyncTimeout; - -import android.Manifest; -import android.annotation.CallbackExecutor; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.RequiresPermission; -import android.annotation.SdkConstant; -import android.annotation.SdkConstant.SdkConstantType; -import android.annotation.SystemApi; -import android.content.AttributionSource; -import android.content.Context; -import android.os.IBinder; -import android.os.ParcelUuid; -import android.os.RemoteException; -import android.util.CloseGuard; -import android.util.Log; - -import com.android.modules.utils.SynchronousResultReceiver; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.Executor; -import java.util.concurrent.TimeoutException; - -/** - * This class provides the public APIs to control the Bluetooth CSIP set coordinator. - * - * <p>BluetoothCsipSetCoordinator is a proxy object for controlling the Bluetooth VC - * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get - * the BluetoothCsipSetCoordinator proxy object. - * - */ -public final class BluetoothCsipSetCoordinator implements BluetoothProfile, AutoCloseable { - private static final String TAG = "BluetoothCsipSetCoordinator"; - private static final boolean DBG = false; - private static final boolean VDBG = false; - - private CloseGuard mCloseGuard; - - /** - * @hide - */ - @SystemApi - public interface ClientLockCallback { - /** - * @hide - */ - @SystemApi void onGroupLockSet(int groupId, int opStatus, boolean isLocked); - } - - private static class BluetoothCsipSetCoordinatorLockCallbackDelegate - extends IBluetoothCsipSetCoordinatorLockCallback.Stub { - private final ClientLockCallback mCallback; - private final Executor mExecutor; - - BluetoothCsipSetCoordinatorLockCallbackDelegate( - Executor executor, ClientLockCallback callback) { - mExecutor = executor; - mCallback = callback; - } - - @Override - public void onGroupLockSet(int groupId, int opStatus, boolean isLocked) { - mExecutor.execute(() -> mCallback.onGroupLockSet(groupId, opStatus, isLocked)); - } - }; - - /** - * Intent used to broadcast the change in connection state of the CSIS - * Client. - * - * <p>This intent will have 3 extras: - * <ul> - * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> - * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> - * </ul> - * - * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of - * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, - * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. - */ - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_CSIS_CONNECTION_STATE_CHANGED = - "android.bluetooth.action.CSIS_CONNECTION_STATE_CHANGED"; - - /** - * Intent used to expose broadcast receiving device. - * - * <p>This intent will have 2 extras: - * <ul> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote Broadcast receiver device. </li> - * <li> {@link #EXTRA_CSIS_GROUP_ID} - Group identifier. </li> - * <li> {@link #EXTRA_CSIS_GROUP_SIZE} - Group size. </li> - * <li> {@link #EXTRA_CSIS_GROUP_TYPE_UUID} - Group type UUID. </li> - * </ul> - * - * @hide - */ - @SystemApi - @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_CSIS_DEVICE_AVAILABLE = - "android.bluetooth.action.CSIS_DEVICE_AVAILABLE"; - - /** - * Used as an extra field in {@link #ACTION_CSIS_DEVICE_AVAILABLE} intent. - * Contains the group id. - * - * @hide - */ - public static final String EXTRA_CSIS_GROUP_ID = "android.bluetooth.extra.CSIS_GROUP_ID"; - - /** - * Group size as int extra field in {@link #ACTION_CSIS_DEVICE_AVAILABLE} intent. - * - * @hide - */ - public static final String EXTRA_CSIS_GROUP_SIZE = "android.bluetooth.extra.CSIS_GROUP_SIZE"; - - /** - * Group type uuid extra field in {@link #ACTION_CSIS_DEVICE_AVAILABLE} intent. - * - * @hide - */ - public static final String EXTRA_CSIS_GROUP_TYPE_UUID = - "android.bluetooth.extra.CSIS_GROUP_TYPE_UUID"; - - /** - * Intent used to broadcast information about identified set member - * ready to connect. - * - * <p>This intent will have one extra: - * <ul> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. It can - * be null if no device is active. </li> - * <li> {@link #EXTRA_CSIS_GROUP_ID} - Group identifier. </li> - * </ul> - * - * @hide - */ - @SystemApi - @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_CSIS_SET_MEMBER_AVAILABLE = - "android.bluetooth.action.CSIS_SET_MEMBER_AVAILABLE"; - - /** - * This represents an invalid group ID. - * - * @hide - */ - public static final int GROUP_ID_INVALID = IBluetoothCsipSetCoordinator.CSIS_GROUP_ID_INVALID; - - /** - * Indicating that group was locked with success. - * - * @hide - */ - public static final int GROUP_LOCK_SUCCESS = 0; - - /** - * Indicating that group locked failed due to invalid group ID. - * - * @hide - */ - public static final int GROUP_LOCK_FAILED_INVALID_GROUP = 1; - - /** - * Indicating that group locked failed due to empty group. - * - * @hide - */ - public static final int GROUP_LOCK_FAILED_GROUP_EMPTY = 2; - - /** - * Indicating that group locked failed due to group members being disconnected. - * - * @hide - */ - public static final int GROUP_LOCK_FAILED_GROUP_NOT_CONNECTED = 3; - - /** - * Indicating that group locked failed due to group member being already locked. - * - * @hide - */ - public static final int GROUP_LOCK_FAILED_LOCKED_BY_OTHER = 4; - - /** - * Indicating that group locked failed due to other reason. - * - * @hide - */ - public static final int GROUP_LOCK_FAILED_OTHER_REASON = 5; - - /** - * Indicating that group member in locked state was lost. - * - * @hide - */ - public static final int LOCKED_GROUP_MEMBER_LOST = 6; - - private final BluetoothAdapter mAdapter; - private final AttributionSource mAttributionSource; - private final BluetoothProfileConnector<IBluetoothCsipSetCoordinator> mProfileConnector = - new BluetoothProfileConnector(this, BluetoothProfile.CSIP_SET_COORDINATOR, TAG, - IBluetoothCsipSetCoordinator.class.getName()) { - @Override - public IBluetoothCsipSetCoordinator getServiceInterface(IBinder service) { - return IBluetoothCsipSetCoordinator.Stub.asInterface(service); - } - }; - - /** - * Create a BluetoothCsipSetCoordinator proxy object for interacting with the local - * Bluetooth CSIS service. - */ - /*package*/ BluetoothCsipSetCoordinator(Context context, ServiceListener listener, BluetoothAdapter adapter) { - mAdapter = adapter; - mAttributionSource = adapter.getAttributionSource(); - mProfileConnector.connect(context, listener); - mCloseGuard = new CloseGuard(); - mCloseGuard.open("close"); - } - - /** - * @hide - */ - protected void finalize() { - if (mCloseGuard != null) { - mCloseGuard.warnIfOpen(); - } - close(); - } - - /** - * @hide - */ - public void close() { - mProfileConnector.disconnect(); - } - - private IBluetoothCsipSetCoordinator getService() { - return mProfileConnector.getService(); - } - - /** - * Lock the set. - * @param groupId group ID to lock, - * @param executor callback executor, - * @param cb callback to report lock and unlock events - stays valid until the app unlocks - * using the returned lock identifier or the lock timeouts on the remote side, - * as per CSIS specification, - * @return unique lock identifier used for unlocking or null if lock has failed. - * - * @hide - */ - @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) - public - @Nullable UUID groupLock(int groupId, @Nullable @CallbackExecutor Executor executor, - @Nullable ClientLockCallback cb) { - if (VDBG) log("groupLockSet()"); - final IBluetoothCsipSetCoordinator service = getService(); - final UUID defaultValue = null; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - IBluetoothCsipSetCoordinatorLockCallback delegate = null; - if ((executor != null) && (cb != null)) { - delegate = new BluetoothCsipSetCoordinatorLockCallbackDelegate(executor, cb); - } - try { - final SynchronousResultReceiver<ParcelUuid> recv = new SynchronousResultReceiver(); - service.groupLock(groupId, delegate, mAttributionSource, recv); - final ParcelUuid ret = recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null); - return ret == null ? defaultValue : ret.getUuid(); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Unlock the set. - * @param lockUuid unique lock identifier - * @return true if unlocked, false on error - * - * @hide - */ - @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) - public boolean groupUnlock(@NonNull UUID lockUuid) { - if (VDBG) log("groupLockSet()"); - if (lockUuid == null) { - return false; - } - final IBluetoothCsipSetCoordinator service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver recv = new SynchronousResultReceiver(); - service.groupUnlock(new ParcelUuid(lockUuid), mAttributionSource, recv); - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null); - return true; - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get device's groups. - * @param device the active device - * @return Map of groups ids and related UUIDs - * - * @hide - */ - @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) - public @NonNull Map getGroupUuidMapByDevice(@Nullable BluetoothDevice device) { - if (VDBG) log("getGroupUuidMapByDevice()"); - final IBluetoothCsipSetCoordinator service = getService(); - final Map defaultValue = new HashMap<>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Map> recv = new SynchronousResultReceiver(); - service.getGroupUuidMapByDevice(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get group id for the given UUID - * @param uuid - * @return list of group IDs - * - * @hide - */ - @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) - public @NonNull List<Integer> getAllGroupIds(@Nullable ParcelUuid uuid) { - if (VDBG) log("getAllGroupIds()"); - final IBluetoothCsipSetCoordinator service = getService(); - final List<Integer> defaultValue = new ArrayList<>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<Integer>> recv = - new SynchronousResultReceiver(); - service.getAllGroupIds(uuid, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - */ - @Override - public @NonNull List<BluetoothDevice> getConnectedDevices() { - if (VDBG) log("getConnectedDevices()"); - final IBluetoothCsipSetCoordinator service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getConnectedDevices(mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - */ - @Override - public - @NonNull List<BluetoothDevice> getDevicesMatchingConnectionStates(@NonNull int[] states) { - if (VDBG) log("getDevicesMatchingStates(states=" + Arrays.toString(states) + ")"); - final IBluetoothCsipSetCoordinator service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - */ - @Override - public - @BluetoothProfile.BtProfileState int getConnectionState(@Nullable BluetoothDevice device) { - if (VDBG) log("getState(" + device + ")"); - final IBluetoothCsipSetCoordinator service = getService(); - final int defaultValue = BluetoothProfile.STATE_DISCONNECTED; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionState(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Set connection policy of the profile - * - * <p> The device should already be paired. - * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED}, - * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Paired bluetooth device - * @param connectionPolicy is the connection policy to set to for this profile - * @return true if connectionPolicy is set, false on error - * - * @hide - */ - @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) - public boolean setConnectionPolicy( - @Nullable BluetoothDevice device, @ConnectionPolicy int connectionPolicy) { - if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); - final IBluetoothCsipSetCoordinator service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device) - && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN - || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the connection policy of the profile. - * - * <p> The connection policy can be any of: - * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, - * {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Bluetooth device - * @return connection policy of the device - * - * @hide - */ - @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) - public @ConnectionPolicy int getConnectionPolicy(@Nullable BluetoothDevice device) { - if (VDBG) log("getConnectionPolicy(" + device + ")"); - final IBluetoothCsipSetCoordinator service = getService(); - final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionPolicy(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - private boolean isEnabled() { - return mAdapter.getState() == BluetoothAdapter.STATE_ON; - } - - private static boolean isValidDevice(@Nullable BluetoothDevice device) { - return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); - } - - private static void log(String msg) { - Log.d(TAG, msg); - } -} diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java deleted file mode 100644 index 984166d98619..000000000000 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ /dev/null @@ -1,2831 +0,0 @@ -/* - * Copyright (C) 2009 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.bluetooth; - -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.RequiresPermission; -import android.annotation.SdkConstant; -import android.annotation.SdkConstant.SdkConstantType; -import android.annotation.SuppressLint; -import android.annotation.SystemApi; //import android.app.PropertyInvalidatedCache; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.bluetooth.annotations.RequiresBluetoothLocationPermission; -import android.bluetooth.annotations.RequiresBluetoothScanPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; -import android.companion.AssociationRequest; -import android.compat.annotation.UnsupportedAppUsage; -import android.content.AttributionSource; -import android.content.Context; -import android.os.Build; -import android.os.Handler; -import android.os.Parcel; -import android.os.ParcelUuid; -import android.os.Parcelable; -import android.os.Process; -import android.os.RemoteException; -import android.util.Log; - -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.UUID; - -/** - * Represents a remote Bluetooth device. A {@link BluetoothDevice} lets you - * create a connection with the respective device or query information about - * it, such as the name, address, class, and bonding state. - * - * <p>This class is really just a thin wrapper for a Bluetooth hardware - * address. Objects of this class are immutable. Operations on this class - * are performed on the remote Bluetooth hardware address, using the - * {@link BluetoothAdapter} that was used to create this {@link - * BluetoothDevice}. - * - * <p>To get a {@link BluetoothDevice}, use - * {@link BluetoothAdapter#getRemoteDevice(String) - * BluetoothAdapter.getRemoteDevice(String)} to create one representing a device - * of a known MAC address (which you can get through device discovery with - * {@link BluetoothAdapter}) or get one from the set of bonded devices - * returned by {@link BluetoothAdapter#getBondedDevices() - * BluetoothAdapter.getBondedDevices()}. You can then open a - * {@link BluetoothSocket} for communication with the remote device, using - * {@link #createRfcommSocketToServiceRecord(UUID)} over Bluetooth BR/EDR or using - * {@link #createL2capChannel(int)} over Bluetooth LE. - * - * <div class="special reference"> - * <h3>Developer Guides</h3> - * <p> - * For more information about using Bluetooth, read the <a href= - * "{@docRoot}guide/topics/connectivity/bluetooth.html">Bluetooth</a> developer - * guide. - * </p> - * </div> - * - * {@see BluetoothAdapter} - * {@see BluetoothSocket} - */ -public final class BluetoothDevice implements Parcelable, Attributable { - private static final String TAG = "BluetoothDevice"; - private static final boolean DBG = false; - - /** - * Connection state bitmask as returned by getConnectionState. - */ - private static final int CONNECTION_STATE_DISCONNECTED = 0; - private static final int CONNECTION_STATE_CONNECTED = 1; - private static final int CONNECTION_STATE_ENCRYPTED_BREDR = 2; - private static final int CONNECTION_STATE_ENCRYPTED_LE = 4; - - /** - * Sentinel error value for this class. Guaranteed to not equal any other - * integer constant in this class. Provided as a convenience for functions - * that require a sentinel error value, for example: - * <p><code>Intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, - * BluetoothDevice.ERROR)</code> - */ - public static final int ERROR = Integer.MIN_VALUE; - - /** - * Broadcast Action: Remote device discovered. - * <p>Sent when a remote device is found during discovery. - * <p>Always contains the extra fields {@link #EXTRA_DEVICE} and {@link - * #EXTRA_CLASS}. Can contain the extra fields {@link #EXTRA_NAME} and/or - * {@link #EXTRA_RSSI} and/or {@link #EXTRA_IS_COORDINATED_SET_MEMBER} if they are available. - */ - // TODO: Change API to not broadcast RSSI if not available (incoming connection) - @RequiresLegacyBluetoothPermission - @RequiresBluetoothScanPermission - @RequiresBluetoothLocationPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_FOUND = - "android.bluetooth.device.action.FOUND"; - - /** - * Broadcast Action: Bluetooth class of a remote device has changed. - * <p>Always contains the extra fields {@link #EXTRA_DEVICE} and {@link - * #EXTRA_CLASS}. - * {@see BluetoothClass} - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_CLASS_CHANGED = - "android.bluetooth.device.action.CLASS_CHANGED"; - - /** - * Broadcast Action: Indicates a low level (ACL) connection has been - * established with a remote device. - * <p>Always contains the extra field {@link #EXTRA_DEVICE}. - * <p>ACL connections are managed automatically by the Android Bluetooth - * stack. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_ACL_CONNECTED = - "android.bluetooth.device.action.ACL_CONNECTED"; - - /** - * Broadcast Action: Indicates that a low level (ACL) disconnection has - * been requested for a remote device, and it will soon be disconnected. - * <p>This is useful for graceful disconnection. Applications should use - * this intent as a hint to immediately terminate higher level connections - * (RFCOMM, L2CAP, or profile connections) to the remote device. - * <p>Always contains the extra field {@link #EXTRA_DEVICE}. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_ACL_DISCONNECT_REQUESTED = - "android.bluetooth.device.action.ACL_DISCONNECT_REQUESTED"; - - /** - * Broadcast Action: Indicates a low level (ACL) disconnection from a - * remote device. - * <p>Always contains the extra field {@link #EXTRA_DEVICE}. - * <p>ACL connections are managed automatically by the Android Bluetooth - * stack. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_ACL_DISCONNECTED = - "android.bluetooth.device.action.ACL_DISCONNECTED"; - - /** - * Broadcast Action: Indicates the friendly name of a remote device has - * been retrieved for the first time, or changed since the last retrieval. - * <p>Always contains the extra fields {@link #EXTRA_DEVICE} and {@link - * #EXTRA_NAME}. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_NAME_CHANGED = - "android.bluetooth.device.action.NAME_CHANGED"; - - /** - * Broadcast Action: Indicates the alias of a remote device has been - * changed. - * <p>Always contains the extra field {@link #EXTRA_DEVICE}. - */ - @SuppressLint("ActionValue") - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_ALIAS_CHANGED = - "android.bluetooth.device.action.ALIAS_CHANGED"; - - /** - * Broadcast Action: Indicates a change in the bond state of a remote - * device. For example, if a device is bonded (paired). - * <p>Always contains the extra fields {@link #EXTRA_DEVICE}, {@link - * #EXTRA_BOND_STATE} and {@link #EXTRA_PREVIOUS_BOND_STATE}. - */ - // Note: When EXTRA_BOND_STATE is BOND_NONE then this will also - // contain a hidden extra field EXTRA_REASON with the result code. - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_BOND_STATE_CHANGED = - "android.bluetooth.device.action.BOND_STATE_CHANGED"; - - /** - * Broadcast Action: Indicates the battery level of a remote device has - * been retrieved for the first time, or changed since the last retrieval - * <p>Always contains the extra fields {@link #EXTRA_DEVICE} and {@link - * #EXTRA_BATTERY_LEVEL}. - * - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_BATTERY_LEVEL_CHANGED = - "android.bluetooth.device.action.BATTERY_LEVEL_CHANGED"; - - /** - * Used as an Integer extra field in {@link #ACTION_BATTERY_LEVEL_CHANGED} - * intent. It contains the most recently retrieved battery level information - * ranging from 0% to 100% for a remote device, {@link #BATTERY_LEVEL_UNKNOWN} - * when the valid is unknown or there is an error - * - * @hide - */ - public static final String EXTRA_BATTERY_LEVEL = - "android.bluetooth.device.extra.BATTERY_LEVEL"; - - /** - * Used as the unknown value for {@link #EXTRA_BATTERY_LEVEL} and {@link #getBatteryLevel()} - * - * @hide - */ - public static final int BATTERY_LEVEL_UNKNOWN = -1; - - /** - * Used as an error value for {@link #getBatteryLevel()} to represent bluetooth is off - * - * @hide - */ - public static final int BATTERY_LEVEL_BLUETOOTH_OFF = -100; - - /** - * Used as a Parcelable {@link BluetoothDevice} extra field in every intent - * broadcast by this class. It contains the {@link BluetoothDevice} that - * the intent applies to. - */ - public static final String EXTRA_DEVICE = "android.bluetooth.device.extra.DEVICE"; - - /** - * Used as a String extra field in {@link #ACTION_NAME_CHANGED} and {@link - * #ACTION_FOUND} intents. It contains the friendly Bluetooth name. - */ - public static final String EXTRA_NAME = "android.bluetooth.device.extra.NAME"; - - /** - * Used as an optional short extra field in {@link #ACTION_FOUND} intents. - * Contains the RSSI value of the remote device as reported by the - * Bluetooth hardware. - */ - public static final String EXTRA_RSSI = "android.bluetooth.device.extra.RSSI"; - - /** - * Used as an bool extra field in {@link #ACTION_FOUND} intents. - * It contains the information if device is discovered as member of a coordinated set or not. - * Pairing with device that belongs to a set would trigger pairing with the rest of set members. - * See Bluetooth CSIP specification for more details. - */ - public static final String EXTRA_IS_COORDINATED_SET_MEMBER = - "android.bluetooth.extra.IS_COORDINATED_SET_MEMBER"; - - /** - * Used as a Parcelable {@link BluetoothClass} extra field in {@link - * #ACTION_FOUND} and {@link #ACTION_CLASS_CHANGED} intents. - */ - public static final String EXTRA_CLASS = "android.bluetooth.device.extra.CLASS"; - - /** - * Used as an int extra field in {@link #ACTION_BOND_STATE_CHANGED} intents. - * Contains the bond state of the remote device. - * <p>Possible values are: - * {@link #BOND_NONE}, - * {@link #BOND_BONDING}, - * {@link #BOND_BONDED}. - */ - public static final String EXTRA_BOND_STATE = "android.bluetooth.device.extra.BOND_STATE"; - /** - * Used as an int extra field in {@link #ACTION_BOND_STATE_CHANGED} intents. - * Contains the previous bond state of the remote device. - * <p>Possible values are: - * {@link #BOND_NONE}, - * {@link #BOND_BONDING}, - * {@link #BOND_BONDED}. - */ - public static final String EXTRA_PREVIOUS_BOND_STATE = - "android.bluetooth.device.extra.PREVIOUS_BOND_STATE"; - /** - * Indicates the remote device is not bonded (paired). - * <p>There is no shared link key with the remote device, so communication - * (if it is allowed at all) will be unauthenticated and unencrypted. - */ - public static final int BOND_NONE = 10; - /** - * Indicates bonding (pairing) is in progress with the remote device. - */ - public static final int BOND_BONDING = 11; - /** - * Indicates the remote device is bonded (paired). - * <p>A shared link keys exists locally for the remote device, so - * communication can be authenticated and encrypted. - * <p><i>Being bonded (paired) with a remote device does not necessarily - * mean the device is currently connected. It just means that the pending - * procedure was completed at some earlier time, and the link key is still - * stored locally, ready to use on the next connection. - * </i> - */ - public static final int BOND_BONDED = 12; - - /** - * Used as an int extra field in {@link #ACTION_PAIRING_REQUEST} - * intents for unbond reason. - * - * @hide - */ - @UnsupportedAppUsage - public static final String EXTRA_REASON = "android.bluetooth.device.extra.REASON"; - - /** - * Used as an int extra field in {@link #ACTION_PAIRING_REQUEST} - * intents to indicate pairing method used. Possible values are: - * {@link #PAIRING_VARIANT_PIN}, - * {@link #PAIRING_VARIANT_PASSKEY_CONFIRMATION}, - */ - public static final String EXTRA_PAIRING_VARIANT = - "android.bluetooth.device.extra.PAIRING_VARIANT"; - - /** - * Used as an int extra field in {@link #ACTION_PAIRING_REQUEST} - * intents as the value of passkey. - */ - public static final String EXTRA_PAIRING_KEY = "android.bluetooth.device.extra.PAIRING_KEY"; - - /** - * Used as an int extra field in {@link #ACTION_PAIRING_REQUEST} - * intents as the value of passkey. - * @hide - */ - public static final String EXTRA_PAIRING_INITIATOR = - "android.bluetooth.device.extra.PAIRING_INITIATOR"; - - /** - * Bluetooth pairing initiator, Foreground App - * @hide - */ - public static final int EXTRA_PAIRING_INITIATOR_FOREGROUND = 1; - - /** - * Bluetooth pairing initiator, Background - * @hide - */ - public static final int EXTRA_PAIRING_INITIATOR_BACKGROUND = 2; - - /** - * Bluetooth device type, Unknown - */ - public static final int DEVICE_TYPE_UNKNOWN = 0; - - /** - * Bluetooth device type, Classic - BR/EDR devices - */ - public static final int DEVICE_TYPE_CLASSIC = 1; - - /** - * Bluetooth device type, Low Energy - LE-only - */ - public static final int DEVICE_TYPE_LE = 2; - - /** - * Bluetooth device type, Dual Mode - BR/EDR/LE - */ - public static final int DEVICE_TYPE_DUAL = 3; - - - /** @hide */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final String ACTION_SDP_RECORD = - "android.bluetooth.device.action.SDP_RECORD"; - - /** @hide */ - @IntDef(prefix = "METADATA_", value = { - METADATA_MANUFACTURER_NAME, - METADATA_MODEL_NAME, - METADATA_SOFTWARE_VERSION, - METADATA_HARDWARE_VERSION, - METADATA_COMPANION_APP, - METADATA_MAIN_ICON, - METADATA_IS_UNTETHERED_HEADSET, - METADATA_UNTETHERED_LEFT_ICON, - METADATA_UNTETHERED_RIGHT_ICON, - METADATA_UNTETHERED_CASE_ICON, - METADATA_UNTETHERED_LEFT_BATTERY, - METADATA_UNTETHERED_RIGHT_BATTERY, - METADATA_UNTETHERED_CASE_BATTERY, - METADATA_UNTETHERED_LEFT_CHARGING, - METADATA_UNTETHERED_RIGHT_CHARGING, - METADATA_UNTETHERED_CASE_CHARGING, - METADATA_ENHANCED_SETTINGS_UI_URI, - METADATA_DEVICE_TYPE, - METADATA_MAIN_BATTERY, - METADATA_MAIN_CHARGING, - METADATA_MAIN_LOW_BATTERY_THRESHOLD, - METADATA_UNTETHERED_LEFT_LOW_BATTERY_THRESHOLD, - METADATA_UNTETHERED_RIGHT_LOW_BATTERY_THRESHOLD, - METADATA_UNTETHERED_CASE_LOW_BATTERY_THRESHOLD}) - @Retention(RetentionPolicy.SOURCE) - public @interface MetadataKey{} - - /** - * Maximum length of a metadata entry, this is to avoid exploding Bluetooth - * disk usage - * @hide - */ - @SystemApi - public static final int METADATA_MAX_LENGTH = 2048; - - /** - * Manufacturer name of this Bluetooth device - * Data type should be {@String} as {@link Byte} array. - * @hide - */ - @SystemApi - public static final int METADATA_MANUFACTURER_NAME = 0; - - /** - * Model name of this Bluetooth device - * Data type should be {@String} as {@link Byte} array. - * @hide - */ - @SystemApi - public static final int METADATA_MODEL_NAME = 1; - - /** - * Software version of this Bluetooth device - * Data type should be {@String} as {@link Byte} array. - * @hide - */ - @SystemApi - public static final int METADATA_SOFTWARE_VERSION = 2; - - /** - * Hardware version of this Bluetooth device - * Data type should be {@String} as {@link Byte} array. - * @hide - */ - @SystemApi - public static final int METADATA_HARDWARE_VERSION = 3; - - /** - * Package name of the companion app, if any - * Data type should be {@String} as {@link Byte} array. - * @hide - */ - @SystemApi - public static final int METADATA_COMPANION_APP = 4; - - /** - * URI to the main icon shown on the settings UI - * Data type should be {@link Byte} array. - * @hide - */ - @SystemApi - public static final int METADATA_MAIN_ICON = 5; - - /** - * Whether this device is an untethered headset with left, right and case - * Data type should be {@String} as {@link Byte} array. - * @hide - */ - @SystemApi - public static final int METADATA_IS_UNTETHERED_HEADSET = 6; - - /** - * URI to icon of the left headset - * Data type should be {@link Byte} array. - * @hide - */ - @SystemApi - public static final int METADATA_UNTETHERED_LEFT_ICON = 7; - - /** - * URI to icon of the right headset - * Data type should be {@link Byte} array. - * @hide - */ - @SystemApi - public static final int METADATA_UNTETHERED_RIGHT_ICON = 8; - - /** - * URI to icon of the headset charging case - * Data type should be {@link Byte} array. - * @hide - */ - @SystemApi - public static final int METADATA_UNTETHERED_CASE_ICON = 9; - - /** - * Battery level of left headset - * Data type should be {@String} 0-100 as {@link Byte} array, otherwise - * as invalid. - * @hide - */ - @SystemApi - public static final int METADATA_UNTETHERED_LEFT_BATTERY = 10; - - /** - * Battery level of rigth headset - * Data type should be {@String} 0-100 as {@link Byte} array, otherwise - * as invalid. - * @hide - */ - @SystemApi - public static final int METADATA_UNTETHERED_RIGHT_BATTERY = 11; - - /** - * Battery level of the headset charging case - * Data type should be {@String} 0-100 as {@link Byte} array, otherwise - * as invalid. - * @hide - */ - @SystemApi - public static final int METADATA_UNTETHERED_CASE_BATTERY = 12; - - /** - * Whether the left headset is charging - * Data type should be {@String} as {@link Byte} array. - * @hide - */ - @SystemApi - public static final int METADATA_UNTETHERED_LEFT_CHARGING = 13; - - /** - * Whether the right headset is charging - * Data type should be {@String} as {@link Byte} array. - * @hide - */ - @SystemApi - public static final int METADATA_UNTETHERED_RIGHT_CHARGING = 14; - - /** - * Whether the headset charging case is charging - * Data type should be {@String} as {@link Byte} array. - * @hide - */ - @SystemApi - public static final int METADATA_UNTETHERED_CASE_CHARGING = 15; - - /** - * URI to the enhanced settings UI slice - * Data type should be {@String} as {@link Byte} array, null means - * the UI does not exist. - * @hide - */ - @SystemApi - public static final int METADATA_ENHANCED_SETTINGS_UI_URI = 16; - - /** - * Type of the Bluetooth device, must be within the list of - * BluetoothDevice.DEVICE_TYPE_* - * Data type should be {@String} as {@link Byte} array. - * @hide - */ - @SystemApi - public static final int METADATA_DEVICE_TYPE = 17; - - /** - * Battery level of the Bluetooth device, use when the Bluetooth device - * does not support HFP battery indicator. - * Data type should be {@String} as {@link Byte} array. - * @hide - */ - @SystemApi - public static final int METADATA_MAIN_BATTERY = 18; - - /** - * Whether the device is charging. - * Data type should be {@String} as {@link Byte} array. - * @hide - */ - @SystemApi - public static final int METADATA_MAIN_CHARGING = 19; - - /** - * The battery threshold of the Bluetooth device to show low battery icon. - * Data type should be {@String} as {@link Byte} array. - * @hide - */ - @SystemApi - public static final int METADATA_MAIN_LOW_BATTERY_THRESHOLD = 20; - - /** - * The battery threshold of the left headset to show low battery icon. - * Data type should be {@String} as {@link Byte} array. - * @hide - */ - @SystemApi - public static final int METADATA_UNTETHERED_LEFT_LOW_BATTERY_THRESHOLD = 21; - - /** - * The battery threshold of the right headset to show low battery icon. - * Data type should be {@String} as {@link Byte} array. - * @hide - */ - @SystemApi - public static final int METADATA_UNTETHERED_RIGHT_LOW_BATTERY_THRESHOLD = 22; - - /** - * The battery threshold of the case to show low battery icon. - * Data type should be {@String} as {@link Byte} array. - * @hide - */ - @SystemApi - public static final int METADATA_UNTETHERED_CASE_LOW_BATTERY_THRESHOLD = 23; - - /** - * Device type which is used in METADATA_DEVICE_TYPE - * Indicates this Bluetooth device is a standard Bluetooth accessory or - * not listed in METADATA_DEVICE_TYPE_*. - * @hide - */ - @SystemApi - public static final String DEVICE_TYPE_DEFAULT = "Default"; - - /** - * Device type which is used in METADATA_DEVICE_TYPE - * Indicates this Bluetooth device is a watch. - * @hide - */ - @SystemApi - public static final String DEVICE_TYPE_WATCH = "Watch"; - - /** - * Device type which is used in METADATA_DEVICE_TYPE - * Indicates this Bluetooth device is an untethered headset. - * @hide - */ - @SystemApi - public static final String DEVICE_TYPE_UNTETHERED_HEADSET = "Untethered Headset"; - - /** - * Broadcast Action: This intent is used to broadcast the {@link UUID} - * wrapped as a {@link android.os.ParcelUuid} of the remote device after it - * has been fetched. This intent is sent only when the UUIDs of the remote - * device are requested to be fetched using Service Discovery Protocol - * <p> Always contains the extra field {@link #EXTRA_DEVICE} - * <p> Always contains the extra field {@link #EXTRA_UUID} - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_UUID = - "android.bluetooth.device.action.UUID"; - - /** @hide */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_MAS_INSTANCE = - "android.bluetooth.device.action.MAS_INSTANCE"; - - /** - * Broadcast Action: Indicates a failure to retrieve the name of a remote - * device. - * <p>Always contains the extra field {@link #EXTRA_DEVICE}. - * - * @hide - */ - //TODO: is this actually useful? - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_NAME_FAILED = - "android.bluetooth.device.action.NAME_FAILED"; - - /** - * Broadcast Action: This intent is used to broadcast PAIRING REQUEST - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_PAIRING_REQUEST = - "android.bluetooth.device.action.PAIRING_REQUEST"; - /** @hide */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - @UnsupportedAppUsage - public static final String ACTION_PAIRING_CANCEL = - "android.bluetooth.device.action.PAIRING_CANCEL"; - - /** @hide */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_CONNECTION_ACCESS_REQUEST = - "android.bluetooth.device.action.CONNECTION_ACCESS_REQUEST"; - - /** @hide */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_CONNECTION_ACCESS_REPLY = - "android.bluetooth.device.action.CONNECTION_ACCESS_REPLY"; - - /** @hide */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_CONNECTION_ACCESS_CANCEL = - "android.bluetooth.device.action.CONNECTION_ACCESS_CANCEL"; - - /** - * Intent to broadcast silence mode changed. - * Alway contains the extra field {@link #EXTRA_DEVICE} - * - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - @SystemApi - public static final String ACTION_SILENCE_MODE_CHANGED = - "android.bluetooth.device.action.SILENCE_MODE_CHANGED"; - - /** - * Used as an extra field in {@link #ACTION_CONNECTION_ACCESS_REQUEST} intent. - * - * @hide - */ - public static final String EXTRA_ACCESS_REQUEST_TYPE = - "android.bluetooth.device.extra.ACCESS_REQUEST_TYPE"; - - /** @hide */ - public static final int REQUEST_TYPE_PROFILE_CONNECTION = 1; - - /** @hide */ - public static final int REQUEST_TYPE_PHONEBOOK_ACCESS = 2; - - /** @hide */ - public static final int REQUEST_TYPE_MESSAGE_ACCESS = 3; - - /** @hide */ - public static final int REQUEST_TYPE_SIM_ACCESS = 4; - - /** - * Used as an extra field in {@link #ACTION_CONNECTION_ACCESS_REQUEST} intents, - * Contains package name to return reply intent to. - * - * @hide - */ - public static final String EXTRA_PACKAGE_NAME = "android.bluetooth.device.extra.PACKAGE_NAME"; - - /** - * Used as an extra field in {@link #ACTION_CONNECTION_ACCESS_REQUEST} intents, - * Contains class name to return reply intent to. - * - * @hide - */ - public static final String EXTRA_CLASS_NAME = "android.bluetooth.device.extra.CLASS_NAME"; - - /** - * Used as an extra field in {@link #ACTION_CONNECTION_ACCESS_REPLY} intent. - * - * @hide - */ - public static final String EXTRA_CONNECTION_ACCESS_RESULT = - "android.bluetooth.device.extra.CONNECTION_ACCESS_RESULT"; - - /** @hide */ - public static final int CONNECTION_ACCESS_YES = 1; - - /** @hide */ - public static final int CONNECTION_ACCESS_NO = 2; - - /** - * Used as an extra field in {@link #ACTION_CONNECTION_ACCESS_REPLY} intents, - * Contains boolean to indicate if the allowed response is once-for-all so that - * next request will be granted without asking user again. - * - * @hide - */ - public static final String EXTRA_ALWAYS_ALLOWED = - "android.bluetooth.device.extra.ALWAYS_ALLOWED"; - - /** - * A bond attempt succeeded - * - * @hide - */ - public static final int BOND_SUCCESS = 0; - - /** - * A bond attempt failed because pins did not match, or remote device did - * not respond to pin request in time - * - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final int UNBOND_REASON_AUTH_FAILED = 1; - - /** - * A bond attempt failed because the other side explicitly rejected - * bonding - * - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final int UNBOND_REASON_AUTH_REJECTED = 2; - - /** - * A bond attempt failed because we canceled the bonding process - * - * @hide - */ - public static final int UNBOND_REASON_AUTH_CANCELED = 3; - - /** - * A bond attempt failed because we could not contact the remote device - * - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final int UNBOND_REASON_REMOTE_DEVICE_DOWN = 4; - - /** - * A bond attempt failed because a discovery is in progress - * - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final int UNBOND_REASON_DISCOVERY_IN_PROGRESS = 5; - - /** - * A bond attempt failed because of authentication timeout - * - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final int UNBOND_REASON_AUTH_TIMEOUT = 6; - - /** - * A bond attempt failed because of repeated attempts - * - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final int UNBOND_REASON_REPEATED_ATTEMPTS = 7; - - /** - * A bond attempt failed because we received an Authentication Cancel - * by remote end - * - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final int UNBOND_REASON_REMOTE_AUTH_CANCELED = 8; - - /** - * An existing bond was explicitly revoked - * - * @hide - */ - public static final int UNBOND_REASON_REMOVED = 9; - - /** - * The user will be prompted to enter a pin or - * an app will enter a pin for user. - */ - public static final int PAIRING_VARIANT_PIN = 0; - - /** - * The user will be prompted to enter a passkey - * - * @hide - */ - public static final int PAIRING_VARIANT_PASSKEY = 1; - - /** - * The user will be prompted to confirm the passkey displayed on the screen or - * an app will confirm the passkey for the user. - */ - public static final int PAIRING_VARIANT_PASSKEY_CONFIRMATION = 2; - - /** - * The user will be prompted to accept or deny the incoming pairing request - * - * @hide - */ - public static final int PAIRING_VARIANT_CONSENT = 3; - - /** - * The user will be prompted to enter the passkey displayed on remote device - * This is used for Bluetooth 2.1 pairing. - * - * @hide - */ - public static final int PAIRING_VARIANT_DISPLAY_PASSKEY = 4; - - /** - * The user will be prompted to enter the PIN displayed on remote device. - * This is used for Bluetooth 2.0 pairing. - * - * @hide - */ - public static final int PAIRING_VARIANT_DISPLAY_PIN = 5; - - /** - * The user will be prompted to accept or deny the OOB pairing request - * - * @hide - */ - public static final int PAIRING_VARIANT_OOB_CONSENT = 6; - - /** - * The user will be prompted to enter a 16 digit pin or - * an app will enter a 16 digit pin for user. - * - * @hide - */ - public static final int PAIRING_VARIANT_PIN_16_DIGITS = 7; - - /** - * Used as an extra field in {@link #ACTION_UUID} intents, - * Contains the {@link android.os.ParcelUuid}s of the remote device which - * is a parcelable version of {@link UUID}. - */ - public static final String EXTRA_UUID = "android.bluetooth.device.extra.UUID"; - - /** @hide */ - public static final String EXTRA_SDP_RECORD = - "android.bluetooth.device.extra.SDP_RECORD"; - - /** @hide */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final String EXTRA_SDP_SEARCH_STATUS = - "android.bluetooth.device.extra.SDP_SEARCH_STATUS"; - - /** @hide */ - @IntDef(prefix = "ACCESS_", value = {ACCESS_UNKNOWN, - ACCESS_ALLOWED, ACCESS_REJECTED}) - @Retention(RetentionPolicy.SOURCE) - public @interface AccessPermission{} - - /** - * For {@link #getPhonebookAccessPermission}, {@link #setPhonebookAccessPermission}, - * {@link #getMessageAccessPermission} and {@link #setMessageAccessPermission}. - * - * @hide - */ - @SystemApi - public static final int ACCESS_UNKNOWN = 0; - - /** - * For {@link #getPhonebookAccessPermission}, {@link #setPhonebookAccessPermission}, - * {@link #getMessageAccessPermission} and {@link #setMessageAccessPermission}. - * - * @hide - */ - @SystemApi - public static final int ACCESS_ALLOWED = 1; - - /** - * For {@link #getPhonebookAccessPermission}, {@link #setPhonebookAccessPermission}, - * {@link #getMessageAccessPermission} and {@link #setMessageAccessPermission}. - * - * @hide - */ - @SystemApi - public static final int ACCESS_REJECTED = 2; - - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef( - prefix = { "TRANSPORT_" }, - value = { - /** Allow host to automatically select a transport (dual-mode only) */ - TRANSPORT_AUTO, - /** Use Classic or BR/EDR transport.*/ - TRANSPORT_BREDR, - /** Use Low Energy transport.*/ - TRANSPORT_LE, - } - ) - public @interface Transport {} - - /** - * No preference of physical transport for GATT connections to remote dual-mode devices - */ - public static final int TRANSPORT_AUTO = 0; - - /** - * Prefer BR/EDR transport for GATT connections to remote dual-mode devices - */ - public static final int TRANSPORT_BREDR = 1; - - /** - * Prefer LE transport for GATT connections to remote dual-mode devices - */ - public static final int TRANSPORT_LE = 2; - - /** - * Bluetooth LE 1M PHY. Used to refer to LE 1M Physical Channel for advertising, scanning or - * connection. - */ - public static final int PHY_LE_1M = 1; - - /** - * Bluetooth LE 2M PHY. Used to refer to LE 2M Physical Channel for advertising, scanning or - * connection. - */ - public static final int PHY_LE_2M = 2; - - /** - * Bluetooth LE Coded PHY. Used to refer to LE Coded Physical Channel for advertising, scanning - * or connection. - */ - public static final int PHY_LE_CODED = 3; - - /** - * Bluetooth LE 1M PHY mask. Used to specify LE 1M Physical Channel as one of many available - * options in a bitmask. - */ - public static final int PHY_LE_1M_MASK = 1; - - /** - * Bluetooth LE 2M PHY mask. Used to specify LE 2M Physical Channel as one of many available - * options in a bitmask. - */ - public static final int PHY_LE_2M_MASK = 2; - - /** - * Bluetooth LE Coded PHY mask. Used to specify LE Coded Physical Channel as one of many - * available options in a bitmask. - */ - public static final int PHY_LE_CODED_MASK = 4; - - /** - * No preferred coding when transmitting on the LE Coded PHY. - */ - public static final int PHY_OPTION_NO_PREFERRED = 0; - - /** - * Prefer the S=2 coding to be used when transmitting on the LE Coded PHY. - */ - public static final int PHY_OPTION_S2 = 1; - - /** - * Prefer the S=8 coding to be used when transmitting on the LE Coded PHY. - */ - public static final int PHY_OPTION_S8 = 2; - - - /** @hide */ - public static final String EXTRA_MAS_INSTANCE = - "android.bluetooth.device.extra.MAS_INSTANCE"; - - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef( - prefix = { "ADDRESS_TYPE_" }, - value = { - /** Hardware MAC Address */ - ADDRESS_TYPE_PUBLIC, - /** Address is either resolvable, non-resolvable or static.*/ - ADDRESS_TYPE_RANDOM, - } - ) - public @interface AddressType {} - - /** Hardware MAC Address of the device */ - public static final int ADDRESS_TYPE_PUBLIC = 0; - /** Address is either resolvable, non-resolvable or static. */ - public static final int ADDRESS_TYPE_RANDOM = 1; - - private static final String NULL_MAC_ADDRESS = "00:00:00:00:00:00"; - - /** - * Lazy initialization. Guaranteed final after first object constructed, or - * getService() called. - * TODO: Unify implementation of sService amongst BluetoothFoo API's - */ - private static volatile IBluetooth sService; - - private final String mAddress; - @AddressType private final int mAddressType; - - private AttributionSource mAttributionSource; - - /*package*/ - @UnsupportedAppUsage - static IBluetooth getService() { - synchronized (BluetoothDevice.class) { - if (sService == null) { - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - sService = adapter.getBluetoothService(sStateChangeCallback); - } - } - return sService; - } - - static IBluetoothManagerCallback sStateChangeCallback = new IBluetoothManagerCallback.Stub() { - - public void onBluetoothServiceUp(IBluetooth bluetoothService) - throws RemoteException { - synchronized (BluetoothDevice.class) { - if (sService == null) { - sService = bluetoothService; - } - } - } - - public void onBluetoothServiceDown() - throws RemoteException { - synchronized (BluetoothDevice.class) { - sService = null; - } - } - - public void onBrEdrDown() { - if (DBG) Log.d(TAG, "onBrEdrDown: reached BLE ON state"); - } - - public void onOobData(@Transport int transport, OobData oobData) { - if (DBG) Log.d(TAG, "onOobData: got data"); - } - }; - - /** - * Create a new BluetoothDevice - * Bluetooth MAC address must be upper case, such as "00:11:22:33:AA:BB", - * and is validated in this constructor. - * - * @param address valid Bluetooth MAC address - * @param attributionSource attribution for permission-protected calls - * @throws RuntimeException Bluetooth is not available on this platform - * @throws IllegalArgumentException address is invalid - * @hide - */ - @UnsupportedAppUsage - /*package*/ BluetoothDevice(String address) { - getService(); // ensures sService is initialized - if (!BluetoothAdapter.checkBluetoothAddress(address)) { - throw new IllegalArgumentException(address + " is not a valid Bluetooth address"); - } - - mAddress = address; - mAddressType = ADDRESS_TYPE_PUBLIC; - mAttributionSource = AttributionSource.myAttributionSource(); - } - - /** {@hide} */ - public void setAttributionSource(@NonNull AttributionSource attributionSource) { - mAttributionSource = attributionSource; - } - - /** {@hide} */ - public void prepareToEnterProcess(@NonNull AttributionSource attributionSource) { - setAttributionSource(attributionSource); - } - - @Override - public boolean equals(@Nullable Object o) { - if (o instanceof BluetoothDevice) { - return mAddress.equals(((BluetoothDevice) o).getAddress()); - } - return false; - } - - @Override - public int hashCode() { - return mAddress.hashCode(); - } - - /** - * Returns a string representation of this BluetoothDevice. - * <p>Currently this is the Bluetooth hardware address, for example - * "00:11:22:AA:BB:CC". However, you should always use {@link #getAddress} - * if you explicitly require the Bluetooth hardware address in case the - * {@link #toString} representation changes in the future. - * - * @return string representation of this BluetoothDevice - */ - @Override - public String toString() { - return mAddress; - } - - @Override - public int describeContents() { - return 0; - } - - public static final @android.annotation.NonNull Parcelable.Creator<BluetoothDevice> CREATOR = - new Parcelable.Creator<BluetoothDevice>() { - public BluetoothDevice createFromParcel(Parcel in) { - return new BluetoothDevice(in.readString()); - } - - public BluetoothDevice[] newArray(int size) { - return new BluetoothDevice[size]; - } - }; - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeString(mAddress); - } - - /** - * Returns the hardware address of this BluetoothDevice. - * <p> For example, "00:11:22:AA:BB:CC". - * - * @return Bluetooth hardware address as string - */ - public String getAddress() { - if (DBG) Log.d(TAG, "mAddress: " + mAddress); - return mAddress; - } - - /** - * Returns the anonymized hardware address of this BluetoothDevice. The first three octets - * will be suppressed for anonymization. - * <p> For example, "XX:XX:XX:AA:BB:CC". - * - * @return Anonymized bluetooth hardware address as string - * @hide - */ - public String getAnonymizedAddress() { - return "XX:XX:XX" + getAddress().substring(8); - } - - /** - * Get the friendly Bluetooth name of the remote device. - * - * <p>The local adapter will automatically retrieve remote names when - * performing a device scan, and will cache them. This method just returns - * the name for this device from the cache. - * - * @return the Bluetooth name, or null if there was a problem. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public String getName() { - final IBluetooth service = sService; - if (service == null) { - Log.e(TAG, "BT not enabled. Cannot get Remote Device name"); - return null; - } - try { - String name = service.getRemoteName(this, mAttributionSource); - if (name != null) { - // remove whitespace characters from the name - return name - .replace('\t', ' ') - .replace('\n', ' ') - .replace('\r', ' '); - } - return null; - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return null; - } - - /** - * Get the Bluetooth device type of the remote device. - * - * @return the device type {@link #DEVICE_TYPE_CLASSIC}, {@link #DEVICE_TYPE_LE} {@link - * #DEVICE_TYPE_DUAL}. {@link #DEVICE_TYPE_UNKNOWN} if it's not available - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public int getType() { - final IBluetooth service = sService; - if (service == null) { - Log.e(TAG, "BT not enabled. Cannot get Remote Device type"); - return DEVICE_TYPE_UNKNOWN; - } - try { - return service.getRemoteType(this, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return DEVICE_TYPE_UNKNOWN; - } - - /** - * Get the locally modifiable name (alias) of the remote Bluetooth device. - * - * @return the Bluetooth alias, the friendly device name if no alias, or - * null if there was a problem - */ - @Nullable - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public String getAlias() { - final IBluetooth service = sService; - if (service == null) { - Log.e(TAG, "BT not enabled. Cannot get Remote Device Alias"); - return null; - } - try { - String alias = service.getRemoteAliasWithAttribution(this, mAttributionSource); - if (alias == null) { - return getName(); - } - return alias - .replace('\t', ' ') - .replace('\n', ' ') - .replace('\r', ' '); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return null; - } - - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(value = { - BluetoothStatusCodes.SUCCESS, - BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED, - BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ALLOWED, - BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_CONNECT_PERMISSION, - BluetoothStatusCodes.ERROR_DEVICE_NOT_BONDED - }) - public @interface SetAliasReturnValues{} - - /** - * Sets the locally modifiable name (alias) of the remote Bluetooth device. This method - * overwrites the previously stored alias. The new alias is saved in local - * storage so that the change is preserved over power cycles. - * - * <p>This method requires the calling app to be associated with Companion Device Manager (see - * {@link android.companion.CompanionDeviceManager#associate(AssociationRequest, - * android.companion.CompanionDeviceManager.Callback, Handler)}) and have the - * {@link android.Manifest.permission#BLUETOOTH_CONNECT} permission. Alternatively, if the - * caller has the {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED} permission, they can - * bypass the Companion Device Manager association requirement as well as other permission - * requirements. - * - * @param alias is the new locally modifiable name for the remote Bluetooth device which must - * be the empty string. If null, we clear the alias. - * @return whether the alias was successfully changed - * @throws IllegalArgumentException if the alias is the empty string - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public @SetAliasReturnValues int setAlias(@Nullable String alias) { - if (alias != null && alias.isEmpty()) { - throw new IllegalArgumentException("alias cannot be the empty string"); - } - final IBluetooth service = sService; - if (service == null) { - Log.e(TAG, "BT not enabled. Cannot set Remote Device name"); - return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED; - } - try { - return service.setRemoteAlias(this, alias, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - throw e.rethrowFromSystemServer(); - } - } - - /** - * Get the most recent identified battery level of this Bluetooth device - * - * @return Battery level in percents from 0 to 100, {@link #BATTERY_LEVEL_BLUETOOTH_OFF} if - * Bluetooth is disabled or {@link #BATTERY_LEVEL_UNKNOWN} if device is disconnected, or does - * not have any battery reporting service, or return value is invalid - * @hide - */ - @UnsupportedAppUsage - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public int getBatteryLevel() { - final IBluetooth service = sService; - if (service == null) { - Log.e(TAG, "Bluetooth disabled. Cannot get remote device battery level"); - return BATTERY_LEVEL_BLUETOOTH_OFF; - } - try { - return service.getBatteryLevel(this, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return BATTERY_LEVEL_UNKNOWN; - } - - /** - * Start the bonding (pairing) process with the remote device. - * <p>This is an asynchronous call, it will return immediately. Register - * for {@link #ACTION_BOND_STATE_CHANGED} intents to be notified when - * the bonding process completes, and its result. - * <p>Android system services will handle the necessary user interactions - * to confirm and complete the bonding process. - * - * @return false on immediate error, true if bonding will begin - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean createBond() { - return createBond(TRANSPORT_AUTO); - } - - /** - * Start the bonding (pairing) process with the remote device using the - * specified transport. - * - * <p>This is an asynchronous call, it will return immediately. Register - * for {@link #ACTION_BOND_STATE_CHANGED} intents to be notified when - * the bonding process completes, and its result. - * <p>Android system services will handle the necessary user interactions - * to confirm and complete the bonding process. - * - * @param transport The transport to use for the pairing procedure. - * @return false on immediate error, true if bonding will begin - * @throws IllegalArgumentException if an invalid transport was specified - * @hide - */ - @SystemApi - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean createBond(int transport) { - return createBondInternal(transport, null, null); - } - - /** - * Start the bonding (pairing) process with the remote device using the - * Out Of Band mechanism. - * - * <p>This is an asynchronous call, it will return immediately. Register - * for {@link #ACTION_BOND_STATE_CHANGED} intents to be notified when - * the bonding process completes, and its result. - * - * <p>Android system services will handle the necessary user interactions - * to confirm and complete the bonding process. - * - * <p>There are two possible versions of OOB Data. This data can come in as - * P192 or P256. This is a reference to the cryptography used to generate the key. - * The caller may pass one or both. If both types of data are passed, then the - * P256 data will be preferred, and thus used. - * - * @param transport - Transport to use - * @param remoteP192Data - Out Of Band data (P192) or null - * @param remoteP256Data - Out Of Band data (P256) or null - * @return false on immediate error, true if bonding will begin - * @hide - */ - @SystemApi - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean createBondOutOfBand(int transport, @Nullable OobData remoteP192Data, - @Nullable OobData remoteP256Data) { - if (remoteP192Data == null && remoteP256Data == null) { - throw new IllegalArgumentException( - "One or both arguments for the OOB data types are required to not be null." - + " Please use createBond() instead if you do not have OOB data to pass."); - } - return createBondInternal(transport, remoteP192Data, remoteP256Data); - } - - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - private boolean createBondInternal(int transport, @Nullable OobData remoteP192Data, - @Nullable OobData remoteP256Data) { - final IBluetooth service = sService; - if (service == null) { - Log.w(TAG, "BT not enabled, createBondOutOfBand failed"); - return false; - } - if (NULL_MAC_ADDRESS.equals(mAddress)) { - Log.e(TAG, "Unable to create bond, invalid address " + mAddress); - return false; - } - try { - return service.createBond( - this, transport, remoteP192Data, remoteP256Data, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return false; - } - - /** - * Gets whether bonding was initiated locally - * - * @return true if bonding is initiated locally, false otherwise - * - * @hide - */ - @UnsupportedAppUsage - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean isBondingInitiatedLocally() { - final IBluetooth service = sService; - if (service == null) { - Log.w(TAG, "BT not enabled, isBondingInitiatedLocally failed"); - return false; - } - try { - return service.isBondingInitiatedLocally(this, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return false; - } - - /** - * Cancel an in-progress bonding request started with {@link #createBond}. - * - * @return true on success, false on error - * @hide - */ - @SystemApi - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean cancelBondProcess() { - final IBluetooth service = sService; - if (service == null) { - Log.e(TAG, "BT not enabled. Cannot cancel Remote Device bond"); - return false; - } - try { - Log.i(TAG, "cancelBondProcess() for device " + getAddress() - + " called by pid: " + Process.myPid() - + " tid: " + Process.myTid()); - return service.cancelBondProcess(this, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return false; - } - - /** - * Remove bond (pairing) with the remote device. - * <p>Delete the link key associated with the remote device, and - * immediately terminate connections to that device that require - * authentication and encryption. - * - * @return true on success, false on error - * @hide - */ - @SystemApi - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean removeBond() { - final IBluetooth service = sService; - if (service == null) { - Log.e(TAG, "BT not enabled. Cannot remove Remote Device bond"); - return false; - } - try { - Log.i(TAG, "removeBond() for device " + getAddress() - + " called by pid: " + Process.myPid() - + " tid: " + Process.myTid()); - return service.removeBond(this, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return false; - } - - /* - private static final String BLUETOOTH_BONDING_CACHE_PROPERTY = - "cache_key.bluetooth.get_bond_state"; - private final PropertyInvalidatedCache<BluetoothDevice, Integer> mBluetoothBondCache = - new PropertyInvalidatedCache<BluetoothDevice, Integer>( - 8, BLUETOOTH_BONDING_CACHE_PROPERTY) { - @Override - @SuppressLint("AndroidFrameworkRequiresPermission") - public Integer recompute(BluetoothDevice query) { - try { - return sService.getBondState(query, mAttributionSource); - } catch (RemoteException e) { - throw e.rethrowAsRuntimeException(); - } - } - }; - */ - - /** @hide */ - /* public void disableBluetoothGetBondStateCache() { - mBluetoothBondCache.disableLocal(); - } */ - - /** @hide */ - /* - public static void invalidateBluetoothGetBondStateCache() { - PropertyInvalidatedCache.invalidateCache(BLUETOOTH_BONDING_CACHE_PROPERTY); - } - */ - - /** - * Get the bond state of the remote device. - * <p>Possible values for the bond state are: - * {@link #BOND_NONE}, - * {@link #BOND_BONDING}, - * {@link #BOND_BONDED}. - * - * @return the bond state - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SuppressLint("AndroidFrameworkRequiresPermission") - public int getBondState() { - final IBluetooth service = sService; - if (service == null) { - Log.e(TAG, "BT not enabled. Cannot get bond state"); - return BOND_NONE; - } - try { - //return mBluetoothBondCache.query(this); - return sService.getBondState(this, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "failed to ", e); - e.rethrowFromSystemServer(); - } - return BOND_NONE; - } - - /** - * Checks whether this bluetooth device is associated with CDM and meets the criteria to skip - * the bluetooth pairing dialog because it has been already consented by the CDM prompt. - * - * @return true if we can bond without the dialog, false otherwise - * - * @hide - */ - @SystemApi - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean canBondWithoutDialog() { - final IBluetooth service = sService; - if (service == null) { - Log.e(TAG, "BT not enabled. Cannot check if we can skip pairing dialog"); - return false; - } - try { - if (DBG) Log.d(TAG, "canBondWithoutDialog, device: " + this); - return service.canBondWithoutDialog(this, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return false; - } - - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(value = { - BluetoothStatusCodes.SUCCESS, - BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED, - BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ALLOWED, - BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_CONNECT_PERMISSION, - BluetoothStatusCodes.ERROR_DEVICE_NOT_BONDED - }) - public @interface ConnectionReturnValues{} - - /** - * Connects all user enabled and supported bluetooth profiles between the local and remote - * device. If no profiles are user enabled (e.g. first connection), we connect all supported - * profiles. If the device is not already connected, this will page the device before initiating - * profile connections. Connection is asynchronous and you should listen to each profile's - * broadcast intent ACTION_CONNECTION_STATE_CHANGED to verify whether connection was successful. - * For example, to verify a2dp is connected, you would listen for - * {@link BluetoothA2dp#ACTION_CONNECTION_STATE_CHANGED} - * - * @return whether the messages were successfully sent to try to connect all profiles - * @throws IllegalArgumentException if the device address is invalid - * - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - android.Manifest.permission.MODIFY_PHONE_STATE, - }) - public @ConnectionReturnValues int connect() { - if (!BluetoothAdapter.checkBluetoothAddress(getAddress())) { - throw new IllegalArgumentException("device cannot have an invalid address"); - } - - try { - if (sService == null) { - Log.e(TAG, "BT not enabled. Cannot connect to remote device."); - return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED; - } - return sService.connectAllEnabledProfiles(this, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - throw e.rethrowFromSystemServer(); - } - } - - /** - * Disconnects all connected bluetooth profiles between the local and remote device. - * Disconnection is asynchronous and you should listen to each profile's broadcast intent - * ACTION_CONNECTION_STATE_CHANGED to verify whether disconnection was successful. For example, - * to verify a2dp is disconnected, you would listen for - * {@link BluetoothA2dp#ACTION_CONNECTION_STATE_CHANGED} - * - * @return whether the messages were successfully sent to try to disconnect all profiles - * @throws IllegalArgumentException if the device address is invalid - * - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public @ConnectionReturnValues int disconnect() { - if (!BluetoothAdapter.checkBluetoothAddress(getAddress())) { - throw new IllegalArgumentException("device cannot have an invalid address"); - } - - try { - if (sService == null) { - Log.e(TAG, "BT not enabled. Cannot disconnect from remote device."); - return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED; - } - return sService.disconnectAllEnabledProfiles(this, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - throw e.rethrowFromSystemServer(); - } - } - - /** - * Returns whether there is an open connection to this device. - * - * @return True if there is at least one open connection to this device. - * @hide - */ - @SystemApi - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean isConnected() { - final IBluetooth service = sService; - if (service == null) { - // BT is not enabled, we cannot be connected. - return false; - } - try { - return service.getConnectionStateWithAttribution(this, mAttributionSource) - != CONNECTION_STATE_DISCONNECTED; - } catch (RemoteException e) { - Log.e(TAG, "", e); - return false; - } - } - - /** - * Returns whether there is an open connection to this device - * that has been encrypted. - * - * @return True if there is at least one encrypted connection to this device. - * @hide - */ - @SystemApi - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean isEncrypted() { - final IBluetooth service = sService; - if (service == null) { - // BT is not enabled, we cannot be connected. - return false; - } - try { - return service.getConnectionStateWithAttribution(this, mAttributionSource) - > CONNECTION_STATE_CONNECTED; - } catch (RemoteException e) { - Log.e(TAG, "", e); - return false; - } - } - - /** - * Get the Bluetooth class of the remote device. - * - * @return Bluetooth class object, or null on error - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothClass getBluetoothClass() { - final IBluetooth service = sService; - if (service == null) { - Log.e(TAG, "BT not enabled. Cannot get Bluetooth Class"); - return null; - } - try { - int classInt = service.getRemoteClass(this, mAttributionSource); - if (classInt == BluetoothClass.ERROR) return null; - return new BluetoothClass(classInt); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return null; - } - - /** - * Returns the supported features (UUIDs) of the remote device. - * - * <p>This method does not start a service discovery procedure to retrieve the UUIDs - * from the remote device. Instead, the local cached copy of the service - * UUIDs are returned. - * <p>Use {@link #fetchUuidsWithSdp} if fresh UUIDs are desired. - * - * @return the supported features (UUIDs) of the remote device, or null on error - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public ParcelUuid[] getUuids() { - final IBluetooth service = sService; - if (service == null || !isBluetoothEnabled()) { - Log.e(TAG, "BT not enabled. Cannot get remote device Uuids"); - return null; - } - try { - return service.getRemoteUuids(this, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return null; - } - - /** - * Perform a service discovery on the remote device to get the UUIDs supported. - * - * <p>This API is asynchronous and {@link #ACTION_UUID} intent is sent, - * with the UUIDs supported by the remote end. If there is an error - * in getting the SDP records or if the process takes a long time, or the device is bonding and - * we have its UUIDs cached, {@link #ACTION_UUID} intent is sent with the UUIDs that is - * currently present in the cache. Clients should use the {@link #getUuids} to get UUIDs - * if service discovery is not to be performed. If there is an ongoing bonding process, - * service discovery or device inquiry, the request will be queued. - * - * @return False if the check fails, True if the process of initiating an ACL connection - * to the remote device was started or cached UUIDs will be broadcast. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean fetchUuidsWithSdp() { - return fetchUuidsWithSdp(TRANSPORT_AUTO); - } - - /** - * Perform a service discovery on the remote device to get the UUIDs supported with the - * specific transport. - * - * <p>This API is asynchronous and {@link #ACTION_UUID} intent is sent, - * with the UUIDs supported by the remote end. If there is an error - * in getting the SDP or GATT records or if the process takes a long time, or the device - * is bonding and we have its UUIDs cached, {@link #ACTION_UUID} intent is sent with the - * UUIDs that is currently present in the cache. Clients should use the {@link #getUuids} - * to get UUIDs if service discovery is not to be performed. If there is an ongoing bonding - * process, service discovery or device inquiry, the request will be queued. - * - * @param transport - provide type of transport (e.g. LE or Classic). - * @return False if the check fails, True if the process of initiating an ACL connection - * to the remote device was started or cached UUIDs will be broadcast with the specific - * transport. - * - * @hide - */ - @SystemApi - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean fetchUuidsWithSdp(@Transport int transport) { - final IBluetooth service = sService; - if (service == null || !isBluetoothEnabled()) { - Log.e(TAG, "BT not enabled. Cannot fetchUuidsWithSdp"); - return false; - } - try { - return service.fetchRemoteUuidsWithAttribution(this, transport, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return false; - } - - /** - * Perform a service discovery on the remote device to get the SDP records associated - * with the specified UUID. - * - * <p>This API is asynchronous and {@link #ACTION_SDP_RECORD} intent is sent, - * with the SDP records found on the remote end. If there is an error - * in getting the SDP records or if the process takes a long time, - * {@link #ACTION_SDP_RECORD} intent is sent with an status value in - * {@link #EXTRA_SDP_SEARCH_STATUS} different from 0. - * Detailed status error codes can be found by members of the Bluetooth package in - * the AbstractionLayer class. - * <p>The SDP record data will be stored in the intent as {@link #EXTRA_SDP_RECORD}. - * The object type will match one of the SdpXxxRecord types, depending on the UUID searched - * for. - * - * @return False if the check fails, True if the process - * of initiating an ACL connection to the remote device - * was started. - */ - /** @hide */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean sdpSearch(ParcelUuid uuid) { - final IBluetooth service = sService; - if (service == null) { - Log.e(TAG, "BT not enabled. Cannot query remote device sdp records"); - return false; - } - try { - return service.sdpSearch(this, uuid, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return false; - } - - /** - * Set the pin during pairing when the pairing method is {@link #PAIRING_VARIANT_PIN} - * - * @return true pin has been set false for error - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean setPin(byte[] pin) { - final IBluetooth service = sService; - if (service == null) { - Log.e(TAG, "BT not enabled. Cannot set Remote Device pin"); - return false; - } - try { - return service.setPin(this, true, pin.length, pin, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return false; - } - - /** - * Set the pin during pairing when the pairing method is {@link #PAIRING_VARIANT_PIN} - * - * @return true pin has been set false for error - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean setPin(@NonNull String pin) { - byte[] pinBytes = convertPinToBytes(pin); - if (pinBytes == null) { - return false; - } - return setPin(pinBytes); - } - - /** - * Confirm passkey for {@link #PAIRING_VARIANT_PASSKEY_CONFIRMATION} pairing. - * - * @return true confirmation has been sent out false for error - */ - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setPairingConfirmation(boolean confirm) { - final IBluetooth service = sService; - if (service == null) { - Log.e(TAG, "BT not enabled. Cannot set pairing confirmation"); - return false; - } - try { - return service.setPairingConfirmation(this, confirm, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return false; - } - - /** - * Cancels pairing to this device - * - * @return true if pairing cancelled successfully, false otherwise - * - * @hide - */ - @UnsupportedAppUsage - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean cancelPairing() { - final IBluetooth service = sService; - if (service == null) { - Log.e(TAG, "BT not enabled. Cannot cancel pairing"); - return false; - } - try { - return service.cancelBondProcess(this, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return false; - } - - boolean isBluetoothEnabled() { - boolean ret = false; - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - if (adapter != null && adapter.isEnabled()) { - ret = true; - } - return ret; - } - - /** - * Gets whether the phonebook access is allowed for this bluetooth device - * - * @return Whether the phonebook access is allowed to this device. Can be {@link - * #ACCESS_UNKNOWN}, {@link #ACCESS_ALLOWED} or {@link #ACCESS_REJECTED}. - * @hide - */ - @UnsupportedAppUsage - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public @AccessPermission int getPhonebookAccessPermission() { - final IBluetooth service = sService; - if (service == null) { - return ACCESS_UNKNOWN; - } - try { - return service.getPhonebookAccessPermission(this, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return ACCESS_UNKNOWN; - } - - /** - * Sets whether the {@link BluetoothDevice} enters silence mode. Audio will not - * be routed to the {@link BluetoothDevice} if set to {@code true}. - * - * When the {@link BluetoothDevice} enters silence mode, and the {@link BluetoothDevice} - * is an active device (for A2DP or HFP), the active device for that profile - * will be set to null. - * If the {@link BluetoothDevice} exits silence mode while the A2DP or HFP - * active device is null, the {@link BluetoothDevice} will be set as the - * active device for that profile. - * If the {@link BluetoothDevice} is disconnected, it exits silence mode. - * If the {@link BluetoothDevice} is set as the active device for A2DP or - * HFP, while silence mode is enabled, then the device will exit silence mode. - * If the {@link BluetoothDevice} is in silence mode, AVRCP position change - * event and HFP AG indicators will be disabled. - * If the {@link BluetoothDevice} is not connected with A2DP or HFP, it cannot - * enter silence mode. - * - * @param silence true to enter silence mode, false to exit - * @return true on success, false on error. - * @throws IllegalStateException if Bluetooth is not turned ON. - * @hide - */ - @SystemApi - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setSilenceMode(boolean silence) { - final IBluetooth service = sService; - if (service == null) { - throw new IllegalStateException("Bluetooth is not turned ON"); - } - try { - return service.setSilenceMode(this, silence, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "setSilenceMode fail", e); - return false; - } - } - - /** - * Check whether the {@link BluetoothDevice} is in silence mode - * - * @return true on device in silence mode, otherwise false. - * @throws IllegalStateException if Bluetooth is not turned ON. - * @hide - */ - @SystemApi - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean isInSilenceMode() { - final IBluetooth service = sService; - if (service == null) { - throw new IllegalStateException("Bluetooth is not turned ON"); - } - try { - return service.getSilenceMode(this, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "isInSilenceMode fail", e); - return false; - } - } - - /** - * Sets whether the phonebook access is allowed to this device. - * - * @param value Can be {@link #ACCESS_UNKNOWN}, {@link #ACCESS_ALLOWED} or {@link - * #ACCESS_REJECTED}. - * @return Whether the value has been successfully set. - * @hide - */ - @SystemApi - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setPhonebookAccessPermission(@AccessPermission int value) { - final IBluetooth service = sService; - if (service == null) { - return false; - } - try { - return service.setPhonebookAccessPermission(this, value, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return false; - } - - /** - * Gets whether message access is allowed to this bluetooth device - * - * @return Whether the message access is allowed to this device. - * @hide - */ - @UnsupportedAppUsage - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public @AccessPermission int getMessageAccessPermission() { - final IBluetooth service = sService; - if (service == null) { - return ACCESS_UNKNOWN; - } - try { - return service.getMessageAccessPermission(this, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return ACCESS_UNKNOWN; - } - - /** - * Sets whether the message access is allowed to this device. - * - * @param value Can be {@link #ACCESS_UNKNOWN} if the device is unbonded, - * {@link #ACCESS_ALLOWED} if the permission is being granted, or {@link #ACCESS_REJECTED} if - * the permission is not being granted. - * @return Whether the value has been successfully set. - * @hide - */ - @SystemApi - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setMessageAccessPermission(@AccessPermission int value) { - // Validates param value is one of the accepted constants - if (value != ACCESS_ALLOWED && value != ACCESS_REJECTED && value != ACCESS_UNKNOWN) { - throw new IllegalArgumentException(value + "is not a valid AccessPermission value"); - } - final IBluetooth service = sService; - if (service == null) { - return false; - } - try { - return service.setMessageAccessPermission(this, value, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return false; - } - - /** - * Gets whether sim access is allowed for this bluetooth device - * - * @return Whether the Sim access is allowed to this device. - * @hide - */ - @SystemApi - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public @AccessPermission int getSimAccessPermission() { - final IBluetooth service = sService; - if (service == null) { - return ACCESS_UNKNOWN; - } - try { - return service.getSimAccessPermission(this, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return ACCESS_UNKNOWN; - } - - /** - * Sets whether the Sim access is allowed to this device. - * - * @param value Can be {@link #ACCESS_UNKNOWN} if the device is unbonded, - * {@link #ACCESS_ALLOWED} if the permission is being granted, or {@link #ACCESS_REJECTED} if - * the permission is not being granted. - * @return Whether the value has been successfully set. - * @hide - */ - @SystemApi - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setSimAccessPermission(int value) { - final IBluetooth service = sService; - if (service == null) { - return false; - } - try { - return service.setSimAccessPermission(this, value, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return false; - } - - /** - * Create an RFCOMM {@link BluetoothSocket} ready to start a secure - * outgoing connection to this remote device on given channel. - * <p>The remote device will be authenticated and communication on this - * socket will be encrypted. - * <p> Use this socket only if an authenticated socket link is possible. - * Authentication refers to the authentication of the link key to - * prevent person-in-the-middle type of attacks. - * For example, for Bluetooth 2.1 devices, if any of the devices does not - * have an input and output capability or just has the ability to - * display a numeric key, a secure socket connection is not possible. - * In such a case, use {@link createInsecureRfcommSocket}. - * For more details, refer to the Security Model section 5.2 (vol 3) of - * Bluetooth Core Specification version 2.1 + EDR. - * <p>Use {@link BluetoothSocket#connect} to initiate the outgoing - * connection. - * <p>Valid RFCOMM channels are in range 1 to 30. - * - * @param channel RFCOMM channel to connect to - * @return a RFCOMM BluetoothServerSocket ready for an outgoing connection - * @throws IOException on error, for example Bluetooth not available, or insufficient - * permissions - * @hide - */ - @UnsupportedAppUsage - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SuppressLint("AndroidFrameworkRequiresPermission") - public BluetoothSocket createRfcommSocket(int channel) throws IOException { - if (!isBluetoothEnabled()) { - Log.e(TAG, "Bluetooth is not enabled"); - throw new IOException(); - } - return new BluetoothSocket(BluetoothSocket.TYPE_RFCOMM, -1, true, true, this, channel, - null); - } - - /** - * Create an L2cap {@link BluetoothSocket} ready to start a secure - * outgoing connection to this remote device on given channel. - * <p>The remote device will be authenticated and communication on this - * socket will be encrypted. - * <p> Use this socket only if an authenticated socket link is possible. - * Authentication refers to the authentication of the link key to - * prevent person-in-the-middle type of attacks. - * For example, for Bluetooth 2.1 devices, if any of the devices does not - * have an input and output capability or just has the ability to - * display a numeric key, a secure socket connection is not possible. - * In such a case, use {@link createInsecureRfcommSocket}. - * For more details, refer to the Security Model section 5.2 (vol 3) of - * Bluetooth Core Specification version 2.1 + EDR. - * <p>Use {@link BluetoothSocket#connect} to initiate the outgoing - * connection. - * <p>Valid L2CAP PSM channels are in range 1 to 2^16. - * - * @param channel L2cap PSM/channel to connect to - * @return a RFCOMM BluetoothServerSocket ready for an outgoing connection - * @throws IOException on error, for example Bluetooth not available, or insufficient - * permissions - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SuppressLint("AndroidFrameworkRequiresPermission") - public BluetoothSocket createL2capSocket(int channel) throws IOException { - return new BluetoothSocket(BluetoothSocket.TYPE_L2CAP, -1, true, true, this, channel, - null); - } - - /** - * Create an L2cap {@link BluetoothSocket} ready to start an insecure - * outgoing connection to this remote device on given channel. - * <p>The remote device will be not authenticated and communication on this - * socket will not be encrypted. - * <p>Use {@link BluetoothSocket#connect} to initiate the outgoing - * connection. - * <p>Valid L2CAP PSM channels are in range 1 to 2^16. - * - * @param channel L2cap PSM/channel to connect to - * @return a RFCOMM BluetoothServerSocket ready for an outgoing connection - * @throws IOException on error, for example Bluetooth not available, or insufficient - * permissions - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SuppressLint("AndroidFrameworkRequiresPermission") - public BluetoothSocket createInsecureL2capSocket(int channel) throws IOException { - return new BluetoothSocket(BluetoothSocket.TYPE_L2CAP, -1, false, false, this, channel, - null); - } - - /** - * Create an RFCOMM {@link BluetoothSocket} ready to start a secure - * outgoing connection to this remote device using SDP lookup of uuid. - * <p>This is designed to be used with {@link - * BluetoothAdapter#listenUsingRfcommWithServiceRecord} for peer-peer - * Bluetooth applications. - * <p>Use {@link BluetoothSocket#connect} to initiate the outgoing - * connection. This will also perform an SDP lookup of the given uuid to - * determine which channel to connect to. - * <p>The remote device will be authenticated and communication on this - * socket will be encrypted. - * <p> Use this socket only if an authenticated socket link is possible. - * Authentication refers to the authentication of the link key to - * prevent person-in-the-middle type of attacks. - * For example, for Bluetooth 2.1 devices, if any of the devices does not - * have an input and output capability or just has the ability to - * display a numeric key, a secure socket connection is not possible. - * In such a case, use {@link #createInsecureRfcommSocketToServiceRecord}. - * For more details, refer to the Security Model section 5.2 (vol 3) of - * Bluetooth Core Specification version 2.1 + EDR. - * <p>Hint: If you are connecting to a Bluetooth serial board then try - * using the well-known SPP UUID 00001101-0000-1000-8000-00805F9B34FB. - * However if you are connecting to an Android peer then please generate - * your own unique UUID. - * - * @param uuid service record uuid to lookup RFCOMM channel - * @return a RFCOMM BluetoothServerSocket ready for an outgoing connection - * @throws IOException on error, for example Bluetooth not available, or insufficient - * permissions - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SuppressLint("AndroidFrameworkRequiresPermission") - public BluetoothSocket createRfcommSocketToServiceRecord(UUID uuid) throws IOException { - if (!isBluetoothEnabled()) { - Log.e(TAG, "Bluetooth is not enabled"); - throw new IOException(); - } - - return new BluetoothSocket(BluetoothSocket.TYPE_RFCOMM, -1, true, true, this, -1, - new ParcelUuid(uuid)); - } - - /** - * Create an RFCOMM {@link BluetoothSocket} socket ready to start an insecure - * outgoing connection to this remote device using SDP lookup of uuid. - * <p> The communication channel will not have an authenticated link key - * i.e it will be subject to person-in-the-middle attacks. For Bluetooth 2.1 - * devices, the link key will be encrypted, as encryption is mandatory. - * For legacy devices (pre Bluetooth 2.1 devices) the link key will - * be not be encrypted. Use {@link #createRfcommSocketToServiceRecord} if an - * encrypted and authenticated communication channel is desired. - * <p>This is designed to be used with {@link - * BluetoothAdapter#listenUsingInsecureRfcommWithServiceRecord} for peer-peer - * Bluetooth applications. - * <p>Use {@link BluetoothSocket#connect} to initiate the outgoing - * connection. This will also perform an SDP lookup of the given uuid to - * determine which channel to connect to. - * <p>The remote device will be authenticated and communication on this - * socket will be encrypted. - * <p>Hint: If you are connecting to a Bluetooth serial board then try - * using the well-known SPP UUID 00001101-0000-1000-8000-00805F9B34FB. - * However if you are connecting to an Android peer then please generate - * your own unique UUID. - * - * @param uuid service record uuid to lookup RFCOMM channel - * @return a RFCOMM BluetoothServerSocket ready for an outgoing connection - * @throws IOException on error, for example Bluetooth not available, or insufficient - * permissions - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SuppressLint("AndroidFrameworkRequiresPermission") - public BluetoothSocket createInsecureRfcommSocketToServiceRecord(UUID uuid) throws IOException { - if (!isBluetoothEnabled()) { - Log.e(TAG, "Bluetooth is not enabled"); - throw new IOException(); - } - return new BluetoothSocket(BluetoothSocket.TYPE_RFCOMM, -1, false, false, this, -1, - new ParcelUuid(uuid)); - } - - /** - * Construct an insecure RFCOMM socket ready to start an outgoing - * connection. - * Call #connect on the returned #BluetoothSocket to begin the connection. - * The remote device will not be authenticated and communication on this - * socket will not be encrypted. - * - * @param port remote port - * @return An RFCOMM BluetoothSocket - * @throws IOException On error, for example Bluetooth not available, or insufficient - * permissions. - * @hide - */ - @UnsupportedAppUsage(publicAlternatives = "Use " - + "{@link #createInsecureRfcommSocketToServiceRecord} instead.") - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SuppressLint("AndroidFrameworkRequiresPermission") - public BluetoothSocket createInsecureRfcommSocket(int port) throws IOException { - if (!isBluetoothEnabled()) { - Log.e(TAG, "Bluetooth is not enabled"); - throw new IOException(); - } - return new BluetoothSocket(BluetoothSocket.TYPE_RFCOMM, -1, false, false, this, port, - null); - } - - /** - * Construct a SCO socket ready to start an outgoing connection. - * Call #connect on the returned #BluetoothSocket to begin the connection. - * - * @return a SCO BluetoothSocket - * @throws IOException on error, for example Bluetooth not available, or insufficient - * permissions. - * @hide - */ - @UnsupportedAppUsage - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SuppressLint("AndroidFrameworkRequiresPermission") - public BluetoothSocket createScoSocket() throws IOException { - if (!isBluetoothEnabled()) { - Log.e(TAG, "Bluetooth is not enabled"); - throw new IOException(); - } - return new BluetoothSocket(BluetoothSocket.TYPE_SCO, -1, true, true, this, -1, null); - } - - /** - * Check that a pin is valid and convert to byte array. - * - * Bluetooth pin's are 1 to 16 bytes of UTF-8 characters. - * - * @param pin pin as java String - * @return the pin code as a UTF-8 byte array, or null if it is an invalid Bluetooth pin. - * @hide - */ - @UnsupportedAppUsage - public static byte[] convertPinToBytes(String pin) { - if (pin == null) { - return null; - } - byte[] pinBytes; - try { - pinBytes = pin.getBytes("UTF-8"); - } catch (UnsupportedEncodingException uee) { - Log.e(TAG, "UTF-8 not supported?!?"); // this should not happen - return null; - } - if (pinBytes.length <= 0 || pinBytes.length > 16) { - return null; - } - return pinBytes; - } - - /** - * Connect to GATT Server hosted by this device. Caller acts as GATT client. - * The callback is used to deliver results to Caller, such as connection status as well - * as any further GATT client operations. - * The method returns a BluetoothGatt instance. You can use BluetoothGatt to conduct - * GATT client operations. - * - * @param callback GATT callback handler that will receive asynchronous callbacks. - * @param autoConnect Whether to directly connect to the remote device (false) or to - * automatically connect as soon as the remote device becomes available (true). - * @throws IllegalArgumentException if callback is null - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothGatt connectGatt(Context context, boolean autoConnect, - BluetoothGattCallback callback) { - return (connectGatt(context, autoConnect, callback, TRANSPORT_AUTO)); - } - - /** - * Connect to GATT Server hosted by this device. Caller acts as GATT client. - * The callback is used to deliver results to Caller, such as connection status as well - * as any further GATT client operations. - * The method returns a BluetoothGatt instance. You can use BluetoothGatt to conduct - * GATT client operations. - * - * @param callback GATT callback handler that will receive asynchronous callbacks. - * @param autoConnect Whether to directly connect to the remote device (false) or to - * automatically connect as soon as the remote device becomes available (true). - * @param transport preferred transport for GATT connections to remote dual-mode devices {@link - * BluetoothDevice#TRANSPORT_AUTO} or {@link BluetoothDevice#TRANSPORT_BREDR} or {@link - * BluetoothDevice#TRANSPORT_LE} - * @throws IllegalArgumentException if callback is null - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothGatt connectGatt(Context context, boolean autoConnect, - BluetoothGattCallback callback, int transport) { - return (connectGatt(context, autoConnect, callback, transport, PHY_LE_1M_MASK)); - } - - /** - * Connect to GATT Server hosted by this device. Caller acts as GATT client. - * The callback is used to deliver results to Caller, such as connection status as well - * as any further GATT client operations. - * The method returns a BluetoothGatt instance. You can use BluetoothGatt to conduct - * GATT client operations. - * - * @param callback GATT callback handler that will receive asynchronous callbacks. - * @param autoConnect Whether to directly connect to the remote device (false) or to - * automatically connect as soon as the remote device becomes available (true). - * @param transport preferred transport for GATT connections to remote dual-mode devices {@link - * BluetoothDevice#TRANSPORT_AUTO} or {@link BluetoothDevice#TRANSPORT_BREDR} or {@link - * BluetoothDevice#TRANSPORT_LE} - * @param phy preferred PHY for connections to remote LE device. Bitwise OR of any of {@link - * BluetoothDevice#PHY_LE_1M_MASK}, {@link BluetoothDevice#PHY_LE_2M_MASK}, and {@link - * BluetoothDevice#PHY_LE_CODED_MASK}. This option does not take effect if {@code autoConnect} - * is set to true. - * @throws NullPointerException if callback is null - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothGatt connectGatt(Context context, boolean autoConnect, - BluetoothGattCallback callback, int transport, int phy) { - return connectGatt(context, autoConnect, callback, transport, phy, null); - } - - /** - * Connect to GATT Server hosted by this device. Caller acts as GATT client. - * The callback is used to deliver results to Caller, such as connection status as well - * as any further GATT client operations. - * The method returns a BluetoothGatt instance. You can use BluetoothGatt to conduct - * GATT client operations. - * - * @param callback GATT callback handler that will receive asynchronous callbacks. - * @param autoConnect Whether to directly connect to the remote device (false) or to - * automatically connect as soon as the remote device becomes available (true). - * @param transport preferred transport for GATT connections to remote dual-mode devices {@link - * BluetoothDevice#TRANSPORT_AUTO} or {@link BluetoothDevice#TRANSPORT_BREDR} or {@link - * BluetoothDevice#TRANSPORT_LE} - * @param phy preferred PHY for connections to remote LE device. Bitwise OR of any of {@link - * BluetoothDevice#PHY_LE_1M_MASK}, {@link BluetoothDevice#PHY_LE_2M_MASK}, an d{@link - * BluetoothDevice#PHY_LE_CODED_MASK}. This option does not take effect if {@code autoConnect} - * is set to true. - * @param handler The handler to use for the callback. If {@code null}, callbacks will happen on - * an un-specified background thread. - * @throws NullPointerException if callback is null - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothGatt connectGatt(Context context, boolean autoConnect, - BluetoothGattCallback callback, int transport, int phy, - Handler handler) { - return connectGatt(context, autoConnect, callback, transport, false, phy, handler); - } - - /** - * Connect to GATT Server hosted by this device. Caller acts as GATT client. - * The callback is used to deliver results to Caller, such as connection status as well - * as any further GATT client operations. - * The method returns a BluetoothGatt instance. You can use BluetoothGatt to conduct - * GATT client operations. - * - * @param callback GATT callback handler that will receive asynchronous callbacks. - * @param autoConnect Whether to directly connect to the remote device (false) or to - * automatically connect as soon as the remote device becomes available (true). - * @param transport preferred transport for GATT connections to remote dual-mode devices {@link - * BluetoothDevice#TRANSPORT_AUTO} or {@link BluetoothDevice#TRANSPORT_BREDR} or {@link - * BluetoothDevice#TRANSPORT_LE} - * @param opportunistic Whether this GATT client is opportunistic. An opportunistic GATT client - * does not hold a GATT connection. It automatically disconnects when no other GATT connections - * are active for the remote device. - * @param phy preferred PHY for connections to remote LE device. Bitwise OR of any of {@link - * BluetoothDevice#PHY_LE_1M_MASK}, {@link BluetoothDevice#PHY_LE_2M_MASK}, an d{@link - * BluetoothDevice#PHY_LE_CODED_MASK}. This option does not take effect if {@code autoConnect} - * is set to true. - * @param handler The handler to use for the callback. If {@code null}, callbacks will happen on - * an un-specified background thread. - * @return A BluetoothGatt instance. You can use BluetoothGatt to conduct GATT client - * operations. - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothGatt connectGatt(Context context, boolean autoConnect, - BluetoothGattCallback callback, int transport, - boolean opportunistic, int phy, Handler handler) { - if (callback == null) { - throw new NullPointerException("callback is null"); - } - - // TODO(Bluetooth) check whether platform support BLE - // Do the check here or in GattServer? - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - IBluetoothManager managerService = adapter.getBluetoothManager(); - try { - IBluetoothGatt iGatt = managerService.getBluetoothGatt(); - if (iGatt == null) { - // BLE is not supported - return null; - } - BluetoothGatt gatt = new BluetoothGatt( - iGatt, this, transport, opportunistic, phy, mAttributionSource); - gatt.connect(autoConnect, callback, handler); - return gatt; - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - return null; - } - - /** - * Create a Bluetooth L2CAP Connection-oriented Channel (CoC) {@link BluetoothSocket} that can - * be used to start a secure outgoing connection to the remote device with the same dynamic - * protocol/service multiplexer (PSM) value. The supported Bluetooth transport is LE only. - * <p>This is designed to be used with {@link BluetoothAdapter#listenUsingL2capChannel()} for - * peer-peer Bluetooth applications. - * <p>Use {@link BluetoothSocket#connect} to initiate the outgoing connection. - * <p>Application using this API is responsible for obtaining PSM value from remote device. - * <p>The remote device will be authenticated and communication on this socket will be - * encrypted. - * <p> Use this socket if an authenticated socket link is possible. Authentication refers - * to the authentication of the link key to prevent person-in-the-middle type of attacks. - * - * @param psm dynamic PSM value from remote device - * @return a CoC #BluetoothSocket ready for an outgoing connection - * @throws IOException on error, for example Bluetooth not available, or insufficient - * permissions - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SuppressLint("AndroidFrameworkRequiresPermission") - public @NonNull BluetoothSocket createL2capChannel(int psm) throws IOException { - if (!isBluetoothEnabled()) { - Log.e(TAG, "createL2capChannel: Bluetooth is not enabled"); - throw new IOException(); - } - if (DBG) Log.d(TAG, "createL2capChannel: psm=" + psm); - return new BluetoothSocket(BluetoothSocket.TYPE_L2CAP_LE, -1, true, true, this, psm, - null); - } - - /** - * Create a Bluetooth L2CAP Connection-oriented Channel (CoC) {@link BluetoothSocket} that can - * be used to start a secure outgoing connection to the remote device with the same dynamic - * protocol/service multiplexer (PSM) value. The supported Bluetooth transport is LE only. - * <p>This is designed to be used with {@link - * BluetoothAdapter#listenUsingInsecureL2capChannel()} for peer-peer Bluetooth applications. - * <p>Use {@link BluetoothSocket#connect} to initiate the outgoing connection. - * <p>Application using this API is responsible for obtaining PSM value from remote device. - * <p> The communication channel may not have an authenticated link key, i.e. it may be subject - * to person-in-the-middle attacks. Use {@link #createL2capChannel(int)} if an encrypted and - * authenticated communication channel is possible. - * - * @param psm dynamic PSM value from remote device - * @return a CoC #BluetoothSocket ready for an outgoing connection - * @throws IOException on error, for example Bluetooth not available, or insufficient - * permissions - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SuppressLint("AndroidFrameworkRequiresPermission") - public @NonNull BluetoothSocket createInsecureL2capChannel(int psm) throws IOException { - if (!isBluetoothEnabled()) { - Log.e(TAG, "createInsecureL2capChannel: Bluetooth is not enabled"); - throw new IOException(); - } - if (DBG) { - Log.d(TAG, "createInsecureL2capChannel: psm=" + psm); - } - return new BluetoothSocket(BluetoothSocket.TYPE_L2CAP_LE, -1, false, false, this, psm, - null); - } - - /** - * Set a keyed metadata of this {@link BluetoothDevice} to a - * {@link String} value. - * Only bonded devices's metadata will be persisted across Bluetooth - * restart. - * Metadata will be removed when the device's bond state is moved to - * {@link #BOND_NONE}. - * - * @param key must be within the list of BluetoothDevice.METADATA_* - * @param value a byte array data to set for key. Must be less than - * {@link BluetoothAdapter#METADATA_MAX_LENGTH} characters in length - * @return true on success, false on error - * @hide - */ - @SystemApi - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setMetadata(@MetadataKey int key, @NonNull byte[] value) { - final IBluetooth service = sService; - if (service == null) { - Log.e(TAG, "Bluetooth is not enabled. Cannot set metadata"); - return false; - } - if (value.length > METADATA_MAX_LENGTH) { - throw new IllegalArgumentException("value length is " + value.length - + ", should not over " + METADATA_MAX_LENGTH); - } - try { - return service.setMetadata(this, key, value, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "setMetadata fail", e); - return false; - } - } - - /** - * Get a keyed metadata for this {@link BluetoothDevice} as {@link String} - * - * @param key must be within the list of BluetoothDevice.METADATA_* - * @return Metadata of the key as byte array, null on error or not found - * @hide - */ - @SystemApi - @Nullable - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public byte[] getMetadata(@MetadataKey int key) { - final IBluetooth service = sService; - if (service == null) { - Log.e(TAG, "Bluetooth is not enabled. Cannot get metadata"); - return null; - } - try { - return service.getMetadata(this, key, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "getMetadata fail", e); - return null; - } - } - - /** - * Get the maxinum metadata key ID. - * - * @return the last supported metadata key - * @hide - */ - public static @MetadataKey int getMaxMetadataKey() { - return METADATA_UNTETHERED_CASE_LOW_BATTERY_THRESHOLD; - } -} diff --git a/core/java/android/bluetooth/BluetoothDevicePicker.java b/core/java/android/bluetooth/BluetoothDevicePicker.java deleted file mode 100644 index 26e46573dd95..000000000000 --- a/core/java/android/bluetooth/BluetoothDevicePicker.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2009 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.bluetooth; - -import android.annotation.RequiresPermission; -import android.annotation.SdkConstant; -import android.annotation.SdkConstant.SdkConstantType; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; - -/** - * A helper to show a system "Device Picker" activity to the user. - * - * @hide - */ -public interface BluetoothDevicePicker { - public static final String EXTRA_NEED_AUTH = - "android.bluetooth.devicepicker.extra.NEED_AUTH"; - public static final String EXTRA_FILTER_TYPE = - "android.bluetooth.devicepicker.extra.FILTER_TYPE"; - public static final String EXTRA_LAUNCH_PACKAGE = - "android.bluetooth.devicepicker.extra.LAUNCH_PACKAGE"; - public static final String EXTRA_LAUNCH_CLASS = - "android.bluetooth.devicepicker.extra.DEVICE_PICKER_LAUNCH_CLASS"; - - /** - * Broadcast when one BT device is selected from BT device picker screen. - * Selected {@link BluetoothDevice} is returned in extra data named - * {@link BluetoothDevice#EXTRA_DEVICE}. - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_DEVICE_SELECTED = - "android.bluetooth.devicepicker.action.DEVICE_SELECTED"; - - /** - * Broadcast when someone want to select one BT device from devices list. - * This intent contains below extra data: - * - {@link #EXTRA_NEED_AUTH} (boolean): if need authentication - * - {@link #EXTRA_FILTER_TYPE} (int): what kinds of device should be - * listed - * - {@link #EXTRA_LAUNCH_PACKAGE} (string): where(which package) this - * intent come from - * - {@link #EXTRA_LAUNCH_CLASS} (string): where(which class) this intent - * come from - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_LAUNCH = - "android.bluetooth.devicepicker.action.LAUNCH"; - - /** Ask device picker to show all kinds of BT devices */ - public static final int FILTER_TYPE_ALL = 0; - /** Ask device picker to show BT devices that support AUDIO profiles */ - public static final int FILTER_TYPE_AUDIO = 1; - /** Ask device picker to show BT devices that support Object Transfer */ - public static final int FILTER_TYPE_TRANSFER = 2; - /** - * Ask device picker to show BT devices that support - * Personal Area Networking User (PANU) profile - */ - public static final int FILTER_TYPE_PANU = 3; - /** Ask device picker to show BT devices that support Network Access Point (NAP) profile */ - public static final int FILTER_TYPE_NAP = 4; -} diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java deleted file mode 100644 index b531829d2940..000000000000 --- a/core/java/android/bluetooth/BluetoothGatt.java +++ /dev/null @@ -1,1848 +0,0 @@ -/* - * Copyright (C) 2013 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.bluetooth; - -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.RequiresNoPermission; -import android.annotation.RequiresPermission; -import android.annotation.SuppressLint; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; -import android.compat.annotation.UnsupportedAppUsage; -import android.content.AttributionSource; -import android.os.Build; -import android.os.Handler; -import android.os.ParcelUuid; -import android.os.RemoteException; -import android.util.Log; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - -/** - * Public API for the Bluetooth GATT Profile. - * - * <p>This class provides Bluetooth GATT functionality to enable communication - * with Bluetooth Smart or Smart Ready devices. - * - * <p>To connect to a remote peripheral device, create a {@link BluetoothGattCallback} - * and call {@link BluetoothDevice#connectGatt} to get a instance of this class. - * GATT capable devices can be discovered using the Bluetooth device discovery or BLE - * scan process. - */ -public final class BluetoothGatt implements BluetoothProfile { - private static final String TAG = "BluetoothGatt"; - private static final boolean DBG = true; - private static final boolean VDBG = false; - - @UnsupportedAppUsage - private IBluetoothGatt mService; - @UnsupportedAppUsage - private volatile BluetoothGattCallback mCallback; - private Handler mHandler; - @UnsupportedAppUsage - private int mClientIf; - private BluetoothDevice mDevice; - @UnsupportedAppUsage - private boolean mAutoConnect; - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) - private int mAuthRetryState; - private int mConnState; - private final Object mStateLock = new Object(); - private final Object mDeviceBusyLock = new Object(); - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - private Boolean mDeviceBusy = false; - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - private int mTransport; - private int mPhy; - private boolean mOpportunistic; - private final AttributionSource mAttributionSource; - - private static final int AUTH_RETRY_STATE_IDLE = 0; - private static final int AUTH_RETRY_STATE_NO_MITM = 1; - private static final int AUTH_RETRY_STATE_MITM = 2; - - private static final int CONN_STATE_IDLE = 0; - private static final int CONN_STATE_CONNECTING = 1; - private static final int CONN_STATE_CONNECTED = 2; - private static final int CONN_STATE_DISCONNECTING = 3; - private static final int CONN_STATE_CLOSED = 4; - - private static final int WRITE_CHARACTERISTIC_MAX_RETRIES = 5; - private static final int WRITE_CHARACTERISTIC_TIME_TO_WAIT = 10; // milliseconds - - private List<BluetoothGattService> mServices; - - /** A GATT operation completed successfully */ - public static final int GATT_SUCCESS = 0; - - /** GATT read operation is not permitted */ - public static final int GATT_READ_NOT_PERMITTED = 0x2; - - /** GATT write operation is not permitted */ - public static final int GATT_WRITE_NOT_PERMITTED = 0x3; - - /** Insufficient authentication for a given operation */ - public static final int GATT_INSUFFICIENT_AUTHENTICATION = 0x5; - - /** The given request is not supported */ - public static final int GATT_REQUEST_NOT_SUPPORTED = 0x6; - - /** Insufficient encryption for a given operation */ - public static final int GATT_INSUFFICIENT_ENCRYPTION = 0xf; - - /** A read or write operation was requested with an invalid offset */ - public static final int GATT_INVALID_OFFSET = 0x7; - - /** Insufficient authorization for a given operation */ - public static final int GATT_INSUFFICIENT_AUTHORIZATION = 0x8; - - /** A write operation exceeds the maximum length of the attribute */ - public static final int GATT_INVALID_ATTRIBUTE_LENGTH = 0xd; - - /** A remote device connection is congested. */ - public static final int GATT_CONNECTION_CONGESTED = 0x8f; - - /** A GATT operation failed, errors other than the above */ - public static final int GATT_FAILURE = 0x101; - - /** - * Connection parameter update - Use the connection parameters recommended by the - * Bluetooth SIG. This is the default value if no connection parameter update - * is requested. - */ - public static final int CONNECTION_PRIORITY_BALANCED = 0; - - /** - * Connection parameter update - Request a high priority, low latency connection. - * An application should only request high priority connection parameters to transfer large - * amounts of data over LE quickly. Once the transfer is complete, the application should - * request {@link BluetoothGatt#CONNECTION_PRIORITY_BALANCED} connection parameters to reduce - * energy use. - */ - public static final int CONNECTION_PRIORITY_HIGH = 1; - - /** Connection parameter update - Request low power, reduced data rate connection parameters. */ - public static final int CONNECTION_PRIORITY_LOW_POWER = 2; - - /** - * No authentication required. - * - * @hide - */ - /*package*/ static final int AUTHENTICATION_NONE = 0; - - /** - * Authentication requested; no person-in-the-middle protection required. - * - * @hide - */ - /*package*/ static final int AUTHENTICATION_NO_MITM = 1; - - /** - * Authentication with person-in-the-middle protection requested. - * - * @hide - */ - /*package*/ static final int AUTHENTICATION_MITM = 2; - - /** - * Bluetooth GATT callbacks. Overrides the default BluetoothGattCallback implementation. - */ - @SuppressLint("AndroidFrameworkBluetoothPermission") - private final IBluetoothGattCallback mBluetoothGattCallback = - new IBluetoothGattCallback.Stub() { - /** - * Application interface registered - app is ready to go - * @hide - */ - @Override - @SuppressLint("AndroidFrameworkRequiresPermission") - public void onClientRegistered(int status, int clientIf) { - if (DBG) { - Log.d(TAG, "onClientRegistered() - status=" + status - + " clientIf=" + clientIf); - } - if (VDBG) { - synchronized (mStateLock) { - if (mConnState != CONN_STATE_CONNECTING) { - Log.e(TAG, "Bad connection state: " + mConnState); - } - } - } - mClientIf = clientIf; - if (status != GATT_SUCCESS) { - runOrQueueCallback(new Runnable() { - @Override - public void run() { - final BluetoothGattCallback callback = mCallback; - if (callback != null) { - callback.onConnectionStateChange(BluetoothGatt.this, - GATT_FAILURE, - BluetoothProfile.STATE_DISCONNECTED); - } - } - }); - - synchronized (mStateLock) { - mConnState = CONN_STATE_IDLE; - } - return; - } - try { - mService.clientConnect(mClientIf, mDevice.getAddress(), - !mAutoConnect, mTransport, mOpportunistic, - mPhy, mAttributionSource); // autoConnect is inverse of "isDirect" - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - - /** - * Phy update callback - * @hide - */ - @Override - public void onPhyUpdate(String address, int txPhy, int rxPhy, int status) { - if (DBG) { - Log.d(TAG, "onPhyUpdate() - status=" + status - + " address=" + address + " txPhy=" + txPhy + " rxPhy=" + rxPhy); - } - if (!address.equals(mDevice.getAddress())) { - return; - } - - runOrQueueCallback(new Runnable() { - @Override - public void run() { - final BluetoothGattCallback callback = mCallback; - if (callback != null) { - callback.onPhyUpdate(BluetoothGatt.this, txPhy, rxPhy, status); - } - } - }); - } - - /** - * Phy read callback - * @hide - */ - @Override - public void onPhyRead(String address, int txPhy, int rxPhy, int status) { - if (DBG) { - Log.d(TAG, "onPhyRead() - status=" + status - + " address=" + address + " txPhy=" + txPhy + " rxPhy=" + rxPhy); - } - if (!address.equals(mDevice.getAddress())) { - return; - } - - runOrQueueCallback(new Runnable() { - @Override - public void run() { - final BluetoothGattCallback callback = mCallback; - if (callback != null) { - callback.onPhyRead(BluetoothGatt.this, txPhy, rxPhy, status); - } - } - }); - } - - /** - * Client connection state changed - * @hide - */ - @Override - public void onClientConnectionState(int status, int clientIf, - boolean connected, String address) { - if (DBG) { - Log.d(TAG, "onClientConnectionState() - status=" + status - + " clientIf=" + clientIf + " device=" + address); - } - if (!address.equals(mDevice.getAddress())) { - return; - } - int profileState = connected ? BluetoothProfile.STATE_CONNECTED : - BluetoothProfile.STATE_DISCONNECTED; - - runOrQueueCallback(new Runnable() { - @Override - public void run() { - final BluetoothGattCallback callback = mCallback; - if (callback != null) { - callback.onConnectionStateChange(BluetoothGatt.this, status, - profileState); - } - } - }); - - synchronized (mStateLock) { - if (connected) { - mConnState = CONN_STATE_CONNECTED; - } else { - mConnState = CONN_STATE_IDLE; - } - } - - synchronized (mDeviceBusyLock) { - mDeviceBusy = false; - } - } - - /** - * Remote search has been completed. - * The internal object structure should now reflect the state - * of the remote device database. Let the application know that - * we are done at this point. - * @hide - */ - @Override - public void onSearchComplete(String address, List<BluetoothGattService> services, - int status) { - if (DBG) { - Log.d(TAG, - "onSearchComplete() = Device=" + address + " Status=" + status); - } - if (!address.equals(mDevice.getAddress())) { - return; - } - - for (BluetoothGattService s : services) { - //services we receive don't have device set properly. - s.setDevice(mDevice); - } - - mServices.addAll(services); - - // Fix references to included services, as they doesn't point to right objects. - for (BluetoothGattService fixedService : mServices) { - ArrayList<BluetoothGattService> includedServices = - new ArrayList(fixedService.getIncludedServices()); - fixedService.getIncludedServices().clear(); - - for (BluetoothGattService brokenRef : includedServices) { - BluetoothGattService includedService = getService(mDevice, - brokenRef.getUuid(), brokenRef.getInstanceId()); - if (includedService != null) { - fixedService.addIncludedService(includedService); - } else { - Log.e(TAG, "Broken GATT database: can't find included service."); - } - } - } - - runOrQueueCallback(new Runnable() { - @Override - public void run() { - final BluetoothGattCallback callback = mCallback; - if (callback != null) { - callback.onServicesDiscovered(BluetoothGatt.this, status); - } - } - }); - } - - /** - * Remote characteristic has been read. - * Updates the internal value. - * @hide - */ - @Override - @SuppressLint("AndroidFrameworkRequiresPermission") - public void onCharacteristicRead(String address, int status, int handle, - byte[] value) { - if (VDBG) { - Log.d(TAG, "onCharacteristicRead() - Device=" + address - + " handle=" + handle + " Status=" + status); - } - - if (!address.equals(mDevice.getAddress())) { - return; - } - - synchronized (mDeviceBusyLock) { - mDeviceBusy = false; - } - - if ((status == GATT_INSUFFICIENT_AUTHENTICATION - || status == GATT_INSUFFICIENT_ENCRYPTION) - && (mAuthRetryState != AUTH_RETRY_STATE_MITM)) { - try { - final int authReq = (mAuthRetryState == AUTH_RETRY_STATE_IDLE) - ? AUTHENTICATION_NO_MITM : AUTHENTICATION_MITM; - mService.readCharacteristic( - mClientIf, address, handle, authReq, mAttributionSource); - mAuthRetryState++; - return; - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - - mAuthRetryState = AUTH_RETRY_STATE_IDLE; - - BluetoothGattCharacteristic characteristic = getCharacteristicById(mDevice, - handle); - if (characteristic == null) { - Log.w(TAG, "onCharacteristicRead() failed to find characteristic!"); - return; - } - - runOrQueueCallback(new Runnable() { - @Override - public void run() { - final BluetoothGattCallback callback = mCallback; - if (callback != null) { - if (status == 0) characteristic.setValue(value); - callback.onCharacteristicRead(BluetoothGatt.this, characteristic, - value, status); - // Keep calling deprecated callback to maintain app compatibility - callback.onCharacteristicRead(BluetoothGatt.this, characteristic, - status); - } - } - }); - } - - /** - * Characteristic has been written to the remote device. - * Let the app know how we did... - * @hide - */ - @Override - @SuppressLint("AndroidFrameworkRequiresPermission") - public void onCharacteristicWrite(String address, int status, int handle, - byte[] value) { - if (VDBG) { - Log.d(TAG, "onCharacteristicWrite() - Device=" + address - + " handle=" + handle + " Status=" + status); - } - - if (!address.equals(mDevice.getAddress())) { - return; - } - - synchronized (mDeviceBusyLock) { - mDeviceBusy = false; - } - - BluetoothGattCharacteristic characteristic = getCharacteristicById(mDevice, - handle); - if (characteristic == null) return; - - if ((status == GATT_INSUFFICIENT_AUTHENTICATION - || status == GATT_INSUFFICIENT_ENCRYPTION) - && (mAuthRetryState != AUTH_RETRY_STATE_MITM)) { - try { - final int authReq = (mAuthRetryState == AUTH_RETRY_STATE_IDLE) - ? AUTHENTICATION_NO_MITM : AUTHENTICATION_MITM; - int requestStatus = BluetoothStatusCodes.ERROR_UNKNOWN; - for (int i = 0; i < WRITE_CHARACTERISTIC_MAX_RETRIES; i++) { - requestStatus = mService.writeCharacteristic(mClientIf, address, - handle, characteristic.getWriteType(), authReq, - value, mAttributionSource); - if (requestStatus - != BluetoothStatusCodes.ERROR_GATT_WRITE_REQUEST_BUSY) { - break; - } - try { - Thread.sleep(WRITE_CHARACTERISTIC_TIME_TO_WAIT); - } catch (InterruptedException e) { - } - } - mAuthRetryState++; - return; - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - - mAuthRetryState = AUTH_RETRY_STATE_IDLE; - runOrQueueCallback(new Runnable() { - @Override - public void run() { - final BluetoothGattCallback callback = mCallback; - if (callback != null) { - callback.onCharacteristicWrite(BluetoothGatt.this, characteristic, - status); - } - } - }); - } - - /** - * Remote characteristic has been updated. - * Updates the internal value. - * @hide - */ - @Override - public void onNotify(String address, int handle, byte[] value) { - if (VDBG) Log.d(TAG, "onNotify() - Device=" + address + " handle=" + handle); - - if (!address.equals(mDevice.getAddress())) { - return; - } - - BluetoothGattCharacteristic characteristic = getCharacteristicById(mDevice, - handle); - if (characteristic == null) return; - - runOrQueueCallback(new Runnable() { - @Override - public void run() { - final BluetoothGattCallback callback = mCallback; - if (callback != null) { - characteristic.setValue(value); - callback.onCharacteristicChanged(BluetoothGatt.this, - characteristic, value); - // Keep calling deprecated callback to maintain app compatibility - callback.onCharacteristicChanged(BluetoothGatt.this, - characteristic); - } - } - }); - } - - /** - * Descriptor has been read. - * @hide - */ - @Override - @SuppressLint("AndroidFrameworkRequiresPermission") - public void onDescriptorRead(String address, int status, int handle, byte[] value) { - if (VDBG) { - Log.d(TAG, - "onDescriptorRead() - Device=" + address + " handle=" + handle); - } - - if (!address.equals(mDevice.getAddress())) { - return; - } - - synchronized (mDeviceBusyLock) { - mDeviceBusy = false; - } - - BluetoothGattDescriptor descriptor = getDescriptorById(mDevice, handle); - if (descriptor == null) return; - - - if ((status == GATT_INSUFFICIENT_AUTHENTICATION - || status == GATT_INSUFFICIENT_ENCRYPTION) - && (mAuthRetryState != AUTH_RETRY_STATE_MITM)) { - try { - final int authReq = (mAuthRetryState == AUTH_RETRY_STATE_IDLE) - ? AUTHENTICATION_NO_MITM : AUTHENTICATION_MITM; - mService.readDescriptor( - mClientIf, address, handle, authReq, mAttributionSource); - mAuthRetryState++; - return; - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - - mAuthRetryState = AUTH_RETRY_STATE_IDLE; - - runOrQueueCallback(new Runnable() { - @Override - public void run() { - final BluetoothGattCallback callback = mCallback; - if (callback != null) { - if (status == 0) descriptor.setValue(value); - callback.onDescriptorRead(BluetoothGatt.this, descriptor, status, - value); - // Keep calling deprecated callback to maintain app compatibility - callback.onDescriptorRead(BluetoothGatt.this, descriptor, status); - } - } - }); - } - - /** - * Descriptor write operation complete. - * @hide - */ - @Override - @SuppressLint("AndroidFrameworkRequiresPermission") - public void onDescriptorWrite(String address, int status, int handle, - byte[] value) { - if (VDBG) { - Log.d(TAG, - "onDescriptorWrite() - Device=" + address + " handle=" + handle); - } - - if (!address.equals(mDevice.getAddress())) { - return; - } - - synchronized (mDeviceBusyLock) { - mDeviceBusy = false; - } - - BluetoothGattDescriptor descriptor = getDescriptorById(mDevice, handle); - if (descriptor == null) return; - - if ((status == GATT_INSUFFICIENT_AUTHENTICATION - || status == GATT_INSUFFICIENT_ENCRYPTION) - && (mAuthRetryState != AUTH_RETRY_STATE_MITM)) { - try { - final int authReq = (mAuthRetryState == AUTH_RETRY_STATE_IDLE) - ? AUTHENTICATION_NO_MITM : AUTHENTICATION_MITM; - mService.writeDescriptor(mClientIf, address, handle, - authReq, value, mAttributionSource); - mAuthRetryState++; - return; - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - - mAuthRetryState = AUTH_RETRY_STATE_IDLE; - - runOrQueueCallback(new Runnable() { - @Override - public void run() { - final BluetoothGattCallback callback = mCallback; - if (callback != null) { - callback.onDescriptorWrite(BluetoothGatt.this, descriptor, status); - } - } - }); - } - - /** - * Prepared write transaction completed (or aborted) - * @hide - */ - @Override - public void onExecuteWrite(String address, int status) { - if (VDBG) { - Log.d(TAG, "onExecuteWrite() - Device=" + address - + " status=" + status); - } - if (!address.equals(mDevice.getAddress())) { - return; - } - - synchronized (mDeviceBusyLock) { - mDeviceBusy = false; - } - - runOrQueueCallback(new Runnable() { - @Override - public void run() { - final BluetoothGattCallback callback = mCallback; - if (callback != null) { - callback.onReliableWriteCompleted(BluetoothGatt.this, status); - } - } - }); - } - - /** - * Remote device RSSI has been read - * @hide - */ - @Override - public void onReadRemoteRssi(String address, int rssi, int status) { - if (VDBG) { - Log.d(TAG, "onReadRemoteRssi() - Device=" + address - + " rssi=" + rssi + " status=" + status); - } - if (!address.equals(mDevice.getAddress())) { - return; - } - runOrQueueCallback(new Runnable() { - @Override - public void run() { - final BluetoothGattCallback callback = mCallback; - if (callback != null) { - callback.onReadRemoteRssi(BluetoothGatt.this, rssi, status); - } - } - }); - } - - /** - * Callback invoked when the MTU for a given connection changes - * @hide - */ - @Override - public void onConfigureMTU(String address, int mtu, int status) { - if (DBG) { - Log.d(TAG, "onConfigureMTU() - Device=" + address - + " mtu=" + mtu + " status=" + status); - } - if (!address.equals(mDevice.getAddress())) { - return; - } - - runOrQueueCallback(new Runnable() { - @Override - public void run() { - final BluetoothGattCallback callback = mCallback; - if (callback != null) { - callback.onMtuChanged(BluetoothGatt.this, mtu, status); - } - } - }); - } - - /** - * Callback invoked when the given connection is updated - * @hide - */ - @Override - public void onConnectionUpdated(String address, int interval, int latency, - int timeout, int status) { - if (DBG) { - Log.d(TAG, "onConnectionUpdated() - Device=" + address - + " interval=" + interval + " latency=" + latency - + " timeout=" + timeout + " status=" + status); - } - if (!address.equals(mDevice.getAddress())) { - return; - } - - runOrQueueCallback(new Runnable() { - @Override - public void run() { - final BluetoothGattCallback callback = mCallback; - if (callback != null) { - callback.onConnectionUpdated(BluetoothGatt.this, interval, latency, - timeout, status); - } - } - }); - } - - /** - * Callback invoked when service changed event is received - * @hide - */ - @Override - public void onServiceChanged(String address) { - if (DBG) { - Log.d(TAG, "onServiceChanged() - Device=" + address); - } - - if (!address.equals(mDevice.getAddress())) { - return; - } - - runOrQueueCallback(new Runnable() { - @Override - public void run() { - final BluetoothGattCallback callback = mCallback; - if (callback != null) { - callback.onServiceChanged(BluetoothGatt.this); - } - } - }); - } - }; - - /* package */ BluetoothGatt(IBluetoothGatt iGatt, BluetoothDevice device, int transport, - boolean opportunistic, int phy, AttributionSource attributionSource) { - mService = iGatt; - mDevice = device; - mTransport = transport; - mPhy = phy; - mOpportunistic = opportunistic; - mAttributionSource = attributionSource; - mServices = new ArrayList<BluetoothGattService>(); - - mConnState = CONN_STATE_IDLE; - mAuthRetryState = AUTH_RETRY_STATE_IDLE; - } - - /** - * Close this Bluetooth GATT client. - * - * Application should call this method as early as possible after it is done with - * this GATT client. - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public void close() { - if (DBG) Log.d(TAG, "close()"); - - unregisterApp(); - mConnState = CONN_STATE_CLOSED; - mAuthRetryState = AUTH_RETRY_STATE_IDLE; - } - - /** - * Returns a service by UUID, instance and type. - * - * @hide - */ - /*package*/ BluetoothGattService getService(BluetoothDevice device, UUID uuid, - int instanceId) { - for (BluetoothGattService svc : mServices) { - if (svc.getDevice().equals(device) - && svc.getInstanceId() == instanceId - && svc.getUuid().equals(uuid)) { - return svc; - } - } - return null; - } - - - /** - * Returns a characteristic with id equal to instanceId. - * - * @hide - */ - /*package*/ BluetoothGattCharacteristic getCharacteristicById(BluetoothDevice device, - int instanceId) { - for (BluetoothGattService svc : mServices) { - for (BluetoothGattCharacteristic charac : svc.getCharacteristics()) { - if (charac.getInstanceId() == instanceId) { - return charac; - } - } - } - return null; - } - - /** - * Returns a descriptor with id equal to instanceId. - * - * @hide - */ - /*package*/ BluetoothGattDescriptor getDescriptorById(BluetoothDevice device, int instanceId) { - for (BluetoothGattService svc : mServices) { - for (BluetoothGattCharacteristic charac : svc.getCharacteristics()) { - for (BluetoothGattDescriptor desc : charac.getDescriptors()) { - if (desc.getInstanceId() == instanceId) { - return desc; - } - } - } - } - return null; - } - - /** - * Queue the runnable on a {@link Handler} provided by the user, or execute the runnable - * immediately if no Handler was provided. - */ - private void runOrQueueCallback(final Runnable cb) { - if (mHandler == null) { - try { - cb.run(); - } catch (Exception ex) { - Log.w(TAG, "Unhandled exception in callback", ex); - } - } else { - mHandler.post(cb); - } - } - - /** - * Register an application callback to start using GATT. - * - * <p>This is an asynchronous call. The callback {@link BluetoothGattCallback#onAppRegistered} - * is used to notify success or failure if the function returns true. - * - * @param callback GATT callback handler that will receive asynchronous callbacks. - * @return If true, the callback will be called to notify success or failure, false on immediate - * error - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - private boolean registerApp(BluetoothGattCallback callback, Handler handler) { - return registerApp(callback, handler, false); - } - - /** - * Register an application callback to start using GATT. - * - * <p>This is an asynchronous call. The callback {@link BluetoothGattCallback#onAppRegistered} - * is used to notify success or failure if the function returns true. - * - * @param callback GATT callback handler that will receive asynchronous callbacks. - * @param eatt_support indicate to allow for eatt support - * @return If true, the callback will be called to notify success or failure, false on immediate - * error - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - private boolean registerApp(BluetoothGattCallback callback, Handler handler, - boolean eatt_support) { - if (DBG) Log.d(TAG, "registerApp()"); - if (mService == null) return false; - - mCallback = callback; - mHandler = handler; - UUID uuid = UUID.randomUUID(); - if (DBG) Log.d(TAG, "registerApp() - UUID=" + uuid); - - try { - mService.registerClient( - new ParcelUuid(uuid), mBluetoothGattCallback, eatt_support, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - return false; - } - - return true; - } - - /** - * Unregister the current application and callbacks. - */ - @UnsupportedAppUsage - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - private void unregisterApp() { - if (DBG) Log.d(TAG, "unregisterApp() - mClientIf=" + mClientIf); - if (mService == null || mClientIf == 0) return; - - try { - mCallback = null; - mService.unregisterClient(mClientIf, mAttributionSource); - mClientIf = 0; - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - - /** - * Initiate a connection to a Bluetooth GATT capable device. - * - * <p>The connection may not be established right away, but will be - * completed when the remote device is available. A - * {@link BluetoothGattCallback#onConnectionStateChange} callback will be - * invoked when the connection state changes as a result of this function. - * - * <p>The autoConnect parameter determines whether to actively connect to - * the remote device, or rather passively scan and finalize the connection - * when the remote device is in range/available. Generally, the first ever - * connection to a device should be direct (autoConnect set to false) and - * subsequent connections to known devices should be invoked with the - * autoConnect parameter set to true. - * - * @param device Remote device to connect to - * @param autoConnect Whether to directly connect to the remote device (false) or to - * automatically connect as soon as the remote device becomes available (true). - * @return true, if the connection attempt was initiated successfully - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - /*package*/ boolean connect(Boolean autoConnect, BluetoothGattCallback callback, - Handler handler) { - if (DBG) { - Log.d(TAG, - "connect() - device: " + mDevice.getAddress() + ", auto: " + autoConnect); - } - synchronized (mStateLock) { - if (mConnState != CONN_STATE_IDLE) { - throw new IllegalStateException("Not idle"); - } - mConnState = CONN_STATE_CONNECTING; - } - - mAutoConnect = autoConnect; - - if (!registerApp(callback, handler)) { - synchronized (mStateLock) { - mConnState = CONN_STATE_IDLE; - } - Log.e(TAG, "Failed to register callback"); - return false; - } - - // The connection will continue in the onClientRegistered callback - return true; - } - - /** - * Disconnects an established connection, or cancels a connection attempt - * currently in progress. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public void disconnect() { - if (DBG) Log.d(TAG, "cancelOpen() - device: " + mDevice.getAddress()); - if (mService == null || mClientIf == 0) return; - - try { - mService.clientDisconnect(mClientIf, mDevice.getAddress(), mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - - /** - * Connect back to remote device. - * - * <p>This method is used to re-connect to a remote device after the - * connection has been dropped. If the device is not in range, the - * re-connection will be triggered once the device is back in range. - * - * @return true, if the connection attempt was initiated successfully - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean connect() { - try { - // autoConnect is inverse of "isDirect" - mService.clientConnect(mClientIf, mDevice.getAddress(), false, mTransport, - mOpportunistic, mPhy, mAttributionSource); - return true; - } catch (RemoteException e) { - Log.e(TAG, "", e); - return false; - } - } - - /** - * Set the preferred connection PHY for this app. Please note that this is just a - * recommendation, whether the PHY change will happen depends on other applications preferences, - * local and remote controller capabilities. Controller can override these settings. - * <p> - * {@link BluetoothGattCallback#onPhyUpdate} will be triggered as a result of this call, even - * if no PHY change happens. It is also triggered when remote device updates the PHY. - * - * @param txPhy preferred transmitter PHY. Bitwise OR of any of {@link - * BluetoothDevice#PHY_LE_1M_MASK}, {@link BluetoothDevice#PHY_LE_2M_MASK}, and {@link - * BluetoothDevice#PHY_LE_CODED_MASK}. - * @param rxPhy preferred receiver PHY. Bitwise OR of any of {@link - * BluetoothDevice#PHY_LE_1M_MASK}, {@link BluetoothDevice#PHY_LE_2M_MASK}, and {@link - * BluetoothDevice#PHY_LE_CODED_MASK}. - * @param phyOptions preferred coding to use when transmitting on the LE Coded PHY. Can be one - * of {@link BluetoothDevice#PHY_OPTION_NO_PREFERRED}, {@link BluetoothDevice#PHY_OPTION_S2} or - * {@link BluetoothDevice#PHY_OPTION_S8} - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public void setPreferredPhy(int txPhy, int rxPhy, int phyOptions) { - try { - mService.clientSetPreferredPhy(mClientIf, mDevice.getAddress(), txPhy, rxPhy, - phyOptions, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - - /** - * Read the current transmitter PHY and receiver PHY of the connection. The values are returned - * in {@link BluetoothGattCallback#onPhyRead} - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public void readPhy() { - try { - mService.clientReadPhy(mClientIf, mDevice.getAddress(), mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - - /** - * Return the remote bluetooth device this GATT client targets to - * - * @return remote bluetooth device - */ - @RequiresNoPermission - public BluetoothDevice getDevice() { - return mDevice; - } - - /** - * Discovers services offered by a remote device as well as their - * characteristics and descriptors. - * - * <p>This is an asynchronous operation. Once service discovery is completed, - * the {@link BluetoothGattCallback#onServicesDiscovered} callback is - * triggered. If the discovery was successful, the remote services can be - * retrieved using the {@link #getServices} function. - * - * @return true, if the remote service discovery has been started - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean discoverServices() { - if (DBG) Log.d(TAG, "discoverServices() - device: " + mDevice.getAddress()); - if (mService == null || mClientIf == 0) return false; - - mServices.clear(); - - try { - mService.discoverServices(mClientIf, mDevice.getAddress(), mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - return false; - } - - return true; - } - - /** - * Discovers a service by UUID. This is exposed only for passing PTS tests. - * It should never be used by real applications. The service is not searched - * for characteristics and descriptors, or returned in any callback. - * - * @return true, if the remote service discovery has been started - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean discoverServiceByUuid(UUID uuid) { - if (DBG) Log.d(TAG, "discoverServiceByUuid() - device: " + mDevice.getAddress()); - if (mService == null || mClientIf == 0) return false; - - mServices.clear(); - - try { - mService.discoverServiceByUuid( - mClientIf, mDevice.getAddress(), new ParcelUuid(uuid), mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - return false; - } - return true; - } - - /** - * Returns a list of GATT services offered by the remote device. - * - * <p>This function requires that service discovery has been completed - * for the given device. - * - * @return List of services on the remote device. Returns an empty list if service discovery has - * not yet been performed. - */ - @RequiresLegacyBluetoothPermission - @RequiresNoPermission - public List<BluetoothGattService> getServices() { - List<BluetoothGattService> result = - new ArrayList<BluetoothGattService>(); - - for (BluetoothGattService service : mServices) { - if (service.getDevice().equals(mDevice)) { - result.add(service); - } - } - - return result; - } - - /** - * Returns a {@link BluetoothGattService}, if the requested UUID is - * supported by the remote device. - * - * <p>This function requires that service discovery has been completed - * for the given device. - * - * <p>If multiple instances of the same service (as identified by UUID) - * exist, the first instance of the service is returned. - * - * @param uuid UUID of the requested service - * @return BluetoothGattService if supported, or null if the requested service is not offered by - * the remote device. - */ - @RequiresLegacyBluetoothPermission - @RequiresNoPermission - public BluetoothGattService getService(UUID uuid) { - for (BluetoothGattService service : mServices) { - if (service.getDevice().equals(mDevice) && service.getUuid().equals(uuid)) { - return service; - } - } - - return null; - } - - /** - * Reads the requested characteristic from the associated remote device. - * - * <p>This is an asynchronous operation. The result of the read operation - * is reported by the {@link BluetoothGattCallback#onCharacteristicRead(BluetoothGatt, - * BluetoothGattCharacteristic, byte[], int)} callback. - * - * @param characteristic Characteristic to read from the remote device - * @return true, if the read operation was initiated successfully - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean readCharacteristic(BluetoothGattCharacteristic characteristic) { - if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_READ) == 0) { - return false; - } - - if (VDBG) Log.d(TAG, "readCharacteristic() - uuid: " + characteristic.getUuid()); - if (mService == null || mClientIf == 0) return false; - - BluetoothGattService service = characteristic.getService(); - if (service == null) return false; - - BluetoothDevice device = service.getDevice(); - if (device == null) return false; - - synchronized (mDeviceBusyLock) { - if (mDeviceBusy) return false; - mDeviceBusy = true; - } - - try { - mService.readCharacteristic(mClientIf, device.getAddress(), - characteristic.getInstanceId(), AUTHENTICATION_NONE, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - synchronized (mDeviceBusyLock) { - mDeviceBusy = false; - } - return false; - } - - return true; - } - - /** - * Reads the characteristic using its UUID from the associated remote device. - * - * <p>This is an asynchronous operation. The result of the read operation - * is reported by the {@link BluetoothGattCallback#onCharacteristicRead(BluetoothGatt, - * BluetoothGattCharacteristic, byte[], int)} callback. - * - * @param uuid UUID of characteristic to read from the remote device - * @return true, if the read operation was initiated successfully - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean readUsingCharacteristicUuid(UUID uuid, int startHandle, int endHandle) { - if (VDBG) Log.d(TAG, "readUsingCharacteristicUuid() - uuid: " + uuid); - if (mService == null || mClientIf == 0) return false; - - synchronized (mDeviceBusyLock) { - if (mDeviceBusy) return false; - mDeviceBusy = true; - } - - try { - mService.readUsingCharacteristicUuid(mClientIf, mDevice.getAddress(), - new ParcelUuid(uuid), startHandle, endHandle, AUTHENTICATION_NONE, - mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - synchronized (mDeviceBusyLock) { - mDeviceBusy = false; - } - return false; - } - - return true; - } - - - /** - * Writes a given characteristic and its values to the associated remote device. - * - * <p>Once the write operation has been completed, the - * {@link BluetoothGattCallback#onCharacteristicWrite} callback is invoked, - * reporting the result of the operation. - * - * @param characteristic Characteristic to write on the remote device - * @return true, if the write operation was initiated successfully - * @throws IllegalArgumentException if characteristic or its value are null - * - * @deprecated Use {@link BluetoothGatt#writeCharacteristic(BluetoothGattCharacteristic, byte[], - * int)} as this is not memory safe. - */ - @Deprecated - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean writeCharacteristic(BluetoothGattCharacteristic characteristic) { - try { - return writeCharacteristic(characteristic, characteristic.getValue(), - characteristic.getWriteType()) == BluetoothStatusCodes.SUCCESS; - } catch (Exception e) { - return false; - } - } - - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(value = { - BluetoothStatusCodes.SUCCESS, - BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_CONNECT_PERMISSION, - BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_PRIVILEGED_PERMISSION, - BluetoothStatusCodes.ERROR_DEVICE_NOT_CONNECTED, - BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND, - BluetoothStatusCodes.ERROR_GATT_WRITE_NOT_ALLOWED, - BluetoothStatusCodes.ERROR_GATT_WRITE_REQUEST_BUSY, - BluetoothStatusCodes.ERROR_UNKNOWN - }) - public @interface WriteOperationReturnValues{} - - /** - * Writes a given characteristic and its values to the associated remote device. - * - * <p>Once the write operation has been completed, the - * {@link BluetoothGattCallback#onCharacteristicWrite} callback is invoked, - * reporting the result of the operation. - * - * @param characteristic Characteristic to write on the remote device - * @return whether the characteristic was successfully written to - * @throws IllegalArgumentException if characteristic or value are null - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @WriteOperationReturnValues - public int writeCharacteristic(@NonNull BluetoothGattCharacteristic characteristic, - @NonNull byte[] value, int writeType) { - if (characteristic == null) { - throw new IllegalArgumentException("characteristic must not be null"); - } - if (value == null) { - throw new IllegalArgumentException("value must not be null"); - } - if (VDBG) Log.d(TAG, "writeCharacteristic() - uuid: " + characteristic.getUuid()); - if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE) == 0 - && (characteristic.getProperties() - & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) == 0) { - return BluetoothStatusCodes.ERROR_GATT_WRITE_NOT_ALLOWED; - } - if (mService == null || mClientIf == 0) { - return BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND; - } - - BluetoothGattService service = characteristic.getService(); - if (service == null) { - throw new IllegalArgumentException("Characteristic must have a non-null service"); - } - - BluetoothDevice device = service.getDevice(); - if (device == null) { - throw new IllegalArgumentException("Service must have a non-null device"); - } - - synchronized (mDeviceBusyLock) { - if (mDeviceBusy) { - return BluetoothStatusCodes.ERROR_GATT_WRITE_REQUEST_BUSY; - } - mDeviceBusy = true; - } - - int requestStatus = BluetoothStatusCodes.ERROR_UNKNOWN; - try { - for (int i = 0; i < WRITE_CHARACTERISTIC_MAX_RETRIES; i++) { - requestStatus = mService.writeCharacteristic(mClientIf, device.getAddress(), - characteristic.getInstanceId(), writeType, AUTHENTICATION_NONE, value, - mAttributionSource); - if (requestStatus != BluetoothStatusCodes.ERROR_GATT_WRITE_REQUEST_BUSY) { - break; - } - try { - Thread.sleep(WRITE_CHARACTERISTIC_TIME_TO_WAIT); - } catch (InterruptedException e) { - } - } - } catch (RemoteException e) { - Log.e(TAG, "", e); - synchronized (mDeviceBusyLock) { - mDeviceBusy = false; - } - throw e.rethrowFromSystemServer(); - } - - return requestStatus; - } - - /** - * Reads the value for a given descriptor from the associated remote device. - * - * <p>Once the read operation has been completed, the - * {@link BluetoothGattCallback#onDescriptorRead} callback is - * triggered, signaling the result of the operation. - * - * @param descriptor Descriptor value to read from the remote device - * @return true, if the read operation was initiated successfully - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean readDescriptor(BluetoothGattDescriptor descriptor) { - if (VDBG) Log.d(TAG, "readDescriptor() - uuid: " + descriptor.getUuid()); - if (mService == null || mClientIf == 0) return false; - - BluetoothGattCharacteristic characteristic = descriptor.getCharacteristic(); - if (characteristic == null) return false; - - BluetoothGattService service = characteristic.getService(); - if (service == null) return false; - - BluetoothDevice device = service.getDevice(); - if (device == null) return false; - - synchronized (mDeviceBusyLock) { - if (mDeviceBusy) return false; - mDeviceBusy = true; - } - - try { - mService.readDescriptor(mClientIf, device.getAddress(), - descriptor.getInstanceId(), AUTHENTICATION_NONE, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - synchronized (mDeviceBusyLock) { - mDeviceBusy = false; - } - return false; - } - - return true; - } - - /** - * Write the value of a given descriptor to the associated remote device. - * - * <p>A {@link BluetoothGattCallback#onDescriptorWrite} callback is triggered to report the - * result of the write operation. - * - * @param descriptor Descriptor to write to the associated remote device - * @return true, if the write operation was initiated successfully - * @throws IllegalArgumentException if descriptor or its value are null - * - * @deprecated Use {@link BluetoothGatt#writeDescriptor(BluetoothGattDescriptor, byte[])} as - * this is not memory safe. - */ - @Deprecated - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean writeDescriptor(BluetoothGattDescriptor descriptor) { - try { - return writeDescriptor(descriptor, descriptor.getValue()) - == BluetoothStatusCodes.SUCCESS; - } catch (Exception e) { - return false; - } - } - - /** - * Write the value of a given descriptor to the associated remote device. - * - * <p>A {@link BluetoothGattCallback#onDescriptorWrite} callback is triggered to report the - * result of the write operation. - * - * @param descriptor Descriptor to write to the associated remote device - * @return true, if the write operation was initiated successfully - * @throws IllegalArgumentException if descriptor or value are null - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @WriteOperationReturnValues - public int writeDescriptor(@NonNull BluetoothGattDescriptor descriptor, - @NonNull byte[] value) { - if (descriptor == null) { - throw new IllegalArgumentException("descriptor must not be null"); - } - if (value == null) { - throw new IllegalArgumentException("value must not be null"); - } - if (VDBG) Log.d(TAG, "writeDescriptor() - uuid: " + descriptor.getUuid()); - if (mService == null || mClientIf == 0) { - return BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND; - } - - BluetoothGattCharacteristic characteristic = descriptor.getCharacteristic(); - if (characteristic == null) { - throw new IllegalArgumentException("Descriptor must have a non-null characteristic"); - } - - BluetoothGattService service = characteristic.getService(); - if (service == null) { - throw new IllegalArgumentException("Characteristic must have a non-null service"); - } - - BluetoothDevice device = service.getDevice(); - if (device == null) { - throw new IllegalArgumentException("Service must have a non-null device"); - } - - synchronized (mDeviceBusyLock) { - if (mDeviceBusy) return BluetoothStatusCodes.ERROR_GATT_WRITE_REQUEST_BUSY; - mDeviceBusy = true; - } - - try { - return mService.writeDescriptor(mClientIf, device.getAddress(), - descriptor.getInstanceId(), AUTHENTICATION_NONE, value, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - synchronized (mDeviceBusyLock) { - mDeviceBusy = false; - } - e.rethrowFromSystemServer(); - } - return BluetoothStatusCodes.ERROR_UNKNOWN; - } - - /** - * Initiates a reliable write transaction for a given remote device. - * - * <p>Once a reliable write transaction has been initiated, all calls - * to {@link #writeCharacteristic} are sent to the remote device for - * verification and queued up for atomic execution. The application will - * receive a {@link BluetoothGattCallback#onCharacteristicWrite} callback in response to every - * {@link #writeCharacteristic(BluetoothGattCharacteristic, byte[], int)} call and is - * responsible for verifying if the value has been transmitted accurately. - * - * <p>After all characteristics have been queued up and verified, - * {@link #executeReliableWrite} will execute all writes. If a characteristic - * was not written correctly, calling {@link #abortReliableWrite} will - * cancel the current transaction without committing any values on the - * remote device. - * - * @return true, if the reliable write transaction has been initiated - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean beginReliableWrite() { - if (VDBG) Log.d(TAG, "beginReliableWrite() - device: " + mDevice.getAddress()); - if (mService == null || mClientIf == 0) return false; - - try { - mService.beginReliableWrite(mClientIf, mDevice.getAddress(), mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - return false; - } - - return true; - } - - /** - * Executes a reliable write transaction for a given remote device. - * - * <p>This function will commit all queued up characteristic write - * operations for a given remote device. - * - * <p>A {@link BluetoothGattCallback#onReliableWriteCompleted} callback is - * invoked to indicate whether the transaction has been executed correctly. - * - * @return true, if the request to execute the transaction has been sent - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean executeReliableWrite() { - if (VDBG) Log.d(TAG, "executeReliableWrite() - device: " + mDevice.getAddress()); - if (mService == null || mClientIf == 0) return false; - - synchronized (mDeviceBusyLock) { - if (mDeviceBusy) return false; - mDeviceBusy = true; - } - - try { - mService.endReliableWrite(mClientIf, mDevice.getAddress(), true, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - synchronized (mDeviceBusyLock) { - mDeviceBusy = false; - } - return false; - } - - return true; - } - - /** - * Cancels a reliable write transaction for a given device. - * - * <p>Calling this function will discard all queued characteristic write - * operations for a given remote device. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public void abortReliableWrite() { - if (VDBG) Log.d(TAG, "abortReliableWrite() - device: " + mDevice.getAddress()); - if (mService == null || mClientIf == 0) return; - - try { - mService.endReliableWrite(mClientIf, mDevice.getAddress(), false, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - - /** - * @deprecated Use {@link #abortReliableWrite()} - */ - @Deprecated - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public void abortReliableWrite(BluetoothDevice mDevice) { - abortReliableWrite(); - } - - /** - * Enable or disable notifications/indications for a given characteristic. - * - * <p>Once notifications are enabled for a characteristic, a - * {@link BluetoothGattCallback#onCharacteristicChanged(BluetoothGatt, - * BluetoothGattCharacteristic, byte[])} callback will be triggered if the remote device - * indicates that the given characteristic has changed. - * - * @param characteristic The characteristic for which to enable notifications - * @param enable Set to true to enable notifications/indications - * @return true, if the requested notification status was set successfully - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean setCharacteristicNotification(BluetoothGattCharacteristic characteristic, - boolean enable) { - if (DBG) { - Log.d(TAG, "setCharacteristicNotification() - uuid: " + characteristic.getUuid() - + " enable: " + enable); - } - if (mService == null || mClientIf == 0) return false; - - BluetoothGattService service = characteristic.getService(); - if (service == null) return false; - - BluetoothDevice device = service.getDevice(); - if (device == null) return false; - - try { - mService.registerForNotification(mClientIf, device.getAddress(), - characteristic.getInstanceId(), enable, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - return false; - } - - return true; - } - - /** - * Clears the internal cache and forces a refresh of the services from the - * remote device. - * - * @hide - */ - @UnsupportedAppUsage - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean refresh() { - if (DBG) Log.d(TAG, "refresh() - device: " + mDevice.getAddress()); - if (mService == null || mClientIf == 0) return false; - - try { - mService.refreshDevice(mClientIf, mDevice.getAddress(), mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - return false; - } - - return true; - } - - /** - * Read the RSSI for a connected remote device. - * - * <p>The {@link BluetoothGattCallback#onReadRemoteRssi} callback will be - * invoked when the RSSI value has been read. - * - * @return true, if the RSSI value has been requested successfully - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean readRemoteRssi() { - if (DBG) Log.d(TAG, "readRssi() - device: " + mDevice.getAddress()); - if (mService == null || mClientIf == 0) return false; - - try { - mService.readRemoteRssi(mClientIf, mDevice.getAddress(), mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - return false; - } - - return true; - } - - /** - * Request an MTU size used for a given connection. - * - * <p>When performing a write request operation (write without response), - * the data sent is truncated to the MTU size. This function may be used - * to request a larger MTU size to be able to send more data at once. - * - * <p>A {@link BluetoothGattCallback#onMtuChanged} callback will indicate - * whether this operation was successful. - * - * @return true, if the new MTU value has been requested successfully - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean requestMtu(int mtu) { - if (DBG) { - Log.d(TAG, "configureMTU() - device: " + mDevice.getAddress() - + " mtu: " + mtu); - } - if (mService == null || mClientIf == 0) return false; - - try { - mService.configureMTU(mClientIf, mDevice.getAddress(), mtu, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - return false; - } - - return true; - } - - /** - * Request a connection parameter update. - * - * <p>This function will send a connection parameter update request to the - * remote device. - * - * @param connectionPriority Request a specific connection priority. Must be one of {@link - * BluetoothGatt#CONNECTION_PRIORITY_BALANCED}, {@link BluetoothGatt#CONNECTION_PRIORITY_HIGH} - * or {@link BluetoothGatt#CONNECTION_PRIORITY_LOW_POWER}. - * @throws IllegalArgumentException If the parameters are outside of their specified range. - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean requestConnectionPriority(int connectionPriority) { - if (connectionPriority < CONNECTION_PRIORITY_BALANCED - || connectionPriority > CONNECTION_PRIORITY_LOW_POWER) { - throw new IllegalArgumentException("connectionPriority not within valid range"); - } - - if (DBG) Log.d(TAG, "requestConnectionPriority() - params: " + connectionPriority); - if (mService == null || mClientIf == 0) return false; - - try { - mService.connectionParameterUpdate( - mClientIf, mDevice.getAddress(), connectionPriority, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - return false; - } - - return true; - } - - /** - * Request an LE connection parameter update. - * - * <p>This function will send an LE connection parameters update request to the remote device. - * - * @return true, if the request is send to the Bluetooth stack. - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean requestLeConnectionUpdate(int minConnectionInterval, int maxConnectionInterval, - int slaveLatency, int supervisionTimeout, - int minConnectionEventLen, int maxConnectionEventLen) { - if (DBG) { - Log.d(TAG, "requestLeConnectionUpdate() - min=(" + minConnectionInterval - + ")" + (1.25 * minConnectionInterval) - + "msec, max=(" + maxConnectionInterval + ")" - + (1.25 * maxConnectionInterval) + "msec, latency=" + slaveLatency - + ", timeout=" + supervisionTimeout + "msec" + ", min_ce=" - + minConnectionEventLen + ", max_ce=" + maxConnectionEventLen); - } - if (mService == null || mClientIf == 0) return false; - - try { - mService.leConnectionUpdate(mClientIf, mDevice.getAddress(), - minConnectionInterval, maxConnectionInterval, - slaveLatency, supervisionTimeout, - minConnectionEventLen, maxConnectionEventLen, - mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - return false; - } - - return true; - } - - /** - * @deprecated Not supported - please use {@link BluetoothManager#getConnectedDevices(int)} - * with {@link BluetoothProfile#GATT} as argument - * @throws UnsupportedOperationException - */ - @Override - @RequiresNoPermission - @Deprecated - public int getConnectionState(BluetoothDevice device) { - throw new UnsupportedOperationException("Use BluetoothManager#getConnectionState instead."); - } - - /** - * @deprecated Not supported - please use {@link BluetoothManager#getConnectedDevices(int)} - * with {@link BluetoothProfile#GATT} as argument - * - * @throws UnsupportedOperationException - */ - @Override - @RequiresNoPermission - @Deprecated - public List<BluetoothDevice> getConnectedDevices() { - throw new UnsupportedOperationException( - "Use BluetoothManager#getConnectedDevices instead."); - } - - /** - * @deprecated Not supported - please use - * {@link BluetoothManager#getDevicesMatchingConnectionStates(int, int[])} - * with {@link BluetoothProfile#GATT} as first argument - * - * @throws UnsupportedOperationException - */ - @Override - @RequiresNoPermission - @Deprecated - public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { - throw new UnsupportedOperationException( - "Use BluetoothManager#getDevicesMatchingConnectionStates instead."); - } -} diff --git a/core/java/android/bluetooth/BluetoothGattCallback.java b/core/java/android/bluetooth/BluetoothGattCallback.java deleted file mode 100644 index d0a5a1e729fe..000000000000 --- a/core/java/android/bluetooth/BluetoothGattCallback.java +++ /dev/null @@ -1,267 +0,0 @@ -/* - * 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 android.bluetooth; - -import android.annotation.NonNull; - -/** - * This abstract class is used to implement {@link BluetoothGatt} callbacks. - */ -public abstract class BluetoothGattCallback { - - /** - * Callback triggered as result of {@link BluetoothGatt#setPreferredPhy}, or as a result of - * remote device changing the PHY. - * - * @param gatt GATT client - * @param txPhy the transmitter PHY in use. One of {@link BluetoothDevice#PHY_LE_1M}, {@link - * BluetoothDevice#PHY_LE_2M}, and {@link BluetoothDevice#PHY_LE_CODED}. - * @param rxPhy the receiver PHY in use. One of {@link BluetoothDevice#PHY_LE_1M}, {@link - * BluetoothDevice#PHY_LE_2M}, and {@link BluetoothDevice#PHY_LE_CODED}. - * @param status Status of the PHY update operation. {@link BluetoothGatt#GATT_SUCCESS} if the - * operation succeeds. - */ - public void onPhyUpdate(BluetoothGatt gatt, int txPhy, int rxPhy, int status) { - } - - /** - * Callback triggered as result of {@link BluetoothGatt#readPhy} - * - * @param gatt GATT client - * @param txPhy the transmitter PHY in use. One of {@link BluetoothDevice#PHY_LE_1M}, {@link - * BluetoothDevice#PHY_LE_2M}, and {@link BluetoothDevice#PHY_LE_CODED}. - * @param rxPhy the receiver PHY in use. One of {@link BluetoothDevice#PHY_LE_1M}, {@link - * BluetoothDevice#PHY_LE_2M}, and {@link BluetoothDevice#PHY_LE_CODED}. - * @param status Status of the PHY read operation. {@link BluetoothGatt#GATT_SUCCESS} if the - * operation succeeds. - */ - public void onPhyRead(BluetoothGatt gatt, int txPhy, int rxPhy, int status) { - } - - /** - * Callback indicating when GATT client has connected/disconnected to/from a remote - * GATT server. - * - * @param gatt GATT client - * @param status Status of the connect or disconnect operation. {@link - * BluetoothGatt#GATT_SUCCESS} if the operation succeeds. - * @param newState Returns the new connection state. Can be one of {@link - * BluetoothProfile#STATE_DISCONNECTED} or {@link BluetoothProfile#STATE_CONNECTED} - */ - public void onConnectionStateChange(BluetoothGatt gatt, int status, - int newState) { - } - - /** - * Callback invoked when the list of remote services, characteristics and descriptors - * for the remote device have been updated, ie new services have been discovered. - * - * @param gatt GATT client invoked {@link BluetoothGatt#discoverServices} - * @param status {@link BluetoothGatt#GATT_SUCCESS} if the remote device has been explored - * successfully. - */ - public void onServicesDiscovered(BluetoothGatt gatt, int status) { - } - - /** - * Callback reporting the result of a characteristic read operation. - * - * @param gatt GATT client invoked - * {@link BluetoothGatt#readCharacteristic(BluetoothGattCharacteristic)} - * @param characteristic Characteristic that was read from the associated remote device. - * @param status {@link BluetoothGatt#GATT_SUCCESS} if the read operation was completed - * successfully. - * @deprecated Use {@link BluetoothGattCallback#onCharacteristicRead(BluetoothGatt, - * BluetoothGattCharacteristic, byte[], int)} as it is memory safe - */ - @Deprecated - public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, - int status) { - } - - /** - * Callback reporting the result of a characteristic read operation. - * - * @param gatt GATT client invoked - * {@link BluetoothGatt#readCharacteristic(BluetoothGattCharacteristic)} - * @param characteristic Characteristic that was read from the associated remote device. - * @param value the value of the characteristic - * @param status {@link BluetoothGatt#GATT_SUCCESS} if the read operation was completed - * successfully. - */ - public void onCharacteristicRead(@NonNull BluetoothGatt gatt, @NonNull - BluetoothGattCharacteristic characteristic, @NonNull byte[] value, int status) { - } - - /** - * Callback indicating the result of a characteristic write operation. - * - * <p>If this callback is invoked while a reliable write transaction is - * in progress, the value of the characteristic represents the value - * reported by the remote device. An application should compare this - * value to the desired value to be written. If the values don't match, - * the application must abort the reliable write transaction. - * - * @param gatt GATT client that invoked - * {@link BluetoothGatt#writeCharacteristic(BluetoothGattCharacteristic, - * byte[], int)} - * @param characteristic Characteristic that was written to the associated remote device. - * @param status The result of the write operation {@link BluetoothGatt#GATT_SUCCESS} if - * the - * operation succeeds. - */ - public void onCharacteristicWrite(BluetoothGatt gatt, - BluetoothGattCharacteristic characteristic, int status) { - } - - /** - * Callback triggered as a result of a remote characteristic notification. - * - * @param gatt GATT client the characteristic is associated with - * @param characteristic Characteristic that has been updated as a result of a remote - * notification event. - * @deprecated Use {@link BluetoothGattCallback#onCharacteristicChanged(BluetoothGatt, - * BluetoothGattCharacteristic, byte[])} as it is memory safe by providing the characteristic - * value at the time of notification. - */ - @Deprecated - public void onCharacteristicChanged(BluetoothGatt gatt, - BluetoothGattCharacteristic characteristic) { - } - - /** - * Callback triggered as a result of a remote characteristic notification. Note that the value - * within the characteristic object may have changed since receiving the remote characteristic - * notification, so check the parameter value for the value at the time of notification. - * - * @param gatt GATT client the characteristic is associated with - * @param characteristic Characteristic that has been updated as a result of a remote - * notification event. - * @param value notified characteristic value - */ - public void onCharacteristicChanged(@NonNull BluetoothGatt gatt, - @NonNull BluetoothGattCharacteristic characteristic, @NonNull byte[] value) { - } - - /** - * Callback reporting the result of a descriptor read operation. - * - * @param gatt GATT client invoked {@link BluetoothGatt#readDescriptor} - * @param descriptor Descriptor that was read from the associated remote device. - * @param status {@link BluetoothGatt#GATT_SUCCESS} if the read operation was completed - * successfully - * @deprecated Use {@link BluetoothGattCallback#onDescriptorRead(BluetoothGatt, - * BluetoothGattDescriptor, int, byte[])} as it is memory safe by providing the descriptor - * value at the time it was read. - */ - @Deprecated - public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, - int status) { - } - - /** - * Callback reporting the result of a descriptor read operation. - * - * @param gatt GATT client invoked {@link BluetoothGatt#readDescriptor} - * @param descriptor Descriptor that was read from the associated remote device. - * @param status {@link BluetoothGatt#GATT_SUCCESS} if the read operation was completed - * successfully - * @param value the descriptor value at the time of the read operation - */ - public void onDescriptorRead(@NonNull BluetoothGatt gatt, - @NonNull BluetoothGattDescriptor descriptor, int status, @NonNull byte[] value) { - } - - /** - * Callback indicating the result of a descriptor write operation. - * - * @param gatt GATT client invoked {@link BluetoothGatt#writeDescriptor} - * @param descriptor Descriptor that was writte to the associated remote device. - * @param status The result of the write operation {@link BluetoothGatt#GATT_SUCCESS} if the - * operation succeeds. - */ - public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, - int status) { - } - - /** - * Callback invoked when a reliable write transaction has been completed. - * - * @param gatt GATT client invoked {@link BluetoothGatt#executeReliableWrite} - * @param status {@link BluetoothGatt#GATT_SUCCESS} if the reliable write transaction was - * executed successfully - */ - public void onReliableWriteCompleted(BluetoothGatt gatt, int status) { - } - - /** - * Callback reporting the RSSI for a remote device connection. - * - * This callback is triggered in response to the - * {@link BluetoothGatt#readRemoteRssi} function. - * - * @param gatt GATT client invoked {@link BluetoothGatt#readRemoteRssi} - * @param rssi The RSSI value for the remote device - * @param status {@link BluetoothGatt#GATT_SUCCESS} if the RSSI was read successfully - */ - public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) { - } - - /** - * Callback indicating the MTU for a given device connection has changed. - * - * This callback is triggered in response to the - * {@link BluetoothGatt#requestMtu} function, or in response to a connection - * event. - * - * @param gatt GATT client invoked {@link BluetoothGatt#requestMtu} - * @param mtu The new MTU size - * @param status {@link BluetoothGatt#GATT_SUCCESS} if the MTU has been changed successfully - */ - public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) { - } - - /** - * Callback indicating the connection parameters were updated. - * - * @param gatt GATT client involved - * @param interval Connection interval used on this connection, 1.25ms unit. Valid range is from - * 6 (7.5ms) to 3200 (4000ms). - * @param latency Worker latency for the connection in number of connection events. Valid range - * is from 0 to 499 - * @param timeout Supervision timeout for this connection, in 10ms unit. Valid range is from 10 - * (0.1s) to 3200 (32s) - * @param status {@link BluetoothGatt#GATT_SUCCESS} if the connection has been updated - * successfully - * @hide - */ - public void onConnectionUpdated(BluetoothGatt gatt, int interval, int latency, int timeout, - int status) { - } - - /** - * Callback indicating service changed event is received - * - * <p>Receiving this event means that the GATT database is out of sync with - * the remote device. {@link BluetoothGatt#discoverServices} should be - * called to re-discover the services. - * - * @param gatt GATT client involved - */ - public void onServiceChanged(@NonNull BluetoothGatt gatt) { - } -} diff --git a/core/java/android/bluetooth/BluetoothGattCharacteristic.java b/core/java/android/bluetooth/BluetoothGattCharacteristic.java deleted file mode 100644 index c5e986e895b2..000000000000 --- a/core/java/android/bluetooth/BluetoothGattCharacteristic.java +++ /dev/null @@ -1,806 +0,0 @@ -/* - * Copyright (C) 2013 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.bluetooth; - -import android.compat.annotation.UnsupportedAppUsage; -import android.os.Parcel; -import android.os.ParcelUuid; -import android.os.Parcelable; - -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - -/** - * Represents a Bluetooth GATT Characteristic - * - * <p>A GATT characteristic is a basic data element used to construct a GATT service, - * {@link BluetoothGattService}. The characteristic contains a value as well as - * additional information and optional GATT descriptors, {@link BluetoothGattDescriptor}. - */ -public class BluetoothGattCharacteristic implements Parcelable { - - /** - * Characteristic proprty: Characteristic is broadcastable. - */ - public static final int PROPERTY_BROADCAST = 0x01; - - /** - * Characteristic property: Characteristic is readable. - */ - public static final int PROPERTY_READ = 0x02; - - /** - * Characteristic property: Characteristic can be written without response. - */ - public static final int PROPERTY_WRITE_NO_RESPONSE = 0x04; - - /** - * Characteristic property: Characteristic can be written. - */ - public static final int PROPERTY_WRITE = 0x08; - - /** - * Characteristic property: Characteristic supports notification - */ - public static final int PROPERTY_NOTIFY = 0x10; - - /** - * Characteristic property: Characteristic supports indication - */ - public static final int PROPERTY_INDICATE = 0x20; - - /** - * Characteristic property: Characteristic supports write with signature - */ - public static final int PROPERTY_SIGNED_WRITE = 0x40; - - /** - * Characteristic property: Characteristic has extended properties - */ - public static final int PROPERTY_EXTENDED_PROPS = 0x80; - - /** - * Characteristic read permission - */ - public static final int PERMISSION_READ = 0x01; - - /** - * Characteristic permission: Allow encrypted read operations - */ - public static final int PERMISSION_READ_ENCRYPTED = 0x02; - - /** - * Characteristic permission: Allow reading with person-in-the-middle protection - */ - public static final int PERMISSION_READ_ENCRYPTED_MITM = 0x04; - - /** - * Characteristic write permission - */ - public static final int PERMISSION_WRITE = 0x10; - - /** - * Characteristic permission: Allow encrypted writes - */ - public static final int PERMISSION_WRITE_ENCRYPTED = 0x20; - - /** - * Characteristic permission: Allow encrypted writes with person-in-the-middle - * protection - */ - public static final int PERMISSION_WRITE_ENCRYPTED_MITM = 0x40; - - /** - * Characteristic permission: Allow signed write operations - */ - public static final int PERMISSION_WRITE_SIGNED = 0x80; - - /** - * Characteristic permission: Allow signed write operations with - * person-in-the-middle protection - */ - public static final int PERMISSION_WRITE_SIGNED_MITM = 0x100; - - /** - * Write characteristic, requesting acknoledgement by the remote device - */ - public static final int WRITE_TYPE_DEFAULT = 0x02; - - /** - * Write characteristic without requiring a response by the remote device - */ - public static final int WRITE_TYPE_NO_RESPONSE = 0x01; - - /** - * Write characteristic including authentication signature - */ - public static final int WRITE_TYPE_SIGNED = 0x04; - - /** - * Characteristic value format type uint8 - */ - public static final int FORMAT_UINT8 = 0x11; - - /** - * Characteristic value format type uint16 - */ - public static final int FORMAT_UINT16 = 0x12; - - /** - * Characteristic value format type uint32 - */ - public static final int FORMAT_UINT32 = 0x14; - - /** - * Characteristic value format type sint8 - */ - public static final int FORMAT_SINT8 = 0x21; - - /** - * Characteristic value format type sint16 - */ - public static final int FORMAT_SINT16 = 0x22; - - /** - * Characteristic value format type sint32 - */ - public static final int FORMAT_SINT32 = 0x24; - - /** - * Characteristic value format type sfloat (16-bit float) - */ - public static final int FORMAT_SFLOAT = 0x32; - - /** - * Characteristic value format type float (32-bit float) - */ - public static final int FORMAT_FLOAT = 0x34; - - - /** - * The UUID of this characteristic. - * - * @hide - */ - protected UUID mUuid; - - /** - * Instance ID for this characteristic. - * - * @hide - */ - @UnsupportedAppUsage - protected int mInstance; - - /** - * Characteristic properties. - * - * @hide - */ - protected int mProperties; - - /** - * Characteristic permissions. - * - * @hide - */ - protected int mPermissions; - - /** - * Key size (default = 16). - * - * @hide - */ - protected int mKeySize = 16; - - /** - * Write type for this characteristic. - * See WRITE_TYPE_* constants. - * - * @hide - */ - protected int mWriteType; - - /** - * Back-reference to the service this characteristic belongs to. - * - * @hide - */ - @UnsupportedAppUsage - protected BluetoothGattService mService; - - /** - * The cached value of this characteristic. - * - * @hide - */ - protected byte[] mValue; - - /** - * List of descriptors included in this characteristic. - */ - protected List<BluetoothGattDescriptor> mDescriptors; - - /** - * Create a new BluetoothGattCharacteristic. - * - * @param uuid The UUID for this characteristic - * @param properties Properties of this characteristic - * @param permissions Permissions for this characteristic - */ - public BluetoothGattCharacteristic(UUID uuid, int properties, int permissions) { - initCharacteristic(null, uuid, 0, properties, permissions); - } - - /** - * Create a new BluetoothGattCharacteristic - * - * @hide - */ - /*package*/ BluetoothGattCharacteristic(BluetoothGattService service, - UUID uuid, int instanceId, - int properties, int permissions) { - initCharacteristic(service, uuid, instanceId, properties, permissions); - } - - /** - * Create a new BluetoothGattCharacteristic - * - * @hide - */ - public BluetoothGattCharacteristic(UUID uuid, int instanceId, - int properties, int permissions) { - initCharacteristic(null, uuid, instanceId, properties, permissions); - } - - private void initCharacteristic(BluetoothGattService service, - UUID uuid, int instanceId, - int properties, int permissions) { - mUuid = uuid; - mInstance = instanceId; - mProperties = properties; - mPermissions = permissions; - mService = service; - mValue = null; - mDescriptors = new ArrayList<BluetoothGattDescriptor>(); - - if ((mProperties & PROPERTY_WRITE_NO_RESPONSE) != 0) { - mWriteType = WRITE_TYPE_NO_RESPONSE; - } else { - mWriteType = WRITE_TYPE_DEFAULT; - } - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeParcelable(new ParcelUuid(mUuid), 0); - out.writeInt(mInstance); - out.writeInt(mProperties); - out.writeInt(mPermissions); - out.writeInt(mKeySize); - out.writeInt(mWriteType); - out.writeTypedList(mDescriptors); - } - - public static final @android.annotation.NonNull Parcelable.Creator<BluetoothGattCharacteristic> CREATOR = - new Parcelable.Creator<BluetoothGattCharacteristic>() { - public BluetoothGattCharacteristic createFromParcel(Parcel in) { - return new BluetoothGattCharacteristic(in); - } - - public BluetoothGattCharacteristic[] newArray(int size) { - return new BluetoothGattCharacteristic[size]; - } - }; - - private BluetoothGattCharacteristic(Parcel in) { - mUuid = ((ParcelUuid) in.readParcelable(null)).getUuid(); - mInstance = in.readInt(); - mProperties = in.readInt(); - mPermissions = in.readInt(); - mKeySize = in.readInt(); - mWriteType = in.readInt(); - - mDescriptors = new ArrayList<BluetoothGattDescriptor>(); - - ArrayList<BluetoothGattDescriptor> descs = - in.createTypedArrayList(BluetoothGattDescriptor.CREATOR); - if (descs != null) { - for (BluetoothGattDescriptor desc : descs) { - desc.setCharacteristic(this); - mDescriptors.add(desc); - } - } - } - - /** - * Returns the desired key size. - * - * @hide - */ - public int getKeySize() { - return mKeySize; - } - - /** - * Adds a descriptor to this characteristic. - * - * @param descriptor Descriptor to be added to this characteristic. - * @return true, if the descriptor was added to the characteristic - */ - public boolean addDescriptor(BluetoothGattDescriptor descriptor) { - mDescriptors.add(descriptor); - descriptor.setCharacteristic(this); - return true; - } - - /** - * Get a descriptor by UUID and isntance id. - * - * @hide - */ - /*package*/ BluetoothGattDescriptor getDescriptor(UUID uuid, int instanceId) { - for (BluetoothGattDescriptor descriptor : mDescriptors) { - if (descriptor.getUuid().equals(uuid) - && descriptor.getInstanceId() == instanceId) { - return descriptor; - } - } - return null; - } - - /** - * Returns the service this characteristic belongs to. - * - * @return The asscociated service - */ - public BluetoothGattService getService() { - return mService; - } - - /** - * Sets the service associated with this device. - * - * @hide - */ - @UnsupportedAppUsage - /*package*/ void setService(BluetoothGattService service) { - mService = service; - } - - /** - * Returns the UUID of this characteristic - * - * @return UUID of this characteristic - */ - public UUID getUuid() { - return mUuid; - } - - /** - * Returns the instance ID for this characteristic. - * - * <p>If a remote device offers multiple characteristics with the same UUID, - * the instance ID is used to distuinguish between characteristics. - * - * @return Instance ID of this characteristic - */ - public int getInstanceId() { - return mInstance; - } - - /** - * Force the instance ID. - * - * @hide - */ - public void setInstanceId(int instanceId) { - mInstance = instanceId; - } - - /** - * Returns the properties of this characteristic. - * - * <p>The properties contain a bit mask of property flags indicating - * the features of this characteristic. - * - * @return Properties of this characteristic - */ - public int getProperties() { - return mProperties; - } - - /** - * Returns the permissions for this characteristic. - * - * @return Permissions of this characteristic - */ - public int getPermissions() { - return mPermissions; - } - - /** - * Gets the write type for this characteristic. - * - * @return Write type for this characteristic - */ - public int getWriteType() { - return mWriteType; - } - - /** - * Set the write type for this characteristic - * - * <p>Setting the write type of a characteristic determines how the - * {@link BluetoothGatt#writeCharacteristic(BluetoothGattCharacteristic, byte[], int)} function - * write this characteristic. - * - * @param writeType The write type to for this characteristic. Can be one of: {@link - * #WRITE_TYPE_DEFAULT}, {@link #WRITE_TYPE_NO_RESPONSE} or {@link #WRITE_TYPE_SIGNED}. - */ - public void setWriteType(int writeType) { - mWriteType = writeType; - } - - /** - * Set the desired key size. - * - * @hide - */ - @UnsupportedAppUsage - public void setKeySize(int keySize) { - mKeySize = keySize; - } - - /** - * Returns a list of descriptors for this characteristic. - * - * @return Descriptors for this characteristic - */ - public List<BluetoothGattDescriptor> getDescriptors() { - return mDescriptors; - } - - /** - * Returns a descriptor with a given UUID out of the list of - * descriptors for this characteristic. - * - * @return GATT descriptor object or null if no descriptor with the given UUID was found. - */ - public BluetoothGattDescriptor getDescriptor(UUID uuid) { - for (BluetoothGattDescriptor descriptor : mDescriptors) { - if (descriptor.getUuid().equals(uuid)) { - return descriptor; - } - } - return null; - } - - /** - * Get the stored value for this characteristic. - * - * <p>This function returns the stored value for this characteristic as - * retrieved by calling {@link BluetoothGatt#readCharacteristic}. The cached - * value of the characteristic is updated as a result of a read characteristic - * operation or if a characteristic update notification has been received. - * - * @return Cached value of the characteristic - * - * @deprecated Use {@link BluetoothGatt#readCharacteristic(BluetoothGattCharacteristic)} instead - */ - @Deprecated - public byte[] getValue() { - return mValue; - } - - /** - * Return the stored value of this characteristic. - * - * <p>The formatType parameter determines how the characteristic value - * is to be interpreted. For example, settting formatType to - * {@link #FORMAT_UINT16} specifies that the first two bytes of the - * characteristic value at the given offset are interpreted to generate the - * return value. - * - * @param formatType The format type used to interpret the characteristic value. - * @param offset Offset at which the integer value can be found. - * @return Cached value of the characteristic or null of offset exceeds value size. - * - * @deprecated Use {@link BluetoothGatt#readCharacteristic(BluetoothGattCharacteristic)} to get - * the characteristic value - */ - @Deprecated - public Integer getIntValue(int formatType, int offset) { - if ((offset + getTypeLen(formatType)) > mValue.length) return null; - - switch (formatType) { - case FORMAT_UINT8: - return unsignedByteToInt(mValue[offset]); - - case FORMAT_UINT16: - return unsignedBytesToInt(mValue[offset], mValue[offset + 1]); - - case FORMAT_UINT32: - return unsignedBytesToInt(mValue[offset], mValue[offset + 1], - mValue[offset + 2], mValue[offset + 3]); - case FORMAT_SINT8: - return unsignedToSigned(unsignedByteToInt(mValue[offset]), 8); - - case FORMAT_SINT16: - return unsignedToSigned(unsignedBytesToInt(mValue[offset], - mValue[offset + 1]), 16); - - case FORMAT_SINT32: - return unsignedToSigned(unsignedBytesToInt(mValue[offset], - mValue[offset + 1], mValue[offset + 2], mValue[offset + 3]), 32); - } - - return null; - } - - /** - * Return the stored value of this characteristic. - * <p>See {@link #getValue} for details. - * - * @param formatType The format type used to interpret the characteristic value. - * @param offset Offset at which the float value can be found. - * @return Cached value of the characteristic at a given offset or null if the requested offset - * exceeds the value size. - * - * @deprecated Use {@link BluetoothGatt#readCharacteristic(BluetoothGattCharacteristic)} to get - * the characteristic value - */ - @Deprecated - public Float getFloatValue(int formatType, int offset) { - if ((offset + getTypeLen(formatType)) > mValue.length) return null; - - switch (formatType) { - case FORMAT_SFLOAT: - return bytesToFloat(mValue[offset], mValue[offset + 1]); - - case FORMAT_FLOAT: - return bytesToFloat(mValue[offset], mValue[offset + 1], - mValue[offset + 2], mValue[offset + 3]); - } - - return null; - } - - /** - * Return the stored value of this characteristic. - * <p>See {@link #getValue} for details. - * - * @param offset Offset at which the string value can be found. - * @return Cached value of the characteristic - * - * @deprecated Use {@link BluetoothGatt#readCharacteristic(BluetoothGattCharacteristic)} to get - * the characteristic value - */ - @Deprecated - public String getStringValue(int offset) { - if (mValue == null || offset > mValue.length) return null; - byte[] strBytes = new byte[mValue.length - offset]; - for (int i = 0; i != (mValue.length - offset); ++i) strBytes[i] = mValue[offset + i]; - return new String(strBytes); - } - - /** - * Updates the locally stored value of this characteristic. - * - * <p>This function modifies the locally stored cached value of this - * characteristic. To send the value to the remote device, call - * {@link BluetoothGatt#writeCharacteristic} to send the value to the - * remote device. - * - * @param value New value for this characteristic - * @return true if the locally stored value has been set, false if the requested value could not - * be stored locally. - * - * @deprecated Pass the characteristic value directly into - * {@link BluetoothGatt#writeCharacteristic(BluetoothGattCharacteristic, byte[], int)} - */ - @Deprecated - public boolean setValue(byte[] value) { - mValue = value; - return true; - } - - /** - * Set the locally stored value of this characteristic. - * <p>See {@link #setValue(byte[])} for details. - * - * @param value New value for this characteristic - * @param formatType Integer format type used to transform the value parameter - * @param offset Offset at which the value should be placed - * @return true if the locally stored value has been set - * - * @deprecated Pass the characteristic value directly into - * {@link BluetoothGatt#writeCharacteristic(BluetoothGattCharacteristic, byte[], int)} - */ - @Deprecated - public boolean setValue(int value, int formatType, int offset) { - int len = offset + getTypeLen(formatType); - if (mValue == null) mValue = new byte[len]; - if (len > mValue.length) return false; - - switch (formatType) { - case FORMAT_SINT8: - value = intToSignedBits(value, 8); - // Fall-through intended - case FORMAT_UINT8: - mValue[offset] = (byte) (value & 0xFF); - break; - - case FORMAT_SINT16: - value = intToSignedBits(value, 16); - // Fall-through intended - case FORMAT_UINT16: - mValue[offset++] = (byte) (value & 0xFF); - mValue[offset] = (byte) ((value >> 8) & 0xFF); - break; - - case FORMAT_SINT32: - value = intToSignedBits(value, 32); - // Fall-through intended - case FORMAT_UINT32: - mValue[offset++] = (byte) (value & 0xFF); - mValue[offset++] = (byte) ((value >> 8) & 0xFF); - mValue[offset++] = (byte) ((value >> 16) & 0xFF); - mValue[offset] = (byte) ((value >> 24) & 0xFF); - break; - - default: - return false; - } - return true; - } - - /** - * Set the locally stored value of this characteristic. - * <p>See {@link #setValue(byte[])} for details. - * - * @param mantissa Mantissa for this characteristic - * @param exponent exponent value for this characteristic - * @param formatType Float format type used to transform the value parameter - * @param offset Offset at which the value should be placed - * @return true if the locally stored value has been set - * - * @deprecated Pass the characteristic value directly into - * {@link BluetoothGatt#writeCharacteristic(BluetoothGattCharacteristic, byte[], int)} - */ - @Deprecated - public boolean setValue(int mantissa, int exponent, int formatType, int offset) { - int len = offset + getTypeLen(formatType); - if (mValue == null) mValue = new byte[len]; - if (len > mValue.length) return false; - - switch (formatType) { - case FORMAT_SFLOAT: - mantissa = intToSignedBits(mantissa, 12); - exponent = intToSignedBits(exponent, 4); - mValue[offset++] = (byte) (mantissa & 0xFF); - mValue[offset] = (byte) ((mantissa >> 8) & 0x0F); - mValue[offset] += (byte) ((exponent & 0x0F) << 4); - break; - - case FORMAT_FLOAT: - mantissa = intToSignedBits(mantissa, 24); - exponent = intToSignedBits(exponent, 8); - mValue[offset++] = (byte) (mantissa & 0xFF); - mValue[offset++] = (byte) ((mantissa >> 8) & 0xFF); - mValue[offset++] = (byte) ((mantissa >> 16) & 0xFF); - mValue[offset] += (byte) (exponent & 0xFF); - break; - - default: - return false; - } - - return true; - } - - /** - * Set the locally stored value of this characteristic. - * <p>See {@link #setValue(byte[])} for details. - * - * @param value New value for this characteristic - * @return true if the locally stored value has been set - * - * @deprecated Pass the characteristic value directly into - * {@link BluetoothGatt#writeCharacteristic(BluetoothGattCharacteristic, byte[], int)} - */ - @Deprecated - public boolean setValue(String value) { - mValue = value.getBytes(); - return true; - } - - /** - * Returns the size of a give value type. - */ - private int getTypeLen(int formatType) { - return formatType & 0xF; - } - - /** - * Convert a signed byte to an unsigned int. - */ - private int unsignedByteToInt(byte b) { - return b & 0xFF; - } - - /** - * Convert signed bytes to a 16-bit unsigned int. - */ - private int unsignedBytesToInt(byte b0, byte b1) { - return (unsignedByteToInt(b0) + (unsignedByteToInt(b1) << 8)); - } - - /** - * Convert signed bytes to a 32-bit unsigned int. - */ - private int unsignedBytesToInt(byte b0, byte b1, byte b2, byte b3) { - return (unsignedByteToInt(b0) + (unsignedByteToInt(b1) << 8)) - + (unsignedByteToInt(b2) << 16) + (unsignedByteToInt(b3) << 24); - } - - /** - * Convert signed bytes to a 16-bit short float value. - */ - private float bytesToFloat(byte b0, byte b1) { - int mantissa = unsignedToSigned(unsignedByteToInt(b0) - + ((unsignedByteToInt(b1) & 0x0F) << 8), 12); - int exponent = unsignedToSigned(unsignedByteToInt(b1) >> 4, 4); - return (float) (mantissa * Math.pow(10, exponent)); - } - - /** - * Convert signed bytes to a 32-bit short float value. - */ - private float bytesToFloat(byte b0, byte b1, byte b2, byte b3) { - int mantissa = unsignedToSigned(unsignedByteToInt(b0) - + (unsignedByteToInt(b1) << 8) - + (unsignedByteToInt(b2) << 16), 24); - return (float) (mantissa * Math.pow(10, b3)); - } - - /** - * Convert an unsigned integer value to a two's-complement encoded - * signed value. - */ - private int unsignedToSigned(int unsigned, int size) { - if ((unsigned & (1 << size - 1)) != 0) { - unsigned = -1 * ((1 << size - 1) - (unsigned & ((1 << size - 1) - 1))); - } - return unsigned; - } - - /** - * Convert an integer into the signed bits of a given length. - */ - private int intToSignedBits(int i, int size) { - if (i < 0) { - i = (1 << size - 1) + (i & ((1 << size - 1) - 1)); - } - return i; - } -} diff --git a/core/java/android/bluetooth/BluetoothGattDescriptor.java b/core/java/android/bluetooth/BluetoothGattDescriptor.java deleted file mode 100644 index a35d5b99fd7b..000000000000 --- a/core/java/android/bluetooth/BluetoothGattDescriptor.java +++ /dev/null @@ -1,291 +0,0 @@ -/* - * Copyright (C) 2013 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.bluetooth; - -import android.compat.annotation.UnsupportedAppUsage; -import android.os.Parcel; -import android.os.ParcelUuid; -import android.os.Parcelable; - -import java.util.UUID; - -/** - * Represents a Bluetooth GATT Descriptor - * - * <p> GATT Descriptors contain additional information and attributes of a GATT - * characteristic, {@link BluetoothGattCharacteristic}. They can be used to describe - * the characteristic's features or to control certain behaviours of the characteristic. - */ -public class BluetoothGattDescriptor implements Parcelable { - - /** - * Value used to enable notification for a client configuration descriptor - */ - public static final byte[] ENABLE_NOTIFICATION_VALUE = {0x01, 0x00}; - - /** - * Value used to enable indication for a client configuration descriptor - */ - public static final byte[] ENABLE_INDICATION_VALUE = {0x02, 0x00}; - - /** - * Value used to disable notifications or indicatinos - */ - public static final byte[] DISABLE_NOTIFICATION_VALUE = {0x00, 0x00}; - - /** - * Descriptor read permission - */ - public static final int PERMISSION_READ = 0x01; - - /** - * Descriptor permission: Allow encrypted read operations - */ - public static final int PERMISSION_READ_ENCRYPTED = 0x02; - - /** - * Descriptor permission: Allow reading with person-in-the-middle protection - */ - public static final int PERMISSION_READ_ENCRYPTED_MITM = 0x04; - - /** - * Descriptor write permission - */ - public static final int PERMISSION_WRITE = 0x10; - - /** - * Descriptor permission: Allow encrypted writes - */ - public static final int PERMISSION_WRITE_ENCRYPTED = 0x20; - - /** - * Descriptor permission: Allow encrypted writes with person-in-the-middle - * protection - */ - public static final int PERMISSION_WRITE_ENCRYPTED_MITM = 0x40; - - /** - * Descriptor permission: Allow signed write operations - */ - public static final int PERMISSION_WRITE_SIGNED = 0x80; - - /** - * Descriptor permission: Allow signed write operations with - * person-in-the-middle protection - */ - public static final int PERMISSION_WRITE_SIGNED_MITM = 0x100; - - /** - * The UUID of this descriptor. - * - * @hide - */ - protected UUID mUuid; - - /** - * Instance ID for this descriptor. - * - * @hide - */ - @UnsupportedAppUsage - protected int mInstance; - - /** - * Permissions for this descriptor - * - * @hide - */ - protected int mPermissions; - - /** - * Back-reference to the characteristic this descriptor belongs to. - * - * @hide - */ - @UnsupportedAppUsage - protected BluetoothGattCharacteristic mCharacteristic; - - /** - * The value for this descriptor. - * - * @hide - */ - protected byte[] mValue; - - /** - * Create a new BluetoothGattDescriptor. - * - * @param uuid The UUID for this descriptor - * @param permissions Permissions for this descriptor - */ - public BluetoothGattDescriptor(UUID uuid, int permissions) { - initDescriptor(null, uuid, 0, permissions); - } - - /** - * Create a new BluetoothGattDescriptor. - * - * @param characteristic The characteristic this descriptor belongs to - * @param uuid The UUID for this descriptor - * @param permissions Permissions for this descriptor - */ - /*package*/ BluetoothGattDescriptor(BluetoothGattCharacteristic characteristic, UUID uuid, - int instance, int permissions) { - initDescriptor(characteristic, uuid, instance, permissions); - } - - /** - * @hide - */ - public BluetoothGattDescriptor(UUID uuid, int instance, int permissions) { - initDescriptor(null, uuid, instance, permissions); - } - - private void initDescriptor(BluetoothGattCharacteristic characteristic, UUID uuid, - int instance, int permissions) { - mCharacteristic = characteristic; - mUuid = uuid; - mInstance = instance; - mPermissions = permissions; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeParcelable(new ParcelUuid(mUuid), 0); - out.writeInt(mInstance); - out.writeInt(mPermissions); - } - - public static final @android.annotation.NonNull Parcelable.Creator<BluetoothGattDescriptor> CREATOR = - new Parcelable.Creator<BluetoothGattDescriptor>() { - public BluetoothGattDescriptor createFromParcel(Parcel in) { - return new BluetoothGattDescriptor(in); - } - - public BluetoothGattDescriptor[] newArray(int size) { - return new BluetoothGattDescriptor[size]; - } - }; - - private BluetoothGattDescriptor(Parcel in) { - mUuid = ((ParcelUuid) in.readParcelable(null)).getUuid(); - mInstance = in.readInt(); - mPermissions = in.readInt(); - } - - /** - * Returns the characteristic this descriptor belongs to. - * - * @return The characteristic. - */ - public BluetoothGattCharacteristic getCharacteristic() { - return mCharacteristic; - } - - /** - * Set the back-reference to the associated characteristic - * - * @hide - */ - @UnsupportedAppUsage - /*package*/ void setCharacteristic(BluetoothGattCharacteristic characteristic) { - mCharacteristic = characteristic; - } - - /** - * Returns the UUID of this descriptor. - * - * @return UUID of this descriptor - */ - public UUID getUuid() { - return mUuid; - } - - /** - * Returns the instance ID for this descriptor. - * - * <p>If a remote device offers multiple descriptors with the same UUID, - * the instance ID is used to distuinguish between descriptors. - * - * @return Instance ID of this descriptor - * @hide - */ - public int getInstanceId() { - return mInstance; - } - - /** - * Force the instance ID. - * - * @hide - */ - public void setInstanceId(int instanceId) { - mInstance = instanceId; - } - - /** - * Returns the permissions for this descriptor. - * - * @return Permissions of this descriptor - */ - public int getPermissions() { - return mPermissions; - } - - /** - * Returns the stored value for this descriptor - * - * <p>This function returns the stored value for this descriptor as - * retrieved by calling {@link BluetoothGatt#readDescriptor}. The cached - * value of the descriptor is updated as a result of a descriptor read - * operation. - * - * @return Cached value of the descriptor - * - * @deprecated Use {@link BluetoothGatt#readDescriptor(BluetoothGattDescriptor)} instead - */ - @Deprecated - public byte[] getValue() { - return mValue; - } - - /** - * Updates the locally stored value of this descriptor. - * - * <p>This function modifies the locally stored cached value of this - * descriptor. To send the value to the remote device, call - * {@link BluetoothGatt#writeDescriptor} to send the value to the - * remote device. - * - * @param value New value for this descriptor - * @return true if the locally stored value has been set, false if the requested value could not - * be stored locally. - * - * @deprecated Pass the descriptor value directly into - * {@link BluetoothGatt#writeDescriptor(BluetoothGattDescriptor, byte[])} - */ - @Deprecated - public boolean setValue(byte[] value) { - mValue = value; - return true; - } -} diff --git a/core/java/android/bluetooth/BluetoothGattIncludedService.java b/core/java/android/bluetooth/BluetoothGattIncludedService.java deleted file mode 100644 index 5580619033a6..000000000000 --- a/core/java/android/bluetooth/BluetoothGattIncludedService.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2016 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.bluetooth; - -import android.os.Parcel; -import android.os.ParcelUuid; -import android.os.Parcelable; - -import java.util.UUID; - -/** - * Represents a Bluetooth GATT Included Service - * - * @hide - */ -public class BluetoothGattIncludedService implements Parcelable { - - /** - * The UUID of this service. - */ - protected UUID mUuid; - - /** - * Instance ID for this service. - */ - protected int mInstanceId; - - /** - * Service type (Primary/Secondary). - */ - protected int mServiceType; - - /** - * Create a new BluetoothGattIncludedService - */ - public BluetoothGattIncludedService(UUID uuid, int instanceId, int serviceType) { - mUuid = uuid; - mInstanceId = instanceId; - mServiceType = serviceType; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeParcelable(new ParcelUuid(mUuid), 0); - out.writeInt(mInstanceId); - out.writeInt(mServiceType); - } - - public static final @android.annotation.NonNull Parcelable.Creator<BluetoothGattIncludedService> CREATOR = - new Parcelable.Creator<BluetoothGattIncludedService>() { - public BluetoothGattIncludedService createFromParcel(Parcel in) { - return new BluetoothGattIncludedService(in); - } - - public BluetoothGattIncludedService[] newArray(int size) { - return new BluetoothGattIncludedService[size]; - } - }; - - private BluetoothGattIncludedService(Parcel in) { - mUuid = ((ParcelUuid) in.readParcelable(null)).getUuid(); - mInstanceId = in.readInt(); - mServiceType = in.readInt(); - } - - /** - * Returns the UUID of this service - * - * @return UUID of this service - */ - public UUID getUuid() { - return mUuid; - } - - /** - * Returns the instance ID for this service - * - * <p>If a remote device offers multiple services with the same UUID - * (ex. multiple battery services for different batteries), the instance - * ID is used to distuinguish services. - * - * @return Instance ID of this service - */ - public int getInstanceId() { - return mInstanceId; - } - - /** - * Get the type of this service (primary/secondary) - */ - public int getType() { - return mServiceType; - } -} diff --git a/core/java/android/bluetooth/BluetoothGattServer.java b/core/java/android/bluetooth/BluetoothGattServer.java deleted file mode 100644 index 08e0178403f1..000000000000 --- a/core/java/android/bluetooth/BluetoothGattServer.java +++ /dev/null @@ -1,954 +0,0 @@ -/* - * Copyright (C) 2013 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.bluetooth; - -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.RequiresNoPermission; -import android.annotation.RequiresPermission; -import android.annotation.SuppressLint; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; -import android.content.AttributionSource; -import android.os.ParcelUuid; -import android.os.RemoteException; -import android.util.Log; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - -/** - * Public API for the Bluetooth GATT Profile server role. - * - * <p>This class provides Bluetooth GATT server role functionality, - * allowing applications to create Bluetooth Smart services and - * characteristics. - * - * <p>BluetoothGattServer is a proxy object for controlling the Bluetooth Service - * via IPC. Use {@link BluetoothManager#openGattServer} to get an instance - * of this class. - */ -public final class BluetoothGattServer implements BluetoothProfile { - private static final String TAG = "BluetoothGattServer"; - private static final boolean DBG = true; - private static final boolean VDBG = false; - - private final IBluetoothGatt mService; - private final BluetoothAdapter mAdapter; - private final AttributionSource mAttributionSource; - - private BluetoothGattServerCallback mCallback; - - private Object mServerIfLock = new Object(); - private int mServerIf; - private int mTransport; - private BluetoothGattService mPendingService; - private List<BluetoothGattService> mServices; - - private static final int CALLBACK_REG_TIMEOUT = 10000; - - /** - * Bluetooth GATT interface callbacks - */ - @SuppressLint("AndroidFrameworkBluetoothPermission") - private final IBluetoothGattServerCallback mBluetoothGattServerCallback = - new IBluetoothGattServerCallback.Stub() { - /** - * Application interface registered - app is ready to go - * @hide - */ - @Override - public void onServerRegistered(int status, int serverIf) { - if (DBG) { - Log.d(TAG, "onServerRegistered() - status=" + status - + " serverIf=" + serverIf); - } - synchronized (mServerIfLock) { - if (mCallback != null) { - mServerIf = serverIf; - mServerIfLock.notify(); - } else { - // registration timeout - Log.e(TAG, "onServerRegistered: mCallback is null"); - } - } - } - - /** - * Server connection state changed - * @hide - */ - @Override - public void onServerConnectionState(int status, int serverIf, - boolean connected, String address) { - if (DBG) { - Log.d(TAG, "onServerConnectionState() - status=" + status - + " serverIf=" + serverIf + " device=" + address); - } - try { - mCallback.onConnectionStateChange(mAdapter.getRemoteDevice(address), status, - connected ? BluetoothProfile.STATE_CONNECTED : - BluetoothProfile.STATE_DISCONNECTED); - } catch (Exception ex) { - Log.w(TAG, "Unhandled exception in callback", ex); - } - } - - /** - * Service has been added - * @hide - */ - @Override - public void onServiceAdded(int status, BluetoothGattService service) { - if (DBG) { - Log.d(TAG, "onServiceAdded() - handle=" + service.getInstanceId() - + " uuid=" + service.getUuid() + " status=" + status); - } - - if (mPendingService == null) { - return; - } - - BluetoothGattService tmp = mPendingService; - mPendingService = null; - - // Rewrite newly assigned handles to existing service. - tmp.setInstanceId(service.getInstanceId()); - List<BluetoothGattCharacteristic> temp_chars = tmp.getCharacteristics(); - List<BluetoothGattCharacteristic> svc_chars = service.getCharacteristics(); - for (int i = 0; i < svc_chars.size(); i++) { - BluetoothGattCharacteristic temp_char = temp_chars.get(i); - BluetoothGattCharacteristic svc_char = svc_chars.get(i); - - temp_char.setInstanceId(svc_char.getInstanceId()); - - List<BluetoothGattDescriptor> temp_descs = temp_char.getDescriptors(); - List<BluetoothGattDescriptor> svc_descs = svc_char.getDescriptors(); - for (int j = 0; j < svc_descs.size(); j++) { - temp_descs.get(j).setInstanceId(svc_descs.get(j).getInstanceId()); - } - } - - mServices.add(tmp); - - try { - mCallback.onServiceAdded((int) status, tmp); - } catch (Exception ex) { - Log.w(TAG, "Unhandled exception in callback", ex); - } - } - - /** - * Remote client characteristic read request. - * @hide - */ - @Override - public void onCharacteristicReadRequest(String address, int transId, - int offset, boolean isLong, int handle) { - if (VDBG) Log.d(TAG, "onCharacteristicReadRequest() - handle=" + handle); - - BluetoothDevice device = mAdapter.getRemoteDevice(address); - BluetoothGattCharacteristic characteristic = getCharacteristicByHandle(handle); - if (characteristic == null) { - Log.w(TAG, "onCharacteristicReadRequest() no char for handle " + handle); - return; - } - - try { - mCallback.onCharacteristicReadRequest(device, transId, offset, - characteristic); - } catch (Exception ex) { - Log.w(TAG, "Unhandled exception in callback", ex); - } - } - - /** - * Remote client descriptor read request. - * @hide - */ - @Override - public void onDescriptorReadRequest(String address, int transId, - int offset, boolean isLong, int handle) { - if (VDBG) Log.d(TAG, "onCharacteristicReadRequest() - handle=" + handle); - - BluetoothDevice device = mAdapter.getRemoteDevice(address); - BluetoothGattDescriptor descriptor = getDescriptorByHandle(handle); - if (descriptor == null) { - Log.w(TAG, "onDescriptorReadRequest() no desc for handle " + handle); - return; - } - - try { - mCallback.onDescriptorReadRequest(device, transId, offset, descriptor); - } catch (Exception ex) { - Log.w(TAG, "Unhandled exception in callback", ex); - } - } - - /** - * Remote client characteristic write request. - * @hide - */ - @Override - public void onCharacteristicWriteRequest(String address, int transId, - int offset, int length, boolean isPrep, boolean needRsp, - int handle, byte[] value) { - if (VDBG) Log.d(TAG, "onCharacteristicWriteRequest() - handle=" + handle); - - BluetoothDevice device = mAdapter.getRemoteDevice(address); - BluetoothGattCharacteristic characteristic = getCharacteristicByHandle(handle); - if (characteristic == null) { - Log.w(TAG, "onCharacteristicWriteRequest() no char for handle " + handle); - return; - } - - try { - mCallback.onCharacteristicWriteRequest(device, transId, characteristic, - isPrep, needRsp, offset, value); - } catch (Exception ex) { - Log.w(TAG, "Unhandled exception in callback", ex); - } - - } - - /** - * Remote client descriptor write request. - * @hide - */ - @Override - public void onDescriptorWriteRequest(String address, int transId, int offset, - int length, boolean isPrep, boolean needRsp, int handle, byte[] value) { - if (VDBG) Log.d(TAG, "onDescriptorWriteRequest() - handle=" + handle); - - BluetoothDevice device = mAdapter.getRemoteDevice(address); - BluetoothGattDescriptor descriptor = getDescriptorByHandle(handle); - if (descriptor == null) { - Log.w(TAG, "onDescriptorWriteRequest() no desc for handle " + handle); - return; - } - - try { - mCallback.onDescriptorWriteRequest(device, transId, descriptor, - isPrep, needRsp, offset, value); - } catch (Exception ex) { - Log.w(TAG, "Unhandled exception in callback", ex); - } - } - - /** - * Execute pending writes. - * @hide - */ - @Override - public void onExecuteWrite(String address, int transId, - boolean execWrite) { - if (DBG) { - Log.d(TAG, "onExecuteWrite() - " - + "device=" + address + ", transId=" + transId - + "execWrite=" + execWrite); - } - - BluetoothDevice device = mAdapter.getRemoteDevice(address); - if (device == null) return; - - try { - mCallback.onExecuteWrite(device, transId, execWrite); - } catch (Exception ex) { - Log.w(TAG, "Unhandled exception in callback", ex); - } - } - - /** - * A notification/indication has been sent. - * @hide - */ - @Override - public void onNotificationSent(String address, int status) { - if (VDBG) { - Log.d(TAG, "onNotificationSent() - " - + "device=" + address + ", status=" + status); - } - - BluetoothDevice device = mAdapter.getRemoteDevice(address); - if (device == null) return; - - try { - mCallback.onNotificationSent(device, status); - } catch (Exception ex) { - Log.w(TAG, "Unhandled exception: " + ex); - } - } - - /** - * The MTU for a connection has changed - * @hide - */ - @Override - public void onMtuChanged(String address, int mtu) { - if (DBG) { - Log.d(TAG, "onMtuChanged() - " - + "device=" + address + ", mtu=" + mtu); - } - - BluetoothDevice device = mAdapter.getRemoteDevice(address); - if (device == null) return; - - try { - mCallback.onMtuChanged(device, mtu); - } catch (Exception ex) { - Log.w(TAG, "Unhandled exception: " + ex); - } - } - - /** - * The PHY for a connection was updated - * @hide - */ - @Override - public void onPhyUpdate(String address, int txPhy, int rxPhy, int status) { - if (DBG) { - Log.d(TAG, - "onPhyUpdate() - " + "device=" + address + ", txPHy=" + txPhy - + ", rxPHy=" + rxPhy); - } - - BluetoothDevice device = mAdapter.getRemoteDevice(address); - if (device == null) return; - - try { - mCallback.onPhyUpdate(device, txPhy, rxPhy, status); - } catch (Exception ex) { - Log.w(TAG, "Unhandled exception: " + ex); - } - } - - /** - * The PHY for a connection was read - * @hide - */ - @Override - public void onPhyRead(String address, int txPhy, int rxPhy, int status) { - if (DBG) { - Log.d(TAG, - "onPhyUpdate() - " + "device=" + address + ", txPHy=" + txPhy - + ", rxPHy=" + rxPhy); - } - - BluetoothDevice device = mAdapter.getRemoteDevice(address); - if (device == null) return; - - try { - mCallback.onPhyRead(device, txPhy, rxPhy, status); - } catch (Exception ex) { - Log.w(TAG, "Unhandled exception: " + ex); - } - } - - /** - * Callback invoked when the given connection is updated - * @hide - */ - @Override - public void onConnectionUpdated(String address, int interval, int latency, - int timeout, int status) { - if (DBG) { - Log.d(TAG, "onConnectionUpdated() - Device=" + address - + " interval=" + interval + " latency=" + latency - + " timeout=" + timeout + " status=" + status); - } - BluetoothDevice device = mAdapter.getRemoteDevice(address); - if (device == null) return; - - try { - mCallback.onConnectionUpdated(device, interval, latency, - timeout, status); - } catch (Exception ex) { - Log.w(TAG, "Unhandled exception: " + ex); - } - } - - }; - - /** - * Create a BluetoothGattServer proxy object. - */ - /* package */ BluetoothGattServer(IBluetoothGatt iGatt, int transport, - BluetoothAdapter adapter) { - mService = iGatt; - mAdapter = adapter; - mAttributionSource = adapter.getAttributionSource(); - mCallback = null; - mServerIf = 0; - mTransport = transport; - mServices = new ArrayList<BluetoothGattService>(); - } - - /** - * Returns a characteristic with given handle. - * - * @hide - */ - /*package*/ BluetoothGattCharacteristic getCharacteristicByHandle(int handle) { - for (BluetoothGattService svc : mServices) { - for (BluetoothGattCharacteristic charac : svc.getCharacteristics()) { - if (charac.getInstanceId() == handle) { - return charac; - } - } - } - return null; - } - - /** - * Returns a descriptor with given handle. - * - * @hide - */ - /*package*/ BluetoothGattDescriptor getDescriptorByHandle(int handle) { - for (BluetoothGattService svc : mServices) { - for (BluetoothGattCharacteristic charac : svc.getCharacteristics()) { - for (BluetoothGattDescriptor desc : charac.getDescriptors()) { - if (desc.getInstanceId() == handle) { - return desc; - } - } - } - } - return null; - } - - /** - * Close this GATT server instance. - * - * Application should call this method as early as possible after it is done with - * this GATT server. - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public void close() { - if (DBG) Log.d(TAG, "close()"); - unregisterCallback(); - } - - /** - * Register an application callback to start using GattServer. - * - * <p>This is an asynchronous call. The callback is used to notify - * success or failure if the function returns true. - * - * @param callback GATT callback handler that will receive asynchronous callbacks. - * @return true, the callback will be called to notify success or failure, false on immediate - * error - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - /*package*/ boolean registerCallback(BluetoothGattServerCallback callback) { - return registerCallback(callback, false); - } - - /** - * Register an application callback to start using GattServer. - * - * <p>This is an asynchronous call. The callback is used to notify - * success or failure if the function returns true. - * - * @param callback GATT callback handler that will receive asynchronous callbacks. - * @param eatt_support indicates if server can use eatt - * @return true, the callback will be called to notify success or failure, false on immediate - * error - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - /*package*/ boolean registerCallback(BluetoothGattServerCallback callback, - boolean eatt_support) { - if (DBG) Log.d(TAG, "registerCallback()"); - if (mService == null) { - Log.e(TAG, "GATT service not available"); - return false; - } - UUID uuid = UUID.randomUUID(); - if (DBG) Log.d(TAG, "registerCallback() - UUID=" + uuid); - - synchronized (mServerIfLock) { - if (mCallback != null) { - Log.e(TAG, "App can register callback only once"); - return false; - } - - mCallback = callback; - try { - mService.registerServer(new ParcelUuid(uuid), mBluetoothGattServerCallback, - eatt_support, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - mCallback = null; - return false; - } - - try { - mServerIfLock.wait(CALLBACK_REG_TIMEOUT); - } catch (InterruptedException e) { - Log.e(TAG, "" + e); - mCallback = null; - } - - if (mServerIf == 0) { - mCallback = null; - return false; - } else { - return true; - } - } - } - - /** - * Unregister the current application and callbacks. - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - private void unregisterCallback() { - if (DBG) Log.d(TAG, "unregisterCallback() - mServerIf=" + mServerIf); - if (mService == null || mServerIf == 0) return; - - try { - mCallback = null; - mService.unregisterServer(mServerIf, mAttributionSource); - mServerIf = 0; - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - - /** - * Returns a service by UUID, instance and type. - * - * @hide - */ - /*package*/ BluetoothGattService getService(UUID uuid, int instanceId, int type) { - for (BluetoothGattService svc : mServices) { - if (svc.getType() == type - && svc.getInstanceId() == instanceId - && svc.getUuid().equals(uuid)) { - return svc; - } - } - return null; - } - - /** - * Initiate a connection to a Bluetooth GATT capable device. - * - * <p>The connection may not be established right away, but will be - * completed when the remote device is available. A - * {@link BluetoothGattServerCallback#onConnectionStateChange} callback will be - * invoked when the connection state changes as a result of this function. - * - * <p>The autoConnect parameter determines whether to actively connect to - * the remote device, or rather passively scan and finalize the connection - * when the remote device is in range/available. Generally, the first ever - * connection to a device should be direct (autoConnect set to false) and - * subsequent connections to known devices should be invoked with the - * autoConnect parameter set to true. - * - * @param autoConnect Whether to directly connect to the remote device (false) or to - * automatically connect as soon as the remote device becomes available (true). - * @return true, if the connection attempt was initiated successfully - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean connect(BluetoothDevice device, boolean autoConnect) { - if (DBG) { - Log.d(TAG, - "connect() - device: " + device.getAddress() + ", auto: " + autoConnect); - } - if (mService == null || mServerIf == 0) return false; - - try { - // autoConnect is inverse of "isDirect" - mService.serverConnect( - mServerIf, device.getAddress(), !autoConnect, mTransport, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - return false; - } - - return true; - } - - /** - * Disconnects an established connection, or cancels a connection attempt - * currently in progress. - * - * @param device Remote device - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public void cancelConnection(BluetoothDevice device) { - if (DBG) Log.d(TAG, "cancelConnection() - device: " + device.getAddress()); - if (mService == null || mServerIf == 0) return; - - try { - mService.serverDisconnect(mServerIf, device.getAddress(), mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - - /** - * Set the preferred connection PHY for this app. Please note that this is just a - * recommendation, whether the PHY change will happen depends on other applications peferences, - * local and remote controller capabilities. Controller can override these settings. <p> {@link - * BluetoothGattServerCallback#onPhyUpdate} will be triggered as a result of this call, even if - * no PHY change happens. It is also triggered when remote device updates the PHY. - * - * @param device The remote device to send this response to - * @param txPhy preferred transmitter PHY. Bitwise OR of any of {@link - * BluetoothDevice#PHY_LE_1M_MASK}, {@link BluetoothDevice#PHY_LE_2M_MASK}, and {@link - * BluetoothDevice#PHY_LE_CODED_MASK}. - * @param rxPhy preferred receiver PHY. Bitwise OR of any of {@link - * BluetoothDevice#PHY_LE_1M_MASK}, {@link BluetoothDevice#PHY_LE_2M_MASK}, and {@link - * BluetoothDevice#PHY_LE_CODED_MASK}. - * @param phyOptions preferred coding to use when transmitting on the LE Coded PHY. Can be one - * of {@link BluetoothDevice#PHY_OPTION_NO_PREFERRED}, {@link BluetoothDevice#PHY_OPTION_S2} or - * {@link BluetoothDevice#PHY_OPTION_S8} - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public void setPreferredPhy(BluetoothDevice device, int txPhy, int rxPhy, int phyOptions) { - try { - mService.serverSetPreferredPhy(mServerIf, device.getAddress(), txPhy, rxPhy, - phyOptions, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - - /** - * Read the current transmitter PHY and receiver PHY of the connection. The values are returned - * in {@link BluetoothGattServerCallback#onPhyRead} - * - * @param device The remote device to send this response to - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public void readPhy(BluetoothDevice device) { - try { - mService.serverReadPhy(mServerIf, device.getAddress(), mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - - /** - * Send a response to a read or write request to a remote device. - * - * <p>This function must be invoked in when a remote read/write request - * is received by one of these callback methods: - * - * <ul> - * <li>{@link BluetoothGattServerCallback#onCharacteristicReadRequest} - * <li>{@link BluetoothGattServerCallback#onCharacteristicWriteRequest} - * <li>{@link BluetoothGattServerCallback#onDescriptorReadRequest} - * <li>{@link BluetoothGattServerCallback#onDescriptorWriteRequest} - * </ul> - * - * @param device The remote device to send this response to - * @param requestId The ID of the request that was received with the callback - * @param status The status of the request to be sent to the remote devices - * @param offset Value offset for partial read/write response - * @param value The value of the attribute that was read/written (optional) - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean sendResponse(BluetoothDevice device, int requestId, - int status, int offset, byte[] value) { - if (VDBG) Log.d(TAG, "sendResponse() - device: " + device.getAddress()); - if (mService == null || mServerIf == 0) return false; - - try { - mService.sendResponse(mServerIf, device.getAddress(), requestId, - status, offset, value, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - return false; - } - return true; - } - - /** - * Send a notification or indication that a local characteristic has been - * updated. - * - * <p>A notification or indication is sent to the remote device to signal - * that the characteristic has been updated. This function should be invoked - * for every client that requests notifications/indications by writing - * to the "Client Configuration" descriptor for the given characteristic. - * - * @param device The remote device to receive the notification/indication - * @param characteristic The local characteristic that has been updated - * @param confirm true to request confirmation from the client (indication), false to send a - * notification - * @return true, if the notification has been triggered successfully - * @throws IllegalArgumentException - * - * @deprecated Use {@link BluetoothGattServer#notifyCharacteristicChanged(BluetoothDevice, - * BluetoothGattCharacteristic, boolean, byte[])} as this is not memory safe. - */ - @Deprecated - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean notifyCharacteristicChanged(BluetoothDevice device, - BluetoothGattCharacteristic characteristic, boolean confirm) { - return notifyCharacteristicChanged(device, characteristic, confirm, - characteristic.getValue()) == BluetoothStatusCodes.SUCCESS; - } - - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(value = { - BluetoothStatusCodes.SUCCESS, - BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_CONNECT_PERMISSION, - BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_PRIVILEGED_PERMISSION, - BluetoothStatusCodes.ERROR_DEVICE_NOT_CONNECTED, - BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND, - BluetoothStatusCodes.ERROR_GATT_WRITE_NOT_ALLOWED, - BluetoothStatusCodes.ERROR_GATT_WRITE_REQUEST_BUSY, - BluetoothStatusCodes.ERROR_UNKNOWN - }) - public @interface NotifyCharacteristicReturnValues{} - - /** - * Send a notification or indication that a local characteristic has been - * updated. - * - * <p>A notification or indication is sent to the remote device to signal - * that the characteristic has been updated. This function should be invoked - * for every client that requests notifications/indications by writing - * to the "Client Configuration" descriptor for the given characteristic. - * - * @param device the remote device to receive the notification/indication - * @param characteristic the local characteristic that has been updated - * @param confirm {@code true} to request confirmation from the client (indication) or - * {@code false} to send a notification - * @param value the characteristic value - * @return whether the notification has been triggered successfully - * @throws IllegalArgumentException if the characteristic value or service is null - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @NotifyCharacteristicReturnValues - public int notifyCharacteristicChanged(@NonNull BluetoothDevice device, - @NonNull BluetoothGattCharacteristic characteristic, boolean confirm, - @NonNull byte[] value) { - if (VDBG) Log.d(TAG, "notifyCharacteristicChanged() - device: " + device.getAddress()); - if (mService == null || mServerIf == 0) { - return BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND; - } - - if (characteristic == null) { - throw new IllegalArgumentException("characteristic must not be null"); - } - if (device == null) { - throw new IllegalArgumentException("device must not be null"); - } - BluetoothGattService service = characteristic.getService(); - if (service == null) { - throw new IllegalArgumentException("Characteristic must have a non-null service"); - } - if (value == null) { - throw new IllegalArgumentException("Characteristic value must not be null"); - } - - try { - return mService.sendNotification(mServerIf, device.getAddress(), - characteristic.getInstanceId(), confirm, - value, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - throw e.rethrowFromSystemServer(); - } - } - - /** - * Add a service to the list of services to be hosted. - * - * <p>Once a service has been addded to the list, the service and its - * included characteristics will be provided by the local device. - * - * <p>If the local device has already exposed services when this function - * is called, a service update notification will be sent to all clients. - * - * <p>The {@link BluetoothGattServerCallback#onServiceAdded} callback will indicate - * whether this service has been added successfully. Do not add another service - * before this callback. - * - * @param service Service to be added to the list of services provided by this device. - * @return true, if the request to add service has been initiated - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean addService(BluetoothGattService service) { - if (DBG) Log.d(TAG, "addService() - service: " + service.getUuid()); - if (mService == null || mServerIf == 0) return false; - - mPendingService = service; - - try { - mService.addService(mServerIf, service, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - return false; - } - - return true; - } - - /** - * Removes a service from the list of services to be provided. - * - * @param service Service to be removed. - * @return true, if the service has been removed - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean removeService(BluetoothGattService service) { - if (DBG) Log.d(TAG, "removeService() - service: " + service.getUuid()); - if (mService == null || mServerIf == 0) return false; - - BluetoothGattService intService = getService(service.getUuid(), - service.getInstanceId(), service.getType()); - if (intService == null) return false; - - try { - mService.removeService(mServerIf, service.getInstanceId(), mAttributionSource); - mServices.remove(intService); - } catch (RemoteException e) { - Log.e(TAG, "", e); - return false; - } - - return true; - } - - /** - * Remove all services from the list of provided services. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public void clearServices() { - if (DBG) Log.d(TAG, "clearServices()"); - if (mService == null || mServerIf == 0) return; - - try { - mService.clearServices(mServerIf, mAttributionSource); - mServices.clear(); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - - /** - * Returns a list of GATT services offered by this device. - * - * <p>An application must call {@link #addService} to add a serice to the - * list of services offered by this device. - * - * @return List of services. Returns an empty list if no services have been added yet. - */ - @RequiresLegacyBluetoothPermission - @RequiresNoPermission - public List<BluetoothGattService> getServices() { - return mServices; - } - - /** - * Returns a {@link BluetoothGattService} from the list of services offered - * by this device. - * - * <p>If multiple instances of the same service (as identified by UUID) - * exist, the first instance of the service is returned. - * - * @param uuid UUID of the requested service - * @return BluetoothGattService if supported, or null if the requested service is not offered by - * this device. - */ - @RequiresLegacyBluetoothPermission - @RequiresNoPermission - public BluetoothGattService getService(UUID uuid) { - for (BluetoothGattService service : mServices) { - if (service.getUuid().equals(uuid)) { - return service; - } - } - - return null; - } - - - /** - * Not supported - please use {@link BluetoothManager#getConnectedDevices(int)} - * with {@link BluetoothProfile#GATT} as argument - * - * @throws UnsupportedOperationException - */ - @Override - @RequiresNoPermission - public int getConnectionState(BluetoothDevice device) { - throw new UnsupportedOperationException("Use BluetoothManager#getConnectionState instead."); - } - - /** - * Not supported - please use {@link BluetoothManager#getConnectedDevices(int)} - * with {@link BluetoothProfile#GATT} as argument - * - * @throws UnsupportedOperationException - */ - @Override - @RequiresNoPermission - public List<BluetoothDevice> getConnectedDevices() { - throw new UnsupportedOperationException( - "Use BluetoothManager#getConnectedDevices instead."); - } - - /** - * Not supported - please use - * {@link BluetoothManager#getDevicesMatchingConnectionStates(int, int[])} - * with {@link BluetoothProfile#GATT} as first argument - * - * @throws UnsupportedOperationException - */ - @Override - @RequiresNoPermission - public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { - throw new UnsupportedOperationException( - "Use BluetoothManager#getDevicesMatchingConnectionStates instead."); - } -} diff --git a/core/java/android/bluetooth/BluetoothGattServerCallback.java b/core/java/android/bluetooth/BluetoothGattServerCallback.java deleted file mode 100644 index 0ead5f57e86c..000000000000 --- a/core/java/android/bluetooth/BluetoothGattServerCallback.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * 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 android.bluetooth; - -/** - * This abstract class is used to implement {@link BluetoothGattServer} callbacks. - */ -public abstract class BluetoothGattServerCallback { - - /** - * Callback indicating when a remote device has been connected or disconnected. - * - * @param device Remote device that has been connected or disconnected. - * @param status Status of the connect or disconnect operation. - * @param newState Returns the new connection state. Can be one of {@link - * BluetoothProfile#STATE_DISCONNECTED} or {@link BluetoothProfile#STATE_CONNECTED} - */ - public void onConnectionStateChange(BluetoothDevice device, int status, - int newState) { - } - - /** - * Indicates whether a local service has been added successfully. - * - * @param status Returns {@link BluetoothGatt#GATT_SUCCESS} if the service was added - * successfully. - * @param service The service that has been added - */ - public void onServiceAdded(int status, BluetoothGattService service) { - } - - /** - * A remote client has requested to read a local characteristic. - * - * <p>An application must call {@link BluetoothGattServer#sendResponse} - * to complete the request. - * - * @param device The remote device that has requested the read operation - * @param requestId The Id of the request - * @param offset Offset into the value of the characteristic - * @param characteristic Characteristic to be read - */ - public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, - int offset, BluetoothGattCharacteristic characteristic) { - } - - /** - * A remote client has requested to write to a local characteristic. - * - * <p>An application must call {@link BluetoothGattServer#sendResponse} - * to complete the request. - * - * @param device The remote device that has requested the write operation - * @param requestId The Id of the request - * @param characteristic Characteristic to be written to. - * @param preparedWrite true, if this write operation should be queued for later execution. - * @param responseNeeded true, if the remote device requires a response - * @param offset The offset given for the value - * @param value The value the client wants to assign to the characteristic - */ - public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, - BluetoothGattCharacteristic characteristic, - boolean preparedWrite, boolean responseNeeded, - int offset, byte[] value) { - } - - /** - * A remote client has requested to read a local descriptor. - * - * <p>An application must call {@link BluetoothGattServer#sendResponse} - * to complete the request. - * - * @param device The remote device that has requested the read operation - * @param requestId The Id of the request - * @param offset Offset into the value of the characteristic - * @param descriptor Descriptor to be read - */ - public void onDescriptorReadRequest(BluetoothDevice device, int requestId, - int offset, BluetoothGattDescriptor descriptor) { - } - - /** - * A remote client has requested to write to a local descriptor. - * - * <p>An application must call {@link BluetoothGattServer#sendResponse} - * to complete the request. - * - * @param device The remote device that has requested the write operation - * @param requestId The Id of the request - * @param descriptor Descriptor to be written to. - * @param preparedWrite true, if this write operation should be queued for later execution. - * @param responseNeeded true, if the remote device requires a response - * @param offset The offset given for the value - * @param value The value the client wants to assign to the descriptor - */ - public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, - BluetoothGattDescriptor descriptor, - boolean preparedWrite, boolean responseNeeded, - int offset, byte[] value) { - } - - /** - * Execute all pending write operations for this device. - * - * <p>An application must call {@link BluetoothGattServer#sendResponse} - * to complete the request. - * - * @param device The remote device that has requested the write operations - * @param requestId The Id of the request - * @param execute Whether the pending writes should be executed (true) or cancelled (false) - */ - public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) { - } - - /** - * Callback invoked when a notification or indication has been sent to - * a remote device. - * - * <p>When multiple notifications are to be sent, an application must - * wait for this callback to be received before sending additional - * notifications. - * - * @param device The remote device the notification has been sent to - * @param status {@link BluetoothGatt#GATT_SUCCESS} if the operation was successful - */ - public void onNotificationSent(BluetoothDevice device, int status) { - } - - /** - * Callback indicating the MTU for a given device connection has changed. - * - * <p>This callback will be invoked if a remote client has requested to change - * the MTU for a given connection. - * - * @param device The remote device that requested the MTU change - * @param mtu The new MTU size - */ - public void onMtuChanged(BluetoothDevice device, int mtu) { - } - - /** - * Callback triggered as result of {@link BluetoothGattServer#setPreferredPhy}, or as a result - * of remote device changing the PHY. - * - * @param device The remote device - * @param txPhy the transmitter PHY in use. One of {@link BluetoothDevice#PHY_LE_1M}, {@link - * BluetoothDevice#PHY_LE_2M}, and {@link BluetoothDevice#PHY_LE_CODED} - * @param rxPhy the receiver PHY in use. One of {@link BluetoothDevice#PHY_LE_1M}, {@link - * BluetoothDevice#PHY_LE_2M}, and {@link BluetoothDevice#PHY_LE_CODED} - * @param status Status of the PHY update operation. {@link BluetoothGatt#GATT_SUCCESS} if the - * operation succeeds. - */ - public void onPhyUpdate(BluetoothDevice device, int txPhy, int rxPhy, int status) { - } - - /** - * Callback triggered as result of {@link BluetoothGattServer#readPhy} - * - * @param device The remote device that requested the PHY read - * @param txPhy the transmitter PHY in use. One of {@link BluetoothDevice#PHY_LE_1M}, {@link - * BluetoothDevice#PHY_LE_2M}, and {@link BluetoothDevice#PHY_LE_CODED} - * @param rxPhy the receiver PHY in use. One of {@link BluetoothDevice#PHY_LE_1M}, {@link - * BluetoothDevice#PHY_LE_2M}, and {@link BluetoothDevice#PHY_LE_CODED} - * @param status Status of the PHY read operation. {@link BluetoothGatt#GATT_SUCCESS} if the - * operation succeeds. - */ - public void onPhyRead(BluetoothDevice device, int txPhy, int rxPhy, int status) { - } - - /** - * Callback indicating the connection parameters were updated. - * - * @param device The remote device involved - * @param interval Connection interval used on this connection, 1.25ms unit. Valid range is from - * 6 (7.5ms) to 3200 (4000ms). - * @param latency Worker latency for the connection in number of connection events. Valid range - * is from 0 to 499 - * @param timeout Supervision timeout for this connection, in 10ms unit. Valid range is from 10 - * (0.1s) to 3200 (32s) - * @param status {@link BluetoothGatt#GATT_SUCCESS} if the connection has been updated - * successfully - * @hide - */ - public void onConnectionUpdated(BluetoothDevice device, int interval, int latency, int timeout, - int status) { - } - -} diff --git a/core/java/android/bluetooth/BluetoothGattService.java b/core/java/android/bluetooth/BluetoothGattService.java deleted file mode 100644 index f64d09fc30d9..000000000000 --- a/core/java/android/bluetooth/BluetoothGattService.java +++ /dev/null @@ -1,395 +0,0 @@ -/* - * Copyright (C) 2013 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.bluetooth; - -import android.annotation.RequiresPermission; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; -import android.compat.annotation.UnsupportedAppUsage; -import android.os.Build; -import android.os.Parcel; -import android.os.ParcelUuid; -import android.os.Parcelable; - -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - -/** - * Represents a Bluetooth GATT Service - * - * <p> Gatt Service contains a collection of {@link BluetoothGattCharacteristic}, - * as well as referenced services. - */ -public class BluetoothGattService implements Parcelable { - - /** - * Primary service - */ - public static final int SERVICE_TYPE_PRIMARY = 0; - - /** - * Secondary service (included by primary services) - */ - public static final int SERVICE_TYPE_SECONDARY = 1; - - - /** - * The remote device this service is associated with. - * This applies to client applications only. - * - * @hide - */ - @UnsupportedAppUsage - protected BluetoothDevice mDevice; - - /** - * The UUID of this service. - * - * @hide - */ - protected UUID mUuid; - - /** - * Instance ID for this service. - * - * @hide - */ - protected int mInstanceId; - - /** - * Handle counter override (for conformance testing). - * - * @hide - */ - protected int mHandles = 0; - - /** - * Service type (Primary/Secondary). - * - * @hide - */ - protected int mServiceType; - - /** - * List of characteristics included in this service. - */ - protected List<BluetoothGattCharacteristic> mCharacteristics; - - /** - * List of included services for this service. - */ - protected List<BluetoothGattService> mIncludedServices; - - /** - * Whether the service uuid should be advertised. - */ - private boolean mAdvertisePreferred; - - /** - * Create a new BluetoothGattService. - * - * @param uuid The UUID for this service - * @param serviceType The type of this service, - * {@link BluetoothGattService#SERVICE_TYPE_PRIMARY} - * or {@link BluetoothGattService#SERVICE_TYPE_SECONDARY} - */ - public BluetoothGattService(UUID uuid, int serviceType) { - mDevice = null; - mUuid = uuid; - mInstanceId = 0; - mServiceType = serviceType; - mCharacteristics = new ArrayList<BluetoothGattCharacteristic>(); - mIncludedServices = new ArrayList<BluetoothGattService>(); - } - - /** - * Create a new BluetoothGattService - * - * @hide - */ - /*package*/ BluetoothGattService(BluetoothDevice device, UUID uuid, - int instanceId, int serviceType) { - mDevice = device; - mUuid = uuid; - mInstanceId = instanceId; - mServiceType = serviceType; - mCharacteristics = new ArrayList<BluetoothGattCharacteristic>(); - mIncludedServices = new ArrayList<BluetoothGattService>(); - } - - /** - * Create a new BluetoothGattService - * - * @hide - */ - public BluetoothGattService(UUID uuid, int instanceId, int serviceType) { - mDevice = null; - mUuid = uuid; - mInstanceId = instanceId; - mServiceType = serviceType; - mCharacteristics = new ArrayList<BluetoothGattCharacteristic>(); - mIncludedServices = new ArrayList<BluetoothGattService>(); - } - - /** - * @hide - */ - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeParcelable(new ParcelUuid(mUuid), 0); - out.writeInt(mInstanceId); - out.writeInt(mServiceType); - out.writeTypedList(mCharacteristics); - - ArrayList<BluetoothGattIncludedService> includedServices = - new ArrayList<BluetoothGattIncludedService>(mIncludedServices.size()); - for (BluetoothGattService s : mIncludedServices) { - includedServices.add(new BluetoothGattIncludedService(s.getUuid(), - s.getInstanceId(), s.getType())); - } - out.writeTypedList(includedServices); - } - - public static final @android.annotation.NonNull Parcelable.Creator<BluetoothGattService> CREATOR = - new Parcelable.Creator<BluetoothGattService>() { - public BluetoothGattService createFromParcel(Parcel in) { - return new BluetoothGattService(in); - } - - public BluetoothGattService[] newArray(int size) { - return new BluetoothGattService[size]; - } - }; - - private BluetoothGattService(Parcel in) { - mUuid = ((ParcelUuid) in.readParcelable(null)).getUuid(); - mInstanceId = in.readInt(); - mServiceType = in.readInt(); - - mCharacteristics = new ArrayList<BluetoothGattCharacteristic>(); - - ArrayList<BluetoothGattCharacteristic> chrcs = - in.createTypedArrayList(BluetoothGattCharacteristic.CREATOR); - if (chrcs != null) { - for (BluetoothGattCharacteristic chrc : chrcs) { - chrc.setService(this); - mCharacteristics.add(chrc); - } - } - - mIncludedServices = new ArrayList<BluetoothGattService>(); - - ArrayList<BluetoothGattIncludedService> inclSvcs = - in.createTypedArrayList(BluetoothGattIncludedService.CREATOR); - if (chrcs != null) { - for (BluetoothGattIncludedService isvc : inclSvcs) { - mIncludedServices.add(new BluetoothGattService(null, isvc.getUuid(), - isvc.getInstanceId(), isvc.getType())); - } - } - } - - /** - * Returns the device associated with this service. - * - * @hide - */ - /*package*/ BluetoothDevice getDevice() { - return mDevice; - } - - /** - * Returns the device associated with this service. - * - * @hide - */ - /*package*/ void setDevice(BluetoothDevice device) { - mDevice = device; - } - - /** - * Add an included service to this service. - * - * @param service The service to be added - * @return true, if the included service was added to the service - */ - @RequiresLegacyBluetoothPermission - public boolean addService(BluetoothGattService service) { - mIncludedServices.add(service); - return true; - } - - /** - * Add a characteristic to this service. - * - * @param characteristic The characteristics to be added - * @return true, if the characteristic was added to the service - */ - @RequiresLegacyBluetoothPermission - public boolean addCharacteristic(BluetoothGattCharacteristic characteristic) { - mCharacteristics.add(characteristic); - characteristic.setService(this); - return true; - } - - /** - * Get characteristic by UUID and instanceId. - * - * @hide - */ - /*package*/ BluetoothGattCharacteristic getCharacteristic(UUID uuid, int instanceId) { - for (BluetoothGattCharacteristic characteristic : mCharacteristics) { - if (uuid.equals(characteristic.getUuid()) - && characteristic.getInstanceId() == instanceId) { - return characteristic; - } - } - return null; - } - - /** - * Force the instance ID. - * - * @hide - */ - @UnsupportedAppUsage - public void setInstanceId(int instanceId) { - mInstanceId = instanceId; - } - - /** - * Get the handle count override (conformance testing. - * - * @hide - */ - /*package*/ int getHandles() { - return mHandles; - } - - /** - * Force the number of handles to reserve for this service. - * This is needed for conformance testing only. - * - * @hide - */ - public void setHandles(int handles) { - mHandles = handles; - } - - /** - * Add an included service to the internal map. - * - * @hide - */ - public void addIncludedService(BluetoothGattService includedService) { - mIncludedServices.add(includedService); - } - - /** - * Returns the UUID of this service - * - * @return UUID of this service - */ - public UUID getUuid() { - return mUuid; - } - - /** - * Returns the instance ID for this service - * - * <p>If a remote device offers multiple services with the same UUID - * (ex. multiple battery services for different batteries), the instance - * ID is used to distuinguish services. - * - * @return Instance ID of this service - */ - public int getInstanceId() { - return mInstanceId; - } - - /** - * Get the type of this service (primary/secondary) - */ - public int getType() { - return mServiceType; - } - - /** - * Get the list of included GATT services for this service. - * - * @return List of included services or empty list if no included services were discovered. - */ - public List<BluetoothGattService> getIncludedServices() { - return mIncludedServices; - } - - /** - * Returns a list of characteristics included in this service. - * - * @return Characteristics included in this service - */ - public List<BluetoothGattCharacteristic> getCharacteristics() { - return mCharacteristics; - } - - /** - * Returns a characteristic with a given UUID out of the list of - * characteristics offered by this service. - * - * <p>This is a convenience function to allow access to a given characteristic - * without enumerating over the list returned by {@link #getCharacteristics} - * manually. - * - * <p>If a remote service offers multiple characteristics with the same - * UUID, the first instance of a characteristic with the given UUID - * is returned. - * - * @return GATT characteristic object or null if no characteristic with the given UUID was - * found. - */ - public BluetoothGattCharacteristic getCharacteristic(UUID uuid) { - for (BluetoothGattCharacteristic characteristic : mCharacteristics) { - if (uuid.equals(characteristic.getUuid())) { - return characteristic; - } - } - return null; - } - - /** - * Returns whether the uuid of the service should be advertised. - * - * @hide - */ - public boolean isAdvertisePreferred() { - return mAdvertisePreferred; - } - - /** - * Set whether the service uuid should be advertised. - * - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public void setAdvertisePreferred(boolean advertisePreferred) { - mAdvertisePreferred = advertisePreferred; - } -} diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java deleted file mode 100644 index 2ed1eb40f8a4..000000000000 --- a/core/java/android/bluetooth/BluetoothHeadset.java +++ /dev/null @@ -1,1516 +0,0 @@ -/* - * Copyright (C) 2008 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.bluetooth; - -import static android.bluetooth.BluetoothUtils.getSyncTimeout; - -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.RequiresPermission; -import android.annotation.SdkConstant; -import android.annotation.SdkConstant.SdkConstantType; -import android.annotation.SuppressLint; -import android.annotation.SystemApi; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; -import android.compat.annotation.UnsupportedAppUsage; -import android.content.AttributionSource; -import android.content.ComponentName; -import android.content.Context; -import android.content.pm.PackageManager; -import android.os.Build; -import android.os.Handler; -import android.os.IBinder; -import android.os.Looper; -import android.os.Message; -import android.os.RemoteException; -import android.util.CloseGuard; -import android.util.Log; - -import com.android.modules.utils.SynchronousResultReceiver; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeoutException; - -/** - * Public API for controlling the Bluetooth Headset Service. This includes both - * Bluetooth Headset and Handsfree (v1.5) profiles. - * - * <p>BluetoothHeadset is a proxy object for controlling the Bluetooth Headset - * Service via IPC. - * - * <p> Use {@link BluetoothAdapter#getProfileProxy} to get - * the BluetoothHeadset proxy object. Use - * {@link BluetoothAdapter#closeProfileProxy} to close the service connection. - * - * <p> Android only supports one connected Bluetooth Headset at a time. - * Each method is protected with its appropriate permission. - */ -public final class BluetoothHeadset implements BluetoothProfile { - private static final String TAG = "BluetoothHeadset"; - private static final boolean DBG = true; - private static final boolean VDBG = false; - - /** - * Intent used to broadcast the change in connection state of the Headset - * profile. - * - * <p>This intent will have 3 extras: - * <ul> - * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> - * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> - * </ul> - * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of - * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, - * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_CONNECTION_STATE_CHANGED = - "android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED"; - - /** - * Intent used to broadcast the change in the Audio Connection state of the - * HFP profile. - * - * <p>This intent will have 3 extras: - * <ul> - * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> - * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> - * </ul> - * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of - * {@link #STATE_AUDIO_CONNECTED}, {@link #STATE_AUDIO_DISCONNECTED}, - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_AUDIO_STATE_CHANGED = - "android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED"; - - /** - * Intent used to broadcast the selection of a connected device as active. - * - * <p>This intent will have one extra: - * <ul> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. It can - * be null if no device is active. </li> - * </ul> - * - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - @UnsupportedAppUsage(trackingBug = 171933273) - public static final String ACTION_ACTIVE_DEVICE_CHANGED = - "android.bluetooth.headset.profile.action.ACTIVE_DEVICE_CHANGED"; - - /** - * Intent used to broadcast that the headset has posted a - * vendor-specific event. - * - * <p>This intent will have 4 extras and 1 category. - * <ul> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote Bluetooth Device - * </li> - * <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD} - The vendor - * specific command </li> - * <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} - The AT - * command type which can be one of {@link #AT_CMD_TYPE_READ}, - * {@link #AT_CMD_TYPE_TEST}, or {@link #AT_CMD_TYPE_SET}, - * {@link #AT_CMD_TYPE_BASIC},{@link #AT_CMD_TYPE_ACTION}. </li> - * <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS} - Command - * arguments. </li> - * </ul> - * - * <p> The category is the Company ID of the vendor defining the - * vendor-specific command. {@link BluetoothAssignedNumbers} - * - * For example, for Plantronics specific events - * Category will be {@link #VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY}.55 - * - * <p> For example, an AT+XEVENT=foo,3 will get translated into - * <ul> - * <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD = +XEVENT </li> - * <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE = AT_CMD_TYPE_SET </li> - * <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS = foo, 3 </li> - * </ul> - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_VENDOR_SPECIFIC_HEADSET_EVENT = - "android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT"; - - /** - * A String extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} - * intents that contains the name of the vendor-specific command. - */ - public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD = - "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD"; - - /** - * An int extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} - * intents that contains the AT command type of the vendor-specific command. - */ - public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE = - "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE"; - - /** - * AT command type READ used with - * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} - * For example, AT+VGM?. There are no arguments for this command type. - */ - public static final int AT_CMD_TYPE_READ = 0; - - /** - * AT command type TEST used with - * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} - * For example, AT+VGM=?. There are no arguments for this command type. - */ - public static final int AT_CMD_TYPE_TEST = 1; - - /** - * AT command type SET used with - * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} - * For example, AT+VGM=<args>. - */ - public static final int AT_CMD_TYPE_SET = 2; - - /** - * AT command type BASIC used with - * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} - * For example, ATD. Single character commands and everything following the - * character are arguments. - */ - public static final int AT_CMD_TYPE_BASIC = 3; - - /** - * AT command type ACTION used with - * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} - * For example, AT+CHUP. There are no arguments for action commands. - */ - public static final int AT_CMD_TYPE_ACTION = 4; - - /** - * A Parcelable String array extra field in - * {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} intents that contains - * the arguments to the vendor-specific command. - */ - public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS = - "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_ARGS"; - - /** - * The intent category to be used with {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} - * for the companyId - */ - public static final String VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY = - "android.bluetooth.headset.intent.category.companyid"; - - /** - * A vendor-specific command for unsolicited result code. - */ - public static final String VENDOR_RESULT_CODE_COMMAND_ANDROID = "+ANDROID"; - - /** - * A vendor-specific AT command - * - * @hide - */ - public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XAPL = "+XAPL"; - - /** - * A vendor-specific AT command - * - * @hide - */ - public static final String VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV = "+IPHONEACCEV"; - - /** - * Battery level indicator associated with - * {@link #VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV} - * - * @hide - */ - public static final int VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL = 1; - - /** - * A vendor-specific AT command - * - * @hide - */ - public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT = "+XEVENT"; - - /** - * Battery level indicator associated with {@link #VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT} - * - * @hide - */ - public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT_BATTERY_LEVEL = "BATTERY"; - - /** - * Headset state when SCO audio is not connected. - * This state can be one of - * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of - * {@link #ACTION_AUDIO_STATE_CHANGED} intent. - */ - public static final int STATE_AUDIO_DISCONNECTED = 10; - - /** - * Headset state when SCO audio is connecting. - * This state can be one of - * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of - * {@link #ACTION_AUDIO_STATE_CHANGED} intent. - */ - public static final int STATE_AUDIO_CONNECTING = 11; - - /** - * Headset state when SCO audio is connected. - * This state can be one of - * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of - * {@link #ACTION_AUDIO_STATE_CHANGED} intent. - */ - public static final int STATE_AUDIO_CONNECTED = 12; - - /** - * Intent used to broadcast the headset's indicator status - * - * <p>This intent will have 3 extras: - * <ul> - * <li> {@link #EXTRA_HF_INDICATORS_IND_ID} - The Assigned number of headset Indicator which - * is supported by the headset ( as indicated by AT+BIND command in the SLC - * sequence) or whose value is changed (indicated by AT+BIEV command) </li> - * <li> {@link #EXTRA_HF_INDICATORS_IND_VALUE} - Updated value of headset indicator. </li> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - Remote device. </li> - * </ul> - * <p>{@link #EXTRA_HF_INDICATORS_IND_ID} is defined by Bluetooth SIG and each of the indicators - * are given an assigned number. Below shows the assigned number of Indicator added so far - * - Enhanced Safety - 1, Valid Values: 0 - Disabled, 1 - Enabled - * - Battery Level - 2, Valid Values: 0~100 - Remaining level of Battery - * - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_HF_INDICATORS_VALUE_CHANGED = - "android.bluetooth.headset.action.HF_INDICATORS_VALUE_CHANGED"; - - /** - * A int extra field in {@link #ACTION_HF_INDICATORS_VALUE_CHANGED} - * intents that contains the assigned number of the headset indicator as defined by - * Bluetooth SIG that is being sent. Value range is 0-65535 as defined in HFP 1.7 - * - * @hide - */ - public static final String EXTRA_HF_INDICATORS_IND_ID = - "android.bluetooth.headset.extra.HF_INDICATORS_IND_ID"; - - /** - * A int extra field in {@link #ACTION_HF_INDICATORS_VALUE_CHANGED} - * intents that contains the value of the Headset indicator that is being sent. - * - * @hide - */ - public static final String EXTRA_HF_INDICATORS_IND_VALUE = - "android.bluetooth.headset.extra.HF_INDICATORS_IND_VALUE"; - - private static final int MESSAGE_HEADSET_SERVICE_CONNECTED = 100; - private static final int MESSAGE_HEADSET_SERVICE_DISCONNECTED = 101; - - private final CloseGuard mCloseGuard = new CloseGuard(); - - private Context mContext; - private ServiceListener mServiceListener; - private volatile IBluetoothHeadset mService; - private final BluetoothAdapter mAdapter; - private final AttributionSource mAttributionSource; - - @SuppressLint("AndroidFrameworkBluetoothPermission") - private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback = - new IBluetoothStateChangeCallback.Stub() { - public void onBluetoothStateChange(boolean up) { - if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up); - if (!up) { - doUnbind(); - } else { - doBind(); - } - } - }; - - /** - * Create a BluetoothHeadset proxy object. - */ - /* package */ BluetoothHeadset(Context context, ServiceListener l, BluetoothAdapter adapter) { - mContext = context; - mServiceListener = l; - mAdapter = adapter; - mAttributionSource = adapter.getAttributionSource(); - - // Preserve legacy compatibility where apps were depending on - // registerStateChangeCallback() performing a permissions check which - // has been relaxed in modern platform versions - if (context.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.R - && context.checkSelfPermission(android.Manifest.permission.BLUETOOTH) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Need BLUETOOTH permission"); - } - - IBluetoothManager mgr = mAdapter.getBluetoothManager(); - if (mgr != null) { - try { - mgr.registerStateChangeCallback(mBluetoothStateChangeCallback); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - - doBind(); - mCloseGuard.open("close"); - } - - private boolean doBind() { - synchronized (mConnection) { - if (mService == null) { - if (VDBG) Log.d(TAG, "Binding service..."); - try { - return mAdapter.getBluetoothManager().bindBluetoothProfileService( - BluetoothProfile.HEADSET, mConnection); - } catch (RemoteException e) { - Log.e(TAG, "Unable to bind HeadsetService", e); - } - } - } - return false; - } - - private void doUnbind() { - synchronized (mConnection) { - if (mService != null) { - if (VDBG) Log.d(TAG, "Unbinding service..."); - try { - mAdapter.getBluetoothManager().unbindBluetoothProfileService( - BluetoothProfile.HEADSET, mConnection); - } catch (RemoteException e) { - Log.e(TAG, "Unable to unbind HeadsetService", e); - } finally { - mService = null; - } - } - } - } - - /** - * Close the connection to the backing service. - * Other public functions of BluetoothHeadset will return default error - * results once close() has been called. Multiple invocations of close() - * are ok. - */ - @UnsupportedAppUsage - /*package*/ void close() { - if (VDBG) log("close()"); - - IBluetoothManager mgr = mAdapter.getBluetoothManager(); - if (mgr != null) { - try { - mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback); - } catch (RemoteException re) { - Log.e(TAG, "", re); - } - } - mServiceListener = null; - doUnbind(); - mCloseGuard.close(); - } - - /** {@hide} */ - @Override - protected void finalize() throws Throwable { - mCloseGuard.warnIfOpen(); - close(); - } - - /** - * Initiate connection to a profile of the remote bluetooth device. - * - * <p> Currently, the system supports only 1 connection to the - * headset/handsfree profile. The API will automatically disconnect connected - * devices before connecting. - * - * <p> This API returns false in scenarios like the profile on the - * device is already connected or Bluetooth is not turned on. - * When this API returns true, it is guaranteed that - * connection state intent for the profile will be broadcasted with - * the state. Users can get the connection state of the profile - * from this intent. - * - * @param device Remote Bluetooth Device - * @return false on immediate error, true otherwise - * @hide - */ - @SystemApi - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.MODIFY_PHONE_STATE, - }) - public boolean connect(BluetoothDevice device) { - if (DBG) log("connect(" + device + ")"); - final IBluetoothHeadset service = mService; - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.connectWithAttribution(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Initiate disconnection from a profile - * - * <p> This API will return false in scenarios like the profile on the - * Bluetooth device is not in connected state etc. When this API returns, - * true, it is guaranteed that the connection state change - * intent will be broadcasted with the state. Users can get the - * disconnection state of the profile from this intent. - * - * <p> If the disconnection is initiated by a remote device, the state - * will transition from {@link #STATE_CONNECTED} to - * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the - * host (local) device the state will transition from - * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to - * state {@link #STATE_DISCONNECTED}. The transition to - * {@link #STATE_DISCONNECTING} can be used to distinguish between the - * two scenarios. - * - * @param device Remote Bluetooth Device - * @return false on immediate error, true otherwise - * @hide - */ - @SystemApi - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean disconnect(BluetoothDevice device) { - if (DBG) log("disconnect(" + device + ")"); - final IBluetoothHeadset service = mService; - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.disconnectWithAttribution(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getConnectedDevices() { - if (VDBG) log("getConnectedDevices()"); - final IBluetoothHeadset service = mService; - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getConnectedDevicesWithAttribution(mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { - if (VDBG) log("getDevicesMatchingStates()"); - final IBluetoothHeadset service = mService; - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public int getConnectionState(BluetoothDevice device) { - if (VDBG) log("getConnectionState(" + device + ")"); - final IBluetoothHeadset service = mService; - final int defaultValue = BluetoothProfile.STATE_DISCONNECTED; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionStateWithAttribution(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Set connection policy of the profile - * - * <p> The device should already be paired. - * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED}, - * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Paired bluetooth device - * @param connectionPolicy is the connection policy to set to for this profile - * @return true if connectionPolicy is set, false on error - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - android.Manifest.permission.MODIFY_PHONE_STATE, - }) - public boolean setConnectionPolicy(@NonNull BluetoothDevice device, - @ConnectionPolicy int connectionPolicy) { - if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); - final IBluetoothHeadset service = mService; - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device) - && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN - || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the priority of the profile. - * - * <p> The priority can be any of: - * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF}, - * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED} - * - * @param device Bluetooth device - * @return priority of the device - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public int getPriority(BluetoothDevice device) { - if (VDBG) log("getPriority(" + device + ")"); - return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device)); - } - - /** - * Get the connection policy of the profile. - * - * <p> The connection policy can be any of: - * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, - * {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Bluetooth device - * @return connection policy of the device - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) { - if (VDBG) log("getConnectionPolicy(" + device + ")"); - final IBluetoothHeadset service = mService; - final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionPolicy(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Checks whether the headset supports some form of noise reduction - * - * @param device Bluetooth device - * @return true if echo cancellation and/or noise reduction is supported, false otherwise - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean isNoiseReductionSupported(@NonNull BluetoothDevice device) { - if (DBG) log("isNoiseReductionSupported()"); - final IBluetoothHeadset service = mService; - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.isNoiseReductionSupported(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Checks whether the headset supports voice recognition - * - * @param device Bluetooth device - * @return true if voice recognition is supported, false otherwise - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean isVoiceRecognitionSupported(@NonNull BluetoothDevice device) { - if (DBG) log("isVoiceRecognitionSupported()"); - final IBluetoothHeadset service = mService; - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.isVoiceRecognitionSupported(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Start Bluetooth voice recognition. This methods sends the voice - * recognition AT command to the headset and establishes the - * audio connection. - * - * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}. - * If this function returns true, this intent will be broadcasted with - * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}. - * - * <p> {@link #EXTRA_STATE} will transition from - * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when - * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED} - * in case of failure to establish the audio connection. - * - * @param device Bluetooth headset - * @return false if there is no headset connected, or the connected headset doesn't support - * voice recognition, or voice recognition is already started, or audio channel is occupied, - * or on error, true otherwise - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.MODIFY_PHONE_STATE, - }) - public boolean startVoiceRecognition(BluetoothDevice device) { - if (DBG) log("startVoiceRecognition()"); - final IBluetoothHeadset service = mService; - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.startVoiceRecognition(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Stop Bluetooth Voice Recognition mode, and shut down the - * Bluetooth audio path. - * - * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}. - * If this function returns true, this intent will be broadcasted with - * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_DISCONNECTED}. - * - * @param device Bluetooth headset - * @return false if there is no headset connected, or voice recognition has not started, - * or voice recognition has ended on this headset, or on error, true otherwise - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean stopVoiceRecognition(BluetoothDevice device) { - if (DBG) log("stopVoiceRecognition()"); - final IBluetoothHeadset service = mService; - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.stopVoiceRecognition(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Check if Bluetooth SCO audio is connected. - * - * @param device Bluetooth headset - * @return true if SCO is connected, false otherwise or on error - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean isAudioConnected(BluetoothDevice device) { - if (VDBG) log("isAudioConnected()"); - final IBluetoothHeadset service = mService; - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.isAudioConnected(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Indicates if current platform supports voice dialing over bluetooth SCO. - * - * @return true if voice dialing over bluetooth is supported, false otherwise. - * @hide - */ - public static boolean isBluetoothVoiceDialingEnabled(Context context) { - return context.getResources().getBoolean( - com.android.internal.R.bool.config_bluetooth_sco_off_call); - } - - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(value = { - BluetoothHeadset.STATE_AUDIO_DISCONNECTED, - BluetoothHeadset.STATE_AUDIO_CONNECTING, - BluetoothHeadset.STATE_AUDIO_CONNECTED, - BluetoothStatusCodes.ERROR_TIMEOUT - }) - public @interface GetAudioStateReturnValues {} - - /** - * Get the current audio state of the Headset. - * - * @param device is the Bluetooth device for which the audio state is being queried - * @return the audio state of the device or an error code - * @throws IllegalArgumentException if the device is null - * - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public @GetAudioStateReturnValues int getAudioState(@NonNull BluetoothDevice device) { - if (VDBG) log("getAudioState"); - if (device == null) { - throw new IllegalArgumentException("device cannot be null"); - } - final IBluetoothHeadset service = mService; - final int defaultValue = BluetoothHeadset.STATE_AUDIO_DISCONNECTED; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (!isDisabled()) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getAudioState(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - throw e.rethrowFromSystemServer(); - } catch (TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - return BluetoothStatusCodes.ERROR_TIMEOUT; - } - } - return defaultValue; - } - - /** - * Sets whether audio routing is allowed. When set to {@code false}, the AG will not route any - * audio to the HF unless explicitly told to. - * This method should be used in cases where the SCO channel is shared between multiple profiles - * and must be delegated by a source knowledgeable - * Note: This is an internal function and shouldn't be exposed - * - * @param allowed {@code true} if the profile can reroute audio, {@code false} otherwise. - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public void setAudioRouteAllowed(boolean allowed) { - if (VDBG) log("setAudioRouteAllowed"); - final IBluetoothHeadset service = mService; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver recv = new SynchronousResultReceiver(); - service.setAudioRouteAllowed(allowed, mAttributionSource, recv); - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - } - - /** - * Returns whether audio routing is allowed. see {@link #setAudioRouteAllowed(boolean)}. - * Note: This is an internal function and shouldn't be exposed - * - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean getAudioRouteAllowed() { - if (VDBG) log("getAudioRouteAllowed"); - final IBluetoothHeadset service = mService; - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.getAudioRouteAllowed(mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Force SCO audio to be opened regardless any other restrictions - * - * @param forced Whether or not SCO audio connection should be forced: True to force SCO audio - * False to use SCO audio in normal manner - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public void setForceScoAudio(boolean forced) { - if (VDBG) log("setForceScoAudio " + String.valueOf(forced)); - final IBluetoothHeadset service = mService; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver recv = new SynchronousResultReceiver(); - service.setForceScoAudio(forced, mAttributionSource, recv); - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - } - - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(value = { - BluetoothStatusCodes.SUCCESS, - BluetoothStatusCodes.ERROR_UNKNOWN, - BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND, - BluetoothStatusCodes.ERROR_TIMEOUT, - BluetoothStatusCodes.ERROR_AUDIO_DEVICE_ALREADY_CONNECTED, - BluetoothStatusCodes.ERROR_NO_ACTIVE_DEVICES, - BluetoothStatusCodes.ERROR_NOT_ACTIVE_DEVICE, - BluetoothStatusCodes.ERROR_AUDIO_ROUTE_BLOCKED, - BluetoothStatusCodes.ERROR_CALL_ACTIVE, - BluetoothStatusCodes.ERROR_PROFILE_NOT_CONNECTED - }) - public @interface ConnectAudioReturnValues {} - - /** - * Initiates a connection of SCO audio to the current active HFP device. The active HFP device - * can be identified with {@link BluetoothAdapter#getActiveDevices(int)}. - * <p> - * If this function returns {@link BluetoothStatusCodes#SUCCESS}, the intent - * {@link #ACTION_AUDIO_STATE_CHANGED} will be broadcasted twice. First with {@link #EXTRA_STATE} - * set to {@link #STATE_AUDIO_CONNECTING}. This will be followed by a broadcast with - * {@link #EXTRA_STATE} set to either {@link #STATE_AUDIO_CONNECTED} if the audio connection is - * established or {@link #STATE_AUDIO_DISCONNECTED} if there was a failure in establishing the - * audio connection. - * - * @return whether the connection was successfully initiated or an error code on failure - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public @ConnectAudioReturnValues int connectAudio() { - if (VDBG) log("connectAudio()"); - final IBluetoothHeadset service = mService; - final int defaultValue = BluetoothStatusCodes.ERROR_UNKNOWN; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.connectAudio(mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - throw e.rethrowFromSystemServer(); - } catch (TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - return BluetoothStatusCodes.ERROR_TIMEOUT; - } - } - return defaultValue; - } - - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(value = { - BluetoothStatusCodes.SUCCESS, - BluetoothStatusCodes.ERROR_UNKNOWN, - BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND, - BluetoothStatusCodes.ERROR_TIMEOUT, - BluetoothStatusCodes.ERROR_PROFILE_NOT_CONNECTED, - BluetoothStatusCodes.ERROR_AUDIO_DEVICE_ALREADY_DISCONNECTED - }) - public @interface DisconnectAudioReturnValues {} - - /** - * Initiates a disconnection of HFP SCO audio from actively connected devices. It also tears - * down voice recognition or virtual voice call, if any exists. - * - * <p> If this function returns {@link BluetoothStatusCodes#SUCCESS}, the intent - * {@link #ACTION_AUDIO_STATE_CHANGED} will be broadcasted with {@link #EXTRA_STATE} set to - * {@link #STATE_AUDIO_DISCONNECTED}. - * - * @return whether the disconnection was initiated successfully or an error code on failure - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public @DisconnectAudioReturnValues int disconnectAudio() { - if (VDBG) log("disconnectAudio()"); - final IBluetoothHeadset service = mService; - final int defaultValue = BluetoothStatusCodes.ERROR_UNKNOWN; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.disconnectAudio(mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - throw e.rethrowFromSystemServer(); - } catch (TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - return BluetoothStatusCodes.ERROR_TIMEOUT; - } - } - return defaultValue; - } - - /** - * Initiates a SCO channel connection as a virtual voice call to the current active device - * Active handsfree device will be notified of incoming call and connected call. - * - * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}. - * If this function returns true, this intent will be broadcasted with - * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}. - * - * <p> {@link #EXTRA_STATE} will transition from - * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when - * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED} - * in case of failure to establish the audio connection. - * - * @return true if successful, false if one of the following case applies - * - SCO audio is not idle (connecting or connected) - * - virtual call has already started - * - there is no active device - * - a Telecom managed call is going on - * - binder is dead or Bluetooth is disabled or other error - * @hide - */ - @SystemApi - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.MODIFY_PHONE_STATE, - }) - public boolean startScoUsingVirtualVoiceCall() { - if (DBG) log("startScoUsingVirtualVoiceCall()"); - final IBluetoothHeadset service = mService; - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.startScoUsingVirtualVoiceCall(mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Terminates an ongoing SCO connection and the associated virtual call. - * - * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}. - * If this function returns true, this intent will be broadcasted with - * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_DISCONNECTED}. - * - * @return true if successful, false if one of the following case applies - * - virtual voice call is not started or has ended - * - binder is dead or Bluetooth is disabled or other error - * @hide - */ - @SystemApi - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.MODIFY_PHONE_STATE, - }) - public boolean stopScoUsingVirtualVoiceCall() { - if (DBG) log("stopScoUsingVirtualVoiceCall()"); - final IBluetoothHeadset service = mService; - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.stopScoUsingVirtualVoiceCall(mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Notify Headset of phone state change. - * This is a backdoor for phone app to call BluetoothHeadset since - * there is currently not a good way to get precise call state change outside - * of phone app. - * - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.MODIFY_PHONE_STATE, - }) - public void phoneStateChanged(int numActive, int numHeld, int callState, String number, - int type, String name) { - final IBluetoothHeadset service = mService; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - service.phoneStateChanged(numActive, numHeld, callState, number, type, name, - mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - } - - /** - * Send Headset of CLCC response - * - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.MODIFY_PHONE_STATE, - }) - public void clccResponse(int index, int direction, int status, int mode, boolean mpty, - String number, int type) { - final IBluetoothHeadset service = mService; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver recv = new SynchronousResultReceiver(); - service.clccResponse(index, direction, status, mode, mpty, number, type, - mAttributionSource, recv); - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - } - - /** - * Sends a vendor-specific unsolicited result code to the headset. - * - * <p>The actual string to be sent is <code>command + ": " + arg</code>. For example, if {@code - * command} is {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} and {@code arg} is {@code "0"}, the - * string <code>"+ANDROID: 0"</code> will be sent. - * - * <p>Currently only {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} is allowed as {@code command}. - * - * @param device Bluetooth headset. - * @param command A vendor-specific command. - * @param arg The argument that will be attached to the command. - * @return {@code false} if there is no headset connected, or if the command is not an allowed - * vendor-specific unsolicited result code, or on error. {@code true} otherwise. - * @throws IllegalArgumentException if {@code command} is {@code null}. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean sendVendorSpecificResultCode(BluetoothDevice device, String command, - String arg) { - if (DBG) { - log("sendVendorSpecificResultCode()"); - } - if (command == null) { - throw new IllegalArgumentException("command is null"); - } - final IBluetoothHeadset service = mService; - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.sendVendorSpecificResultCode(device, command, arg, - mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Select a connected device as active. - * - * The active device selection is per profile. An active device's - * purpose is profile-specific. For example, in HFP and HSP profiles, - * it is the device used for phone call audio. If a remote device is not - * connected, it cannot be selected as active. - * - * <p> This API returns false in scenarios like the profile on the - * device is not connected or Bluetooth is not turned on. - * When this API returns true, it is guaranteed that the - * {@link #ACTION_ACTIVE_DEVICE_CHANGED} intent will be broadcasted - * with the active device. - * - * @param device Remote Bluetooth Device, could be null if phone call audio should not be - * streamed to a headset - * @return false on immediate error, true otherwise - * @hide - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.MODIFY_PHONE_STATE, - }) - @UnsupportedAppUsage(trackingBug = 171933273) - public boolean setActiveDevice(@Nullable BluetoothDevice device) { - if (DBG) { - Log.d(TAG, "setActiveDevice: " + device); - } - final IBluetoothHeadset service = mService; - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && (device == null || isValidDevice(device))) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.setActiveDevice(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the connected device that is active. - * - * @return the connected device that is active or null if no device - * is active. - * @hide - */ - @UnsupportedAppUsage(trackingBug = 171933273) - @Nullable - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothDevice getActiveDevice() { - if (VDBG) Log.d(TAG, "getActiveDevice"); - final IBluetoothHeadset service = mService; - final BluetoothDevice defaultValue = null; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<BluetoothDevice> recv = - new SynchronousResultReceiver(); - service.getActiveDevice(mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Check if in-band ringing is currently enabled. In-band ringing could be disabled during an - * active connection. - * - * @return true if in-band ringing is enabled, false if in-band ringing is disabled - * @hide - */ - @SystemApi - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean isInbandRingingEnabled() { - if (DBG) log("isInbandRingingEnabled()"); - final IBluetoothHeadset service = mService; - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.isInbandRingingEnabled(mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Check if in-band ringing is supported for this platform. - * - * @return true if in-band ringing is supported, false if in-band ringing is not supported - * @hide - */ - public static boolean isInbandRingingSupported(Context context) { - return context.getResources().getBoolean( - com.android.internal.R.bool.config_bluetooth_hfp_inband_ringing_support); - } - - @SuppressLint("AndroidFrameworkBluetoothPermission") - private final IBluetoothProfileServiceConnection mConnection = - new IBluetoothProfileServiceConnection.Stub() { - @Override - public void onServiceConnected(ComponentName className, IBinder service) { - if (DBG) Log.d(TAG, "Proxy object connected"); - mService = IBluetoothHeadset.Stub.asInterface(service); - mHandler.sendMessage(mHandler.obtainMessage( - MESSAGE_HEADSET_SERVICE_CONNECTED)); - } - - @Override - public void onServiceDisconnected(ComponentName className) { - if (DBG) Log.d(TAG, "Proxy object disconnected"); - doUnbind(); - mHandler.sendMessage(mHandler.obtainMessage( - MESSAGE_HEADSET_SERVICE_DISCONNECTED)); - } - }; - - @UnsupportedAppUsage - private boolean isEnabled() { - return mAdapter.getState() == BluetoothAdapter.STATE_ON; - } - - private boolean isDisabled() { - return mAdapter.getState() == BluetoothAdapter.STATE_OFF; - } - - private static boolean isValidDevice(BluetoothDevice device) { - return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); - } - - private static void log(String msg) { - Log.d(TAG, msg); - } - - @SuppressLint("AndroidFrameworkBluetoothPermission") - private final Handler mHandler = new Handler(Looper.getMainLooper()) { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MESSAGE_HEADSET_SERVICE_CONNECTED: { - if (mServiceListener != null) { - mServiceListener.onServiceConnected(BluetoothProfile.HEADSET, - BluetoothHeadset.this); - } - break; - } - case MESSAGE_HEADSET_SERVICE_DISCONNECTED: { - if (mServiceListener != null) { - mServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET); - } - break; - } - } - } - }; -} diff --git a/core/java/android/bluetooth/BluetoothHeadsetClient.java b/core/java/android/bluetooth/BluetoothHeadsetClient.java deleted file mode 100644 index 7d7a7f798bb9..000000000000 --- a/core/java/android/bluetooth/BluetoothHeadsetClient.java +++ /dev/null @@ -1,1356 +0,0 @@ -/* - * Copyright (C) 2014 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.bluetooth; - -import static android.bluetooth.BluetoothUtils.getSyncTimeout; - -import android.annotation.NonNull; -import android.annotation.RequiresPermission; -import android.annotation.SdkConstant; -import android.annotation.SdkConstant.SdkConstantType; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; -import android.compat.annotation.UnsupportedAppUsage; -import android.content.AttributionSource; -import android.content.Context; -import android.os.Build; -import android.os.Bundle; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.Log; - -import com.android.modules.utils.SynchronousResultReceiver; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeoutException; - -/** - * Public API to control Hands Free Profile (HFP role only). - * <p> - * This class defines methods that shall be used by application to manage profile - * connection, calls states and calls actions. - * <p> - * - * @hide - */ -public final class BluetoothHeadsetClient implements BluetoothProfile { - private static final String TAG = "BluetoothHeadsetClient"; - private static final boolean DBG = true; - private static final boolean VDBG = false; - - /** - * Intent sent whenever connection to remote changes. - * - * <p>It includes two extras: - * <code>BluetoothProfile.EXTRA_PREVIOUS_STATE</code> - * and <code>BluetoothProfile.EXTRA_STATE</code>, which - * are mandatory. - * <p>There are also non mandatory feature extras: - * {@link #EXTRA_AG_FEATURE_3WAY_CALLING}, - * {@link #EXTRA_AG_FEATURE_VOICE_RECOGNITION}, - * {@link #EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT}, - * {@link #EXTRA_AG_FEATURE_REJECT_CALL}, - * {@link #EXTRA_AG_FEATURE_ECC}, - * {@link #EXTRA_AG_FEATURE_RESPONSE_AND_HOLD}, - * {@link #EXTRA_AG_FEATURE_ACCEPT_HELD_OR_WAITING_CALL}, - * {@link #EXTRA_AG_FEATURE_RELEASE_HELD_OR_WAITING_CALL}, - * {@link #EXTRA_AG_FEATURE_RELEASE_AND_ACCEPT}, - * {@link #EXTRA_AG_FEATURE_MERGE}, - * {@link #EXTRA_AG_FEATURE_MERGE_AND_DETACH}, - * sent as boolean values only when <code>EXTRA_STATE</code> - * is set to <code>STATE_CONNECTED</code>.</p> - * - * <p>Note that features supported by AG are being sent as - * booleans with value <code>true</code>, - * and not supported ones are <strong>not</strong> being sent at all.</p> - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_CONNECTION_STATE_CHANGED = - "android.bluetooth.headsetclient.profile.action.CONNECTION_STATE_CHANGED"; - - /** - * Intent sent whenever audio state changes. - * - * <p>It includes two mandatory extras: - * {@link BluetoothProfile#EXTRA_STATE}, - * {@link BluetoothProfile#EXTRA_PREVIOUS_STATE}, - * with possible values: - * {@link #STATE_AUDIO_CONNECTING}, - * {@link #STATE_AUDIO_CONNECTED}, - * {@link #STATE_AUDIO_DISCONNECTED}</p> - * <p>When <code>EXTRA_STATE</code> is set - * to </code>STATE_AUDIO_CONNECTED</code>, - * it also includes {@link #EXTRA_AUDIO_WBS} - * indicating wide band speech support.</p> - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_AUDIO_STATE_CHANGED = - "android.bluetooth.headsetclient.profile.action.AUDIO_STATE_CHANGED"; - - /** - * Intent sending updates of the Audio Gateway state. - * Each extra is being sent only when value it - * represents has been changed recently on AG. - * <p>It can contain one or more of the following extras: - * {@link #EXTRA_NETWORK_STATUS}, - * {@link #EXTRA_NETWORK_SIGNAL_STRENGTH}, - * {@link #EXTRA_NETWORK_ROAMING}, - * {@link #EXTRA_BATTERY_LEVEL}, - * {@link #EXTRA_OPERATOR_NAME}, - * {@link #EXTRA_VOICE_RECOGNITION}, - * {@link #EXTRA_IN_BAND_RING}</p> - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_AG_EVENT = - "android.bluetooth.headsetclient.profile.action.AG_EVENT"; - - /** - * Intent sent whenever state of a call changes. - * - * <p>It includes: - * {@link #EXTRA_CALL}, - * with value of {@link BluetoothHeadsetClientCall} instance, - * representing actual call state.</p> - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_CALL_CHANGED = - "android.bluetooth.headsetclient.profile.action.AG_CALL_CHANGED"; - - /** - * Intent that notifies about the result of the last issued action. - * Please note that not every action results in explicit action result code being sent. - * Instead other notifications about new Audio Gateway state might be sent, - * like <code>ACTION_AG_EVENT</code> with <code>EXTRA_VOICE_RECOGNITION</code> value - * when for example user started voice recognition from HF unit. - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_RESULT = - "android.bluetooth.headsetclient.profile.action.RESULT"; - - /** - * Intent that notifies about vendor specific event arrival. Events not defined in - * HFP spec will be matched with supported vendor event list and this intent will - * be broadcasted upon a match. Supported vendor events are of format of - * of "+eventCode" or "+eventCode=xxxx" or "+eventCode:=xxxx". - * Vendor event can be a response to an vendor specific command or unsolicited. - * - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_VENDOR_SPECIFIC_HEADSETCLIENT_EVENT = - "android.bluetooth.headsetclient.profile.action.VENDOR_SPECIFIC_EVENT"; - - /** - * Intent that notifies about the number attached to the last voice tag - * recorded on AG. - * - * <p>It contains: - * {@link #EXTRA_NUMBER}, - * with a <code>String</code> value representing phone number.</p> - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_LAST_VTAG = - "android.bluetooth.headsetclient.profile.action.LAST_VTAG"; - - public static final int STATE_AUDIO_DISCONNECTED = 0; - public static final int STATE_AUDIO_CONNECTING = 1; - public static final int STATE_AUDIO_CONNECTED = 2; - - /** - * Extra with information if connected audio is WBS. - * <p>Possible values: <code>true</code>, - * <code>false</code>.</p> - */ - public static final String EXTRA_AUDIO_WBS = - "android.bluetooth.headsetclient.extra.AUDIO_WBS"; - - /** - * Extra for AG_EVENT indicates network status. - * <p>Value: 0 - network unavailable, - * 1 - network available </p> - */ - public static final String EXTRA_NETWORK_STATUS = - "android.bluetooth.headsetclient.extra.NETWORK_STATUS"; - /** - * Extra for AG_EVENT intent indicates network signal strength. - * <p>Value: <code>Integer</code> representing signal strength.</p> - */ - public static final String EXTRA_NETWORK_SIGNAL_STRENGTH = - "android.bluetooth.headsetclient.extra.NETWORK_SIGNAL_STRENGTH"; - /** - * Extra for AG_EVENT intent indicates roaming state. - * <p>Value: 0 - no roaming - * 1 - active roaming</p> - */ - public static final String EXTRA_NETWORK_ROAMING = - "android.bluetooth.headsetclient.extra.NETWORK_ROAMING"; - /** - * Extra for AG_EVENT intent indicates the battery level. - * <p>Value: <code>Integer</code> representing signal strength.</p> - */ - public static final String EXTRA_BATTERY_LEVEL = - "android.bluetooth.headsetclient.extra.BATTERY_LEVEL"; - /** - * Extra for AG_EVENT intent indicates operator name. - * <p>Value: <code>String</code> representing operator name.</p> - */ - public static final String EXTRA_OPERATOR_NAME = - "android.bluetooth.headsetclient.extra.OPERATOR_NAME"; - /** - * Extra for AG_EVENT intent indicates voice recognition state. - * <p>Value: - * 0 - voice recognition stopped, - * 1 - voice recognition started.</p> - */ - public static final String EXTRA_VOICE_RECOGNITION = - "android.bluetooth.headsetclient.extra.VOICE_RECOGNITION"; - /** - * Extra for AG_EVENT intent indicates in band ring state. - * <p>Value: - * 0 - in band ring tone not supported, or - * 1 - in band ring tone supported.</p> - */ - public static final String EXTRA_IN_BAND_RING = - "android.bluetooth.headsetclient.extra.IN_BAND_RING"; - - /** - * Extra for AG_EVENT intent indicates subscriber info. - * <p>Value: <code>String</code> containing subscriber information.</p> - */ - public static final String EXTRA_SUBSCRIBER_INFO = - "android.bluetooth.headsetclient.extra.SUBSCRIBER_INFO"; - - /** - * Extra for AG_CALL_CHANGED intent indicates the - * {@link BluetoothHeadsetClientCall} object that has changed. - */ - public static final String EXTRA_CALL = - "android.bluetooth.headsetclient.extra.CALL"; - - /** - * Extra for ACTION_LAST_VTAG intent. - * <p>Value: <code>String</code> representing phone number - * corresponding to last voice tag recorded on AG</p> - */ - public static final String EXTRA_NUMBER = - "android.bluetooth.headsetclient.extra.NUMBER"; - - /** - * Extra for ACTION_RESULT intent that shows the result code of - * last issued action. - * <p>Possible results: - * {@link #ACTION_RESULT_OK}, - * {@link #ACTION_RESULT_ERROR}, - * {@link #ACTION_RESULT_ERROR_NO_CARRIER}, - * {@link #ACTION_RESULT_ERROR_BUSY}, - * {@link #ACTION_RESULT_ERROR_NO_ANSWER}, - * {@link #ACTION_RESULT_ERROR_DELAYED}, - * {@link #ACTION_RESULT_ERROR_BLACKLISTED}, - * {@link #ACTION_RESULT_ERROR_CME}</p> - */ - public static final String EXTRA_RESULT_CODE = - "android.bluetooth.headsetclient.extra.RESULT_CODE"; - - /** - * Extra for ACTION_RESULT intent that shows the extended result code of - * last issued action. - * <p>Value: <code>Integer</code> - error code.</p> - */ - public static final String EXTRA_CME_CODE = - "android.bluetooth.headsetclient.extra.CME_CODE"; - - /** - * Extra for VENDOR_SPECIFIC_HEADSETCLIENT_EVENT intent that - * indicates vendor ID. - */ - public static final String EXTRA_VENDOR_ID = - "android.bluetooth.headsetclient.extra.VENDOR_ID"; - - /** - * Extra for VENDOR_SPECIFIC_HEADSETCLIENT_EVENT intent that - * indicates vendor event code. - */ - public static final String EXTRA_VENDOR_EVENT_CODE = - "android.bluetooth.headsetclient.extra.VENDOR_EVENT_CODE"; - - /** - * Extra for VENDOR_SPECIFIC_HEADSETCLIENT_EVENT intent that - * contains full vendor event including event code and full arguments. - */ - public static final String EXTRA_VENDOR_EVENT_FULL_ARGS = - "android.bluetooth.headsetclient.extra.VENDOR_EVENT_FULL_ARGS"; - - - /* Extras for AG_FEATURES, extras type is boolean */ - // TODO verify if all of those are actually useful - /** - * AG feature: three way calling. - */ - public static final String EXTRA_AG_FEATURE_3WAY_CALLING = - "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_3WAY_CALLING"; - /** - * AG feature: voice recognition. - */ - public static final String EXTRA_AG_FEATURE_VOICE_RECOGNITION = - "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_VOICE_RECOGNITION"; - /** - * AG feature: fetching phone number for voice tagging procedure. - */ - public static final String EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT = - "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT"; - /** - * AG feature: ability to reject incoming call. - */ - public static final String EXTRA_AG_FEATURE_REJECT_CALL = - "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_REJECT_CALL"; - /** - * AG feature: enhanced call handling (terminate specific call, private consultation). - */ - public static final String EXTRA_AG_FEATURE_ECC = - "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_ECC"; - /** - * AG feature: response and hold. - */ - public static final String EXTRA_AG_FEATURE_RESPONSE_AND_HOLD = - "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_RESPONSE_AND_HOLD"; - /** - * AG call handling feature: accept held or waiting call in three way calling scenarios. - */ - public static final String EXTRA_AG_FEATURE_ACCEPT_HELD_OR_WAITING_CALL = - "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_ACCEPT_HELD_OR_WAITING_CALL"; - /** - * AG call handling feature: release held or waiting call in three way calling scenarios. - */ - public static final String EXTRA_AG_FEATURE_RELEASE_HELD_OR_WAITING_CALL = - "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_RELEASE_HELD_OR_WAITING_CALL"; - /** - * AG call handling feature: release active call and accept held or waiting call in three way - * calling scenarios. - */ - public static final String EXTRA_AG_FEATURE_RELEASE_AND_ACCEPT = - "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_RELEASE_AND_ACCEPT"; - /** - * AG call handling feature: merge two calls, held and active - multi party conference mode. - */ - public static final String EXTRA_AG_FEATURE_MERGE = - "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_MERGE"; - /** - * AG call handling feature: merge calls and disconnect from multi party - * conversation leaving peers connected to each other. - * Note that this feature needs to be supported by mobile network operator - * as it requires connection and billing transfer. - */ - public static final String EXTRA_AG_FEATURE_MERGE_AND_DETACH = - "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_MERGE_AND_DETACH"; - - /* Action result codes */ - public static final int ACTION_RESULT_OK = 0; - public static final int ACTION_RESULT_ERROR = 1; - public static final int ACTION_RESULT_ERROR_NO_CARRIER = 2; - public static final int ACTION_RESULT_ERROR_BUSY = 3; - public static final int ACTION_RESULT_ERROR_NO_ANSWER = 4; - public static final int ACTION_RESULT_ERROR_DELAYED = 5; - public static final int ACTION_RESULT_ERROR_BLACKLISTED = 6; - public static final int ACTION_RESULT_ERROR_CME = 7; - - /* Detailed CME error codes */ - public static final int CME_PHONE_FAILURE = 0; - public static final int CME_NO_CONNECTION_TO_PHONE = 1; - public static final int CME_OPERATION_NOT_ALLOWED = 3; - public static final int CME_OPERATION_NOT_SUPPORTED = 4; - public static final int CME_PHSIM_PIN_REQUIRED = 5; - public static final int CME_PHFSIM_PIN_REQUIRED = 6; - public static final int CME_PHFSIM_PUK_REQUIRED = 7; - public static final int CME_SIM_NOT_INSERTED = 10; - public static final int CME_SIM_PIN_REQUIRED = 11; - public static final int CME_SIM_PUK_REQUIRED = 12; - public static final int CME_SIM_FAILURE = 13; - public static final int CME_SIM_BUSY = 14; - public static final int CME_SIM_WRONG = 15; - public static final int CME_INCORRECT_PASSWORD = 16; - public static final int CME_SIM_PIN2_REQUIRED = 17; - public static final int CME_SIM_PUK2_REQUIRED = 18; - public static final int CME_MEMORY_FULL = 20; - public static final int CME_INVALID_INDEX = 21; - public static final int CME_NOT_FOUND = 22; - public static final int CME_MEMORY_FAILURE = 23; - public static final int CME_TEXT_STRING_TOO_LONG = 24; - public static final int CME_INVALID_CHARACTER_IN_TEXT_STRING = 25; - public static final int CME_DIAL_STRING_TOO_LONG = 26; - public static final int CME_INVALID_CHARACTER_IN_DIAL_STRING = 27; - public static final int CME_NO_NETWORK_SERVICE = 30; - public static final int CME_NETWORK_TIMEOUT = 31; - public static final int CME_EMERGENCY_SERVICE_ONLY = 32; - public static final int CME_NO_SIMULTANOUS_VOIP_CS_CALLS = 33; - public static final int CME_NOT_SUPPORTED_FOR_VOIP = 34; - public static final int CME_SIP_RESPONSE_CODE = 35; - public static final int CME_NETWORK_PERSONALIZATION_PIN_REQUIRED = 40; - public static final int CME_NETWORK_PERSONALIZATION_PUK_REQUIRED = 41; - public static final int CME_NETWORK_SUBSET_PERSONALIZATION_PIN_REQUIRED = 42; - public static final int CME_NETWORK_SUBSET_PERSONALIZATION_PUK_REQUIRED = 43; - public static final int CME_SERVICE_PROVIDER_PERSONALIZATION_PIN_REQUIRED = 44; - public static final int CME_SERVICE_PROVIDER_PERSONALIZATION_PUK_REQUIRED = 45; - public static final int CME_CORPORATE_PERSONALIZATION_PIN_REQUIRED = 46; - public static final int CME_CORPORATE_PERSONALIZATION_PUK_REQUIRED = 47; - public static final int CME_HIDDEN_KEY_REQUIRED = 48; - public static final int CME_EAP_NOT_SUPPORTED = 49; - public static final int CME_INCORRECT_PARAMETERS = 50; - - /* Action policy for other calls when accepting call */ - public static final int CALL_ACCEPT_NONE = 0; - public static final int CALL_ACCEPT_HOLD = 1; - public static final int CALL_ACCEPT_TERMINATE = 2; - - private final BluetoothAdapter mAdapter; - private final AttributionSource mAttributionSource; - private final BluetoothProfileConnector<IBluetoothHeadsetClient> mProfileConnector = - new BluetoothProfileConnector(this, BluetoothProfile.HEADSET_CLIENT, - "BluetoothHeadsetClient", IBluetoothHeadsetClient.class.getName()) { - @Override - public IBluetoothHeadsetClient getServiceInterface(IBinder service) { - return IBluetoothHeadsetClient.Stub.asInterface(service); - } - }; - - /** - * Create a BluetoothHeadsetClient proxy object. - */ - /* package */ BluetoothHeadsetClient(Context context, ServiceListener listener, - BluetoothAdapter adapter) { - mAdapter = adapter; - mAttributionSource = adapter.getAttributionSource(); - mProfileConnector.connect(context, listener); - } - - /** - * Close the connection to the backing service. - * Other public functions of BluetoothHeadsetClient will return default error - * results once close() has been called. Multiple invocations of close() - * are ok. - */ - /*package*/ void close() { - if (VDBG) log("close()"); - mProfileConnector.disconnect(); - } - - private IBluetoothHeadsetClient getService() { - return mProfileConnector.getService(); - } - - /** - * Connects to remote device. - * - * Currently, the system supports only 1 connection. So, in case of the - * second connection, this implementation will disconnect already connected - * device automatically and will process the new one. - * - * @param device a remote device we want connect to - * @return <code>true</code> if command has been issued successfully; <code>false</code> - * otherwise; upon completion HFP sends {@link #ACTION_CONNECTION_STATE_CHANGED} intent. - * - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean connect(BluetoothDevice device) { - if (DBG) log("connect(" + device + ")"); - final IBluetoothHeadsetClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.connect(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Disconnects remote device - * - * @param device a remote device we want disconnect - * @return <code>true</code> if command has been issued successfully; <code>false</code> - * otherwise; upon completion HFP sends {@link #ACTION_CONNECTION_STATE_CHANGED} intent. - * - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean disconnect(BluetoothDevice device) { - if (DBG) log("disconnect(" + device + ")"); - final IBluetoothHeadsetClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.disconnect(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Return the list of connected remote devices - * - * @return list of connected devices; empty list if nothing is connected. - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getConnectedDevices() { - if (VDBG) log("getConnectedDevices()"); - final IBluetoothHeadsetClient service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getConnectedDevices(mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Returns list of remote devices in a particular state - * - * @param states collection of states - * @return list of devices that state matches the states listed in <code>states</code>; empty - * list if nothing matches the <code>states</code> - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { - if (VDBG) log("getDevicesMatchingStates()"); - final IBluetoothHeadsetClient service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Returns state of the <code>device</code> - * - * @param device a remote device - * @return the state of connection of the device - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public int getConnectionState(BluetoothDevice device) { - if (VDBG) log("getConnectionState(" + device + ")"); - final IBluetoothHeadsetClient service = getService(); - final int defaultValue = BluetoothProfile.STATE_DISCONNECTED; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionState(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Set priority of the profile - * - * <p> The device should already be paired. - * Priority can be one of {@link #PRIORITY_ON} or {@link #PRIORITY_OFF} - * - * @param device Paired bluetooth device - * @param priority - * @return true if priority is set, false on error - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean setPriority(BluetoothDevice device, int priority) { - if (DBG) log("setPriority(" + device + ", " + priority + ")"); - return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority)); - } - - /** - * Set connection policy of the profile - * - * <p> The device should already be paired. - * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED}, - * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Paired bluetooth device - * @param connectionPolicy is the connection policy to set to for this profile - * @return true if connectionPolicy is set, false on error - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean setConnectionPolicy(@NonNull BluetoothDevice device, - @ConnectionPolicy int connectionPolicy) { - if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); - final IBluetoothHeadsetClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device) - && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN - || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the priority of the profile. - * - * <p> The priority can be any of: - * {@link #PRIORITY_OFF}, {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED} - * - * @param device Bluetooth device - * @return priority of the device - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public int getPriority(BluetoothDevice device) { - if (VDBG) log("getPriority(" + device + ")"); - return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device)); - } - - /** - * Get the connection policy of the profile. - * - * <p> The connection policy can be any of: - * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, - * {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Bluetooth device - * @return connection policy of the device - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) { - if (VDBG) log("getConnectionPolicy(" + device + ")"); - final IBluetoothHeadsetClient service = getService(); - final @ConnectionPolicy int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionPolicy(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Starts voice recognition. - * - * @param device remote device - * @return <code>true</code> if command has been issued successfully; <code>false</code> - * otherwise; upon completion HFP sends {@link #ACTION_AG_EVENT} intent. - * - * <p>Feature required for successful execution is being reported by: {@link - * #EXTRA_AG_FEATURE_VOICE_RECOGNITION}. This method invocation will fail silently when feature - * is not supported.</p> - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean startVoiceRecognition(BluetoothDevice device) { - if (DBG) log("startVoiceRecognition()"); - final IBluetoothHeadsetClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.startVoiceRecognition(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Send vendor specific AT command. - * - * @param device remote device - * @param vendorId vendor number by Bluetooth SIG - * @param atCommand command to be sent. It start with + prefix and only one command at one time. - * @return <code>true</code> if command has been issued successfully; <code>false</code> - * otherwise. - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean sendVendorAtCommand(BluetoothDevice device, int vendorId, String atCommand) { - if (DBG) log("sendVendorSpecificCommand()"); - final IBluetoothHeadsetClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.sendVendorAtCommand(device, vendorId, atCommand, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Stops voice recognition. - * - * @param device remote device - * @return <code>true</code> if command has been issued successfully; <code>false</code> - * otherwise; upon completion HFP sends {@link #ACTION_AG_EVENT} intent. - * - * <p>Feature required for successful execution is being reported by: {@link - * #EXTRA_AG_FEATURE_VOICE_RECOGNITION}. This method invocation will fail silently when feature - * is not supported.</p> - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean stopVoiceRecognition(BluetoothDevice device) { - if (DBG) log("stopVoiceRecognition()"); - final IBluetoothHeadsetClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.stopVoiceRecognition(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Returns list of all calls in any state. - * - * @param device remote device - * @return list of calls; empty list if none call exists - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothHeadsetClientCall> getCurrentCalls(BluetoothDevice device) { - if (DBG) log("getCurrentCalls()"); - final IBluetoothHeadsetClient service = getService(); - final List<BluetoothHeadsetClientCall> defaultValue = null; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<List<BluetoothHeadsetClientCall>> recv = - new SynchronousResultReceiver(); - service.getCurrentCalls(device, mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Returns list of current values of AG indicators. - * - * @param device remote device - * @return bundle of AG indicators; null if device is not in CONNECTED state - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public Bundle getCurrentAgEvents(BluetoothDevice device) { - if (DBG) log("getCurrentAgEvents()"); - final IBluetoothHeadsetClient service = getService(); - final Bundle defaultValue = null; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Bundle> recv = new SynchronousResultReceiver(); - service.getCurrentAgEvents(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Accepts a call - * - * @param device remote device - * @param flag action policy while accepting a call. Possible values {@link #CALL_ACCEPT_NONE}, - * {@link #CALL_ACCEPT_HOLD}, {@link #CALL_ACCEPT_TERMINATE} - * @return <code>true</code> if command has been issued successfully; <code>false</code> - * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent. - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean acceptCall(BluetoothDevice device, int flag) { - if (DBG) log("acceptCall()"); - final IBluetoothHeadsetClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.acceptCall(device, flag, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Holds a call. - * - * @param device remote device - * @return <code>true</code> if command has been issued successfully; <code>false</code> - * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent. - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean holdCall(BluetoothDevice device) { - if (DBG) log("holdCall()"); - final IBluetoothHeadsetClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.holdCall(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Rejects a call. - * - * @param device remote device - * @return <code>true</code> if command has been issued successfully; <code>false</code> - * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent. - * - * <p>Feature required for successful execution is being reported by: {@link - * #EXTRA_AG_FEATURE_REJECT_CALL}. This method invocation will fail silently when feature is not - * supported.</p> - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean rejectCall(BluetoothDevice device) { - if (DBG) log("rejectCall()"); - final IBluetoothHeadsetClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.rejectCall(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Terminates a specified call. - * - * Works only when Extended Call Control is supported by Audio Gateway. - * - * @param device remote device - * @param call Handle of call obtained in {@link #dial(BluetoothDevice, String)} or obtained via - * {@link #ACTION_CALL_CHANGED}. {@code call} may be null in which case we will hangup all active - * calls. - * @return <code>true</code> if command has been issued successfully; <code>false</code> - * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent. - * - * <p>Feature required for successful execution is being reported by: {@link - * #EXTRA_AG_FEATURE_ECC}. This method invocation will fail silently when feature is not - * supported.</p> - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean terminateCall(BluetoothDevice device, BluetoothHeadsetClientCall call) { - if (DBG) log("terminateCall()"); - final IBluetoothHeadsetClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.terminateCall(device, call, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Enters private mode with a specified call. - * - * Works only when Extended Call Control is supported by Audio Gateway. - * - * @param device remote device - * @param index index of the call to connect in private mode - * @return <code>true</code> if command has been issued successfully; <code>false</code> - * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent. - * - * <p>Feature required for successful execution is being reported by: {@link - * #EXTRA_AG_FEATURE_ECC}. This method invocation will fail silently when feature is not - * supported.</p> - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean enterPrivateMode(BluetoothDevice device, int index) { - if (DBG) log("enterPrivateMode()"); - final IBluetoothHeadsetClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.enterPrivateMode(device, index, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Performs explicit call transfer. - * - * That means connect other calls and disconnect. - * - * @param device remote device - * @return <code>true</code> if command has been issued successfully; <code>false</code> - * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent. - * - * <p>Feature required for successful execution is being reported by: {@link - * #EXTRA_AG_FEATURE_MERGE_AND_DETACH}. This method invocation will fail silently when feature - * is not supported.</p> - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean explicitCallTransfer(BluetoothDevice device) { - if (DBG) log("explicitCallTransfer()"); - final IBluetoothHeadsetClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.explicitCallTransfer(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Places a call with specified number. - * - * @param device remote device - * @param number valid phone number - * @return <code>{@link BluetoothHeadsetClientCall} call</code> if command has been issued - * successfully; <code>{@link null}</code> otherwise; upon completion HFP sends {@link - * #ACTION_CALL_CHANGED} intent in case of success; {@link #ACTION_RESULT} is sent otherwise; - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothHeadsetClientCall dial(BluetoothDevice device, String number) { - if (DBG) log("dial()"); - final IBluetoothHeadsetClient service = getService(); - final BluetoothHeadsetClientCall defaultValue = null; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<BluetoothHeadsetClientCall> recv = - new SynchronousResultReceiver(); - service.dial(device, number, mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Sends DTMF code. - * - * Possible code values : 0,1,2,3,4,5,6,7,8,9,A,B,C,D,*,# - * - * @param device remote device - * @param code ASCII code - * @return <code>true</code> if command has been issued successfully; <code>false</code> - * otherwise; upon completion HFP sends {@link #ACTION_RESULT} intent; - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean sendDTMF(BluetoothDevice device, byte code) { - if (DBG) log("sendDTMF()"); - final IBluetoothHeadsetClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.sendDTMF(device, code, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get a number corresponding to last voice tag recorded on AG. - * - * @param device remote device - * @return <code>true</code> if command has been issued successfully; <code>false</code> - * otherwise; upon completion HFP sends {@link #ACTION_LAST_VTAG} or {@link #ACTION_RESULT} - * intent; - * - * <p>Feature required for successful execution is being reported by: {@link - * #EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT}. This method invocation will fail silently when - * feature is not supported.</p> - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean getLastVoiceTagNumber(BluetoothDevice device) { - if (DBG) log("getLastVoiceTagNumber()"); - final IBluetoothHeadsetClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.getLastVoiceTagNumber(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Returns current audio state of Audio Gateway. - * - * Note: This is an internal function and shouldn't be exposed - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public int getAudioState(BluetoothDevice device) { - if (VDBG) log("getAudioState"); - final IBluetoothHeadsetClient service = getService(); - final int defaultValue = BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getAudioState(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } else { - return defaultValue; - } - return BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED; - } - - /** - * Sets whether audio routing is allowed. - * - * @param device remote device - * @param allowed if routing is allowed to the device Note: This is an internal function and - * shouldn't be exposed - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public void setAudioRouteAllowed(BluetoothDevice device, boolean allowed) { - if (VDBG) log("setAudioRouteAllowed"); - final IBluetoothHeadsetClient service = getService(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver recv = new SynchronousResultReceiver(); - service.setAudioRouteAllowed(device, allowed, mAttributionSource, recv); - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - } - - /** - * Returns whether audio routing is allowed. - * - * @param device remote device - * @return whether the command succeeded Note: This is an internal function and shouldn't be - * exposed - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean getAudioRouteAllowed(BluetoothDevice device) { - if (VDBG) log("getAudioRouteAllowed"); - final IBluetoothHeadsetClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.getAudioRouteAllowed(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Initiates a connection of audio channel. - * - * It setup SCO channel with remote connected Handsfree AG device. - * - * @param device remote device - * @return <code>true</code> if command has been issued successfully; <code>false</code> - * otherwise; upon completion HFP sends {@link #ACTION_AUDIO_STATE_CHANGED} intent; - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean connectAudio(BluetoothDevice device) { - if (VDBG) log("connectAudio"); - final IBluetoothHeadsetClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.connectAudio(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Disconnects audio channel. - * - * It tears down the SCO channel from remote AG device. - * - * @param device remote device - * @return <code>true</code> if command has been issued successfully; <code>false</code> - * otherwise; upon completion HFP sends {@link #ACTION_AUDIO_STATE_CHANGED} intent; - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean disconnectAudio(BluetoothDevice device) { - if (VDBG) log("disconnectAudio"); - final IBluetoothHeadsetClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.disconnectAudio(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get Audio Gateway features - * - * @param device remote device - * @return bundle of AG features; null if no service or AG not connected - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public Bundle getCurrentAgFeatures(BluetoothDevice device) { - if (VDBG) log("getCurrentAgFeatures"); - final IBluetoothHeadsetClient service = getService(); - final Bundle defaultValue = null; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Bundle> recv = new SynchronousResultReceiver(); - service.getCurrentAgFeatures(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - private boolean isEnabled() { - return mAdapter.getState() == BluetoothAdapter.STATE_ON; - } - - private static boolean isValidDevice(BluetoothDevice device) { - return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); - } - - private static void log(String msg) { - Log.d(TAG, msg); - } -} diff --git a/core/java/android/bluetooth/BluetoothHeadsetClientCall.java b/core/java/android/bluetooth/BluetoothHeadsetClientCall.java deleted file mode 100644 index 032b507f5d3c..000000000000 --- a/core/java/android/bluetooth/BluetoothHeadsetClientCall.java +++ /dev/null @@ -1,323 +0,0 @@ -/* - * Copyright (C) 2014 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.bluetooth; - -import android.annotation.NonNull; -import android.compat.annotation.UnsupportedAppUsage; -import android.content.AttributionSource; -import android.os.Build; -import android.os.Parcel; -import android.os.Parcelable; -import android.os.SystemClock; - -import java.util.UUID; - -/** - * This class represents a single call, its state and properties. - * It implements {@link Parcelable} for inter-process message passing. - * - * @hide - */ -public final class BluetoothHeadsetClientCall implements Parcelable, Attributable { - - /* Call state */ - /** - * Call is active. - */ - public static final int CALL_STATE_ACTIVE = 0; - /** - * Call is in held state. - */ - public static final int CALL_STATE_HELD = 1; - /** - * Outgoing call that is being dialed right now. - */ - public static final int CALL_STATE_DIALING = 2; - /** - * Outgoing call that remote party has already been alerted about. - */ - public static final int CALL_STATE_ALERTING = 3; - /** - * Incoming call that can be accepted or rejected. - */ - public static final int CALL_STATE_INCOMING = 4; - /** - * Waiting call state when there is already an active call. - */ - public static final int CALL_STATE_WAITING = 5; - /** - * Call that has been held by response and hold - * (see Bluetooth specification for further references). - */ - public static final int CALL_STATE_HELD_BY_RESPONSE_AND_HOLD = 6; - /** - * Call that has been already terminated and should not be referenced as a valid call. - */ - public static final int CALL_STATE_TERMINATED = 7; - - private final BluetoothDevice mDevice; - private final int mId; - private int mState; - private String mNumber; - private boolean mMultiParty; - private final boolean mOutgoing; - private final UUID mUUID; - private final long mCreationElapsedMilli; - private final boolean mInBandRing; - - /** - * Creates BluetoothHeadsetClientCall instance. - */ - public BluetoothHeadsetClientCall(BluetoothDevice device, int id, int state, String number, - boolean multiParty, boolean outgoing, boolean inBandRing) { - this(device, id, UUID.randomUUID(), state, number, multiParty, outgoing, inBandRing); - } - - public BluetoothHeadsetClientCall(BluetoothDevice device, int id, UUID uuid, int state, - String number, boolean multiParty, boolean outgoing, boolean inBandRing) { - mDevice = device; - mId = id; - mUUID = uuid; - mState = state; - mNumber = number != null ? number : ""; - mMultiParty = multiParty; - mOutgoing = outgoing; - mInBandRing = inBandRing; - mCreationElapsedMilli = SystemClock.elapsedRealtime(); - } - - /** {@hide} */ - public void setAttributionSource(@NonNull AttributionSource attributionSource) { - Attributable.setAttributionSource(mDevice, attributionSource); - } - - /** - * Sets call's state. - * - * <p>Note: This is an internal function and shouldn't be exposed</p> - * - * @param state new call state. - */ - public void setState(int state) { - mState = state; - } - - /** - * Sets call's number. - * - * <p>Note: This is an internal function and shouldn't be exposed</p> - * - * @param number String representing phone number. - */ - public void setNumber(String number) { - mNumber = number; - } - - /** - * Sets this call as multi party call. - * - * <p>Note: This is an internal function and shouldn't be exposed</p> - * - * @param multiParty if <code>true</code> sets this call as a part of multi party conference. - */ - public void setMultiParty(boolean multiParty) { - mMultiParty = multiParty; - } - - /** - * Gets call's device. - * - * @return call device. - */ - public BluetoothDevice getDevice() { - return mDevice; - } - - /** - * Gets call's Id. - * - * @return call id. - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public int getId() { - return mId; - } - - /** - * Gets call's UUID. - * - * @return call uuid - * @hide - */ - public UUID getUUID() { - return mUUID; - } - - /** - * Gets call's current state. - * - * @return state of this particular phone call. - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public int getState() { - return mState; - } - - /** - * Gets call's number. - * - * @return string representing phone number. - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public String getNumber() { - return mNumber; - } - - /** - * Gets call's creation time in millis since epoch. - * - * @return long representing the creation time. - */ - public long getCreationElapsedMilli() { - return mCreationElapsedMilli; - } - - /** - * Checks if call is an active call in a conference mode (aka multi party). - * - * @return <code>true</code> if call is a multi party call, <code>false</code> otherwise. - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public boolean isMultiParty() { - return mMultiParty; - } - - /** - * Checks if this call is an outgoing call. - * - * @return <code>true</code> if its outgoing call, <code>false</code> otherwise. - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public boolean isOutgoing() { - return mOutgoing; - } - - /** - * Checks if the ringtone will be generated by the connected phone - * - * @return <code>true</code> if in band ring is enabled, <code>false</code> otherwise. - */ - public boolean isInBandRing() { - return mInBandRing; - } - - - @Override - public String toString() { - return toString(false); - } - - /** - * Generate a log string for this call - * @param loggable whether device address should be logged - * @return log string - */ - public String toString(boolean loggable) { - StringBuilder builder = new StringBuilder("BluetoothHeadsetClientCall{mDevice: "); - builder.append(loggable ? mDevice : mDevice.hashCode()); - builder.append(", mId: "); - builder.append(mId); - builder.append(", mUUID: "); - builder.append(mUUID); - builder.append(", mState: "); - switch (mState) { - case CALL_STATE_ACTIVE: - builder.append("ACTIVE"); - break; - case CALL_STATE_HELD: - builder.append("HELD"); - break; - case CALL_STATE_DIALING: - builder.append("DIALING"); - break; - case CALL_STATE_ALERTING: - builder.append("ALERTING"); - break; - case CALL_STATE_INCOMING: - builder.append("INCOMING"); - break; - case CALL_STATE_WAITING: - builder.append("WAITING"); - break; - case CALL_STATE_HELD_BY_RESPONSE_AND_HOLD: - builder.append("HELD_BY_RESPONSE_AND_HOLD"); - break; - case CALL_STATE_TERMINATED: - builder.append("TERMINATED"); - break; - default: - builder.append(mState); - break; - } - builder.append(", mNumber: "); - builder.append(loggable ? mNumber : mNumber.hashCode()); - builder.append(", mMultiParty: "); - builder.append(mMultiParty); - builder.append(", mOutgoing: "); - builder.append(mOutgoing); - builder.append(", mInBandRing: "); - builder.append(mInBandRing); - builder.append("}"); - return builder.toString(); - } - - /** - * {@link Parcelable.Creator} interface implementation. - */ - public static final @android.annotation.NonNull Parcelable.Creator<BluetoothHeadsetClientCall> CREATOR = - new Parcelable.Creator<BluetoothHeadsetClientCall>() { - @Override - public BluetoothHeadsetClientCall createFromParcel(Parcel in) { - return new BluetoothHeadsetClientCall((BluetoothDevice) in.readParcelable(null), - in.readInt(), UUID.fromString(in.readString()), in.readInt(), - in.readString(), in.readInt() == 1, in.readInt() == 1, - in.readInt() == 1); - } - - @Override - public BluetoothHeadsetClientCall[] newArray(int size) { - return new BluetoothHeadsetClientCall[size]; - } - }; - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeParcelable(mDevice, 0); - out.writeInt(mId); - out.writeString(mUUID.toString()); - out.writeInt(mState); - out.writeString(mNumber); - out.writeInt(mMultiParty ? 1 : 0); - out.writeInt(mOutgoing ? 1 : 0); - out.writeInt(mInBandRing ? 1 : 0); - } - - @Override - public int describeContents() { - return 0; - } -} diff --git a/core/java/android/bluetooth/BluetoothHealth.java b/core/java/android/bluetooth/BluetoothHealth.java deleted file mode 100644 index 65f68a943e08..000000000000 --- a/core/java/android/bluetooth/BluetoothHealth.java +++ /dev/null @@ -1,386 +0,0 @@ -/* - * Copyright (C) 2011 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.bluetooth; - -import android.annotation.RequiresPermission; -import android.annotation.SuppressLint; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; -import android.os.ParcelFileDescriptor; -import android.util.Log; - -import java.util.ArrayList; -import java.util.List; - -/** - * Public API for Bluetooth Health Profile. - * - * <p>BluetoothHealth is a proxy object for controlling the Bluetooth - * Service via IPC. - * - * <p> How to connect to a health device which is acting in the source role. - * <li> Use {@link BluetoothAdapter#getProfileProxy} to get - * the BluetoothHealth proxy object. </li> - * <li> Create an {@link BluetoothHealth} callback and call - * {@link #registerSinkAppConfiguration} to register an application - * configuration </li> - * <li> Pair with the remote device. This currently needs to be done manually - * from Bluetooth Settings </li> - * <li> Connect to a health device using {@link #connectChannelToSource}. Some - * devices will connect the channel automatically. The {@link BluetoothHealth} - * callback will inform the application of channel state change. </li> - * <li> Use the file descriptor provided with a connected channel to read and - * write data to the health channel. </li> - * <li> The received data needs to be interpreted using a health manager which - * implements the IEEE 11073-xxxxx specifications. - * <li> When done, close the health channel by calling {@link #disconnectChannel} - * and unregister the application configuration calling - * {@link #unregisterAppConfiguration} - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New apps - * should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ -@Deprecated -public final class BluetoothHealth implements BluetoothProfile { - private static final String TAG = "BluetoothHealth"; - /** - * Health Profile Source Role - the health device. - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ - @Deprecated - public static final int SOURCE_ROLE = 1 << 0; - - /** - * Health Profile Sink Role the device talking to the health device. - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ - @Deprecated - public static final int SINK_ROLE = 1 << 1; - - /** - * Health Profile - Channel Type used - Reliable - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ - @Deprecated - public static final int CHANNEL_TYPE_RELIABLE = 10; - - /** - * Health Profile - Channel Type used - Streaming - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ - @Deprecated - public static final int CHANNEL_TYPE_STREAMING = 11; - - /** - * Hide auto-created default constructor - * @hide - */ - BluetoothHealth() {} - - /** - * Register an application configuration that acts as a Health SINK. - * This is the configuration that will be used to communicate with health devices - * which will act as the {@link #SOURCE_ROLE}. This is an asynchronous call and so - * the callback is used to notify success or failure if the function returns true. - * - * @param name The friendly name associated with the application or configuration. - * @param dataType The dataType of the Source role of Health Profile to which the sink wants to - * connect to. - * @param callback A callback to indicate success or failure of the registration and all - * operations done on this application configuration. - * @return If true, callback will be called. - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ - @Deprecated - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SuppressLint("AndroidFrameworkRequiresPermission") - public boolean registerSinkAppConfiguration(String name, int dataType, - BluetoothHealthCallback callback) { - Log.e(TAG, "registerSinkAppConfiguration(): BluetoothHealth is deprecated"); - return false; - } - - /** - * Unregister an application configuration that has been registered using - * {@link #registerSinkAppConfiguration} - * - * @param config The health app configuration - * @return Success or failure. - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ - @Deprecated - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SuppressLint("AndroidFrameworkRequiresPermission") - public boolean unregisterAppConfiguration(BluetoothHealthAppConfiguration config) { - Log.e(TAG, "unregisterAppConfiguration(): BluetoothHealth is deprecated"); - return false; - } - - /** - * Connect to a health device which has the {@link #SOURCE_ROLE}. - * This is an asynchronous call. If this function returns true, the callback - * associated with the application configuration will be called. - * - * @param device The remote Bluetooth device. - * @param config The application configuration which has been registered using {@link - * #registerSinkAppConfiguration(String, int, BluetoothHealthCallback) } - * @return If true, the callback associated with the application config will be called. - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ - @Deprecated - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SuppressLint("AndroidFrameworkRequiresPermission") - public boolean connectChannelToSource(BluetoothDevice device, - BluetoothHealthAppConfiguration config) { - Log.e(TAG, "connectChannelToSource(): BluetoothHealth is deprecated"); - return false; - } - - /** - * Disconnect a connected health channel. - * This is an asynchronous call. If this function returns true, the callback - * associated with the application configuration will be called. - * - * @param device The remote Bluetooth device. - * @param config The application configuration which has been registered using {@link - * #registerSinkAppConfiguration(String, int, BluetoothHealthCallback) } - * @param channelId The channel id associated with the channel - * @return If true, the callback associated with the application config will be called. - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ - @Deprecated - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SuppressLint("AndroidFrameworkRequiresPermission") - public boolean disconnectChannel(BluetoothDevice device, - BluetoothHealthAppConfiguration config, int channelId) { - Log.e(TAG, "disconnectChannel(): BluetoothHealth is deprecated"); - return false; - } - - /** - * Get the file descriptor of the main channel associated with the remote device - * and application configuration. - * - * <p> Its the responsibility of the caller to close the ParcelFileDescriptor - * when done. - * - * @param device The remote Bluetooth health device - * @param config The application configuration - * @return null on failure, ParcelFileDescriptor on success. - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ - @Deprecated - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SuppressLint("AndroidFrameworkRequiresPermission") - public ParcelFileDescriptor getMainChannelFd(BluetoothDevice device, - BluetoothHealthAppConfiguration config) { - Log.e(TAG, "getMainChannelFd(): BluetoothHealth is deprecated"); - return null; - } - - /** - * Get the current connection state of the profile. - * - * This is not specific to any application configuration but represents the connection - * state of the local Bluetooth adapter with the remote device. This can be used - * by applications like status bar which would just like to know the state of the - * local adapter. - * - * @param device Remote bluetooth device. - * @return State of the profile connection. One of {@link #STATE_CONNECTED}, {@link - * #STATE_CONNECTING}, {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING} - */ - @Override - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SuppressLint("AndroidFrameworkRequiresPermission") - public int getConnectionState(BluetoothDevice device) { - Log.e(TAG, "getConnectionState(): BluetoothHealth is deprecated"); - return STATE_DISCONNECTED; - } - - /** - * Get connected devices for the health profile. - * - * <p> Return the set of devices which are in state {@link #STATE_CONNECTED} - * - * This is not specific to any application configuration but represents the connection - * state of the local Bluetooth adapter for this profile. This can be used - * by applications like status bar which would just like to know the state of the - * local adapter. - * - * @return List of devices. The list will be empty on error. - */ - @Override - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SuppressLint("AndroidFrameworkRequiresPermission") - public List<BluetoothDevice> getConnectedDevices() { - Log.e(TAG, "getConnectedDevices(): BluetoothHealth is deprecated"); - return new ArrayList<>(); - } - - /** - * Get a list of devices that match any of the given connection - * states. - * - * <p> If none of the devices match any of the given states, - * an empty list will be returned. - * - * <p>This is not specific to any application configuration but represents the connection - * state of the local Bluetooth adapter for this profile. This can be used - * by applications like status bar which would just like to know the state of the - * local adapter. - * - * @param states Array of states. States can be one of {@link #STATE_CONNECTED}, {@link - * #STATE_CONNECTING}, {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING}, - * @return List of devices. The list will be empty on error. - */ - @Override - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SuppressLint("AndroidFrameworkRequiresPermission") - public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { - Log.e(TAG, "getDevicesMatchingConnectionStates(): BluetoothHealth is deprecated"); - return new ArrayList<>(); - } - - /** Health Channel Connection State - Disconnected - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ - @Deprecated - public static final int STATE_CHANNEL_DISCONNECTED = 0; - /** Health Channel Connection State - Connecting - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ - @Deprecated - public static final int STATE_CHANNEL_CONNECTING = 1; - /** Health Channel Connection State - Connected - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ - @Deprecated - public static final int STATE_CHANNEL_CONNECTED = 2; - /** Health Channel Connection State - Disconnecting - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ - @Deprecated - public static final int STATE_CHANNEL_DISCONNECTING = 3; - - /** Health App Configuration registration success - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ - @Deprecated - public static final int APP_CONFIG_REGISTRATION_SUCCESS = 0; - /** Health App Configuration registration failure - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ - @Deprecated - public static final int APP_CONFIG_REGISTRATION_FAILURE = 1; - /** Health App Configuration un-registration success - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ - @Deprecated - public static final int APP_CONFIG_UNREGISTRATION_SUCCESS = 2; - /** Health App Configuration un-registration failure - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ - @Deprecated - public static final int APP_CONFIG_UNREGISTRATION_FAILURE = 3; -} diff --git a/core/java/android/bluetooth/BluetoothHealthAppConfiguration.java b/core/java/android/bluetooth/BluetoothHealthAppConfiguration.java deleted file mode 100644 index 2f66df258b53..000000000000 --- a/core/java/android/bluetooth/BluetoothHealthAppConfiguration.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) 2011 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.bluetooth; - -import android.os.Parcel; -import android.os.Parcelable; - -/** - * The Bluetooth Health Application Configuration that is used in conjunction with - * the {@link BluetoothHealth} class. This class represents an application configuration - * that the Bluetooth Health third party application will register to communicate with the - * remote Bluetooth health device. - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ -@Deprecated -public final class BluetoothHealthAppConfiguration implements Parcelable { - - /** - * Hide auto-created default constructor - * @hide - */ - BluetoothHealthAppConfiguration() {} - - @Override - public int describeContents() { - return 0; - } - - /** - * Return the data type associated with this application configuration. - * - * @return dataType - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ - @Deprecated - public int getDataType() { - return 0; - } - - /** - * Return the name of the application configuration. - * - * @return String name - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ - @Deprecated - public String getName() { - return null; - } - - /** - * Return the role associated with this application configuration. - * - * @return One of {@link BluetoothHealth#SOURCE_ROLE} or {@link BluetoothHealth#SINK_ROLE} - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ - @Deprecated - public int getRole() { - return 0; - } - - /** - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ - @Deprecated - public static final @android.annotation.NonNull Parcelable.Creator<BluetoothHealthAppConfiguration> CREATOR = - new Parcelable.Creator<BluetoothHealthAppConfiguration>() { - @Override - public BluetoothHealthAppConfiguration createFromParcel(Parcel in) { - return new BluetoothHealthAppConfiguration(); - } - - @Override - public BluetoothHealthAppConfiguration[] newArray(int size) { - return new BluetoothHealthAppConfiguration[size]; - } - }; - - @Override - public void writeToParcel(Parcel out, int flags) {} -} diff --git a/core/java/android/bluetooth/BluetoothHealthCallback.java b/core/java/android/bluetooth/BluetoothHealthCallback.java deleted file mode 100644 index 4769212c5361..000000000000 --- a/core/java/android/bluetooth/BluetoothHealthCallback.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2011 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.bluetooth; - -import android.annotation.BinderThread; -import android.os.ParcelFileDescriptor; -import android.util.Log; - -/** - * This abstract class is used to implement {@link BluetoothHealth} callbacks. - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ -@Deprecated -public abstract class BluetoothHealthCallback { - private static final String TAG = "BluetoothHealthCallback"; - - /** - * Callback to inform change in registration state of the health - * application. - * <p> This callback is called on the binder thread (not on the UI thread) - * - * @param config Bluetooth Health app configuration - * @param status Success or failure of the registration or unregistration calls. Can be one of - * {@link BluetoothHealth#APP_CONFIG_REGISTRATION_SUCCESS} or {@link - * BluetoothHealth#APP_CONFIG_REGISTRATION_FAILURE} or - * {@link BluetoothHealth#APP_CONFIG_UNREGISTRATION_SUCCESS} - * or {@link BluetoothHealth#APP_CONFIG_UNREGISTRATION_FAILURE} - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ - @BinderThread - @Deprecated - public void onHealthAppConfigurationStatusChange(BluetoothHealthAppConfiguration config, - int status) { - Log.d(TAG, "onHealthAppConfigurationStatusChange: " + config + "Status: " + status); - } - - /** - * Callback to inform change in channel state. - * <p> Its the responsibility of the implementor of this callback to close the - * parcel file descriptor when done. This callback is called on the Binder - * thread (not the UI thread) - * - * @param config The Health app configutation - * @param device The Bluetooth Device - * @param prevState The previous state of the channel - * @param newState The new state of the channel. - * @param fd The Parcel File Descriptor when the channel state is connected. - * @param channelId The id associated with the channel. This id will be used in future calls - * like when disconnecting the channel. - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ - @BinderThread - @Deprecated - public void onHealthChannelStateChange(BluetoothHealthAppConfiguration config, - BluetoothDevice device, int prevState, int newState, ParcelFileDescriptor fd, - int channelId) { - Log.d(TAG, "onHealthChannelStateChange: " + config + "Device: " + device - + "prevState:" + prevState + "newState:" + newState + "ParcelFd:" + fd - + "ChannelId:" + channelId); - } -} diff --git a/core/java/android/bluetooth/BluetoothHearingAid.java b/core/java/android/bluetooth/BluetoothHearingAid.java deleted file mode 100644 index 339a75fe0fbe..000000000000 --- a/core/java/android/bluetooth/BluetoothHearingAid.java +++ /dev/null @@ -1,691 +0,0 @@ -/* - * Copyright 2018 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.bluetooth; - -import static android.bluetooth.BluetoothUtils.getSyncTimeout; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.RequiresPermission; -import android.annotation.SdkConstant; -import android.annotation.SdkConstant.SdkConstantType; -import android.annotation.SystemApi; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; -import android.compat.annotation.UnsupportedAppUsage; -import android.content.AttributionSource; -import android.content.Context; -import android.os.Build; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.Log; - -import com.android.modules.utils.SynchronousResultReceiver; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeoutException; - -/** - * This class provides the public APIs to control the Hearing Aid profile. - * - * <p>BluetoothHearingAid is a proxy object for controlling the Bluetooth Hearing Aid - * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get - * the BluetoothHearingAid proxy object. - * - * <p> Android only supports one set of connected Bluetooth Hearing Aid device at a time. Each - * method is protected with its appropriate permission. - */ -public final class BluetoothHearingAid implements BluetoothProfile { - private static final String TAG = "BluetoothHearingAid"; - private static final boolean DBG = true; - private static final boolean VDBG = false; - - /** - * Intent used to broadcast the change in connection state of the Hearing Aid - * profile. Please note that in the binaural case, there will be two different LE devices for - * the left and right side and each device will have their own connection state changes.S - * - * <p>This intent will have 3 extras: - * <ul> - * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> - * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> - * </ul> - * - * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of - * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, - * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_CONNECTION_STATE_CHANGED = - "android.bluetooth.hearingaid.profile.action.CONNECTION_STATE_CHANGED"; - - /** - * Intent used to broadcast the selection of a connected device as active. - * - * <p>This intent will have one extra: - * <ul> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. It can - * be null if no device is active. </li> - * </ul> - * - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_ACTIVE_DEVICE_CHANGED = - "android.bluetooth.hearingaid.profile.action.ACTIVE_DEVICE_CHANGED"; - - /** - * This device represents Left Hearing Aid. - * - * @hide - */ - public static final int SIDE_LEFT = IBluetoothHearingAid.SIDE_LEFT; - - /** - * This device represents Right Hearing Aid. - * - * @hide - */ - public static final int SIDE_RIGHT = IBluetoothHearingAid.SIDE_RIGHT; - - /** - * This device is Monaural. - * - * @hide - */ - public static final int MODE_MONAURAL = IBluetoothHearingAid.MODE_MONAURAL; - - /** - * This device is Binaural (should receive only left or right audio). - * - * @hide - */ - public static final int MODE_BINAURAL = IBluetoothHearingAid.MODE_BINAURAL; - - /** - * Indicates the HiSyncID could not be read and is unavailable. - * - * @hide - */ - public static final long HI_SYNC_ID_INVALID = IBluetoothHearingAid.HI_SYNC_ID_INVALID; - - private final BluetoothAdapter mAdapter; - private final AttributionSource mAttributionSource; - private final BluetoothProfileConnector<IBluetoothHearingAid> mProfileConnector = - new BluetoothProfileConnector(this, BluetoothProfile.HEARING_AID, - "BluetoothHearingAid", IBluetoothHearingAid.class.getName()) { - @Override - public IBluetoothHearingAid getServiceInterface(IBinder service) { - return IBluetoothHearingAid.Stub.asInterface(service); - } - }; - - /** - * Create a BluetoothHearingAid proxy object for interacting with the local - * Bluetooth Hearing Aid service. - */ - /* package */ BluetoothHearingAid(Context context, ServiceListener listener, - BluetoothAdapter adapter) { - mAdapter = adapter; - mAttributionSource = adapter.getAttributionSource(); - mProfileConnector.connect(context, listener); - } - - /*package*/ void close() { - mProfileConnector.disconnect(); - } - - private IBluetoothHearingAid getService() { - return mProfileConnector.getService(); - } - - /** - * Initiate connection to a profile of the remote bluetooth device. - * - * <p> This API returns false in scenarios like the profile on the - * device is already connected or Bluetooth is not turned on. - * When this API returns true, it is guaranteed that - * connection state intent for the profile will be broadcasted with - * the state. Users can get the connection state of the profile - * from this intent. - * - * @param device Remote Bluetooth Device - * @return false on immediate error, true otherwise - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean connect(BluetoothDevice device) { - if (DBG) log("connect(" + device + ")"); - final IBluetoothHearingAid service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.connect(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Initiate disconnection from a profile - * - * <p> This API will return false in scenarios like the profile on the - * Bluetooth device is not in connected state etc. When this API returns, - * true, it is guaranteed that the connection state change - * intent will be broadcasted with the state. Users can get the - * disconnection state of the profile from this intent. - * - * <p> If the disconnection is initiated by a remote device, the state - * will transition from {@link #STATE_CONNECTED} to - * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the - * host (local) device the state will transition from - * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to - * state {@link #STATE_DISCONNECTED}. The transition to - * {@link #STATE_DISCONNECTING} can be used to distinguish between the - * two scenarios. - * - * @param device Remote Bluetooth Device - * @return false on immediate error, true otherwise - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean disconnect(BluetoothDevice device) { - if (DBG) log("disconnect(" + device + ")"); - final IBluetoothHearingAid service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.disconnect(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public @NonNull List<BluetoothDevice> getConnectedDevices() { - if (VDBG) log("getConnectedDevices()"); - final IBluetoothHearingAid service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getConnectedDevices(mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public @NonNull List<BluetoothDevice> getDevicesMatchingConnectionStates( - @NonNull int[] states) { - if (VDBG) log("getDevicesMatchingStates()"); - final IBluetoothHearingAid service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public @BluetoothProfile.BtProfileState int getConnectionState( - @NonNull BluetoothDevice device) { - if (VDBG) log("getState(" + device + ")"); - final IBluetoothHearingAid service = getService(); - final int defaultValue = BluetoothProfile.STATE_DISCONNECTED; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionState(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Select a connected device as active. - * - * The active device selection is per profile. An active device's - * purpose is profile-specific. For example, Hearing Aid audio - * streaming is to the active Hearing Aid device. If a remote device - * is not connected, it cannot be selected as active. - * - * <p> This API returns false in scenarios like the profile on the - * device is not connected or Bluetooth is not turned on. - * When this API returns true, it is guaranteed that the - * {@link #ACTION_ACTIVE_DEVICE_CHANGED} intent will be broadcasted - * with the active device. - * - * @param device the remote Bluetooth device. Could be null to clear - * the active device and stop streaming audio to a Bluetooth device. - * @return false on immediate error, true otherwise - * @hide - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public boolean setActiveDevice(@Nullable BluetoothDevice device) { - if (DBG) log("setActiveDevice(" + device + ")"); - final IBluetoothHearingAid service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && ((device == null) || isValidDevice(device))) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.setActiveDevice(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the connected physical Hearing Aid devices that are active - * - * @return the list of active devices. The first element is the left active - * device; the second element is the right active device. If either or both side - * is not active, it will be null on that position. Returns empty list on error. - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public @NonNull List<BluetoothDevice> getActiveDevices() { - if (VDBG) log("getActiveDevices()"); - final IBluetoothHearingAid service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getActiveDevices(mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Set priority of the profile - * - * <p> The device should already be paired. - * Priority can be one of {@link #PRIORITY_ON} or {@link #PRIORITY_OFF}, - * - * @param device Paired bluetooth device - * @param priority - * @return true if priority is set, false on error - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setPriority(BluetoothDevice device, int priority) { - if (DBG) log("setPriority(" + device + ", " + priority + ")"); - return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority)); - } - - /** - * Set connection policy of the profile - * - * <p> The device should already be paired. - * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED}, - * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Paired bluetooth device - * @param connectionPolicy is the connection policy to set to for this profile - * @return true if connectionPolicy is set, false on error - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setConnectionPolicy(@NonNull BluetoothDevice device, - @ConnectionPolicy int connectionPolicy) { - if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); - verifyDeviceNotNull(device, "setConnectionPolicy"); - final IBluetoothHearingAid service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device) - && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN - || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the priority of the profile. - * - * <p> The priority can be any of: - * {@link #PRIORITY_OFF}, {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED} - * - * @param device Bluetooth device - * @return priority of the device - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public int getPriority(BluetoothDevice device) { - if (VDBG) log("getPriority(" + device + ")"); - return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device)); - } - - /** - * Get the connection policy of the profile. - * - * <p> The connection policy can be any of: - * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, - * {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Bluetooth device - * @return connection policy of the device - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) { - if (VDBG) log("getConnectionPolicy(" + device + ")"); - verifyDeviceNotNull(device, "getConnectionPolicy"); - final IBluetoothHearingAid service = getService(); - final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionPolicy(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Helper for converting a state to a string. - * - * For debug use only - strings are not internationalized. - * - * @hide - */ - public static String stateToString(int state) { - switch (state) { - case STATE_DISCONNECTED: - return "disconnected"; - case STATE_CONNECTING: - return "connecting"; - case STATE_CONNECTED: - return "connected"; - case STATE_DISCONNECTING: - return "disconnecting"; - default: - return "<unknown state " + state + ">"; - } - } - - /** - * Tells remote device to set an absolute volume. - * - * @param volume Absolute volume to be set on remote - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public void setVolume(int volume) { - if (DBG) Log.d(TAG, "setVolume(" + volume + ")"); - final IBluetoothHearingAid service = getService(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver recv = new SynchronousResultReceiver(); - service.setVolume(volume, mAttributionSource, recv); - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - } - - /** - * Get the HiSyncId (unique hearing aid device identifier) of the device. - * - * <a href=https://source.android.com/devices/bluetooth/asha#hisyncid>HiSyncId documentation - * can be found here</a> - * - * @param device Bluetooth device - * @return the HiSyncId of the device - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public long getHiSyncId(@NonNull BluetoothDevice device) { - if (VDBG) log("getHiSyncId(" + device + ")"); - verifyDeviceNotNull(device, "getConnectionPolicy"); - final IBluetoothHearingAid service = getService(); - final long defaultValue = HI_SYNC_ID_INVALID; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Long> recv = new SynchronousResultReceiver(); - service.getHiSyncId(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the side of the device. - * - * @param device Bluetooth device. - * @return SIDE_LEFT or SIDE_RIGHT - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public int getDeviceSide(BluetoothDevice device) { - if (VDBG) log("getDeviceSide(" + device + ")"); - final IBluetoothHearingAid service = getService(); - final int defaultValue = SIDE_LEFT; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getDeviceSide(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the mode of the device. - * - * @param device Bluetooth device - * @return MODE_MONAURAL or MODE_BINAURAL - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public int getDeviceMode(BluetoothDevice device) { - if (VDBG) log("getDeviceMode(" + device + ")"); - final IBluetoothHearingAid service = getService(); - final int defaultValue = MODE_MONAURAL; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getDeviceMode(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - private boolean isEnabled() { - if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true; - return false; - } - - private void verifyDeviceNotNull(BluetoothDevice device, String methodName) { - if (device == null) { - Log.e(TAG, methodName + ": device param is null"); - throw new IllegalArgumentException("Device cannot be null"); - } - } - - private boolean isValidDevice(BluetoothDevice device) { - if (device == null) return false; - - if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; - return false; - } - - private static void log(String msg) { - Log.d(TAG, msg); - } -} diff --git a/core/java/android/bluetooth/BluetoothHidDevice.java b/core/java/android/bluetooth/BluetoothHidDevice.java deleted file mode 100644 index 44a355b5f75c..000000000000 --- a/core/java/android/bluetooth/BluetoothHidDevice.java +++ /dev/null @@ -1,848 +0,0 @@ -/* - * Copyright (C) 2016 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.bluetooth; - -import static android.bluetooth.BluetoothUtils.getSyncTimeout; - -import android.Manifest; -import android.annotation.NonNull; -import android.annotation.RequiresPermission; -import android.annotation.SdkConstant; -import android.annotation.SdkConstant.SdkConstantType; -import android.annotation.SystemApi; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; -import android.content.AttributionSource; -import android.content.Context; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.Log; - -import com.android.modules.utils.SynchronousResultReceiver; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.Executor; -import java.util.concurrent.TimeoutException; - -/** - * Provides the public APIs to control the Bluetooth HID Device profile. - * - * <p>BluetoothHidDevice is a proxy object for controlling the Bluetooth HID Device Service via IPC. - * Use {@link BluetoothAdapter#getProfileProxy} to get the BluetoothHidDevice proxy object. - */ -public final class BluetoothHidDevice implements BluetoothProfile { - private static final String TAG = BluetoothHidDevice.class.getSimpleName(); - private static final boolean DBG = false; - - /** - * Intent used to broadcast the change in connection state of the Input Host profile. - * - * <p>This intent will have 3 extras: - * - * <ul> - * <li>{@link #EXTRA_STATE} - The current state of the profile. - * <li>{@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. - * <li>{@link BluetoothDevice#EXTRA_DEVICE} - The remote device. - * </ul> - * - * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of {@link - * #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, {@link #STATE_CONNECTED}, {@link - * #STATE_DISCONNECTING}. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_CONNECTION_STATE_CHANGED = - "android.bluetooth.hiddevice.profile.action.CONNECTION_STATE_CHANGED"; - - /** - * Constant representing unspecified HID device subclass. - * - * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings, - * BluetoothHidDeviceAppQosSettings, Executor, Callback) - */ - public static final byte SUBCLASS1_NONE = (byte) 0x00; - /** - * Constant representing keyboard subclass. - * - * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings, - * BluetoothHidDeviceAppQosSettings, Executor, Callback) - */ - public static final byte SUBCLASS1_KEYBOARD = (byte) 0x40; - /** - * Constant representing mouse subclass. - * - * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings, - * BluetoothHidDeviceAppQosSettings, Executor, Callback) - */ - public static final byte SUBCLASS1_MOUSE = (byte) 0x80; - /** - * Constant representing combo keyboard and mouse subclass. - * - * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings, - * BluetoothHidDeviceAppQosSettings, Executor, Callback) - */ - public static final byte SUBCLASS1_COMBO = (byte) 0xC0; - - /** - * Constant representing uncategorized HID device subclass. - * - * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings, - * BluetoothHidDeviceAppQosSettings, Executor, Callback) - */ - public static final byte SUBCLASS2_UNCATEGORIZED = (byte) 0x00; - /** - * Constant representing joystick subclass. - * - * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings, - * BluetoothHidDeviceAppQosSettings, Executor, Callback) - */ - public static final byte SUBCLASS2_JOYSTICK = (byte) 0x01; - /** - * Constant representing gamepad subclass. - * - * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings, - * BluetoothHidDeviceAppQosSettings, Executor, Callback) - */ - public static final byte SUBCLASS2_GAMEPAD = (byte) 0x02; - /** - * Constant representing remote control subclass. - * - * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings, - * BluetoothHidDeviceAppQosSettings, Executor, Callback) - */ - public static final byte SUBCLASS2_REMOTE_CONTROL = (byte) 0x03; - /** - * Constant representing sensing device subclass. - * - * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings, - * BluetoothHidDeviceAppQosSettings, Executor, Callback) - */ - public static final byte SUBCLASS2_SENSING_DEVICE = (byte) 0x04; - /** - * Constant representing digitizer tablet subclass. - * - * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings, - * BluetoothHidDeviceAppQosSettings, Executor, Callback) - */ - public static final byte SUBCLASS2_DIGITIZER_TABLET = (byte) 0x05; - /** - * Constant representing card reader subclass. - * - * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings, - * BluetoothHidDeviceAppQosSettings, Executor, Callback) - */ - public static final byte SUBCLASS2_CARD_READER = (byte) 0x06; - - /** - * Constant representing HID Input Report type. - * - * @see Callback#onGetReport(BluetoothDevice, byte, byte, int) - * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[]) - * @see Callback#onInterruptData(BluetoothDevice, byte, byte[]) - */ - public static final byte REPORT_TYPE_INPUT = (byte) 1; - /** - * Constant representing HID Output Report type. - * - * @see Callback#onGetReport(BluetoothDevice, byte, byte, int) - * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[]) - * @see Callback#onInterruptData(BluetoothDevice, byte, byte[]) - */ - public static final byte REPORT_TYPE_OUTPUT = (byte) 2; - /** - * Constant representing HID Feature Report type. - * - * @see Callback#onGetReport(BluetoothDevice, byte, byte, int) - * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[]) - * @see Callback#onInterruptData(BluetoothDevice, byte, byte[]) - */ - public static final byte REPORT_TYPE_FEATURE = (byte) 3; - - /** - * Constant representing success response for Set Report. - * - * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[]) - */ - public static final byte ERROR_RSP_SUCCESS = (byte) 0; - /** - * Constant representing error response for Set Report due to "not ready". - * - * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[]) - */ - public static final byte ERROR_RSP_NOT_READY = (byte) 1; - /** - * Constant representing error response for Set Report due to "invalid report ID". - * - * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[]) - */ - public static final byte ERROR_RSP_INVALID_RPT_ID = (byte) 2; - /** - * Constant representing error response for Set Report due to "unsupported request". - * - * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[]) - */ - public static final byte ERROR_RSP_UNSUPPORTED_REQ = (byte) 3; - /** - * Constant representing error response for Set Report due to "invalid parameter". - * - * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[]) - */ - public static final byte ERROR_RSP_INVALID_PARAM = (byte) 4; - /** - * Constant representing error response for Set Report with unknown reason. - * - * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[]) - */ - public static final byte ERROR_RSP_UNKNOWN = (byte) 14; - - /** - * Constant representing boot protocol mode used set by host. Default is always {@link - * #PROTOCOL_REPORT_MODE} unless notified otherwise. - * - * @see Callback#onSetProtocol(BluetoothDevice, byte) - */ - public static final byte PROTOCOL_BOOT_MODE = (byte) 0; - /** - * Constant representing report protocol mode used set by host. Default is always {@link - * #PROTOCOL_REPORT_MODE} unless notified otherwise. - * - * @see Callback#onSetProtocol(BluetoothDevice, byte) - */ - public static final byte PROTOCOL_REPORT_MODE = (byte) 1; - - /** - * The template class that applications use to call callback functions on events from the HID - * host. Callback functions are wrapped in this class and registered to the Android system - * during app registration. - */ - public abstract static class Callback { - - private static final String TAG = "BluetoothHidDevCallback"; - - /** - * Callback called when application registration state changes. Usually it's called due to - * either {@link BluetoothHidDevice#registerApp (String, String, String, byte, byte[], - * Executor, Callback)} or {@link BluetoothHidDevice#unregisterApp()} , but can be also - * unsolicited in case e.g. Bluetooth was turned off in which case application is - * unregistered automatically. - * - * @param pluggedDevice {@link BluetoothDevice} object which represents host that currently - * has Virtual Cable established with device. Only valid when application is registered, - * can be <code>null</code>. - * @param registered <code>true</code> if application is registered, <code>false</code> - * otherwise. - */ - public void onAppStatusChanged(BluetoothDevice pluggedDevice, boolean registered) { - Log.d( - TAG, - "onAppStatusChanged: pluggedDevice=" - + pluggedDevice - + " registered=" - + registered); - } - - /** - * Callback called when connection state with remote host was changed. Application can - * assume than Virtual Cable is established when called with {@link - * BluetoothProfile#STATE_CONNECTED} <code>state</code>. - * - * @param device {@link BluetoothDevice} object representing host device which connection - * state was changed. - * @param state Connection state as defined in {@link BluetoothProfile}. - */ - public void onConnectionStateChanged(BluetoothDevice device, int state) { - Log.d(TAG, "onConnectionStateChanged: device=" + device + " state=" + state); - } - - /** - * Callback called when GET_REPORT is received from remote host. Should be replied by - * application using {@link BluetoothHidDevice#replyReport(BluetoothDevice, byte, byte, - * byte[])}. - * - * @param type Requested Report Type. - * @param id Requested Report Id, can be 0 if no Report Id are defined in descriptor. - * @param bufferSize Requested buffer size, application shall respond with at least given - * number of bytes. - */ - public void onGetReport(BluetoothDevice device, byte type, byte id, int bufferSize) { - Log.d( - TAG, - "onGetReport: device=" - + device - + " type=" - + type - + " id=" - + id - + " bufferSize=" - + bufferSize); - } - - /** - * Callback called when SET_REPORT is received from remote host. In case received data are - * invalid, application shall respond with {@link - * BluetoothHidDevice#reportError(BluetoothDevice, byte)}. - * - * @param type Report Type. - * @param id Report Id. - * @param data Report data. - */ - public void onSetReport(BluetoothDevice device, byte type, byte id, byte[] data) { - Log.d(TAG, "onSetReport: device=" + device + " type=" + type + " id=" + id); - } - - /** - * Callback called when SET_PROTOCOL is received from remote host. Application shall use - * this information to send only reports valid for given protocol mode. By default, {@link - * BluetoothHidDevice#PROTOCOL_REPORT_MODE} shall be assumed. - * - * @param protocol Protocol Mode. - */ - public void onSetProtocol(BluetoothDevice device, byte protocol) { - Log.d(TAG, "onSetProtocol: device=" + device + " protocol=" + protocol); - } - - /** - * Callback called when report data is received over interrupt channel. Report Type is - * assumed to be {@link BluetoothHidDevice#REPORT_TYPE_OUTPUT}. - * - * @param reportId Report Id. - * @param data Report data. - */ - public void onInterruptData(BluetoothDevice device, byte reportId, byte[] data) { - Log.d(TAG, "onInterruptData: device=" + device + " reportId=" + reportId); - } - - /** - * Callback called when Virtual Cable is removed. After this callback is received connection - * will be disconnected automatically. - */ - public void onVirtualCableUnplug(BluetoothDevice device) { - Log.d(TAG, "onVirtualCableUnplug: device=" + device); - } - } - - private static class CallbackWrapper extends IBluetoothHidDeviceCallback.Stub { - - private final Executor mExecutor; - private final Callback mCallback; - private final AttributionSource mAttributionSource; - - CallbackWrapper(Executor executor, Callback callback, AttributionSource attributionSource) { - mExecutor = executor; - mCallback = callback; - mAttributionSource = attributionSource; - } - - @Override - public void onAppStatusChanged(BluetoothDevice pluggedDevice, boolean registered) { - Attributable.setAttributionSource(pluggedDevice, mAttributionSource); - final long token = clearCallingIdentity(); - try { - mExecutor.execute(() -> mCallback.onAppStatusChanged(pluggedDevice, registered)); - } finally { - restoreCallingIdentity(token); - } - } - - @Override - public void onConnectionStateChanged(BluetoothDevice device, int state) { - Attributable.setAttributionSource(device, mAttributionSource); - final long token = clearCallingIdentity(); - try { - mExecutor.execute(() -> mCallback.onConnectionStateChanged(device, state)); - } finally { - restoreCallingIdentity(token); - } - } - - @Override - public void onGetReport(BluetoothDevice device, byte type, byte id, int bufferSize) { - Attributable.setAttributionSource(device, mAttributionSource); - final long token = clearCallingIdentity(); - try { - mExecutor.execute(() -> mCallback.onGetReport(device, type, id, bufferSize)); - } finally { - restoreCallingIdentity(token); - } - } - - @Override - public void onSetReport(BluetoothDevice device, byte type, byte id, byte[] data) { - Attributable.setAttributionSource(device, mAttributionSource); - final long token = clearCallingIdentity(); - try { - mExecutor.execute(() -> mCallback.onSetReport(device, type, id, data)); - } finally { - restoreCallingIdentity(token); - } - } - - @Override - public void onSetProtocol(BluetoothDevice device, byte protocol) { - Attributable.setAttributionSource(device, mAttributionSource); - final long token = clearCallingIdentity(); - try { - mExecutor.execute(() -> mCallback.onSetProtocol(device, protocol)); - } finally { - restoreCallingIdentity(token); - } - } - - @Override - public void onInterruptData(BluetoothDevice device, byte reportId, byte[] data) { - Attributable.setAttributionSource(device, mAttributionSource); - final long token = clearCallingIdentity(); - try { - mExecutor.execute(() -> mCallback.onInterruptData(device, reportId, data)); - } finally { - restoreCallingIdentity(token); - } - } - - @Override - public void onVirtualCableUnplug(BluetoothDevice device) { - Attributable.setAttributionSource(device, mAttributionSource); - final long token = clearCallingIdentity(); - try { - mExecutor.execute(() -> mCallback.onVirtualCableUnplug(device)); - } finally { - restoreCallingIdentity(token); - } - } - } - - private final BluetoothAdapter mAdapter; - private final AttributionSource mAttributionSource; - private final BluetoothProfileConnector<IBluetoothHidDevice> mProfileConnector = - new BluetoothProfileConnector(this, BluetoothProfile.HID_DEVICE, - "BluetoothHidDevice", IBluetoothHidDevice.class.getName()) { - @Override - public IBluetoothHidDevice getServiceInterface(IBinder service) { - return IBluetoothHidDevice.Stub.asInterface(service); - } - }; - - BluetoothHidDevice(Context context, ServiceListener listener, BluetoothAdapter adapter) { - mAdapter = adapter; - mAttributionSource = adapter.getAttributionSource(); - mProfileConnector.connect(context, listener); - } - - void close() { - mProfileConnector.disconnect(); - } - - private IBluetoothHidDevice getService() { - return mProfileConnector.getService(); - } - - /** {@inheritDoc} */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getConnectedDevices() { - final IBluetoothHidDevice service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getConnectedDevices(mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** {@inheritDoc} */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { - final IBluetoothHidDevice service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** {@inheritDoc} */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - public int getConnectionState(BluetoothDevice device) { - final IBluetoothHidDevice service = getService(); - final int defaultValue = STATE_DISCONNECTED; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionState(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Registers application to be used for HID device. Connections to HID Device are only possible - * when application is registered. Only one application can be registered at one time. When an - * application is registered, the HID Host service will be disabled until it is unregistered. - * When no longer used, application should be unregistered using {@link #unregisterApp()}. The - * app will be automatically unregistered if it is not foreground. The registration status - * should be tracked by the application by handling callback from Callback#onAppStatusChanged. - * The app registration status is not related to the return value of this method. - * - * @param sdp {@link BluetoothHidDeviceAppSdpSettings} object of HID Device SDP record. The HID - * Device SDP record is required. - * @param inQos {@link BluetoothHidDeviceAppQosSettings} object of Incoming QoS Settings. The - * Incoming QoS Settings is not required. Use null or default - * BluetoothHidDeviceAppQosSettings.Builder for default values. - * @param outQos {@link BluetoothHidDeviceAppQosSettings} object of Outgoing QoS Settings. The - * Outgoing QoS Settings is not required. Use null or default - * BluetoothHidDeviceAppQosSettings.Builder for default values. - * @param executor {@link Executor} object on which callback will be executed. The Executor - * object is required. - * @param callback {@link Callback} object to which callback messages will be sent. The Callback - * object is required. - * @return true if the command is successfully sent; otherwise false. - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean registerApp( - BluetoothHidDeviceAppSdpSettings sdp, - BluetoothHidDeviceAppQosSettings inQos, - BluetoothHidDeviceAppQosSettings outQos, - Executor executor, - Callback callback) { - boolean result = false; - - if (sdp == null) { - throw new IllegalArgumentException("sdp parameter cannot be null"); - } - - if (executor == null) { - throw new IllegalArgumentException("executor parameter cannot be null"); - } - - if (callback == null) { - throw new IllegalArgumentException("callback parameter cannot be null"); - } - - final IBluetoothHidDevice service = getService(); - final boolean defaultValue = result; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - CallbackWrapper cbw = new CallbackWrapper(executor, callback, mAttributionSource); - service.registerApp(sdp, inQos, outQos, cbw, mAttributionSource, recv); - result = recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Unregisters application. Active connection will be disconnected and no new connections will - * be allowed until registered again using {@link #registerApp - * (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings, - * BluetoothHidDeviceAppQosSettings, Executor, Callback)}. The registration status should be - * tracked by the application by handling callback from Callback#onAppStatusChanged. The app - * registration status is not related to the return value of this method. - * - * @return true if the command is successfully sent; otherwise false. - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean unregisterApp() { - final IBluetoothHidDevice service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.unregisterApp(mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Sends report to remote host using interrupt channel. - * - * @param id Report Id, as defined in descriptor. Can be 0 in case Report Id are not defined in - * descriptor. - * @param data Report data, not including Report Id. - * @return true if the command is successfully sent; otherwise false. - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean sendReport(BluetoothDevice device, int id, byte[] data) { - final IBluetoothHidDevice service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.sendReport(device, id, data, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Sends report to remote host as reply for GET_REPORT request from {@link - * Callback#onGetReport(BluetoothDevice, byte, byte, int)}. - * - * @param type Report Type, as in request. - * @param id Report Id, as in request. - * @param data Report data, not including Report Id. - * @return true if the command is successfully sent; otherwise false. - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean replyReport(BluetoothDevice device, byte type, byte id, byte[] data) { - final IBluetoothHidDevice service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.replyReport(device, type, id, data, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Sends error handshake message as reply for invalid SET_REPORT request from {@link - * Callback#onSetReport(BluetoothDevice, byte, byte, byte[])}. - * - * @param error Error to be sent for SET_REPORT via HANDSHAKE. - * @return true if the command is successfully sent; otherwise false. - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean reportError(BluetoothDevice device, byte error) { - final IBluetoothHidDevice service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.reportError(device, error, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Gets the application name of the current HidDeviceService user. - * - * @return the current user name, or empty string if cannot get the name - * {@hide} - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public String getUserAppName() { - final IBluetoothHidDevice service = getService(); - final String defaultValue = ""; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<String> recv = new SynchronousResultReceiver(); - service.getUserAppName(mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Initiates connection to host which is currently paired with this device. If the application - * is not registered, #connect(BluetoothDevice) will fail. The connection state should be - * tracked by the application by handling callback from Callback#onConnectionStateChanged. The - * connection state is not related to the return value of this method. - * - * @return true if the command is successfully sent; otherwise false. - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean connect(BluetoothDevice device) { - final IBluetoothHidDevice service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.connect(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Disconnects from currently connected host. The connection state should be tracked by the - * application by handling callback from Callback#onConnectionStateChanged. The connection state - * is not related to the return value of this method. - * - * @return true if the command is successfully sent; otherwise false. - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean disconnect(BluetoothDevice device) { - final IBluetoothHidDevice service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.disconnect(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Connects Hid Device if connectionPolicy is {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED} - * and disconnects Hid device if connectionPolicy is - * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}. - * - * <p> The device should already be paired. - * Connection policy can be one of: - * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED}, - * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}, - * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN} - * - * @param device Paired bluetooth device - * @param connectionPolicy determines whether hid device should be connected or disconnected - * @return true if hid device is connected or disconnected, false otherwise - * - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setConnectionPolicy(@NonNull BluetoothDevice device, - @ConnectionPolicy int connectionPolicy) { - if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); - final IBluetoothHidDevice service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device) - && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN - || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - private boolean isEnabled() { - if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true; - return false; - } - - private boolean isValidDevice(BluetoothDevice device) { - if (device == null) return false; - - if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; - return false; - } - - private static void log(String msg) { - if (DBG) { - Log.d(TAG, msg); - } - } -} diff --git a/core/java/android/bluetooth/BluetoothHidDeviceAppQosSettings.java b/core/java/android/bluetooth/BluetoothHidDeviceAppQosSettings.java deleted file mode 100644 index b21ebe59d816..000000000000 --- a/core/java/android/bluetooth/BluetoothHidDeviceAppQosSettings.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (C) 2016 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.bluetooth; - -import android.os.Parcel; -import android.os.Parcelable; - -/** - * Represents the Quality of Service (QoS) settings for a Bluetooth HID Device application. - * - * <p>The BluetoothHidDevice framework will update the L2CAP QoS settings for the app during - * registration. - * - * <p>{@see BluetoothHidDevice} - */ -public final class BluetoothHidDeviceAppQosSettings implements Parcelable { - - private final int mServiceType; - private final int mTokenRate; - private final int mTokenBucketSize; - private final int mPeakBandwidth; - private final int mLatency; - private final int mDelayVariation; - - public static final int SERVICE_NO_TRAFFIC = 0x00; - public static final int SERVICE_BEST_EFFORT = 0x01; - public static final int SERVICE_GUARANTEED = 0x02; - - public static final int MAX = (int) 0xffffffff; - - /** - * Create a BluetoothHidDeviceAppQosSettings object for the Bluetooth L2CAP channel. The QoS - * Settings is optional. Please refer to Bluetooth HID Specfication v1.1.1 Section 5.2 and - * Appendix D for parameters. - * - * @param serviceType L2CAP service type, default = SERVICE_BEST_EFFORT - * @param tokenRate L2CAP token rate, default = 0 - * @param tokenBucketSize L2CAP token bucket size, default = 0 - * @param peakBandwidth L2CAP peak bandwidth, default = 0 - * @param latency L2CAP latency, default = MAX - * @param delayVariation L2CAP delay variation, default = MAX - */ - public BluetoothHidDeviceAppQosSettings( - int serviceType, - int tokenRate, - int tokenBucketSize, - int peakBandwidth, - int latency, - int delayVariation) { - mServiceType = serviceType; - mTokenRate = tokenRate; - mTokenBucketSize = tokenBucketSize; - mPeakBandwidth = peakBandwidth; - mLatency = latency; - mDelayVariation = delayVariation; - } - - public int getServiceType() { - return mServiceType; - } - - public int getTokenRate() { - return mTokenRate; - } - - public int getTokenBucketSize() { - return mTokenBucketSize; - } - - public int getPeakBandwidth() { - return mPeakBandwidth; - } - - public int getLatency() { - return mLatency; - } - - public int getDelayVariation() { - return mDelayVariation; - } - - @Override - public int describeContents() { - return 0; - } - - public static final @android.annotation.NonNull Parcelable.Creator<BluetoothHidDeviceAppQosSettings> CREATOR = - new Parcelable.Creator<BluetoothHidDeviceAppQosSettings>() { - - @Override - public BluetoothHidDeviceAppQosSettings createFromParcel(Parcel in) { - - return new BluetoothHidDeviceAppQosSettings( - in.readInt(), - in.readInt(), - in.readInt(), - in.readInt(), - in.readInt(), - in.readInt()); - } - - @Override - public BluetoothHidDeviceAppQosSettings[] newArray(int size) { - return new BluetoothHidDeviceAppQosSettings[size]; - } - }; - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeInt(mServiceType); - out.writeInt(mTokenRate); - out.writeInt(mTokenBucketSize); - out.writeInt(mPeakBandwidth); - out.writeInt(mLatency); - out.writeInt(mDelayVariation); - } -} diff --git a/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java b/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java deleted file mode 100644 index 4e1a2aaedcf0..000000000000 --- a/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (C) 2016 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.bluetooth; - -import android.os.Parcel; -import android.os.Parcelable; -import android.util.EventLog; - - -/** - * Represents the Service Discovery Protocol (SDP) settings for a Bluetooth HID Device application. - * - * <p>The BluetoothHidDevice framework adds the SDP record during app registration, so that the - * Android device can be discovered as a Bluetooth HID Device. - * - * <p>{@see BluetoothHidDevice} - */ -public final class BluetoothHidDeviceAppSdpSettings implements Parcelable { - - private static final int MAX_DESCRIPTOR_SIZE = 2048; - - private final String mName; - private final String mDescription; - private final String mProvider; - private final byte mSubclass; - private final byte[] mDescriptors; - - /** - * Create a BluetoothHidDeviceAppSdpSettings object for the Bluetooth SDP record. - * - * @param name Name of this Bluetooth HID device. Maximum length is 50 bytes. - * @param description Description for this Bluetooth HID device. Maximum length is 50 bytes. - * @param provider Provider of this Bluetooth HID device. Maximum length is 50 bytes. - * @param subclass Subclass of this Bluetooth HID device. See <a - * href="www.usb.org/developers/hidpage/HID1_11.pdf"> - * www.usb.org/developers/hidpage/HID1_11.pdf Section 4.2</a> - * @param descriptors Descriptors of this Bluetooth HID device. See <a - * href="www.usb.org/developers/hidpage/HID1_11.pdf"> - * www.usb.org/developers/hidpage/HID1_11.pdf Chapter 6</a> Maximum length is 2048 bytes. - */ - public BluetoothHidDeviceAppSdpSettings( - String name, String description, String provider, byte subclass, byte[] descriptors) { - mName = name; - mDescription = description; - mProvider = provider; - mSubclass = subclass; - - if (descriptors == null || descriptors.length > MAX_DESCRIPTOR_SIZE) { - EventLog.writeEvent(0x534e4554, "119819889", -1, ""); - throw new IllegalArgumentException("descriptors must be not null and shorter than " - + MAX_DESCRIPTOR_SIZE); - } - mDescriptors = descriptors.clone(); - } - - public String getName() { - return mName; - } - - public String getDescription() { - return mDescription; - } - - public String getProvider() { - return mProvider; - } - - public byte getSubclass() { - return mSubclass; - } - - public byte[] getDescriptors() { - return mDescriptors; - } - - @Override - public int describeContents() { - return 0; - } - - public static final @android.annotation.NonNull Parcelable.Creator<BluetoothHidDeviceAppSdpSettings> CREATOR = - new Parcelable.Creator<BluetoothHidDeviceAppSdpSettings>() { - - @Override - public BluetoothHidDeviceAppSdpSettings createFromParcel(Parcel in) { - - return new BluetoothHidDeviceAppSdpSettings( - in.readString(), - in.readString(), - in.readString(), - in.readByte(), - in.createByteArray()); - } - - @Override - public BluetoothHidDeviceAppSdpSettings[] newArray(int size) { - return new BluetoothHidDeviceAppSdpSettings[size]; - } - }; - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeString(mName); - out.writeString(mDescription); - out.writeString(mProvider); - out.writeByte(mSubclass); - out.writeByteArray(mDescriptors); - } -} diff --git a/core/java/android/bluetooth/BluetoothHidHost.java b/core/java/android/bluetooth/BluetoothHidHost.java deleted file mode 100644 index ecbeddf2b853..000000000000 --- a/core/java/android/bluetooth/BluetoothHidHost.java +++ /dev/null @@ -1,831 +0,0 @@ -/* - * Copyright (C) 2011 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.bluetooth; - -import static android.bluetooth.BluetoothUtils.getSyncTimeout; - -import android.Manifest; -import android.annotation.NonNull; -import android.annotation.RequiresPermission; -import android.annotation.SdkConstant; -import android.annotation.SdkConstant.SdkConstantType; -import android.annotation.SuppressLint; -import android.annotation.SystemApi; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; -import android.content.AttributionSource; -import android.content.Context; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.Log; - -import com.android.modules.utils.SynchronousResultReceiver; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeoutException; - - -/** - * This class provides the public APIs to control the Bluetooth Input - * Device Profile. - * - * <p>BluetoothHidHost is a proxy object for controlling the Bluetooth - * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get - * the BluetoothHidHost proxy object. - * - * <p>Each method is protected with its appropriate permission. - * - * @hide - */ -@SystemApi -public final class BluetoothHidHost implements BluetoothProfile { - private static final String TAG = "BluetoothHidHost"; - private static final boolean DBG = true; - private static final boolean VDBG = false; - - /** - * Intent used to broadcast the change in connection state of the Input - * Device profile. - * - * <p>This intent will have 3 extras: - * <ul> - * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> - * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> - * </ul> - * - * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of - * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, - * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. - */ - @SuppressLint("ActionValue") - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_CONNECTION_STATE_CHANGED = - "android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED"; - - /** - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_PROTOCOL_MODE_CHANGED = - "android.bluetooth.input.profile.action.PROTOCOL_MODE_CHANGED"; - - /** - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_HANDSHAKE = - "android.bluetooth.input.profile.action.HANDSHAKE"; - - /** - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_REPORT = - "android.bluetooth.input.profile.action.REPORT"; - - /** - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_VIRTUAL_UNPLUG_STATUS = - "android.bluetooth.input.profile.action.VIRTUAL_UNPLUG_STATUS"; - - /** - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_IDLE_TIME_CHANGED = - "android.bluetooth.input.profile.action.IDLE_TIME_CHANGED"; - - /** - * Return codes for the connect and disconnect Bluez / Dbus calls. - * - * @hide - */ - public static final int INPUT_DISCONNECT_FAILED_NOT_CONNECTED = 5000; - - /** - * @hide - */ - public static final int INPUT_CONNECT_FAILED_ALREADY_CONNECTED = 5001; - - /** - * @hide - */ - public static final int INPUT_CONNECT_FAILED_ATTEMPT_FAILED = 5002; - - /** - * @hide - */ - public static final int INPUT_OPERATION_GENERIC_FAILURE = 5003; - - /** - * @hide - */ - public static final int INPUT_OPERATION_SUCCESS = 5004; - - /** - * @hide - */ - public static final int PROTOCOL_REPORT_MODE = 0; - - /** - * @hide - */ - public static final int PROTOCOL_BOOT_MODE = 1; - - /** - * @hide - */ - public static final int PROTOCOL_UNSUPPORTED_MODE = 255; - - /* int reportType, int reportType, int bufferSize */ - /** - * @hide - */ - public static final byte REPORT_TYPE_INPUT = 1; - - /** - * @hide - */ - public static final byte REPORT_TYPE_OUTPUT = 2; - - /** - * @hide - */ - public static final byte REPORT_TYPE_FEATURE = 3; - - /** - * @hide - */ - public static final int VIRTUAL_UNPLUG_STATUS_SUCCESS = 0; - - /** - * @hide - */ - public static final int VIRTUAL_UNPLUG_STATUS_FAIL = 1; - - /** - * @hide - */ - public static final String EXTRA_PROTOCOL_MODE = - "android.bluetooth.BluetoothHidHost.extra.PROTOCOL_MODE"; - - /** - * @hide - */ - public static final String EXTRA_REPORT_TYPE = - "android.bluetooth.BluetoothHidHost.extra.REPORT_TYPE"; - - /** - * @hide - */ - public static final String EXTRA_REPORT_ID = - "android.bluetooth.BluetoothHidHost.extra.REPORT_ID"; - - /** - * @hide - */ - public static final String EXTRA_REPORT_BUFFER_SIZE = - "android.bluetooth.BluetoothHidHost.extra.REPORT_BUFFER_SIZE"; - - /** - * @hide - */ - public static final String EXTRA_REPORT = "android.bluetooth.BluetoothHidHost.extra.REPORT"; - - /** - * @hide - */ - public static final String EXTRA_STATUS = "android.bluetooth.BluetoothHidHost.extra.STATUS"; - - /** - * @hide - */ - public static final String EXTRA_VIRTUAL_UNPLUG_STATUS = - "android.bluetooth.BluetoothHidHost.extra.VIRTUAL_UNPLUG_STATUS"; - - /** - * @hide - */ - public static final String EXTRA_IDLE_TIME = - "android.bluetooth.BluetoothHidHost.extra.IDLE_TIME"; - - private final BluetoothAdapter mAdapter; - private final AttributionSource mAttributionSource; - private final BluetoothProfileConnector<IBluetoothHidHost> mProfileConnector = - new BluetoothProfileConnector(this, BluetoothProfile.HID_HOST, - "BluetoothHidHost", IBluetoothHidHost.class.getName()) { - @Override - public IBluetoothHidHost getServiceInterface(IBinder service) { - return IBluetoothHidHost.Stub.asInterface(service); - } - }; - - /** - * Create a BluetoothHidHost proxy object for interacting with the local - * Bluetooth Service which handles the InputDevice profile - */ - /* package */ BluetoothHidHost(Context context, ServiceListener listener, - BluetoothAdapter adapter) { - mAdapter = adapter; - mAttributionSource = adapter.getAttributionSource(); - mProfileConnector.connect(context, listener); - } - - /*package*/ void close() { - if (VDBG) log("close()"); - mProfileConnector.disconnect(); - } - - private IBluetoothHidHost getService() { - return mProfileConnector.getService(); - } - - /** - * Initiate connection to a profile of the remote bluetooth device. - * - * <p> The system supports connection to multiple input devices. - * - * <p> This API returns false in scenarios like the profile on the - * device is already connected or Bluetooth is not turned on. - * When this API returns true, it is guaranteed that - * connection state intent for the profile will be broadcasted with - * the state. Users can get the connection state of the profile - * from this intent. - * - * @param device Remote Bluetooth Device - * @return false on immediate error, true otherwise - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean connect(BluetoothDevice device) { - if (DBG) log("connect(" + device + ")"); - final IBluetoothHidHost service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.connect(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Initiate disconnection from a profile - * - * <p> This API will return false in scenarios like the profile on the - * Bluetooth device is not in connected state etc. When this API returns, - * true, it is guaranteed that the connection state change - * intent will be broadcasted with the state. Users can get the - * disconnection state of the profile from this intent. - * - * <p> If the disconnection is initiated by a remote device, the state - * will transition from {@link #STATE_CONNECTED} to - * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the - * host (local) device the state will transition from - * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to - * state {@link #STATE_DISCONNECTED}. The transition to - * {@link #STATE_DISCONNECTING} can be used to distinguish between the - * two scenarios. - * - * @param device Remote Bluetooth Device - * @return false on immediate error, true otherwise - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean disconnect(BluetoothDevice device) { - if (DBG) log("disconnect(" + device + ")"); - final IBluetoothHidHost service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.disconnect(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - * - * @hide - */ - @SystemApi - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - public @NonNull List<BluetoothDevice> getConnectedDevices() { - if (VDBG) log("getConnectedDevices()"); - final IBluetoothHidHost service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getConnectedDevices(mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - * - * @hide - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { - if (VDBG) log("getDevicesMatchingStates()"); - final IBluetoothHidHost service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - * - * @hide - */ - @SystemApi - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - public int getConnectionState(@NonNull BluetoothDevice device) { - if (VDBG) log("getState(" + device + ")"); - if (device == null) { - throw new IllegalArgumentException("device must not be null"); - } - final IBluetoothHidHost service = getService(); - final int defaultValue = BluetoothProfile.STATE_DISCONNECTED; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionState(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Set priority of the profile - * - * <p> The device should already be paired. - * Priority can be one of {@link #PRIORITY_ON} or {@link #PRIORITY_OFF}, - * - * @param device Paired bluetooth device - * @param priority - * @return true if priority is set, false on error - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setPriority(BluetoothDevice device, int priority) { - if (DBG) log("setPriority(" + device + ", " + priority + ")"); - return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority)); - } - - /** - * Set connection policy of the profile - * - * <p> The device should already be paired. - * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED}, - * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Paired bluetooth device - * @param connectionPolicy is the connection policy to set to for this profile - * @return true if connectionPolicy is set, false on error - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setConnectionPolicy(@NonNull BluetoothDevice device, - @ConnectionPolicy int connectionPolicy) { - if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); - if (device == null) { - throw new IllegalArgumentException("device must not be null"); - } - final IBluetoothHidHost service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device) - && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN - || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the priority of the profile. - * - * <p> The priority can be any of: - * {@link #PRIORITY_OFF}, {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED} - * - * @param device Bluetooth device - * @return priority of the device - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public int getPriority(BluetoothDevice device) { - if (VDBG) log("getPriority(" + device + ")"); - return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device)); - } - - /** - * Get the connection policy of the profile. - * - * <p> The connection policy can be any of: - * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, - * {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Bluetooth device - * @return connection policy of the device - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) { - if (VDBG) log("getConnectionPolicy(" + device + ")"); - if (device == null) { - throw new IllegalArgumentException("device must not be null"); - } - final IBluetoothHidHost service = getService(); - final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionPolicy(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - private boolean isEnabled() { - return mAdapter.getState() == BluetoothAdapter.STATE_ON; - } - - private static boolean isValidDevice(BluetoothDevice device) { - return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); - } - - /** - * Initiate virtual unplug for a HID input device. - * - * @param device Remote Bluetooth Device - * @return false on immediate error, true otherwise - * @hide - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean virtualUnplug(BluetoothDevice device) { - if (DBG) log("virtualUnplug(" + device + ")"); - final IBluetoothHidHost service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.virtualUnplug(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Send Get_Protocol_Mode command to the connected HID input device. - * - * @param device Remote Bluetooth Device - * @return false on immediate error, true otherwise - * @hide - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean getProtocolMode(BluetoothDevice device) { - if (VDBG) log("getProtocolMode(" + device + ")"); - final IBluetoothHidHost service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.getProtocolMode(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Send Set_Protocol_Mode command to the connected HID input device. - * - * @param device Remote Bluetooth Device - * @return false on immediate error, true otherwise - * @hide - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean setProtocolMode(BluetoothDevice device, int protocolMode) { - if (DBG) log("setProtocolMode(" + device + ")"); - final IBluetoothHidHost service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.setProtocolMode(device, protocolMode, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Send Get_Report command to the connected HID input device. - * - * @param device Remote Bluetooth Device - * @param reportType Report type - * @param reportId Report ID - * @param bufferSize Report receiving buffer size - * @return false on immediate error, true otherwise - * @hide - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean getReport(BluetoothDevice device, byte reportType, byte reportId, - int bufferSize) { - if (VDBG) { - log("getReport(" + device + "), reportType=" + reportType + " reportId=" + reportId - + "bufferSize=" + bufferSize); - } - final IBluetoothHidHost service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.getReport(device, reportType, reportId, bufferSize, mAttributionSource, - recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Send Set_Report command to the connected HID input device. - * - * @param device Remote Bluetooth Device - * @param reportType Report type - * @param report Report receiving buffer size - * @return false on immediate error, true otherwise - * @hide - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean setReport(BluetoothDevice device, byte reportType, String report) { - if (VDBG) log("setReport(" + device + "), reportType=" + reportType + " report=" + report); - final IBluetoothHidHost service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.setReport(device, reportType, report, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Send Send_Data command to the connected HID input device. - * - * @param device Remote Bluetooth Device - * @param report Report to send - * @return false on immediate error, true otherwise - * @hide - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean sendData(BluetoothDevice device, String report) { - if (DBG) log("sendData(" + device + "), report=" + report); - final IBluetoothHidHost service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.sendData(device, report, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Send Get_Idle_Time command to the connected HID input device. - * - * @param device Remote Bluetooth Device - * @return false on immediate error, true otherwise - * @hide - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean getIdleTime(BluetoothDevice device) { - if (DBG) log("getIdletime(" + device + ")"); - final IBluetoothHidHost service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.getIdleTime(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Send Set_Idle_Time command to the connected HID input device. - * - * @param device Remote Bluetooth Device - * @param idleTime Idle time to be set on HID Device - * @return false on immediate error, true otherwise - * @hide - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean setIdleTime(BluetoothDevice device, byte idleTime) { - if (DBG) log("setIdletime(" + device + "), idleTime=" + idleTime); - final IBluetoothHidHost service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.setIdleTime(device, idleTime, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - private static void log(String msg) { - Log.d(TAG, msg); - } -} diff --git a/core/java/android/bluetooth/BluetoothInputStream.java b/core/java/android/bluetooth/BluetoothInputStream.java deleted file mode 100644 index 95f9229f0446..000000000000 --- a/core/java/android/bluetooth/BluetoothInputStream.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2009 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.bluetooth; - -import android.annotation.SuppressLint; - -import java.io.IOException; -import java.io.InputStream; - -/** - * BluetoothInputStream. - * - * Used to write to a Bluetooth socket. - * - * @hide - */ -@SuppressLint("AndroidFrameworkBluetoothPermission") -/*package*/ final class BluetoothInputStream extends InputStream { - private BluetoothSocket mSocket; - - /*package*/ BluetoothInputStream(BluetoothSocket s) { - mSocket = s; - } - - /** - * Return number of bytes available before this stream will block. - */ - public int available() throws IOException { - return mSocket.available(); - } - - public void close() throws IOException { - mSocket.close(); - } - - /** - * Reads a single byte from this stream and returns it as an integer in the - * range from 0 to 255. Returns -1 if the end of the stream has been - * reached. Blocks until one byte has been read, the end of the source - * stream is detected or an exception is thrown. - * - * @return the byte read or -1 if the end of stream has been reached. - * @throws IOException if the stream is closed or another IOException occurs. - * @since Android 1.5 - */ - public int read() throws IOException { - byte[] b = new byte[1]; - int ret = mSocket.read(b, 0, 1); - if (ret == 1) { - return (int) b[0] & 0xff; - } else { - return -1; - } - } - - /** - * Reads at most {@code length} bytes from this stream and stores them in - * the byte array {@code b} starting at {@code offset}. - * - * @param b the byte array in which to store the bytes read. - * @param offset the initial position in {@code buffer} to store the bytes read from this - * stream. - * @param length the maximum number of bytes to store in {@code b}. - * @return the number of bytes actually read or -1 if the end of the stream has been reached. - * @throws IndexOutOfBoundsException if {@code offset < 0} or {@code length < 0}, or if {@code - * offset + length} is greater than the length of {@code b}. - * @throws IOException if the stream is closed or another IOException occurs. - * @since Android 1.5 - */ - public int read(byte[] b, int offset, int length) throws IOException { - if (b == null) { - throw new NullPointerException("byte array is null"); - } - if ((offset | length) < 0 || length > b.length - offset) { - throw new ArrayIndexOutOfBoundsException("invalid offset or length"); - } - return mSocket.read(b, offset, length); - } -} diff --git a/core/java/android/bluetooth/BluetoothLeAudio.java b/core/java/android/bluetooth/BluetoothLeAudio.java deleted file mode 100644 index 15db686b3be4..000000000000 --- a/core/java/android/bluetooth/BluetoothLeAudio.java +++ /dev/null @@ -1,829 +0,0 @@ -/* - * Copyright 2020 HIMSA II K/S - www.himsa.com. - * Represented by EHIMA - www.ehima.com - * - * 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.bluetooth; - -import static android.bluetooth.BluetoothUtils.getSyncTimeout; - -import android.Manifest; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.RequiresPermission; -import android.annotation.SdkConstant; -import android.annotation.SdkConstant.SdkConstantType; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; -import android.content.AttributionSource; -import android.content.Context; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.CloseGuard; -import android.util.Log; - -import com.android.modules.utils.SynchronousResultReceiver; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeoutException; - -/** - * This class provides the public APIs to control the LeAudio profile. - * - * <p>BluetoothLeAudio is a proxy object for controlling the Bluetooth LE Audio - * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get - * the BluetoothLeAudio proxy object. - * - * <p> Android only supports one set of connected Bluetooth LeAudio device at a time. Each - * method is protected with its appropriate permission. - */ -public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable { - private static final String TAG = "BluetoothLeAudio"; - private static final boolean DBG = false; - private static final boolean VDBG = false; - - private CloseGuard mCloseGuard; - - /** - * Intent used to broadcast the change in connection state of the LeAudio - * profile. Please note that in the binaural case, there will be two different LE devices for - * the left and right side and each device will have their own connection state changes. - * - * <p>This intent will have 3 extras: - * <ul> - * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> - * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> - * </ul> - * - * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of - * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, - * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED = - "android.bluetooth.action.LE_AUDIO_CONNECTION_STATE_CHANGED"; - - /** - * Intent used to broadcast the selection of a connected device as active. - * - * <p>This intent will have one extra: - * <ul> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. It can - * be null if no device is active. </li> - * </ul> - * - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED = - "android.bluetooth.action.LE_AUDIO_ACTIVE_DEVICE_CHANGED"; - - /** - * Intent used to broadcast group node status information. - * - * <p>This intent will have 3 extra: - * <ul> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. It can - * be null if no device is active. </li> - * <li> {@link #EXTRA_LE_AUDIO_GROUP_ID} - Group id. </li> - * <li> {@link #EXTRA_LE_AUDIO_GROUP_NODE_STATUS} - Group node status. </li> - * </ul> - * - * @hide - */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_LE_AUDIO_GROUP_NODE_STATUS_CHANGED = - "android.bluetooth.action.LE_AUDIO_GROUP_NODE_STATUS_CHANGED"; - - - /** - * Intent used to broadcast group status information. - * - * <p>This intent will have 4 extra: - * <ul> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. It can - * be null if no device is active. </li> - * <li> {@link #EXTRA_LE_AUDIO_GROUP_ID} - Group id. </li> - * <li> {@link #EXTRA_LE_AUDIO_GROUP_STATUS} - Group status. </li> - * </ul> - * - * @hide - */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_LE_AUDIO_GROUP_STATUS_CHANGED = - "android.bluetooth.action.LE_AUDIO_GROUP_STATUS_CHANGED"; - - /** - * Intent used to broadcast group audio configuration changed information. - * - * <p>This intent will have 5 extra: - * <ul> - * <li> {@link #EXTRA_LE_AUDIO_GROUP_ID} - Group id. </li> - * <li> {@link #EXTRA_LE_AUDIO_DIRECTION} - Direction as bit mask. </li> - * <li> {@link #EXTRA_LE_AUDIO_SINK_LOCATION} - Sink location as per Bluetooth Assigned - * Numbers </li> - * <li> {@link #EXTRA_LE_AUDIO_SOURCE_LOCATION} - Source location as per Bluetooth Assigned - * Numbers </li> - * <li> {@link #EXTRA_LE_AUDIO_AVAILABLE_CONTEXTS} - Available contexts for group as per - * Bluetooth Assigned Numbers </li> - * </ul> - * - * @hide - */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_LE_AUDIO_CONF_CHANGED = - "android.bluetooth.action.LE_AUDIO_CONF_CHANGED"; - - /** - * Indicates unspecified audio content. - * @hide - */ - public static final int CONTEXT_TYPE_UNSPECIFIED = 0x0001; - - /** - * Indicates conversation between humans as, for example, in telephony or video calls. - * @hide - */ - public static final int CONTEXT_TYPE_COMMUNICATION = 0x0002; - - /** - * Indicates media as, for example, in music, public radio, podcast or video soundtrack. - * @hide - */ - public static final int CONTEXT_TYPE_MEDIA = 0x0004; - - /** - * Indicates instructional audio as, for example, in navigation, traffic announcements - * or user guidance. - * @hide - */ - public static final int CONTEXT_TYPE_INSTRUCTIONAL = 0x0008; - - /** - * Indicates attention seeking audio as, for example, in beeps signalling arrival of a message - * or keyboard clicks. - * @hide - */ - public static final int CONTEXT_TYPE_ATTENTION_SEEKING = 0x0010; - - /** - * Indicates immediate alerts as, for example, in a low battery alarm, timer expiry or alarm - * clock. - * @hide - */ - public static final int CONTEXT_TYPE_IMMEDIATE_ALERT = 0x0020; - - /** - * Indicates man machine communication as, for example, with voice recognition or virtual - * assistant. - * @hide - */ - public static final int CONTEXT_TYPE_MAN_MACHINE = 0x0040; - - /** - * Indicates emergency alerts as, for example, with fire alarms or other urgent alerts. - * @hide - */ - public static final int CONTEXT_TYPE_EMERGENCY_ALERT = 0x0080; - - /** - * Indicates ringtone as in a call alert. - * @hide - */ - public static final int CONTEXT_TYPE_RINGTONE = 0x0100; - - /** - * Indicates audio associated with a television program and/or with metadata conforming to the - * Bluetooth Broadcast TV profile. - * @hide - */ - public static final int CONTEXT_TYPE_TV = 0x0200; - - /** - * Indicates audio associated with a low latency live audio stream. - * - * @hide - */ - public static final int CONTEXT_TYPE_LIVE = 0x0400; - - /** - * Indicates audio associated with a video game stream. - * @hide - */ - public static final int CONTEXT_TYPE_GAME = 0x0800; - - /** - * This represents an invalid group ID. - * - * @hide - */ - public static final int GROUP_ID_INVALID = IBluetoothLeAudio.LE_AUDIO_GROUP_ID_INVALID; - - /** - * Contains group id. - * @hide - */ - public static final String EXTRA_LE_AUDIO_GROUP_ID = - "android.bluetooth.extra.LE_AUDIO_GROUP_ID"; - - /** - * Contains group node status, can be any of - * <p> - * <ul> - * <li> {@link #GROUP_NODE_ADDED} </li> - * <li> {@link #GROUP_NODE_REMOVED} </li> - * </ul> - * <p> - * @hide - */ - public static final String EXTRA_LE_AUDIO_GROUP_NODE_STATUS = - "android.bluetooth.extra.LE_AUDIO_GROUP_NODE_STATUS"; - - /** - * Contains group status, can be any of - * - * <p> - * <ul> - * <li> {@link #GROUP_STATUS_ACTIVE} </li> - * <li> {@link #GROUP_STATUS_INACTIVE} </li> - * </ul> - * <p> - * @hide - */ - public static final String EXTRA_LE_AUDIO_GROUP_STATUS = - "android.bluetooth.extra.LE_AUDIO_GROUP_STATUS"; - - /** - * Contains bit mask for direction, bit 0 set when Sink, bit 1 set when Source. - * @hide - */ - public static final String EXTRA_LE_AUDIO_DIRECTION = - "android.bluetooth.extra.LE_AUDIO_DIRECTION"; - - /** - * Contains source location as per Bluetooth Assigned Numbers - * @hide - */ - public static final String EXTRA_LE_AUDIO_SOURCE_LOCATION = - "android.bluetooth.extra.LE_AUDIO_SOURCE_LOCATION"; - - /** - * Contains sink location as per Bluetooth Assigned Numbers - * @hide - */ - public static final String EXTRA_LE_AUDIO_SINK_LOCATION = - "android.bluetooth.extra.LE_AUDIO_SINK_LOCATION"; - - /** - * Contains available context types for group as per Bluetooth Assigned Numbers - * @hide - */ - public static final String EXTRA_LE_AUDIO_AVAILABLE_CONTEXTS = - "android.bluetooth.extra.LE_AUDIO_AVAILABLE_CONTEXTS"; - - private final BluetoothAdapter mAdapter; - private final AttributionSource mAttributionSource; - /** - * Indicating that group is Active ( Audio device is available ) - * @hide - */ - public static final int GROUP_STATUS_ACTIVE = IBluetoothLeAudio.GROUP_STATUS_ACTIVE; - - /** - * Indicating that group is Inactive ( Audio device is not available ) - * @hide - */ - public static final int GROUP_STATUS_INACTIVE = IBluetoothLeAudio.GROUP_STATUS_INACTIVE; - - /** - * Indicating that node has been added to the group. - * @hide - */ - public static final int GROUP_NODE_ADDED = IBluetoothLeAudio.GROUP_NODE_ADDED; - - /** - * Indicating that node has been removed from the group. - * @hide - */ - public static final int GROUP_NODE_REMOVED = IBluetoothLeAudio.GROUP_NODE_REMOVED; - - private final BluetoothProfileConnector<IBluetoothLeAudio> mProfileConnector = - new BluetoothProfileConnector(this, BluetoothProfile.LE_AUDIO, "BluetoothLeAudio", - IBluetoothLeAudio.class.getName()) { - @Override - public IBluetoothLeAudio getServiceInterface(IBinder service) { - return IBluetoothLeAudio.Stub.asInterface(service); - } - }; - - /** - * Create a BluetoothLeAudio proxy object for interacting with the local - * Bluetooth LeAudio service. - */ - /* package */ BluetoothLeAudio(Context context, ServiceListener listener, - BluetoothAdapter adapter) { - mAdapter = adapter; - mAttributionSource = adapter.getAttributionSource(); - mProfileConnector.connect(context, listener); - mCloseGuard = new CloseGuard(); - mCloseGuard.open("close"); - } - - /** - * @hide - */ - public void close() { - mProfileConnector.disconnect(); - } - - private IBluetoothLeAudio getService() { - return mProfileConnector.getService(); - } - - protected void finalize() { - if (mCloseGuard != null) { - mCloseGuard.warnIfOpen(); - } - close(); - } - - /** - * Initiate connection to a profile of the remote bluetooth device. - * - * <p> This API returns false in scenarios like the profile on the - * device is already connected or Bluetooth is not turned on. - * When this API returns true, it is guaranteed that - * connection state intent for the profile will be broadcasted with - * the state. Users can get the connection state of the profile - * from this intent. - * - * - * @param device Remote Bluetooth Device - * @return false on immediate error, true otherwise - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean connect(@Nullable BluetoothDevice device) { - if (DBG) log("connect(" + device + ")"); - final IBluetoothLeAudio service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (mAdapter.isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.connect(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Initiate disconnection from a profile - * - * <p> This API will return false in scenarios like the profile on the - * Bluetooth device is not in connected state etc. When this API returns, - * true, it is guaranteed that the connection state change - * intent will be broadcasted with the state. Users can get the - * disconnection state of the profile from this intent. - * - * <p> If the disconnection is initiated by a remote device, the state - * will transition from {@link #STATE_CONNECTED} to - * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the - * host (local) device the state will transition from - * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to - * state {@link #STATE_DISCONNECTED}. The transition to - * {@link #STATE_DISCONNECTING} can be used to distinguish between the - * two scenarios. - * - * - * @param device Remote Bluetooth Device - * @return false on immediate error, true otherwise - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean disconnect(@Nullable BluetoothDevice device) { - if (DBG) log("disconnect(" + device + ")"); - final IBluetoothLeAudio service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (mAdapter.isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.disconnect(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public @NonNull List<BluetoothDevice> getConnectedDevices() { - if (VDBG) log("getConnectedDevices()"); - final IBluetoothLeAudio service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (mAdapter.isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getConnectedDevices(mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public @NonNull List<BluetoothDevice> getDevicesMatchingConnectionStates( - @NonNull int[] states) { - if (VDBG) log("getDevicesMatchingStates()"); - final IBluetoothLeAudio service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (mAdapter.isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - */ - @Override - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public @BtProfileState int getConnectionState(@NonNull BluetoothDevice device) { - if (VDBG) log("getState(" + device + ")"); - final IBluetoothLeAudio service = getService(); - final int defaultValue = BluetoothProfile.STATE_DISCONNECTED; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (mAdapter.isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionState(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Select a connected device as active. - * - * The active device selection is per profile. An active device's - * purpose is profile-specific. For example, LeAudio audio - * streaming is to the active LeAudio device. If a remote device - * is not connected, it cannot be selected as active. - * - * <p> This API returns false in scenarios like the profile on the - * device is not connected or Bluetooth is not turned on. - * When this API returns true, it is guaranteed that the - * {@link #ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED} intent will be broadcasted - * with the active device. - * - * - * @param device the remote Bluetooth device. Could be null to clear - * the active device and stop streaming audio to a Bluetooth device. - * @return false on immediate error, true otherwise - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean setActiveDevice(@Nullable BluetoothDevice device) { - if (DBG) log("setActiveDevice(" + device + ")"); - final IBluetoothLeAudio service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (mAdapter.isEnabled() && ((device == null) || isValidDevice(device))) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.setActiveDevice(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the connected LeAudio devices that are active - * - * @return the list of active devices. Returns empty list on error. - * @hide - */ - @NonNull - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getActiveDevices() { - if (VDBG) log("getActiveDevice()"); - final IBluetoothLeAudio service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (mAdapter.isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getActiveDevices(mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get device group id. Devices with same group id belong to same group (i.e left and right - * earbud) - * @param device LE Audio capable device - * @return group id that this device currently belongs to - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public int getGroupId(@NonNull BluetoothDevice device) { - if (VDBG) log("getGroupId()"); - final IBluetoothLeAudio service = getService(); - final int defaultValue = GROUP_ID_INVALID; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (mAdapter.isEnabled()) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getGroupId(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Set volume for the streaming devices - * - * @param volume volume to set - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) - public void setVolume(int volume) { - if (VDBG) log("setVolume(vol: " + volume + " )"); - final IBluetoothLeAudio service = getService(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (mAdapter.isEnabled()) { - try { - final SynchronousResultReceiver recv = new SynchronousResultReceiver(); - service.setVolume(volume, mAttributionSource, recv); - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - } - - /** - * Add device to the given group. - * @param group_id group ID the device is being added to - * @param device the active device - * @return true on success, otherwise false - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED - }) - public boolean groupAddNode(int group_id, @NonNull BluetoothDevice device) { - if (VDBG) log("groupAddNode()"); - final IBluetoothLeAudio service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (mAdapter.isEnabled()) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.groupAddNode(group_id, device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Remove device from a given group. - * @param group_id group ID the device is being removed from - * @param device the active device - * @return true on success, otherwise false - * - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED - }) - public boolean groupRemoveNode(int group_id, @NonNull BluetoothDevice device) { - if (VDBG) log("groupRemoveNode()"); - final IBluetoothLeAudio service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (mAdapter.isEnabled()) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.groupRemoveNode(group_id, device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Set connection policy of the profile - * - * <p> The device should already be paired. - * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED}, - * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Paired bluetooth device - * @param connectionPolicy is the connection policy to set to for this profile - * @return true if connectionPolicy is set, false on error - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setConnectionPolicy(@NonNull BluetoothDevice device, - @ConnectionPolicy int connectionPolicy) { - if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); - final IBluetoothLeAudio service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (mAdapter.isEnabled() && isValidDevice(device) - && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN - || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the connection policy of the profile. - * - * <p> The connection policy can be any of: - * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, - * {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Bluetooth device - * @return connection policy of the device - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public @ConnectionPolicy int getConnectionPolicy(@Nullable BluetoothDevice device) { - if (VDBG) log("getConnectionPolicy(" + device + ")"); - final IBluetoothLeAudio service = getService(); - final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (mAdapter.isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionPolicy(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - - /** - * Helper for converting a state to a string. - * - * For debug use only - strings are not internationalized. - * - * @hide - */ - public static String stateToString(int state) { - switch (state) { - case STATE_DISCONNECTED: - return "disconnected"; - case STATE_CONNECTING: - return "connecting"; - case STATE_CONNECTED: - return "connected"; - case STATE_DISCONNECTING: - return "disconnecting"; - default: - return "<unknown state " + state + ">"; - } - } - - private boolean isValidDevice(@Nullable BluetoothDevice device) { - if (device == null) return false; - - if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; - return false; - } - - private static void log(String msg) { - Log.d(TAG, msg); - } -} diff --git a/core/java/android/bluetooth/BluetoothLeAudioCodecConfig.java b/core/java/android/bluetooth/BluetoothLeAudioCodecConfig.java deleted file mode 100644 index dcaf4b682f44..000000000000 --- a/core/java/android/bluetooth/BluetoothLeAudioCodecConfig.java +++ /dev/null @@ -1,129 +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 android.bluetooth; - -import android.annotation.IntDef; -import android.annotation.NonNull; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * Represents the codec configuration for a Bluetooth LE Audio source device. - * <p>Contains the source codec type. - * <p>The source codec type values are the same as those supported by the - * device hardware. - * - * {@see BluetoothLeAudioCodecConfig} - */ -public final class BluetoothLeAudioCodecConfig { - // Add an entry for each source codec here. - - /** @hide */ - @IntDef(prefix = "SOURCE_CODEC_TYPE_", value = { - SOURCE_CODEC_TYPE_LC3, - SOURCE_CODEC_TYPE_INVALID - }) - @Retention(RetentionPolicy.SOURCE) - public @interface SourceCodecType {}; - - public static final int SOURCE_CODEC_TYPE_LC3 = 0; - public static final int SOURCE_CODEC_TYPE_INVALID = 1000 * 1000; - - /** - * Represents the count of valid source codec types. Can be accessed via - * {@link #getMaxCodecType}. - */ - private static final int SOURCE_CODEC_TYPE_MAX = 1; - - private final @SourceCodecType int mCodecType; - - /** - * Creates a new BluetoothLeAudioCodecConfig. - * - * @param codecType the source codec type - */ - private BluetoothLeAudioCodecConfig(@SourceCodecType int codecType) { - mCodecType = codecType; - } - - @Override - public String toString() { - return "{codecName:" + getCodecName() + "}"; - } - - /** - * Gets the codec type. - * - * @return the codec type - */ - public @SourceCodecType int getCodecType() { - return mCodecType; - } - - /** - * Returns the valid codec types count. - */ - public static int getMaxCodecType() { - return SOURCE_CODEC_TYPE_MAX; - } - - /** - * Gets the codec name. - * - * @return the codec name - */ - public @NonNull String getCodecName() { - switch (mCodecType) { - case SOURCE_CODEC_TYPE_LC3: - return "LC3"; - case SOURCE_CODEC_TYPE_INVALID: - return "INVALID CODEC"; - default: - break; - } - return "UNKNOWN CODEC(" + mCodecType + ")"; - } - - /** - * Builder for {@link BluetoothLeAudioCodecConfig}. - * <p> By default, the codec type will be set to - * {@link BluetoothLeAudioCodecConfig#SOURCE_CODEC_TYPE_INVALID} - */ - public static final class Builder { - private int mCodecType = BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_INVALID; - - /** - * Set codec type for Bluetooth codec config. - * - * @param codecType of this codec - * @return the same Builder instance - */ - public @NonNull Builder setCodecType(@SourceCodecType int codecType) { - mCodecType = codecType; - return this; - } - - /** - * Build {@link BluetoothLeAudioCodecConfig}. - * @return new BluetoothLeAudioCodecConfig built - */ - public @NonNull BluetoothLeAudioCodecConfig build() { - return new BluetoothLeAudioCodecConfig(mCodecType); - } - } -} diff --git a/core/java/android/bluetooth/BluetoothLeBroadcast.java b/core/java/android/bluetooth/BluetoothLeBroadcast.java deleted file mode 100644 index fed9f911d5b3..000000000000 --- a/core/java/android/bluetooth/BluetoothLeBroadcast.java +++ /dev/null @@ -1,287 +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.bluetooth; - -import android.annotation.IntDef; -import android.content.Context; -import android.util.Log; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.List; - -/** - * This class provides the public APIs to control the Bluetooth LE Broadcast Source profile. - * - * <p>BluetoothLeBroadcast is a proxy object for controlling the Bluetooth LE Broadcast - * Source Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} - * to get the BluetoothLeBroadcast proxy object. - * - * @hide - */ -public final class BluetoothLeBroadcast implements BluetoothProfile { - private static final String TAG = "BluetoothLeBroadcast"; - private static final boolean DBG = true; - private static final boolean VDBG = false; - - /** - * Constants used by the LE Audio Broadcast profile for the Broadcast state - * - * @hide - */ - @IntDef(prefix = {"LE_AUDIO_BROADCAST_STATE_"}, value = { - LE_AUDIO_BROADCAST_STATE_DISABLED, - LE_AUDIO_BROADCAST_STATE_ENABLING, - LE_AUDIO_BROADCAST_STATE_ENABLED, - LE_AUDIO_BROADCAST_STATE_DISABLING, - LE_AUDIO_BROADCAST_STATE_PLAYING, - LE_AUDIO_BROADCAST_STATE_NOT_PLAYING - }) - @Retention(RetentionPolicy.SOURCE) - public @interface LeAudioBroadcastState {} - - /** - * Indicates that LE Audio Broadcast mode is currently disabled - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_STATE_DISABLED = 10; - - /** - * Indicates that LE Audio Broadcast mode is being enabled - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_STATE_ENABLING = 11; - - /** - * Indicates that LE Audio Broadcast mode is currently enabled - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_STATE_ENABLED = 12; - /** - * Indicates that LE Audio Broadcast mode is being disabled - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_STATE_DISABLING = 13; - - /** - * Indicates that an LE Audio Broadcast mode is currently playing - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_STATE_PLAYING = 14; - - /** - * Indicates that LE Audio Broadcast is currently not playing - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_STATE_NOT_PLAYING = 15; - - /** - * Constants used by the LE Audio Broadcast profile for encryption key length - * - * @hide - */ - @IntDef(prefix = {"LE_AUDIO_BROADCAST_ENCRYPTION_KEY_"}, value = { - LE_AUDIO_BROADCAST_ENCRYPTION_KEY_32BIT, - LE_AUDIO_BROADCAST_ENCRYPTION_KEY_128BIT - }) - @Retention(RetentionPolicy.SOURCE) - public @interface LeAudioEncryptionKeyLength {} - - /** - * Indicates that the LE Audio Broadcast encryption key size is 32 bits. - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_ENCRYPTION_KEY_32BIT = 16; - - /** - * Indicates that the LE Audio Broadcast encryption key size is 128 bits. - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_ENCRYPTION_KEY_128BIT = 17; - - /** - * Interface for receiving events related to broadcasts - */ - public interface Callback { - /** - * Called when broadcast state has changed - * - * @param prevState broadcast state before the change - * @param newState broadcast state after the change - */ - @LeAudioBroadcastState - void onBroadcastStateChange(int prevState, int newState); - /** - * Called when encryption key has been updated - * - * @param success true if the key was updated successfully, false otherwise - */ - void onEncryptionKeySet(boolean success); - } - - /** - * Create a BluetoothLeBroadcast proxy object for interacting with the local - * LE Audio Broadcast Source service. - * - * @hide - */ - /*package*/ BluetoothLeBroadcast(Context context, - BluetoothProfile.ServiceListener listener) { - } - - /** - * Not supported since LE Audio Broadcasts do not establish a connection - * - * @throws UnsupportedOperationException - * - * @hide - */ - @Override - public int getConnectionState(BluetoothDevice device) { - throw new UnsupportedOperationException( - "LE Audio Broadcasts are not connection-oriented."); - } - - /** - * Not supported since LE Audio Broadcasts do not establish a connection - * - * @throws UnsupportedOperationException - * - * @hide - */ - @Override - public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { - throw new UnsupportedOperationException( - "LE Audio Broadcasts are not connection-oriented."); - } - - /** - * Not supported since LE Audio Broadcasts do not establish a connection - * - * @throws UnsupportedOperationException - * - * @hide - */ - @Override - public List<BluetoothDevice> getConnectedDevices() { - throw new UnsupportedOperationException( - "LE Audio Broadcasts are not connection-oriented."); - } - - /** - * Enable LE Audio Broadcast mode. - * - * Generates a new broadcast ID and enables sending of encrypted or unencrypted - * isochronous PDUs - * - * @hide - */ - public int enableBroadcastMode() { - if (DBG) log("enableBroadcastMode"); - return BluetoothStatusCodes.ERROR_LE_AUDIO_BROADCAST_SOURCE_SET_BROADCAST_MODE_FAILED; - } - - /** - * Disable LE Audio Broadcast mode. - * - * @hide - */ - public int disableBroadcastMode() { - if (DBG) log("disableBroadcastMode"); - return BluetoothStatusCodes.ERROR_LE_AUDIO_BROADCAST_SOURCE_SET_BROADCAST_MODE_FAILED; - } - - /** - * Get the current LE Audio broadcast state - * - * @hide - */ - @LeAudioBroadcastState - public int getBroadcastState() { - if (DBG) log("getBroadcastState"); - return LE_AUDIO_BROADCAST_STATE_DISABLED; - } - - /** - * Enable LE Audio broadcast encryption - * - * @param keyLength if useExisting is true, this specifies the length of the key that should - * be generated - * @param useExisting true, if an existing key should be used - * false, if a new key should be generated - * - * @hide - */ - @LeAudioEncryptionKeyLength - public int enableEncryption(boolean useExisting, int keyLength) { - if (DBG) log("enableEncryption useExisting=" + useExisting + " keyLength=" + keyLength); - return BluetoothStatusCodes.ERROR_LE_AUDIO_BROADCAST_SOURCE_ENABLE_ENCRYPTION_FAILED; - } - - /** - * Disable LE Audio broadcast encryption - * - * @param removeExisting true, if the existing key should be removed - * false, otherwise - * - * @hide - */ - public int disableEncryption(boolean removeExisting) { - if (DBG) log("disableEncryption removeExisting=" + removeExisting); - return BluetoothStatusCodes.ERROR_LE_AUDIO_BROADCAST_SOURCE_DISABLE_ENCRYPTION_FAILED; - } - - /** - * Enable or disable LE Audio broadcast encryption - * - * @param key use the provided key if non-null, generate a new key if null - * @param keyLength 0 if encryption is disabled, 4 bytes (low security), - * 16 bytes (high security) - * - * @hide - */ - @LeAudioEncryptionKeyLength - public int setEncryptionKey(byte[] key, int keyLength) { - if (DBG) log("setEncryptionKey key=" + key + " keyLength=" + keyLength); - return BluetoothStatusCodes.ERROR_LE_AUDIO_BROADCAST_SOURCE_SET_ENCRYPTION_KEY_FAILED; - } - - - /** - * Get the encryption key that was set before - * - * @return encryption key as a byte array or null if no encryption key was set - * - * @hide - */ - public byte[] getEncryptionKey() { - if (DBG) log("getEncryptionKey"); - return null; - } - - private static void log(String msg) { - Log.d(TAG, msg); - } -} diff --git a/core/java/android/bluetooth/BluetoothLeBroadcastAssistantCallback.java b/core/java/android/bluetooth/BluetoothLeBroadcastAssistantCallback.java deleted file mode 100644 index b866cce22470..000000000000 --- a/core/java/android/bluetooth/BluetoothLeBroadcastAssistantCallback.java +++ /dev/null @@ -1,140 +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.bluetooth; - -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.bluetooth.le.ScanResult; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * This class provides a set of callbacks that are invoked when scanning for Broadcast Sources is - * offloaded to a Broadcast Assistant. - * - * <p>An LE Audio Broadcast Assistant can help a Broadcast Sink to scan for available Broadcast - * Sources. The Broadcast Sink achieves this by offloading the scan to a Broadcast Assistant. This - * is facilitated by the Broadcast Audio Scan Service (BASS). A BASS server is a GATT server that is - * part of the Scan Delegator on a Broadcast Sink. A BASS client instead runs on the Broadcast - * Assistant. - * - * <p>Once a GATT connection is established between the BASS client and the BASS server, the - * Broadcast Sink can offload the scans to the Broadcast Assistant. Upon finding new Broadcast - * Sources, the Broadcast Assistant then notifies the Broadcast Sink about these over the - * established GATT connection. The Scan Delegator on the Broadcast Sink can also notify the - * Assistant about changes such as addition and removal of Broadcast Sources. - * - * @hide - */ -public abstract class BluetoothLeBroadcastAssistantCallback { - - /** - * Broadcast Audio Scan Service (BASS) codes returned by a BASS Server - * - * @hide - */ - @IntDef( - prefix = "BASS_STATUS_", - value = { - BASS_STATUS_SUCCESS, - BASS_STATUS_FAILURE, - BASS_STATUS_INVALID_GATT_HANDLE, - BASS_STATUS_TXN_TIMEOUT, - BASS_STATUS_INVALID_SOURCE_ID, - BASS_STATUS_COLOCATED_SRC_UNAVAILABLE, - BASS_STATUS_INVALID_SOURCE_SELECTED, - BASS_STATUS_SOURCE_UNAVAILABLE, - BASS_STATUS_DUPLICATE_ADDITION, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface BassStatus {} - - public static final int BASS_STATUS_SUCCESS = 0x00; - public static final int BASS_STATUS_FAILURE = 0x01; - public static final int BASS_STATUS_INVALID_GATT_HANDLE = 0x02; - public static final int BASS_STATUS_TXN_TIMEOUT = 0x03; - - public static final int BASS_STATUS_INVALID_SOURCE_ID = 0x04; - public static final int BASS_STATUS_COLOCATED_SRC_UNAVAILABLE = 0x05; - public static final int BASS_STATUS_INVALID_SOURCE_SELECTED = 0x06; - public static final int BASS_STATUS_SOURCE_UNAVAILABLE = 0x07; - public static final int BASS_STATUS_DUPLICATE_ADDITION = 0x08; - public static final int BASS_STATUS_NO_EMPTY_SLOT = 0x09; - public static final int BASS_STATUS_INVALID_GROUP_OP = 0x10; - - /** - * Callback invoked when a new LE Audio Broadcast Source is found. - * - * @param result {@link ScanResult} scan result representing a Broadcast Source - */ - public void onBluetoothLeBroadcastSourceFound(@NonNull ScanResult result) {} - - /** - * Callback invoked when the Broadcast Assistant synchronizes with Periodic Advertisements (PAs) - * of an LE Audio Broadcast Source. - * - * @param source the selected Broadcast Source - */ - public void onBluetoothLeBroadcastSourceSelected( - @NonNull BluetoothLeBroadcastSourceInfo source, @BassStatus int status) {} - - /** - * Callback invoked when the Broadcast Assistant loses synchronization with an LE Audio - * Broadcast Source. - * - * @param source the Broadcast Source with which synchronization was lost - */ - public void onBluetoothLeBroadcastSourceLost( - @NonNull BluetoothLeBroadcastSourceInfo source, @BassStatus int status) {} - - /** - * Callback invoked when a new LE Audio Broadcast Source has been successfully added to the Scan - * Delegator (within a Broadcast Sink, for example). - * - * @param sink Scan Delegator device on which a new Broadcast Source has been added - * @param source the added Broadcast Source - */ - public void onBluetoothLeBroadcastSourceAdded( - @NonNull BluetoothDevice sink, - @NonNull BluetoothLeBroadcastSourceInfo source, - @BassStatus int status) {} - - /** - * Callback invoked when an existing LE Audio Broadcast Source within a remote Scan Delegator - * has been updated. - * - * @param sink Scan Delegator device on which a Broadcast Source has been updated - * @param source the updated Broadcast Source - */ - public void onBluetoothLeBroadcastSourceUpdated( - @NonNull BluetoothDevice sink, - @NonNull BluetoothLeBroadcastSourceInfo source, - @BassStatus int status) {} - - /** - * Callback invoked when an LE Audio Broadcast Source has been successfully removed from the - * Scan Delegator (within a Broadcast Sink, for example). - * - * @param sink Scan Delegator device from which a Broadcast Source has been removed - * @param source the removed Broadcast Source - */ - public void onBluetoothLeBroadcastSourceRemoved( - @NonNull BluetoothDevice sink, - @NonNull BluetoothLeBroadcastSourceInfo source, - @BassStatus int status) {} -} diff --git a/core/java/android/bluetooth/BluetoothLeBroadcastSourceInfo.java b/core/java/android/bluetooth/BluetoothLeBroadcastSourceInfo.java deleted file mode 100644 index cb47280acc7e..000000000000 --- a/core/java/android/bluetooth/BluetoothLeBroadcastSourceInfo.java +++ /dev/null @@ -1,788 +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.bluetooth; - -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.os.Parcel; -import android.os.Parcelable; -import android.util.Log; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; - -/** - * This class represents an LE Audio Broadcast Source and the associated information that is needed - * by Broadcast Audio Scan Service (BASS) residing on a Scan Delegator. - * - * <p>For example, the Scan Delegator on an LE Audio Broadcast Sink can use the information - * contained within an instance of this class to synchronize with an LE Audio Broadcast Source in - * order to listen to a Broadcast Audio Stream. - * - * <p>BroadcastAssistant has a BASS client which facilitates scanning and discovery of Broadcast - * Sources on behalf of say a Broadcast Sink. Upon successful discovery of one or more Broadcast - * sources, this information needs to be communicated to the BASS Server residing within the Scan - * Delegator on a Broadcast Sink. This is achieved using the Periodic Advertising Synchronization - * Transfer (PAST) procedure. This procedure uses information contained within an instance of this - * class. - * - * @hide - */ -public final class BluetoothLeBroadcastSourceInfo implements Parcelable { - private static final String TAG = "BluetoothLeBroadcastSourceInfo"; - private static final boolean DBG = true; - - /** - * Constants representing Broadcast Source address types - * - * @hide - */ - @IntDef( - prefix = "LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_", - value = { - LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_PUBLIC, - LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_RANDOM, - LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_INVALID - }) - @Retention(RetentionPolicy.SOURCE) - public @interface LeAudioBroadcastSourceAddressType {} - - /** - * Represents a public address used by an LE Audio Broadcast Source - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_PUBLIC = 0; - - /** - * Represents a random address used by an LE Audio Broadcast Source - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_RANDOM = 1; - - /** - * Represents an invalid address used by an LE Audio Broadcast Seurce - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_INVALID = 0xFFFF; - - /** - * Periodic Advertising Synchronization state - * - * <p>Periodic Advertising (PA) enables the LE Audio Broadcast Assistant to discover broadcast - * audio streams as well as the audio stream configuration on behalf of an LE Audio Broadcast - * Sink. This information can then be transferred to the LE Audio Broadcast Sink using the - * Periodic Advertising Synchronizaton Transfer (PAST) procedure. - * - * @hide - */ - @IntDef( - prefix = "LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_", - value = { - LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_IDLE, - LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_SYNCINFO_REQ, - LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_IN_SYNC, - LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_SYNC_FAIL, - LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_NO_PAST - }) - @Retention(RetentionPolicy.SOURCE) - public @interface LeAudioBroadcastSinkPaSyncState {} - - /** - * Indicates that the Broadcast Sink is not synchronized with the Periodic Advertisements (PA) - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_IDLE = 0; - - /** - * Indicates that the Broadcast Sink requested the Broadcast Assistant to synchronize with the - * Periodic Advertisements (PA). - * - * <p>This is also known as scan delegation or scan offloading. - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_SYNCINFO_REQ = 1; - - /** - * Indicates that the Broadcast Sink is synchronized with the Periodic Advertisements (PA). - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_IN_SYNC = 2; - - /** - * Indicates that the Broadcast Sink was unable to synchronize with the Periodic Advertisements - * (PA). - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_SYNC_FAIL = 3; - - /** - * Indicates that the Broadcast Sink should be synchronized with the Periodic Advertisements - * (PA) using the Periodic Advertisements Synchronization Transfert (PAST) procedure. - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_NO_PAST = 4; - - /** - * Indicates that the Broadcast Sink synchornization state is invalid. - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_INVALID = 0xFFFF; - - /** @hide */ - @IntDef( - prefix = "LE_AUDIO_BROADCAST_SINK_AUDIO_SYNC_STATE_", - value = { - LE_AUDIO_BROADCAST_SINK_AUDIO_SYNC_STATE_NOT_SYNCHRONIZED, - LE_AUDIO_BROADCAST_SINK_AUDIO_SYNC_STATE_SYNCHRONIZED - }) - @Retention(RetentionPolicy.SOURCE) - public @interface LeAudioBroadcastSinkAudioSyncState {} - - /** - * Indicates that the Broadcast Sink is not synchronized with a Broadcast Audio Stream. - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SINK_AUDIO_SYNC_STATE_NOT_SYNCHRONIZED = 0; - - /** - * Indicates that the Broadcast Sink is synchronized with a Broadcast Audio Stream. - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SINK_AUDIO_SYNC_STATE_SYNCHRONIZED = 1; - - /** - * Indicates that the Broadcast Sink audio synchronization state is invalid. - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SINK_AUDIO_SYNC_STATE_INVALID = 0xFFFF; - - /** @hide */ - @IntDef( - prefix = "LE_AUDIO_BROADCAST_SINK_ENC_STATE_", - value = { - LE_AUDIO_BROADCAST_SINK_ENC_STATE_NOT_ENCRYPTED, - LE_AUDIO_BROADCAST_SINK_ENC_STATE_CODE_REQUIRED, - LE_AUDIO_BROADCAST_SINK_ENC_STATE_DECRYPTING, - LE_AUDIO_BROADCAST_SINK_ENC_STATE_BAD_CODE - }) - @Retention(RetentionPolicy.SOURCE) - public @interface LeAudioBroadcastSinkEncryptionState {} - - /** - * Indicates that the Broadcast Sink is synchronized with an unencrypted audio stream. - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SINK_ENC_STATE_NOT_ENCRYPTED = 0; - - /** - * Indicates that the Broadcast Sink needs a Broadcast Code to synchronize with the audio - * stream. - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SINK_ENC_STATE_CODE_REQUIRED = 1; - - /** - * Indicates that the Broadcast Sink is synchronized with an encrypted audio stream. - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SINK_ENC_STATE_DECRYPTING = 2; - - /** - * Indicates that the Broadcast Sink is unable to decrypt an audio stream due to an incorrect - * Broadcast Code - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SINK_ENC_STATE_BAD_CODE = 3; - - /** - * Indicates that the Broadcast Sink encryption state is invalid. - * - * @hide - */ - public static final int LE_AUDIO_BROADCAST_SINK_ENC_STATE_INVALID = 0xFF; - - /** - * Represents an invalid LE Audio Broadcast Source ID - * - * @hide - */ - public static final byte LE_AUDIO_BROADCAST_SINK_INVALID_SOURCE_ID = (byte) 0x00; - - /** - * Represents an invalid Broadcast ID of a Broadcast Source - * - * @hide - */ - public static final int INVALID_BROADCAST_ID = 0xFFFFFF; - - private byte mSourceId; - private @LeAudioBroadcastSourceAddressType int mSourceAddressType; - private BluetoothDevice mSourceDevice; - private byte mSourceAdvSid; - private int mBroadcastId; - private @LeAudioBroadcastSinkPaSyncState int mPaSyncState; - private @LeAudioBroadcastSinkEncryptionState int mEncryptionStatus; - private @LeAudioBroadcastSinkAudioSyncState int mAudioSyncState; - private byte[] mBadBroadcastCode; - private byte mNumSubGroups; - private Map<Integer, Integer> mSubgroupBisSyncState = new HashMap<Integer, Integer>(); - private Map<Integer, byte[]> mSubgroupMetadata = new HashMap<Integer, byte[]>(); - - private String mBroadcastCode; - private static final int BIS_NO_PREF = 0xFFFFFFFF; - private static final int BROADCAST_CODE_SIZE = 16; - - /** - * Constructor to create an Empty object of {@link BluetoothLeBroadcastSourceInfo } with the - * given Source Id. - * - * <p>This is mainly used to represent the Empty Broadcast Source entries - * - * @param sourceId Source Id for this Broadcast Source info object - * @hide - */ - public BluetoothLeBroadcastSourceInfo(byte sourceId) { - mSourceId = sourceId; - mSourceAddressType = LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_INVALID; - mSourceDevice = null; - mSourceAdvSid = (byte) 0x00; - mBroadcastId = INVALID_BROADCAST_ID; - mPaSyncState = LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_INVALID; - mAudioSyncState = LE_AUDIO_BROADCAST_SINK_AUDIO_SYNC_STATE_INVALID; - mEncryptionStatus = LE_AUDIO_BROADCAST_SINK_ENC_STATE_INVALID; - mBadBroadcastCode = null; - mNumSubGroups = 0; - mBroadcastCode = null; - } - - /*package*/ BluetoothLeBroadcastSourceInfo( - byte sourceId, - @LeAudioBroadcastSourceAddressType int addressType, - @NonNull BluetoothDevice device, - byte advSid, - int broadcastId, - @LeAudioBroadcastSinkPaSyncState int paSyncstate, - @LeAudioBroadcastSinkEncryptionState int encryptionStatus, - @LeAudioBroadcastSinkAudioSyncState int audioSyncstate, - @Nullable byte[] badCode, - byte numSubGroups, - @NonNull Map<Integer, Integer> bisSyncState, - @Nullable Map<Integer, byte[]> subgroupMetadata, - @NonNull String broadcastCode) { - mSourceId = sourceId; - mSourceAddressType = addressType; - mSourceDevice = device; - mSourceAdvSid = advSid; - mBroadcastId = broadcastId; - mPaSyncState = paSyncstate; - mEncryptionStatus = encryptionStatus; - mAudioSyncState = audioSyncstate; - - if (badCode != null && badCode.length != 0) { - mBadBroadcastCode = new byte[badCode.length]; - System.arraycopy(badCode, 0, mBadBroadcastCode, 0, badCode.length); - } - mNumSubGroups = numSubGroups; - mSubgroupBisSyncState = new HashMap<Integer, Integer>(bisSyncState); - mSubgroupMetadata = new HashMap<Integer, byte[]>(subgroupMetadata); - mBroadcastCode = broadcastCode; - } - - @Override - public boolean equals(Object o) { - if (o instanceof BluetoothLeBroadcastSourceInfo) { - BluetoothLeBroadcastSourceInfo other = (BluetoothLeBroadcastSourceInfo) o; - return (other.mSourceId == mSourceId - && other.mSourceAddressType == mSourceAddressType - && other.mSourceDevice == mSourceDevice - && other.mSourceAdvSid == mSourceAdvSid - && other.mBroadcastId == mBroadcastId - && other.mPaSyncState == mPaSyncState - && other.mEncryptionStatus == mEncryptionStatus - && other.mAudioSyncState == mAudioSyncState - && Arrays.equals(other.mBadBroadcastCode, mBadBroadcastCode) - && other.mNumSubGroups == mNumSubGroups - && mSubgroupBisSyncState.equals(other.mSubgroupBisSyncState) - && mSubgroupMetadata.equals(other.mSubgroupMetadata) - && other.mBroadcastCode == mBroadcastCode); - } - return false; - } - - /** - * Checks if an instance of {@link BluetoothLeBroadcastSourceInfo} is empty. - * - * @hide - */ - public boolean isEmpty() { - boolean ret = false; - if (mSourceAddressType == LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_INVALID - && mSourceDevice == null - && mSourceAdvSid == (byte) 0 - && mPaSyncState == LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_INVALID - && mEncryptionStatus == LE_AUDIO_BROADCAST_SINK_ENC_STATE_INVALID - && mAudioSyncState == LE_AUDIO_BROADCAST_SINK_AUDIO_SYNC_STATE_INVALID - && mBadBroadcastCode == null - && mNumSubGroups == 0 - && mSubgroupBisSyncState.size() == 0 - && mSubgroupMetadata.size() == 0 - && mBroadcastCode == null) { - ret = true; - } - return ret; - } - - /** - * Compares an instance of {@link BluetoothLeBroadcastSourceInfo} with the provided instance. - * - * @hide - */ - public boolean matches(BluetoothLeBroadcastSourceInfo srcInfo) { - boolean ret = false; - if (srcInfo == null) { - ret = false; - } else { - if (mSourceDevice == null) { - if (mSourceAdvSid == srcInfo.getAdvertisingSid() - && mSourceAddressType == srcInfo.getAdvAddressType()) { - ret = true; - } - } else { - if (mSourceDevice.equals(srcInfo.getSourceDevice()) - && mSourceAdvSid == srcInfo.getAdvertisingSid() - && mSourceAddressType == srcInfo.getAdvAddressType() - && mBroadcastId == srcInfo.getBroadcastId()) { - ret = true; - } - } - } - return ret; - } - - @Override - public int hashCode() { - return Objects.hash( - mSourceId, - mSourceAddressType, - mSourceDevice, - mSourceAdvSid, - mBroadcastId, - mPaSyncState, - mEncryptionStatus, - mAudioSyncState, - mBadBroadcastCode, - mNumSubGroups, - mSubgroupBisSyncState, - mSubgroupMetadata, - mBroadcastCode); - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public String toString() { - return "{BluetoothLeBroadcastSourceInfo : mSourceId" - + mSourceId - + " addressType: " - + mSourceAddressType - + " sourceDevice: " - + mSourceDevice - + " mSourceAdvSid:" - + mSourceAdvSid - + " mBroadcastId:" - + mBroadcastId - + " mPaSyncState:" - + mPaSyncState - + " mEncryptionStatus:" - + mEncryptionStatus - + " mAudioSyncState:" - + mAudioSyncState - + " mBadBroadcastCode:" - + mBadBroadcastCode - + " mNumSubGroups:" - + mNumSubGroups - + " mSubgroupBisSyncState:" - + mSubgroupBisSyncState - + " mSubgroupMetadata:" - + mSubgroupMetadata - + " mBroadcastCode:" - + mBroadcastCode - + "}"; - } - - /** - * Get the Source Id - * - * @return byte representing the Source Id, {@link - * #LE_AUDIO_BROADCAST_ASSISTANT_INVALID_SOURCE_ID} if invalid - * @hide - */ - public byte getSourceId() { - return mSourceId; - } - - /** - * Set the Source Id - * - * @param sourceId source Id - * @hide - */ - public void setSourceId(byte sourceId) { - mSourceId = sourceId; - } - - /** - * Set the Broadcast Source device - * - * @param sourceDevice the Broadcast Source BluetoothDevice - * @hide - */ - public void setSourceDevice(@NonNull BluetoothDevice sourceDevice) { - mSourceDevice = sourceDevice; - } - - /** - * Get the Broadcast Source BluetoothDevice - * - * @return Broadcast Source BluetoothDevice - * @hide - */ - public @NonNull BluetoothDevice getSourceDevice() { - return mSourceDevice; - } - - /** - * Set the address type of the Broadcast Source advertisements - * - * @hide - */ - public void setAdvAddressType(@LeAudioBroadcastSourceAddressType int addressType) { - mSourceAddressType = addressType; - } - - /** - * Get the address type used by advertisements from the Broadcast Source. - * BluetoothLeBroadcastSourceInfo Object - * - * @hide - */ - @LeAudioBroadcastSourceAddressType - public int getAdvAddressType() { - return mSourceAddressType; - } - - /** - * Set the advertising SID of the Broadcast Source advertisement. - * - * @param advSid advertising SID of the Broadcast Source - * @hide - */ - public void setAdvertisingSid(byte advSid) { - mSourceAdvSid = advSid; - } - - /** - * Get the advertising SID of the Broadcast Source advertisement. - * - * @return advertising SID of the Broadcast Source - * @hide - */ - public byte getAdvertisingSid() { - return mSourceAdvSid; - } - - /** - * Get the Broadcast ID of the Broadcast Source. - * - * @return broadcast ID - * @hide - */ - public int getBroadcastId() { - return mBroadcastId; - } - - /** - * Set the Periodic Advertising (PA) Sync State. - * - * @hide - */ - /*package*/ void setPaSyncState(@LeAudioBroadcastSinkPaSyncState int paSyncState) { - mPaSyncState = paSyncState; - } - - /** - * Get the Periodic Advertising (PA) Sync State - * - * @hide - */ - public @LeAudioBroadcastSinkPaSyncState int getMetadataSyncState() { - return mPaSyncState; - } - - /** - * Set the audio sync state - * - * @hide - */ - /*package*/ void setAudioSyncState(@LeAudioBroadcastSinkAudioSyncState int audioSyncState) { - mAudioSyncState = audioSyncState; - } - - /** - * Get the audio sync state - * - * @hide - */ - public @LeAudioBroadcastSinkAudioSyncState int getAudioSyncState() { - return mAudioSyncState; - } - - /** - * Set the encryption status - * - * @hide - */ - /*package*/ void setEncryptionStatus( - @LeAudioBroadcastSinkEncryptionState int encryptionStatus) { - mEncryptionStatus = encryptionStatus; - } - - /** - * Get the encryption status - * - * @hide - */ - public @LeAudioBroadcastSinkEncryptionState int getEncryptionStatus() { - return mEncryptionStatus; - } - - /** - * Get the incorrect broadcast code that the Scan delegator used to decrypt the Broadcast Audio - * Stream and failed. - * - * <p>This code is valid only if {@link #getEncryptionStatus} returns {@link - * #LE_AUDIO_BROADCAST_SINK_ENC_STATE_BAD_CODE} - * - * @return byte array containing bad broadcast value, null if the current encryption status is - * not {@link #LE_AUDIO_BROADCAST_SINK_ENC_STATE_BAD_CODE} - * @hide - */ - public @Nullable byte[] getBadBroadcastCode() { - return mBadBroadcastCode; - } - - /** - * Get the number of subgroups. - * - * @return number of subgroups - * @hide - */ - public byte getNumberOfSubGroups() { - return mNumSubGroups; - } - - public @NonNull Map<Integer, Integer> getSubgroupBisSyncState() { - return mSubgroupBisSyncState; - } - - public void setSubgroupBisSyncState(@NonNull Map<Integer, Integer> bisSyncState) { - mSubgroupBisSyncState = new HashMap<Integer, Integer>(bisSyncState); - } - - /*package*/ void setBroadcastCode(@NonNull String broadcastCode) { - mBroadcastCode = broadcastCode; - } - - /** - * Get the broadcast code - * - * @return - * @hide - */ - public @NonNull String getBroadcastCode() { - return mBroadcastCode; - } - - /** - * Set the broadcast ID - * - * @param broadcastId broadcast ID of the Broadcast Source - * @hide - */ - public void setBroadcastId(int broadcastId) { - mBroadcastId = broadcastId; - } - - private void writeSubgroupBisSyncStateToParcel( - @NonNull Parcel dest, @NonNull Map<Integer, Integer> subgroupBisSyncState) { - dest.writeInt(subgroupBisSyncState.size()); - for (Map.Entry<Integer, Integer> entry : subgroupBisSyncState.entrySet()) { - dest.writeInt(entry.getKey()); - dest.writeInt(entry.getValue()); - } - } - - private static void readSubgroupBisSyncStateFromParcel( - @NonNull Parcel in, @NonNull Map<Integer, Integer> subgroupBisSyncState) { - int size = in.readInt(); - - for (int i = 0; i < size; i++) { - Integer key = in.readInt(); - Integer value = in.readInt(); - subgroupBisSyncState.put(key, value); - } - } - - private void writeSubgroupMetadataToParcel( - @NonNull Parcel dest, @Nullable Map<Integer, byte[]> subgroupMetadata) { - if (subgroupMetadata == null) { - dest.writeInt(0); - return; - } - - dest.writeInt(subgroupMetadata.size()); - for (Map.Entry<Integer, byte[]> entry : subgroupMetadata.entrySet()) { - dest.writeInt(entry.getKey()); - byte[] metadata = entry.getValue(); - if (metadata != null) { - dest.writeInt(metadata.length); - dest.writeByteArray(metadata); - } - } - } - - private static void readSubgroupMetadataFromParcel( - @NonNull Parcel in, @NonNull Map<Integer, byte[]> subgroupMetadata) { - int size = in.readInt(); - - for (int i = 0; i < size; i++) { - Integer key = in.readInt(); - Integer metaDataLen = in.readInt(); - byte[] metadata = null; - if (metaDataLen != 0) { - metadata = new byte[metaDataLen]; - in.readByteArray(metadata); - } - subgroupMetadata.put(key, metadata); - } - } - - public static final @NonNull Parcelable.Creator<BluetoothLeBroadcastSourceInfo> CREATOR = - new Parcelable.Creator<BluetoothLeBroadcastSourceInfo>() { - public @NonNull BluetoothLeBroadcastSourceInfo createFromParcel( - @NonNull Parcel in) { - final byte sourceId = in.readByte(); - final int sourceAddressType = in.readInt(); - final BluetoothDevice sourceDevice = - in.readTypedObject(BluetoothDevice.CREATOR); - final byte sourceAdvSid = in.readByte(); - final int broadcastId = in.readInt(); - final int paSyncState = in.readInt(); - final int audioSyncState = in.readInt(); - final int encryptionStatus = in.readInt(); - final int badBroadcastLen = in.readInt(); - byte[] badBroadcastCode = null; - - if (badBroadcastLen > 0) { - badBroadcastCode = new byte[badBroadcastLen]; - in.readByteArray(badBroadcastCode); - } - final byte numSubGroups = in.readByte(); - final String broadcastCode = in.readString(); - Map<Integer, Integer> subgroupBisSyncState = new HashMap<Integer, Integer>(); - readSubgroupBisSyncStateFromParcel(in, subgroupBisSyncState); - Map<Integer, byte[]> subgroupMetadata = new HashMap<Integer, byte[]>(); - readSubgroupMetadataFromParcel(in, subgroupMetadata); - - BluetoothLeBroadcastSourceInfo srcInfo = - new BluetoothLeBroadcastSourceInfo( - sourceId, - sourceAddressType, - sourceDevice, - sourceAdvSid, - broadcastId, - paSyncState, - encryptionStatus, - audioSyncState, - badBroadcastCode, - numSubGroups, - subgroupBisSyncState, - subgroupMetadata, - broadcastCode); - return srcInfo; - } - - public @NonNull BluetoothLeBroadcastSourceInfo[] newArray(int size) { - return new BluetoothLeBroadcastSourceInfo[size]; - } - }; - - @Override - public void writeToParcel(@NonNull Parcel out, int flags) { - out.writeByte(mSourceId); - out.writeInt(mSourceAddressType); - out.writeTypedObject(mSourceDevice, 0); - out.writeByte(mSourceAdvSid); - out.writeInt(mBroadcastId); - out.writeInt(mPaSyncState); - out.writeInt(mAudioSyncState); - out.writeInt(mEncryptionStatus); - - if (mBadBroadcastCode != null) { - out.writeInt(mBadBroadcastCode.length); - out.writeByteArray(mBadBroadcastCode); - } else { - // zero indicates that there is no "bad broadcast code" - out.writeInt(0); - } - out.writeByte(mNumSubGroups); - out.writeString(mBroadcastCode); - writeSubgroupBisSyncStateToParcel(out, mSubgroupBisSyncState); - writeSubgroupMetadataToParcel(out, mSubgroupMetadata); - } - - private static void log(@NonNull String msg) { - if (DBG) { - Log.d(TAG, msg); - } - } -} -; diff --git a/core/java/android/bluetooth/BluetoothLeCall.java b/core/java/android/bluetooth/BluetoothLeCall.java deleted file mode 100644 index fb7789db25c7..000000000000 --- a/core/java/android/bluetooth/BluetoothLeCall.java +++ /dev/null @@ -1,285 +0,0 @@ -/* - * Copyright 2021 HIMSA II K/S - www.himsa.com. - * Represented by EHIMA - www.ehima.com - * - * 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.bluetooth; - -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.os.Parcel; -import android.os.Parcelable; -import android.os.ParcelUuid; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.Objects; -import java.util.UUID; - -/** - * Representation of Call - * - * @hide - */ -public final class BluetoothLeCall implements Parcelable { - - /** @hide */ - @IntDef(prefix = "STATE_", value = { - STATE_INCOMING, - STATE_DIALING, - STATE_ALERTING, - STATE_ACTIVE, - STATE_LOCALLY_HELD, - STATE_REMOTELY_HELD, - STATE_LOCALLY_AND_REMOTELY_HELD - }) - @Retention(RetentionPolicy.SOURCE) - public @interface State { - } - - /** - * A remote party is calling (incoming call). - * - * @hide - */ - public static final int STATE_INCOMING = 0x00; - - /** - * The process to call the remote party has started but the remote party is not - * being alerted (outgoing call). - * - * @hide - */ - public static final int STATE_DIALING = 0x01; - - /** - * A remote party is being alerted (outgoing call). - * - * @hide - */ - public static final int STATE_ALERTING = 0x02; - - /** - * The call is in an active conversation. - * - * @hide - */ - public static final int STATE_ACTIVE = 0x03; - - /** - * The call is connected but held locally. “Locally Held” implies that either - * the server or the client can affect the state. - * - * @hide - */ - public static final int STATE_LOCALLY_HELD = 0x04; - - /** - * The call is connected but held remotely. “Remotely Held” means that the state - * is controlled by the remote party of a call. - * - * @hide - */ - public static final int STATE_REMOTELY_HELD = 0x05; - - /** - * The call is connected but held both locally and remotely. - * - * @hide - */ - public static final int STATE_LOCALLY_AND_REMOTELY_HELD = 0x06; - - /** - * Whether the call direction is outgoing. - * - * @hide - */ - public static final int FLAG_OUTGOING_CALL = 0x00000001; - - /** - * Whether the call URI and Friendly Name are withheld by server. - * - * @hide - */ - public static final int FLAG_WITHHELD_BY_SERVER = 0x00000002; - - /** - * Whether the call URI and Friendly Name are withheld by network. - * - * @hide - */ - public static final int FLAG_WITHHELD_BY_NETWORK = 0x00000004; - - /** Unique UUID that identifies this call */ - private UUID mUuid; - - /** Remote Caller URI */ - private String mUri; - - /** Caller friendly name */ - private String mFriendlyName; - - /** Call state */ - private @State int mState; - - /** Call flags */ - private int mCallFlags; - - /** @hide */ - public BluetoothLeCall(@NonNull BluetoothLeCall that) { - mUuid = new UUID(that.getUuid().getMostSignificantBits(), - that.getUuid().getLeastSignificantBits()); - mUri = that.mUri; - mFriendlyName = that.mFriendlyName; - mState = that.mState; - mCallFlags = that.mCallFlags; - } - - /** @hide */ - public BluetoothLeCall(@NonNull UUID uuid, @NonNull String uri, @NonNull String friendlyName, - @State int state, int callFlags) { - mUuid = uuid; - mUri = uri; - mFriendlyName = friendlyName; - mState = state; - mCallFlags = callFlags; - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - BluetoothLeCall that = (BluetoothLeCall) o; - return mUuid.equals(that.mUuid) && mUri.equals(that.mUri) - && mFriendlyName.equals(that.mFriendlyName) && mState == that.mState - && mCallFlags == that.mCallFlags; - } - - @Override - public int hashCode() { - return Objects.hash(mUuid, mUri, mFriendlyName, mState, mCallFlags); - } - - /** - * Returns a string representation of this BluetoothLeCall. - * - * <p> - * Currently this is the UUID. - * - * @return string representation of this BluetoothLeCall - */ - @Override - public String toString() { - return mUuid.toString(); - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(@NonNull Parcel out, int flags) { - out.writeParcelable(new ParcelUuid(mUuid), 0); - out.writeString(mUri); - out.writeString(mFriendlyName); - out.writeInt(mState); - out.writeInt(mCallFlags); - } - - public static final @android.annotation.NonNull Parcelable.Creator<BluetoothLeCall> CREATOR = - new Parcelable.Creator<BluetoothLeCall>() { - public BluetoothLeCall createFromParcel(Parcel in) { - return new BluetoothLeCall(in); - } - - public BluetoothLeCall[] newArray(int size) { - return new BluetoothLeCall[size]; - } - }; - - private BluetoothLeCall(Parcel in) { - mUuid = ((ParcelUuid) in.readParcelable(null)).getUuid(); - mUri = in.readString(); - mFriendlyName = in.readString(); - mState = in.readInt(); - mCallFlags = in.readInt(); - } - - /** - * Returns an UUID of this BluetoothLeCall. - * - * <p> - * An UUID is unique identifier of a BluetoothLeCall. - * - * @return UUID of this BluetoothLeCall - * @hide - */ - public @NonNull UUID getUuid() { - return mUuid; - } - - /** - * Returns a URI of the remote party of this BluetoothLeCall. - * - * @return string representation of this BluetoothLeCall - * @hide - */ - public @NonNull String getUri() { - return mUri; - } - - /** - * Returns a friendly name of the call. - * - * @return friendly name representation of this BluetoothLeCall - * @hide - */ - public @NonNull String getFriendlyName() { - return mFriendlyName; - } - - /** - * Returns the call state. - * - * @return the state of this BluetoothLeCall - * @hide - */ - public @State int getState() { - return mState; - } - - /** - * Returns the call flags. - * - * @return call flags - * @hide - */ - public int getCallFlags() { - return mCallFlags; - } - - /** - * Whether the call direction is incoming. - * - * @return true if incoming call, false otherwise - * @hide - */ - public boolean isIncomingCall() { - return (mCallFlags & FLAG_OUTGOING_CALL) == 0; - } -} diff --git a/core/java/android/bluetooth/BluetoothLeCallControl.java b/core/java/android/bluetooth/BluetoothLeCallControl.java deleted file mode 100644 index fb080c9ec3e3..000000000000 --- a/core/java/android/bluetooth/BluetoothLeCallControl.java +++ /dev/null @@ -1,899 +0,0 @@ -/* - * Copyright 2019 HIMSA II K/S - www.himsa.com. - * Represented by EHIMA - www.ehima.com - * - * 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.bluetooth; - -import android.Manifest; -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.RequiresPermission; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.content.ComponentName; -import android.content.Context; -import android.os.Binder; -import android.os.Handler; -import android.os.IBinder; -import android.os.Looper; -import android.os.Message; -import android.os.ParcelUuid; -import android.os.RemoteException; -import android.util.Log; -import android.annotation.SuppressLint; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.Executor; - -/** - * This class provides the APIs to control the Call Control profile. - * - * <p> - * This class provides Bluetooth Telephone Bearer Service functionality, - * allowing applications to expose a GATT Service based interface to control the - * state of the calls by remote devices such as LE audio devices. - * - * <p> - * BluetoothLeCallControl is a proxy object for controlling the Bluetooth Telephone Bearer - * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get the - * BluetoothLeCallControl proxy object. - * - * @hide - */ -public final class BluetoothLeCallControl implements BluetoothProfile { - private static final String TAG = "BluetoothLeCallControl"; - private static final boolean DBG = true; - private static final boolean VDBG = false; - - /** @hide */ - @IntDef(prefix = "RESULT_", value = { - RESULT_SUCCESS, - RESULT_ERROR_UNKNOWN_CALL_ID, - RESULT_ERROR_INVALID_URI, - RESULT_ERROR_APPLICATION - }) - @Retention(RetentionPolicy.SOURCE) - public @interface Result { - } - - /** - * Opcode write was successful. - * - * @hide - */ - public static final int RESULT_SUCCESS = 0; - - /** - * Unknown call Id has been used in the operation. - * - * @hide - */ - public static final int RESULT_ERROR_UNKNOWN_CALL_ID = 1; - - /** - * The URI provided in {@link Callback#onPlaceCallRequest} is invalid. - * - * @hide - */ - public static final int RESULT_ERROR_INVALID_URI = 2; - - /** - * Application internal error. - * - * @hide - */ - public static final int RESULT_ERROR_APPLICATION = 3; - - /** @hide */ - @IntDef(prefix = "TERMINATION_REASON_", value = { - TERMINATION_REASON_INVALID_URI, - TERMINATION_REASON_FAIL, - TERMINATION_REASON_REMOTE_HANGUP, - TERMINATION_REASON_SERVER_HANGUP, - TERMINATION_REASON_LINE_BUSY, - TERMINATION_REASON_NETWORK_CONGESTION, - TERMINATION_REASON_CLIENT_HANGUP, - TERMINATION_REASON_NO_SERVICE, - TERMINATION_REASON_NO_ANSWER - }) - @Retention(RetentionPolicy.SOURCE) - public @interface TerminationReason { - } - - /** - * Remote Caller ID value used to place a call was formed improperly. - * - * @hide - */ - public static final int TERMINATION_REASON_INVALID_URI = 0x00; - - /** - * Call fail. - * - * @hide - */ - public static final int TERMINATION_REASON_FAIL = 0x01; - - /** - * Remote party ended call. - * - * @hide - */ - public static final int TERMINATION_REASON_REMOTE_HANGUP = 0x02; - - /** - * Call ended from the server. - * - * @hide - */ - public static final int TERMINATION_REASON_SERVER_HANGUP = 0x03; - - /** - * Line busy. - * - * @hide - */ - public static final int TERMINATION_REASON_LINE_BUSY = 0x04; - - /** - * Network congestion. - * - * @hide - */ - public static final int TERMINATION_REASON_NETWORK_CONGESTION = 0x05; - - /** - * Client terminated. - * - * @hide - */ - public static final int TERMINATION_REASON_CLIENT_HANGUP = 0x06; - - /** - * No service. - * - * @hide - */ - public static final int TERMINATION_REASON_NO_SERVICE = 0x07; - - /** - * No answer. - * - * @hide - */ - public static final int TERMINATION_REASON_NO_ANSWER = 0x08; - - /* - * Flag indicating support for hold/unhold call feature. - * - * @hide - */ - public static final int CAPABILITY_HOLD_CALL = 0x00000001; - - /** - * Flag indicating support for joining calls feature. - * - * @hide - */ - public static final int CAPABILITY_JOIN_CALLS = 0x00000002; - - private static final int MESSAGE_TBS_SERVICE_CONNECTED = 102; - private static final int MESSAGE_TBS_SERVICE_DISCONNECTED = 103; - - private static final int REG_TIMEOUT = 10000; - - /** - * The template class is used to call callback functions on events from the TBS - * server. Callback functions are wrapped in this class and registered to the - * Android system during app registration. - * - * @hide - */ - public abstract static class Callback { - - private static final String TAG = "BluetoothLeCallControl.Callback"; - - /** - * Called when a remote client requested to accept the call. - * - * <p> - * An application must call {@link BluetoothLeCallControl#requestResult} to complete the - * request. - * - * @param requestId The Id of the request - * @param callId The call Id requested to be accepted - * @hide - */ - public abstract void onAcceptCall(int requestId, @NonNull UUID callId); - - /** - * A remote client has requested to terminate the call. - * - * <p> - * An application must call {@link BluetoothLeCallControl#requestResult} to complete the - * request. - * - * @param requestId The Id of the request - * @param callId The call Id requested to terminate - * @hide - */ - public abstract void onTerminateCall(int requestId, @NonNull UUID callId); - - /** - * A remote client has requested to hold the call. - * - * <p> - * An application must call {@link BluetoothLeCallControl#requestResult} to complete the - * request. - * - * @param requestId The Id of the request - * @param callId The call Id requested to be put on hold - * @hide - */ - public void onHoldCall(int requestId, @NonNull UUID callId) { - Log.e(TAG, "onHoldCall: unimplemented, however CAPABILITY_HOLD_CALL is set!"); - } - - /** - * A remote client has requested to unhold the call. - * - * <p> - * An application must call {@link BluetoothLeCallControl#requestResult} to complete the - * request. - * - * @param requestId The Id of the request - * @param callId The call Id requested to unhold - * @hide - */ - public void onUnholdCall(int requestId, @NonNull UUID callId) { - Log.e(TAG, "onUnholdCall: unimplemented, however CAPABILITY_HOLD_CALL is set!"); - } - - /** - * A remote client has requested to place a call. - * - * <p> - * An application must call {@link BluetoothLeCallControl#requestResult} to complete the - * request. - * - * @param requestId The Id of the request - * @param callId The Id to be assigned for the new call - * @param uri The caller URI requested - * @hide - */ - public abstract void onPlaceCall(int requestId, @NonNull UUID callId, @NonNull String uri); - - /** - * A remote client has requested to join the calls. - * - * <p> - * An application must call {@link BluetoothLeCallControl#requestResult} to complete the - * request. - * - * @param requestId The Id of the request - * @param callIds The call Id list requested to join - * @hide - */ - public void onJoinCalls(int requestId, @NonNull List<UUID> callIds) { - Log.e(TAG, "onJoinCalls: unimplemented, however CAPABILITY_JOIN_CALLS is set!"); - } - } - - private class CallbackWrapper extends IBluetoothLeCallControlCallback.Stub { - - private final Executor mExecutor; - private final Callback mCallback; - - CallbackWrapper(Executor executor, Callback callback) { - mExecutor = executor; - mCallback = callback; - } - - @Override - public void onBearerRegistered(int ccid) { - if (mCallback != null) { - mCcid = ccid; - } else { - // registration timeout - Log.e(TAG, "onBearerRegistered: mCallback is null"); - } - } - - @Override - public void onAcceptCall(int requestId, ParcelUuid uuid) { - final long identityToken = Binder.clearCallingIdentity(); - try { - mExecutor.execute(() -> mCallback.onAcceptCall(requestId, uuid.getUuid())); - } finally { - Binder.restoreCallingIdentity(identityToken); - } - } - - @Override - public void onTerminateCall(int requestId, ParcelUuid uuid) { - final long identityToken = Binder.clearCallingIdentity(); - try { - mExecutor.execute(() -> mCallback.onTerminateCall(requestId, uuid.getUuid())); - } finally { - Binder.restoreCallingIdentity(identityToken); - } - } - - @Override - public void onHoldCall(int requestId, ParcelUuid uuid) { - final long identityToken = Binder.clearCallingIdentity(); - try { - mExecutor.execute(() -> mCallback.onHoldCall(requestId, uuid.getUuid())); - } finally { - Binder.restoreCallingIdentity(identityToken); - } - } - - @Override - public void onUnholdCall(int requestId, ParcelUuid uuid) { - final long identityToken = Binder.clearCallingIdentity(); - try { - mExecutor.execute(() -> mCallback.onUnholdCall(requestId, uuid.getUuid())); - } finally { - Binder.restoreCallingIdentity(identityToken); - } - } - - @Override - public void onPlaceCall(int requestId, ParcelUuid uuid, String uri) { - final long identityToken = Binder.clearCallingIdentity(); - try { - mExecutor.execute(() -> mCallback.onPlaceCall(requestId, uuid.getUuid(), uri)); - } finally { - Binder.restoreCallingIdentity(identityToken); - } - } - - @Override - public void onJoinCalls(int requestId, List<ParcelUuid> parcelUuids) { - List<UUID> uuids = new ArrayList<>(); - for (ParcelUuid parcelUuid : parcelUuids) { - uuids.add(parcelUuid.getUuid()); - } - - final long identityToken = Binder.clearCallingIdentity(); - try { - mExecutor.execute(() -> mCallback.onJoinCalls(requestId, uuids)); - } finally { - Binder.restoreCallingIdentity(identityToken); - } - } - }; - - private Context mContext; - private ServiceListener mServiceListener; - private volatile IBluetoothLeCallControl mService; - private BluetoothAdapter mAdapter; - private int mCcid = 0; - private String mToken; - private Callback mCallback = null; - - private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback = - new IBluetoothStateChangeCallback.Stub() { - public void onBluetoothStateChange(boolean up) { - if (DBG) - Log.d(TAG, "onBluetoothStateChange: up=" + up); - if (!up) { - doUnbind(); - } else { - doBind(); - } - } - }; - - /** - * Create a BluetoothLeCallControl proxy object for interacting with the local Bluetooth - * telephone bearer service. - */ - /* package */ BluetoothLeCallControl(Context context, ServiceListener listener) { - mContext = context; - mAdapter = BluetoothAdapter.getDefaultAdapter(); - mServiceListener = listener; - - IBluetoothManager mgr = mAdapter.getBluetoothManager(); - if (mgr != null) { - try { - mgr.registerStateChangeCallback(mBluetoothStateChangeCallback); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - - doBind(); - } - - private boolean doBind() { - synchronized (mConnection) { - if (mService == null) { - if (VDBG) - Log.d(TAG, "Binding service..."); - try { - return mAdapter.getBluetoothManager(). - bindBluetoothProfileService(BluetoothProfile.LE_CALL_CONTROL, - mConnection); - } catch (RemoteException e) { - Log.e(TAG, "Unable to bind TelephoneBearerService", e); - } - } - } - return false; - } - - private void doUnbind() { - synchronized (mConnection) { - if (mService != null) { - if (VDBG) - Log.d(TAG, "Unbinding service..."); - try { - mAdapter.getBluetoothManager(). - unbindBluetoothProfileService(BluetoothProfile.LE_CALL_CONTROL, - mConnection); - } catch (RemoteException e) { - Log.e(TAG, "Unable to unbind TelephoneBearerService", e); - } finally { - mService = null; - } - } - } - } - - /* package */ void close() { - if (VDBG) - log("close()"); - unregisterBearer(); - - IBluetoothManager mgr = mAdapter.getBluetoothManager(); - if (mgr != null) { - try { - mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback); - } catch (RemoteException re) { - Log.e(TAG, "", re); - } - } - mServiceListener = null; - doUnbind(); - } - - private IBluetoothLeCallControl getService() { - return mService; - } - - /** - * Not supported - * - * @throws UnsupportedOperationException - */ - @Override - public int getConnectionState(@Nullable BluetoothDevice device) { - throw new UnsupportedOperationException("not supported"); - } - - /** - * Not supported - * - * @throws UnsupportedOperationException - */ - @Override - public @NonNull List<BluetoothDevice> getConnectedDevices() { - throw new UnsupportedOperationException("not supported"); - } - - /** - * Not supported - * - * @throws UnsupportedOperationException - */ - @Override - public @NonNull List<BluetoothDevice> getDevicesMatchingConnectionStates( - @NonNull int[] states) { - throw new UnsupportedOperationException("not supported"); - } - - /** - * Register Telephone Bearer exposing the interface that allows remote devices - * to track and control the call states. - * - * <p> - * This is an asynchronous call. The callback is used to notify success or - * failure if the function returns true. - * - * <p> - * Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * - * <!-- The UCI is a String identifier of the telephone bearer as defined at - * https://www.bluetooth.com/specifications/assigned-numbers/uniform-caller-identifiers - * (login required). --> - * - * <!-- The examples of common URI schemes can be found in - * https://iana.org/assignments/uri-schemes/uri-schemes.xhtml --> - * - * <!-- The Technology is an integer value. The possible values are defined at - * https://www.bluetooth.com/specifications/assigned-numbers (login required). - * --> - * - * @param uci Bearer Unique Client Identifier - * @param uriSchemes URI Schemes supported list - * @param capabilities bearer capabilities - * @param provider Network provider name - * @param technology Network technology - * @param executor {@link Executor} object on which callback will be - * executed. The Executor object is required. - * @param callback {@link Callback} object to which callback messages will - * be sent. The Callback object is required. - * @return true on success, false otherwise - * @hide - */ - @SuppressLint("ExecutorRegistration") - @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) - public boolean registerBearer(@Nullable String uci, - @NonNull List<String> uriSchemes, int capabilities, - @NonNull String provider, int technology, - @NonNull Executor executor, @NonNull Callback callback) { - if (DBG) { - Log.d(TAG, "registerBearer"); - } - if (callback == null) { - throw new IllegalArgumentException("null parameter: " + callback); - } - if (mCcid != 0) { - return false; - } - - mToken = uci; - - final IBluetoothLeCallControl service = getService(); - if (service != null) { - if (mCallback != null) { - Log.e(TAG, "Bearer can be opened only once"); - return false; - } - - mCallback = callback; - try { - CallbackWrapper callbackWrapper = new CallbackWrapper(executor, callback); - service.registerBearer(mToken, callbackWrapper, uci, uriSchemes, capabilities, - provider, technology); - } catch (RemoteException e) { - Log.e(TAG, "", e); - mCallback = null; - return false; - } - - if (mCcid == 0) { - mCallback = null; - return false; - } - - return true; - } - - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - } - - return false; - } - - /** - * Unregister Telephone Bearer Service and destroy all the associated data. - * - * @hide - */ - @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) - public void unregisterBearer() { - if (DBG) { - Log.d(TAG, "unregisterBearer"); - } - if (mCcid == 0) { - return; - } - - int ccid = mCcid; - mCcid = 0; - mCallback = null; - - final IBluetoothLeCallControl service = getService(); - if (service != null) { - try { - service.unregisterBearer(mToken); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - } - } - - /** - * Get the Content Control ID (CCID) value. - * - * @return ccid Content Control ID value - * @hide - */ - @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) - public int getContentControlId() { - return mCcid; - } - - /** - * Notify about the newly added call. - * - * <p> - * This shall be called as early as possible after the call has been added. - * - * <p> - * Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * - * @param call Newly added call - * @hide - */ - @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) - public void onCallAdded(@NonNull BluetoothLeCall call) { - if (DBG) { - Log.d(TAG, "onCallAdded: call=" + call); - } - if (mCcid == 0) { - return; - } - - final IBluetoothLeCallControl service = getService(); - if (service != null) { - try { - service.callAdded(mCcid, call); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - } - } - - /** - * Notify about the removed call. - * - * <p> - * This shall be called as early as possible after the call has been removed. - * - * <p> - * Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * - * @param callId The Id of a call that has been removed - * @param reason Call termination reason - * @hide - */ - @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) - public void onCallRemoved(@NonNull UUID callId, @TerminationReason int reason) { - if (DBG) { - Log.d(TAG, "callRemoved: callId=" + callId); - } - if (mCcid == 0) { - return; - } - - final IBluetoothLeCallControl service = getService(); - if (service != null) { - try { - service.callRemoved(mCcid, new ParcelUuid(callId), reason); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - } - } - - /** - * Notify the call state change - * - * <p> - * This shall be called as early as possible after the state of the call has - * changed. - * - * <p> - * Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * - * @param callId The call Id that state has been changed - * @param state Call state - * @hide - */ - @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) - public void onCallStateChanged(@NonNull UUID callId, @BluetoothLeCall.State int state) { - if (DBG) { - Log.d(TAG, "callStateChanged: callId=" + callId + " state=" + state); - } - if (mCcid == 0) { - return; - } - - final IBluetoothLeCallControl service = getService(); - if (service != null) { - try { - service.callStateChanged(mCcid, new ParcelUuid(callId), state); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - } - } - - /** - * Provide the current calls list - * - * <p> - * This function must be invoked after registration if application has any - * calls. - * - * @param calls current calls list - * @hide - */ - @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) - public void currentCallsList(@NonNull List<BluetoothLeCall> calls) { - final IBluetoothLeCallControl service = getService(); - if (service != null) { - try { - service.currentCallsList(mCcid, calls); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - } - - /** - * Provide the network current status - * - * <p> - * This function must be invoked on change of network state. - * - * <p> - * Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * - * <!-- The Technology is an integer value. The possible values are defined at - * https://www.bluetooth.com/specifications/assigned-numbers (login required). - * --> - * - * @param provider Network provider name - * @param technology Network technology - * @hide - */ - @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) - public void networkStateChanged(@NonNull String provider, int technology) { - if (DBG) { - Log.d(TAG, "networkStateChanged: provider=" + provider + ", technology=" + technology); - } - if (mCcid == 0) { - return; - } - - final IBluetoothLeCallControl service = getService(); - if (service != null) { - try { - service.networkStateChanged(mCcid, provider, technology); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - } - } - - /** - * Send a response to a call control request to a remote device. - * - * <p> - * This function must be invoked in when a request is received by one of these - * callback methods: - * - * <ul> - * <li>{@link Callback#onAcceptCall} - * <li>{@link Callback#onTerminateCall} - * <li>{@link Callback#onHoldCall} - * <li>{@link Callback#onUnholdCall} - * <li>{@link Callback#onPlaceCall} - * <li>{@link Callback#onJoinCalls} - * </ul> - * - * @param requestId The ID of the request that was received with the callback - * @param result The result of the request to be sent to the remote devices - */ - @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) - public void requestResult(int requestId, @Result int result) { - if (DBG) { - Log.d(TAG, "requestResult: requestId=" + requestId + " result=" + result); - } - if (mCcid == 0) { - return; - } - - final IBluetoothLeCallControl service = getService(); - if (service != null) { - try { - service.requestResult(mCcid, requestId, result); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - } - } - - @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) - private static boolean isValidDevice(@Nullable BluetoothDevice device) { - return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); - } - - private static void log(String msg) { - Log.d(TAG, msg); - } - - private final IBluetoothProfileServiceConnection mConnection = - new IBluetoothProfileServiceConnection.Stub() { - @Override - public void onServiceConnected(ComponentName className, IBinder service) { - if (DBG) { - Log.d(TAG, "Proxy object connected"); - } - mService = IBluetoothLeCallControl.Stub.asInterface(Binder.allowBlocking(service)); - mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_TBS_SERVICE_CONNECTED)); - } - - @Override - public void onServiceDisconnected(ComponentName className) { - if (DBG) { - Log.d(TAG, "Proxy object disconnected"); - } - doUnbind(); - mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_TBS_SERVICE_DISCONNECTED)); - } - }; - - private final Handler mHandler = new Handler(Looper.getMainLooper()) { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MESSAGE_TBS_SERVICE_CONNECTED: { - if (mServiceListener != null) { - mServiceListener.onServiceConnected(BluetoothProfile.LE_CALL_CONTROL, - BluetoothLeCallControl.this); - } - break; - } - case MESSAGE_TBS_SERVICE_DISCONNECTED: { - if (mServiceListener != null) { - mServiceListener.onServiceDisconnected(BluetoothProfile.LE_CALL_CONTROL); - } - break; - } - } - } - }; -} diff --git a/core/java/android/bluetooth/BluetoothManager.java b/core/java/android/bluetooth/BluetoothManager.java deleted file mode 100644 index fef6f225dd3b..000000000000 --- a/core/java/android/bluetooth/BluetoothManager.java +++ /dev/null @@ -1,283 +0,0 @@ -/* - * Copyright (C) 2013 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.bluetooth; - -import android.annotation.RequiresFeature; -import android.annotation.RequiresNoPermission; -import android.annotation.RequiresPermission; -import android.annotation.SystemService; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; -import android.content.AttributionSource; -import android.content.Context; -import android.content.pm.PackageManager; -import android.os.RemoteException; -import android.util.Log; - -import java.util.ArrayList; -import java.util.List; - -/** - * High level manager used to obtain an instance of an {@link BluetoothAdapter} - * and to conduct overall Bluetooth Management. - * <p> - * Use {@link android.content.Context#getSystemService(java.lang.String)} - * with {@link Context#BLUETOOTH_SERVICE} to create an {@link BluetoothManager}, - * then call {@link #getAdapter} to obtain the {@link BluetoothAdapter}. - * </p> - * <div class="special reference"> - * <h3>Developer Guides</h3> - * <p> - * For more information about using BLUETOOTH, read the <a href= - * "{@docRoot}guide/topics/connectivity/bluetooth.html">Bluetooth</a> developer - * guide. - * </p> - * </div> - * - * @see Context#getSystemService - * @see BluetoothAdapter#getDefaultAdapter() - */ -@SystemService(Context.BLUETOOTH_SERVICE) -@RequiresFeature(PackageManager.FEATURE_BLUETOOTH) -public final class BluetoothManager { - private static final String TAG = "BluetoothManager"; - private static final boolean DBG = false; - - private final AttributionSource mAttributionSource; - private final BluetoothAdapter mAdapter; - - /** - * @hide - */ - public BluetoothManager(Context context) { - mAttributionSource = (context != null) ? context.getAttributionSource() : - AttributionSource.myAttributionSource(); - mAdapter = BluetoothAdapter.createAdapter(mAttributionSource); - } - - /** - * Get the BLUETOOTH Adapter for this device. - * - * @return the BLUETOOTH Adapter - */ - @RequiresNoPermission - public BluetoothAdapter getAdapter() { - return mAdapter; - } - - /** - * Get the current connection state of the profile to the remote device. - * - * <p>This is not specific to any application configuration but represents - * the connection state of the local Bluetooth adapter for certain profile. - * This can be used by applications like status bar which would just like - * to know the state of Bluetooth. - * - * @param device Remote bluetooth device. - * @param profile GATT or GATT_SERVER - * @return State of the profile connection. One of {@link BluetoothProfile#STATE_CONNECTED}, - * {@link BluetoothProfile#STATE_CONNECTING}, {@link BluetoothProfile#STATE_DISCONNECTED}, - * {@link BluetoothProfile#STATE_DISCONNECTING} - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public int getConnectionState(BluetoothDevice device, int profile) { - if (DBG) Log.d(TAG, "getConnectionState()"); - - List<BluetoothDevice> connectedDevices = getConnectedDevices(profile); - for (BluetoothDevice connectedDevice : connectedDevices) { - if (device.equals(connectedDevice)) { - return BluetoothProfile.STATE_CONNECTED; - } - } - - return BluetoothProfile.STATE_DISCONNECTED; - } - - /** - * Get connected devices for the specified profile. - * - * <p> Return the set of devices which are in state {@link BluetoothProfile#STATE_CONNECTED} - * - * <p>This is not specific to any application configuration but represents - * the connection state of Bluetooth for this profile. - * This can be used by applications like status bar which would just like - * to know the state of Bluetooth. - * - * @param profile GATT or GATT_SERVER - * @return List of devices. The list will be empty on error. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getConnectedDevices(int profile) { - if (DBG) Log.d(TAG, "getConnectedDevices"); - return getDevicesMatchingConnectionStates(profile, new int[] { - BluetoothProfile.STATE_CONNECTED - }); - } - - /** - * Get a list of devices that match any of the given connection - * states. - * - * <p> If none of the devices match any of the given states, - * an empty list will be returned. - * - * <p>This is not specific to any application configuration but represents - * the connection state of the local Bluetooth adapter for this profile. - * This can be used by applications like status bar which would just like - * to know the state of the local adapter. - * - * @param profile GATT or GATT_SERVER - * @param states Array of states. States can be one of {@link BluetoothProfile#STATE_CONNECTED}, - * {@link BluetoothProfile#STATE_CONNECTING}, {@link BluetoothProfile#STATE_DISCONNECTED}, - * {@link BluetoothProfile#STATE_DISCONNECTING}, - * @return List of devices. The list will be empty on error. - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getDevicesMatchingConnectionStates(int profile, int[] states) { - if (DBG) Log.d(TAG, "getDevicesMatchingConnectionStates"); - - if (profile != BluetoothProfile.GATT && profile != BluetoothProfile.GATT_SERVER) { - throw new IllegalArgumentException("Profile not supported: " + profile); - } - - List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>(); - - try { - IBluetoothManager managerService = mAdapter.getBluetoothManager(); - IBluetoothGatt iGatt = managerService.getBluetoothGatt(); - if (iGatt == null) return devices; - devices = Attributable.setAttributionSource( - iGatt.getDevicesMatchingConnectionStates(states, mAttributionSource), - mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - - return devices; - } - - /** - * Open a GATT Server - * The callback is used to deliver results to Caller, such as connection status as well - * as the results of any other GATT server operations. - * The method returns a BluetoothGattServer instance. You can use BluetoothGattServer - * to conduct GATT server operations. - * - * @param context App context - * @param callback GATT server callback handler that will receive asynchronous callbacks. - * @return BluetoothGattServer instance - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothGattServer openGattServer(Context context, - BluetoothGattServerCallback callback) { - - return (openGattServer(context, callback, BluetoothDevice.TRANSPORT_AUTO)); - } - - /** - * Open a GATT Server - * The callback is used to deliver results to Caller, such as connection status as well - * as the results of any other GATT server operations. - * The method returns a BluetoothGattServer instance. You can use BluetoothGattServer - * to conduct GATT server operations. - * - * @param context App context - * @param callback GATT server callback handler that will receive asynchronous callbacks. - * @param eatt_support idicates if server should use eatt channel for notifications. - * @return BluetoothGattServer instance - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothGattServer openGattServer(Context context, - BluetoothGattServerCallback callback, boolean eatt_support) { - return (openGattServer(context, callback, BluetoothDevice.TRANSPORT_AUTO, eatt_support)); - } - - /** - * Open a GATT Server - * The callback is used to deliver results to Caller, such as connection status as well - * as the results of any other GATT server operations. - * The method returns a BluetoothGattServer instance. You can use BluetoothGattServer - * to conduct GATT server operations. - * - * @param context App context - * @param callback GATT server callback handler that will receive asynchronous callbacks. - * @param transport preferred transport for GATT connections to remote dual-mode devices {@link - * BluetoothDevice#TRANSPORT_AUTO} or {@link BluetoothDevice#TRANSPORT_BREDR} or {@link - * BluetoothDevice#TRANSPORT_LE} - * @return BluetoothGattServer instance - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothGattServer openGattServer(Context context, - BluetoothGattServerCallback callback, int transport) { - return (openGattServer(context, callback, transport, false)); - } - - /** - * Open a GATT Server - * The callback is used to deliver results to Caller, such as connection status as well - * as the results of any other GATT server operations. - * The method returns a BluetoothGattServer instance. You can use BluetoothGattServer - * to conduct GATT server operations. - * - * @param context App context - * @param callback GATT server callback handler that will receive asynchronous callbacks. - * @param transport preferred transport for GATT connections to remote dual-mode devices {@link - * BluetoothDevice#TRANSPORT_AUTO} or {@link BluetoothDevice#TRANSPORT_BREDR} or {@link - * BluetoothDevice#TRANSPORT_LE} - * @param eatt_support idicates if server should use eatt channel for notifications. - * @return BluetoothGattServer instance - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothGattServer openGattServer(Context context, - BluetoothGattServerCallback callback, int transport, boolean eatt_support) { - if (context == null || callback == null) { - throw new IllegalArgumentException("null parameter: " + context + " " + callback); - } - - // TODO(Bluetooth) check whether platform support BLE - // Do the check here or in GattServer? - - try { - IBluetoothManager managerService = mAdapter.getBluetoothManager(); - IBluetoothGatt iGatt = managerService.getBluetoothGatt(); - if (iGatt == null) { - Log.e(TAG, "Fail to get GATT Server connection"); - return null; - } - BluetoothGattServer mGattServer = - new BluetoothGattServer(iGatt, transport, mAdapter); - Boolean regStatus = mGattServer.registerCallback(callback, eatt_support); - return regStatus ? mGattServer : null; - } catch (RemoteException e) { - Log.e(TAG, "", e); - return null; - } - } -} diff --git a/core/java/android/bluetooth/BluetoothMap.java b/core/java/android/bluetooth/BluetoothMap.java deleted file mode 100644 index 56e497262421..000000000000 --- a/core/java/android/bluetooth/BluetoothMap.java +++ /dev/null @@ -1,513 +0,0 @@ -/* - * Copyright (C) 2008 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.bluetooth; - -import static android.bluetooth.BluetoothUtils.getSyncTimeout; - -import android.Manifest; -import android.annotation.NonNull; -import android.annotation.RequiresNoPermission; -import android.annotation.RequiresPermission; -import android.annotation.SdkConstant; -import android.annotation.SdkConstant.SdkConstantType; -import android.annotation.SuppressLint; -import android.annotation.SystemApi; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.compat.annotation.UnsupportedAppUsage; -import android.content.AttributionSource; -import android.content.Context; -import android.os.Build; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.CloseGuard; -import android.util.Log; - -import com.android.modules.utils.SynchronousResultReceiver; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeoutException; - -/** - * This class provides the APIs to control the Bluetooth MAP - * Profile. - * - * @hide - */ -@SystemApi -public final class BluetoothMap implements BluetoothProfile, AutoCloseable { - - private static final String TAG = "BluetoothMap"; - private static final boolean DBG = true; - private static final boolean VDBG = false; - - private CloseGuard mCloseGuard; - - /** @hide */ - @SuppressLint("ActionValue") - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_CONNECTION_STATE_CHANGED = - "android.bluetooth.map.profile.action.CONNECTION_STATE_CHANGED"; - - /** - * There was an error trying to obtain the state - * - * @hide - */ - public static final int STATE_ERROR = -1; - - /** @hide */ - public static final int RESULT_FAILURE = 0; - /** @hide */ - public static final int RESULT_SUCCESS = 1; - /** - * Connection canceled before completion. - * - * @hide - */ - public static final int RESULT_CANCELED = 2; - - private final BluetoothAdapter mAdapter; - private final AttributionSource mAttributionSource; - private final BluetoothProfileConnector<IBluetoothMap> mProfileConnector = - new BluetoothProfileConnector(this, BluetoothProfile.MAP, - "BluetoothMap", IBluetoothMap.class.getName()) { - @Override - public IBluetoothMap getServiceInterface(IBinder service) { - return IBluetoothMap.Stub.asInterface(service); - } - }; - - /** - * Create a BluetoothMap proxy object. - */ - /* package */ BluetoothMap(Context context, ServiceListener listener, - BluetoothAdapter adapter) { - if (DBG) Log.d(TAG, "Create BluetoothMap proxy object"); - mAdapter = adapter; - mAttributionSource = adapter.getAttributionSource(); - mProfileConnector.connect(context, listener); - mCloseGuard = new CloseGuard(); - mCloseGuard.open("close"); - } - - protected void finalize() { - if (mCloseGuard != null) { - mCloseGuard.warnIfOpen(); - } - close(); - } - - /** - * Close the connection to the backing service. - * Other public functions of BluetoothMap will return default error - * results once close() has been called. Multiple invocations of close() - * are ok. - * - * @hide - */ - @SystemApi - public void close() { - if (VDBG) log("close()"); - mProfileConnector.disconnect(); - } - - private IBluetoothMap getService() { - return mProfileConnector.getService(); - } - - /** - * Get the current state of the BluetoothMap service. - * - * @return One of the STATE_ return codes, or STATE_ERROR if this proxy object is currently not - * connected to the Map service. - * - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public int getState() { - if (VDBG) log("getState()"); - final IBluetoothMap service = getService(); - final int defaultValue = BluetoothMap.STATE_ERROR; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getState(mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the currently connected remote Bluetooth device (PCE). - * - * @return The remote Bluetooth device, or null if not in connected or connecting state, or if - * this proxy object is not connected to the Map service. - * - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothDevice getClient() { - if (VDBG) log("getClient()"); - final IBluetoothMap service = getService(); - final BluetoothDevice defaultValue = null; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<BluetoothDevice> recv = - new SynchronousResultReceiver(); - service.getClient(mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Returns true if the specified Bluetooth device is connected. - * Returns false if not connected, or if this proxy object is not - * currently connected to the Map service. - * - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean isConnected(BluetoothDevice device) { - if (VDBG) log("isConnected(" + device + ")"); - final IBluetoothMap service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.isConnected(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Initiate connection. Initiation of outgoing connections is not - * supported for MAP server. - * - * @hide - */ - @RequiresNoPermission - public boolean connect(BluetoothDevice device) { - if (DBG) log("connect(" + device + ")" + "not supported for MAPS"); - return false; - } - - /** - * Initiate disconnect. - * - * @param device Remote Bluetooth Device - * @return false on error, true otherwise - * - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean disconnect(BluetoothDevice device) { - if (DBG) log("disconnect(" + device + ")"); - final IBluetoothMap service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.disconnect(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Check class bits for possible Map support. - * This is a simple heuristic that tries to guess if a device with the - * given class bits might support Map. It is not accurate for all - * devices. It tries to err on the side of false positives. - * - * @return True if this device might support Map. - * - * @hide - */ - public static boolean doesClassMatchSink(BluetoothClass btClass) { - // TODO optimize the rule - switch (btClass.getDeviceClass()) { - case BluetoothClass.Device.COMPUTER_DESKTOP: - case BluetoothClass.Device.COMPUTER_LAPTOP: - case BluetoothClass.Device.COMPUTER_SERVER: - case BluetoothClass.Device.COMPUTER_UNCATEGORIZED: - return true; - default: - return false; - } - } - - /** - * Get the list of connected devices. Currently at most one. - * - * @return list of connected devices - * - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public @NonNull List<BluetoothDevice> getConnectedDevices() { - if (DBG) log("getConnectedDevices()"); - final IBluetoothMap service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getConnectedDevices(mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the list of devices matching specified states. Currently at most one. - * - * @return list of matching devices - * - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { - if (DBG) log("getDevicesMatchingStates()"); - final IBluetoothMap service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get connection state of device - * - * @return device connection state - * - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - public int getConnectionState(BluetoothDevice device) { - if (DBG) log("getConnectionState(" + device + ")"); - final IBluetoothMap service = getService(); - final int defaultValue = BluetoothProfile.STATE_DISCONNECTED; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = - new SynchronousResultReceiver(); - service.getConnectionState(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Set priority of the profile - * - * <p> The device should already be paired. - * Priority can be one of {@link #PRIORITY_ON} or {@link #PRIORITY_OFF}, - * - * @param device Paired bluetooth device - * @param priority - * @return true if priority is set, false on error - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setPriority(BluetoothDevice device, int priority) { - if (DBG) log("setPriority(" + device + ", " + priority + ")"); - return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority)); - } - - /** - * Set connection policy of the profile - * - * <p> The device should already be paired. - * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED}, - * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Paired bluetooth device - * @param connectionPolicy is the connection policy to set to for this profile - * @return true if connectionPolicy is set, false on error - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setConnectionPolicy(@NonNull BluetoothDevice device, - @ConnectionPolicy int connectionPolicy) { - if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); - final IBluetoothMap service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device) - && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN - || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the priority of the profile. - * - * <p> The priority can be any of: - * {@link #PRIORITY_OFF}, {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED} - * - * @param device Bluetooth device - * @return priority of the device - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public int getPriority(BluetoothDevice device) { - if (VDBG) log("getPriority(" + device + ")"); - return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device)); - } - - /** - * Get the connection policy of the profile. - * - * <p> The connection policy can be any of: - * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, - * {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Bluetooth device - * @return connection policy of the device - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) { - if (VDBG) log("getConnectionPolicy(" + device + ")"); - final IBluetoothMap service = getService(); - final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionPolicy(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - private static void log(String msg) { - Log.d(TAG, msg); - } - - private boolean isEnabled() { - return mAdapter.isEnabled(); - } - - private static boolean isValidDevice(BluetoothDevice device) { - return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); - } -} diff --git a/core/java/android/bluetooth/BluetoothMapClient.java b/core/java/android/bluetooth/BluetoothMapClient.java deleted file mode 100644 index 03536f9aad39..000000000000 --- a/core/java/android/bluetooth/BluetoothMapClient.java +++ /dev/null @@ -1,686 +0,0 @@ -/* - * Copyright (C) 2016 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.bluetooth; - -import static android.bluetooth.BluetoothUtils.getSyncTimeout; - -import android.Manifest; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.RequiresPermission; -import android.annotation.SdkConstant; -import android.annotation.SdkConstant.SdkConstantType; -import android.annotation.SystemApi; -import android.app.PendingIntent; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.compat.annotation.UnsupportedAppUsage; -import android.content.AttributionSource; -import android.content.Context; -import android.net.Uri; -import android.os.Build; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.Log; - -import com.android.modules.utils.SynchronousResultReceiver; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.concurrent.TimeoutException; - -/** - * This class provides the APIs to control the Bluetooth MAP MCE Profile. - * - * @hide - */ -@SystemApi -public final class BluetoothMapClient implements BluetoothProfile { - - private static final String TAG = "BluetoothMapClient"; - private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); - private static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE); - - /** @hide */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_CONNECTION_STATE_CHANGED = - "android.bluetooth.mapmce.profile.action.CONNECTION_STATE_CHANGED"; - /** @hide */ - @RequiresPermission(android.Manifest.permission.RECEIVE_SMS) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_MESSAGE_RECEIVED = - "android.bluetooth.mapmce.profile.action.MESSAGE_RECEIVED"; - /* Actions to be used for pending intents */ - /** @hide */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_MESSAGE_SENT_SUCCESSFULLY = - "android.bluetooth.mapmce.profile.action.MESSAGE_SENT_SUCCESSFULLY"; - /** @hide */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_MESSAGE_DELIVERED_SUCCESSFULLY = - "android.bluetooth.mapmce.profile.action.MESSAGE_DELIVERED_SUCCESSFULLY"; - - /** - * Action to notify read status changed - * - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_MESSAGE_READ_STATUS_CHANGED = - "android.bluetooth.mapmce.profile.action.MESSAGE_READ_STATUS_CHANGED"; - - /** - * Action to notify deleted status changed - * - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_MESSAGE_DELETED_STATUS_CHANGED = - "android.bluetooth.mapmce.profile.action.MESSAGE_DELETED_STATUS_CHANGED"; - - /** - * Extras used in ACTION_MESSAGE_RECEIVED intent. - * NOTE: HANDLE is only valid for a single session with the device. - */ - /** @hide */ - public static final String EXTRA_MESSAGE_HANDLE = - "android.bluetooth.mapmce.profile.extra.MESSAGE_HANDLE"; - /** @hide */ - public static final String EXTRA_MESSAGE_TIMESTAMP = - "android.bluetooth.mapmce.profile.extra.MESSAGE_TIMESTAMP"; - /** @hide */ - public static final String EXTRA_MESSAGE_READ_STATUS = - "android.bluetooth.mapmce.profile.extra.MESSAGE_READ_STATUS"; - /** @hide */ - public static final String EXTRA_SENDER_CONTACT_URI = - "android.bluetooth.mapmce.profile.extra.SENDER_CONTACT_URI"; - /** @hide */ - public static final String EXTRA_SENDER_CONTACT_NAME = - "android.bluetooth.mapmce.profile.extra.SENDER_CONTACT_NAME"; - - /** - * Used as a boolean extra in ACTION_MESSAGE_DELETED_STATUS_CHANGED - * Contains the MAP message deleted status - * Possible values are: - * true: deleted - * false: undeleted - * - * @hide - */ - public static final String EXTRA_MESSAGE_DELETED_STATUS = - "android.bluetooth.mapmce.profile.extra.MESSAGE_DELETED_STATUS"; - - /** - * Extra used in ACTION_MESSAGE_READ_STATUS_CHANGED or ACTION_MESSAGE_DELETED_STATUS_CHANGED - * Possible values are: - * 0: failure - * 1: success - * - * @hide - */ - public static final String EXTRA_RESULT_CODE = - "android.bluetooth.device.extra.RESULT_CODE"; - - /** - * There was an error trying to obtain the state - * @hide - */ - public static final int STATE_ERROR = -1; - - /** @hide */ - public static final int RESULT_FAILURE = 0; - /** @hide */ - public static final int RESULT_SUCCESS = 1; - /** - * Connection canceled before completion. - * @hide - */ - public static final int RESULT_CANCELED = 2; - /** @hide */ - private static final int UPLOADING_FEATURE_BITMASK = 0x08; - - /* - * UNREAD, READ, UNDELETED, DELETED are passed as parameters - * to setMessageStatus to indicate the messages new state. - */ - - /** @hide */ - public static final int UNREAD = 0; - /** @hide */ - public static final int READ = 1; - /** @hide */ - public static final int UNDELETED = 2; - /** @hide */ - public static final int DELETED = 3; - - private final BluetoothAdapter mAdapter; - private final AttributionSource mAttributionSource; - private final BluetoothProfileConnector<IBluetoothMapClient> mProfileConnector = - new BluetoothProfileConnector(this, BluetoothProfile.MAP_CLIENT, - "BluetoothMapClient", IBluetoothMapClient.class.getName()) { - @Override - public IBluetoothMapClient getServiceInterface(IBinder service) { - return IBluetoothMapClient.Stub.asInterface(service); - } - }; - - /** - * Create a BluetoothMapClient proxy object. - */ - /* package */ BluetoothMapClient(Context context, ServiceListener listener, - BluetoothAdapter adapter) { - if (DBG) Log.d(TAG, "Create BluetoothMapClient proxy object"); - mAdapter = adapter; - mAttributionSource = adapter.getAttributionSource(); - mProfileConnector.connect(context, listener); - } - - /** - * Close the connection to the backing service. - * Other public functions of BluetoothMap will return default error - * results once close() has been called. Multiple invocations of close() - * are ok. - * @hide - */ - public void close() { - mProfileConnector.disconnect(); - } - - private IBluetoothMapClient getService() { - return mProfileConnector.getService(); - } - - /** - * Returns true if the specified Bluetooth device is connected. - * Returns false if not connected, or if this proxy object is not - * currently connected to the Map service. - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean isConnected(BluetoothDevice device) { - if (VDBG) Log.d(TAG, "isConnected(" + device + ")"); - final IBluetoothMapClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.isConnected(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Initiate connection. Initiation of outgoing connections is not - * supported for MAP server. - * - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean connect(BluetoothDevice device) { - if (DBG) Log.d(TAG, "connect(" + device + ")" + "for MAPS MCE"); - final IBluetoothMapClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.connect(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Initiate disconnect. - * - * @param device Remote Bluetooth Device - * @return false on error, true otherwise - * - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean disconnect(BluetoothDevice device) { - if (DBG) Log.d(TAG, "disconnect(" + device + ")"); - final IBluetoothMapClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.disconnect(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the list of connected devices. Currently at most one. - * - * @return list of connected devices - * @hide - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getConnectedDevices() { - if (DBG) Log.d(TAG, "getConnectedDevices()"); - final IBluetoothMapClient service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getConnectedDevices(mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the list of devices matching specified states. Currently at most one. - * - * @return list of matching devices - * @hide - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { - if (DBG) Log.d(TAG, "getDevicesMatchingStates()"); - final IBluetoothMapClient service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get connection state of device - * - * @return device connection state - * @hide - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - public int getConnectionState(BluetoothDevice device) { - if (DBG) Log.d(TAG, "getConnectionState(" + device + ")"); - final IBluetoothMapClient service = getService(); - final int defaultValue = BluetoothProfile.STATE_DISCONNECTED; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver<>(); - service.getConnectionState(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Set priority of the profile - * - * <p> The device should already be paired. - * Priority can be one of {@link #PRIORITY_ON} or {@link #PRIORITY_OFF}, - * - * @param device Paired bluetooth device - * @param priority - * @return true if priority is set, false on error - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setPriority(BluetoothDevice device, int priority) { - if (DBG) Log.d(TAG, "setPriority(" + device + ", " + priority + ")"); - return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority)); - } - - /** - * Set connection policy of the profile - * - * <p> The device should already be paired. - * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED}, - * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Paired bluetooth device - * @param connectionPolicy is the connection policy to set to for this profile - * @return true if connectionPolicy is set, false on error - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setConnectionPolicy(@NonNull BluetoothDevice device, - @ConnectionPolicy int connectionPolicy) { - if (DBG) Log.d(TAG, "setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); - final IBluetoothMapClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device) - && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN - || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the priority of the profile. - * - * <p> The priority can be any of: - * {@link #PRIORITY_OFF}, {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED} - * - * @param device Bluetooth device - * @return priority of the device - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public int getPriority(BluetoothDevice device) { - if (VDBG) Log.d(TAG, "getPriority(" + device + ")"); - return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device)); - } - - /** - * Get the connection policy of the profile. - * - * <p> The connection policy can be any of: - * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, - * {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Bluetooth device - * @return connection policy of the device - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) { - if (VDBG) Log.d(TAG, "getConnectionPolicy(" + device + ")"); - final IBluetoothMapClient service = getService(); - final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionPolicy(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Send a message. - * - * Send an SMS message to either the contacts primary number or the telephone number specified. - * - * @param device Bluetooth device - * @param contacts Uri Collection of the contacts - * @param message Message to be sent - * @param sentIntent intent issued when message is sent - * @param deliveredIntent intent issued when message is delivered - * @return true if the message is enqueued, false on error - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.SEND_SMS, - }) - public boolean sendMessage(@NonNull BluetoothDevice device, @NonNull Collection<Uri> contacts, - @NonNull String message, @Nullable PendingIntent sentIntent, - @Nullable PendingIntent deliveredIntent) { - return sendMessage(device, contacts.toArray(new Uri[contacts.size()]), message, sentIntent, - deliveredIntent); - } - - /** - * Send a message. - * - * Send an SMS message to either the contacts primary number or the telephone number specified. - * - * @param device Bluetooth device - * @param contacts Uri[] of the contacts - * @param message Message to be sent - * @param sentIntent intent issued when message is sent - * @param deliveredIntent intent issued when message is delivered - * @return true if the message is enqueued, false on error - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.SEND_SMS, - }) - public boolean sendMessage(BluetoothDevice device, Uri[] contacts, String message, - PendingIntent sentIntent, PendingIntent deliveredIntent) { - if (DBG) Log.d(TAG, "sendMessage(" + device + ", " + contacts + ", " + message); - final IBluetoothMapClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.sendMessage(device, contacts, message, sentIntent, deliveredIntent, - mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get unread messages. Unread messages will be published via {@link #ACTION_MESSAGE_RECEIVED}. - * - * @param device Bluetooth device - * @return true if the message is enqueued, false on error - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.READ_SMS, - }) - public boolean getUnreadMessages(BluetoothDevice device) { - if (DBG) Log.d(TAG, "getUnreadMessages(" + device + ")"); - final IBluetoothMapClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.getUnreadMessages(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Returns the "Uploading" feature bit value from the SDP record's - * MapSupportedFeatures field (see Bluetooth MAP 1.4 spec, page 114). - * @param device The Bluetooth device to get this value for. - * @return Returns true if the Uploading bit value in SDP record's - * MapSupportedFeatures field is set. False is returned otherwise. - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean isUploadingSupported(BluetoothDevice device) { - if (DBG) Log.d(TAG, "isUploadingSupported(" + device + ")"); - final IBluetoothMapClient service = getService(); - final int defaultValue = 0; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getSupportedFeatures(device, mAttributionSource, recv); - return (recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue) - & UPLOADING_FEATURE_BITMASK) > 0; - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return false; - } - - /** - * Set message status of message on MSE - * <p> - * When read status changed, the result will be published via - * {@link #ACTION_MESSAGE_READ_STATUS_CHANGED} - * When deleted status changed, the result will be published via - * {@link #ACTION_MESSAGE_DELETED_STATUS_CHANGED} - * - * @param device Bluetooth device - * @param handle message handle - * @param status <code>UNREAD</code> for "unread", <code>READ</code> for - * "read", <code>UNDELETED</code> for "undeleted", <code>DELETED</code> for - * "deleted", otherwise return error - * @return <code>true</code> if request has been sent, <code>false</code> on error - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.READ_SMS, - }) - public boolean setMessageStatus(BluetoothDevice device, String handle, int status) { - if (DBG) Log.d(TAG, "setMessageStatus(" + device + ", " + handle + ", " + status + ")"); - final IBluetoothMapClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device) && handle != null && (status == READ - || status == UNREAD || status == UNDELETED || status == DELETED)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.setMessageStatus(device, handle, status, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - private boolean isEnabled() { - return mAdapter.isEnabled(); - } - - private static boolean isValidDevice(BluetoothDevice device) { - return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); - } -} diff --git a/core/java/android/bluetooth/BluetoothMasInstance.java b/core/java/android/bluetooth/BluetoothMasInstance.java deleted file mode 100644 index eeaf08545146..000000000000 --- a/core/java/android/bluetooth/BluetoothMasInstance.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) 2014 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.bluetooth; - -import android.annotation.Nullable; -import android.os.Parcel; -import android.os.Parcelable; - -/** @hide */ -public final class BluetoothMasInstance implements Parcelable { - private final int mId; - private final String mName; - private final int mChannel; - private final int mMsgTypes; - - public BluetoothMasInstance(int id, String name, int channel, int msgTypes) { - mId = id; - mName = name; - mChannel = channel; - mMsgTypes = msgTypes; - } - - @Override - public boolean equals(@Nullable Object o) { - if (o instanceof BluetoothMasInstance) { - return mId == ((BluetoothMasInstance) o).mId; - } - return false; - } - - @Override - public int hashCode() { - return mId + (mChannel << 8) + (mMsgTypes << 16); - } - - @Override - public String toString() { - return Integer.toString(mId) + ":" + mName + ":" + mChannel + ":" - + Integer.toHexString(mMsgTypes); - } - - @Override - public int describeContents() { - return 0; - } - - public static final @android.annotation.NonNull Parcelable.Creator<BluetoothMasInstance> CREATOR = - new Parcelable.Creator<BluetoothMasInstance>() { - public BluetoothMasInstance createFromParcel(Parcel in) { - return new BluetoothMasInstance(in.readInt(), in.readString(), - in.readInt(), in.readInt()); - } - - public BluetoothMasInstance[] newArray(int size) { - return new BluetoothMasInstance[size]; - } - }; - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeInt(mId); - out.writeString(mName); - out.writeInt(mChannel); - out.writeInt(mMsgTypes); - } - - public static final class MessageType { - public static final int EMAIL = 0x01; - public static final int SMS_GSM = 0x02; - public static final int SMS_CDMA = 0x04; - public static final int MMS = 0x08; - } - - public int getId() { - return mId; - } - - public String getName() { - return mName; - } - - public int getChannel() { - return mChannel; - } - - public int getMsgTypes() { - return mMsgTypes; - } - - public boolean msgSupported(int msg) { - return (mMsgTypes & msg) != 0; - } -} diff --git a/core/java/android/bluetooth/BluetoothOutputStream.java b/core/java/android/bluetooth/BluetoothOutputStream.java deleted file mode 100644 index ac2b3edb0eb8..000000000000 --- a/core/java/android/bluetooth/BluetoothOutputStream.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2009 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.bluetooth; - -import android.annotation.SuppressLint; - -import java.io.IOException; -import java.io.OutputStream; - -/** - * BluetoothOutputStream. - * - * Used to read from a Bluetooth socket. - * - * @hide - */ -@SuppressLint("AndroidFrameworkBluetoothPermission") -/*package*/ final class BluetoothOutputStream extends OutputStream { - private BluetoothSocket mSocket; - - /*package*/ BluetoothOutputStream(BluetoothSocket s) { - mSocket = s; - } - - /** - * Close this output stream and the socket associated with it. - */ - public void close() throws IOException { - mSocket.close(); - } - - /** - * Writes a single byte to this stream. Only the least significant byte of - * the integer {@code oneByte} is written to the stream. - * - * @param oneByte the byte to be written. - * @throws IOException if an error occurs while writing to this stream. - * @since Android 1.0 - */ - public void write(int oneByte) throws IOException { - byte[] b = new byte[1]; - b[0] = (byte) oneByte; - mSocket.write(b, 0, 1); - } - - /** - * Writes {@code count} bytes from the byte array {@code buffer} starting - * at position {@code offset} to this stream. - * - * @param b the buffer to be written. - * @param offset the start position in {@code buffer} from where to get bytes. - * @param count the number of bytes from {@code buffer} to write to this stream. - * @throws IOException if an error occurs while writing to this stream. - * @throws IndexOutOfBoundsException if {@code offset < 0} or {@code count < 0}, or if {@code - * offset + count} is bigger than the length of {@code buffer}. - * @since Android 1.0 - */ - public void write(byte[] b, int offset, int count) throws IOException { - if (b == null) { - throw new NullPointerException("buffer is null"); - } - if ((offset | count) < 0 || count > b.length - offset) { - throw new IndexOutOfBoundsException("invalid offset or length"); - } - mSocket.write(b, offset, count); - } -} diff --git a/core/java/android/bluetooth/BluetoothPan.java b/core/java/android/bluetooth/BluetoothPan.java deleted file mode 100644 index d4ad4ef47acd..000000000000 --- a/core/java/android/bluetooth/BluetoothPan.java +++ /dev/null @@ -1,525 +0,0 @@ -/* - * Copyright (C) 2008 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.bluetooth; - -import static android.bluetooth.BluetoothUtils.getSyncTimeout; - -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.RequiresPermission; -import android.annotation.SdkConstant; -import android.annotation.SdkConstant.SdkConstantType; -import android.annotation.SuppressLint; -import android.annotation.SystemApi; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; -import android.compat.annotation.UnsupportedAppUsage; -import android.content.AttributionSource; -import android.content.Context; -import android.os.Build; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.Log; - -import com.android.modules.utils.SynchronousResultReceiver; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeoutException; - -/** - * This class provides the APIs to control the Bluetooth Pan - * Profile. - * - * <p>BluetoothPan is a proxy object for controlling the Bluetooth - * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get - * the BluetoothPan proxy object. - * - * <p>Each method is protected with its appropriate permission. - * - * @hide - */ -@SystemApi -public final class BluetoothPan implements BluetoothProfile { - private static final String TAG = "BluetoothPan"; - private static final boolean DBG = true; - private static final boolean VDBG = false; - - /** - * Intent used to broadcast the change in connection state of the Pan - * profile. - * - * <p>This intent will have 4 extras: - * <ul> - * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> - * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> - * <li> {@link #EXTRA_LOCAL_ROLE} - Which local role the remote device is - * bound to. </li> - * </ul> - * - * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of - * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, - * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. - * - * <p> {@link #EXTRA_LOCAL_ROLE} can be one of {@link #LOCAL_NAP_ROLE} or - * {@link #LOCAL_PANU_ROLE} - */ - @SuppressLint("ActionValue") - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_CONNECTION_STATE_CHANGED = - "android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED"; - - /** - * Extra for {@link #ACTION_CONNECTION_STATE_CHANGED} intent - * The local role of the PAN profile that the remote device is bound to. - * It can be one of {@link #LOCAL_NAP_ROLE} or {@link #LOCAL_PANU_ROLE}. - */ - @SuppressLint("ActionValue") - public static final String EXTRA_LOCAL_ROLE = "android.bluetooth.pan.extra.LOCAL_ROLE"; - - /** - * Intent used to broadcast the change in tethering state of the Pan - * Profile - * - * <p>This intent will have 1 extra: - * <ul> - * <li> {@link #EXTRA_TETHERING_STATE} - The current state of Bluetooth - * tethering. </li> - * </ul> - * - * <p> {@link #EXTRA_TETHERING_STATE} can be any of {@link #TETHERING_STATE_OFF} or - * {@link #TETHERING_STATE_ON} - */ - @RequiresLegacyBluetoothPermission - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_TETHERING_STATE_CHANGED = - "android.bluetooth.action.TETHERING_STATE_CHANGED"; - - /** - * Extra for {@link #ACTION_TETHERING_STATE_CHANGED} intent - * The tethering state of the PAN profile. - * It can be one of {@link #TETHERING_STATE_OFF} or {@link #TETHERING_STATE_ON}. - */ - public static final String EXTRA_TETHERING_STATE = - "android.bluetooth.extra.TETHERING_STATE"; - - /** @hide */ - @IntDef({PAN_ROLE_NONE, LOCAL_NAP_ROLE, LOCAL_PANU_ROLE}) - @Retention(RetentionPolicy.SOURCE) - public @interface LocalPanRole {} - - public static final int PAN_ROLE_NONE = 0; - /** - * The local device is acting as a Network Access Point. - */ - public static final int LOCAL_NAP_ROLE = 1; - - /** - * The local device is acting as a PAN User. - */ - public static final int LOCAL_PANU_ROLE = 2; - - /** @hide */ - @IntDef({PAN_ROLE_NONE, REMOTE_NAP_ROLE, REMOTE_PANU_ROLE}) - @Retention(RetentionPolicy.SOURCE) - public @interface RemotePanRole {} - - public static final int REMOTE_NAP_ROLE = 1; - - public static final int REMOTE_PANU_ROLE = 2; - - /** @hide **/ - @IntDef({TETHERING_STATE_OFF, TETHERING_STATE_ON}) - @Retention(RetentionPolicy.SOURCE) - public @interface TetheringState{} - - public static final int TETHERING_STATE_OFF = 1; - - public static final int TETHERING_STATE_ON = 2; - /** - * Return codes for the connect and disconnect Bluez / Dbus calls. - * - * @hide - */ - public static final int PAN_DISCONNECT_FAILED_NOT_CONNECTED = 1000; - - /** - * @hide - */ - public static final int PAN_CONNECT_FAILED_ALREADY_CONNECTED = 1001; - - /** - * @hide - */ - public static final int PAN_CONNECT_FAILED_ATTEMPT_FAILED = 1002; - - /** - * @hide - */ - public static final int PAN_OPERATION_GENERIC_FAILURE = 1003; - - /** - * @hide - */ - public static final int PAN_OPERATION_SUCCESS = 1004; - - private final Context mContext; - - private final BluetoothAdapter mAdapter; - private final AttributionSource mAttributionSource; - private final BluetoothProfileConnector<IBluetoothPan> mProfileConnector = - new BluetoothProfileConnector(this, BluetoothProfile.PAN, - "BluetoothPan", IBluetoothPan.class.getName()) { - @Override - public IBluetoothPan getServiceInterface(IBinder service) { - return IBluetoothPan.Stub.asInterface(service); - } - }; - - - /** - * Create a BluetoothPan proxy object for interacting with the local - * Bluetooth Service which handles the Pan profile - * - * @hide - */ - @UnsupportedAppUsage - /* package */ BluetoothPan(Context context, ServiceListener listener, - BluetoothAdapter adapter) { - mAdapter = adapter; - mAttributionSource = adapter.getAttributionSource(); - mContext = context; - mProfileConnector.connect(context, listener); - } - - /** - * Closes the connection to the service and unregisters callbacks - */ - @UnsupportedAppUsage - void close() { - if (VDBG) log("close()"); - mProfileConnector.disconnect(); - } - - private IBluetoothPan getService() { - return mProfileConnector.getService(); - } - - /** @hide */ - protected void finalize() { - close(); - } - - /** - * Initiate connection to a profile of the remote bluetooth device. - * - * <p> This API returns false in scenarios like the profile on the - * device is already connected or Bluetooth is not turned on. - * When this API returns true, it is guaranteed that - * connection state intent for the profile will be broadcasted with - * the state. Users can get the connection state of the profile - * from this intent. - * - * @param device Remote Bluetooth Device - * @return false on immediate error, true otherwise - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean connect(BluetoothDevice device) { - if (DBG) log("connect(" + device + ")"); - final IBluetoothPan service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.connect(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Initiate disconnection from a profile - * - * <p> This API will return false in scenarios like the profile on the - * Bluetooth device is not in connected state etc. When this API returns, - * true, it is guaranteed that the connection state change - * intent will be broadcasted with the state. Users can get the - * disconnection state of the profile from this intent. - * - * <p> If the disconnection is initiated by a remote device, the state - * will transition from {@link #STATE_CONNECTED} to - * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the - * host (local) device the state will transition from - * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to - * state {@link #STATE_DISCONNECTED}. The transition to - * {@link #STATE_DISCONNECTING} can be used to distinguish between the - * two scenarios. - * - * @param device Remote Bluetooth Device - * @return false on immediate error, true otherwise - * @hide - */ - @UnsupportedAppUsage - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean disconnect(BluetoothDevice device) { - if (DBG) log("disconnect(" + device + ")"); - final IBluetoothPan service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.disconnect(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Set connection policy of the profile - * - * <p> The device should already be paired. - * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED}, - * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Paired bluetooth device - * @param connectionPolicy is the connection policy to set to for this profile - * @return true if connectionPolicy is set, false on error - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setConnectionPolicy(@NonNull BluetoothDevice device, - @ConnectionPolicy int connectionPolicy) { - if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); - final IBluetoothPan service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device) - && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN - || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - * @hide - */ - @SystemApi - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public @NonNull List<BluetoothDevice> getConnectedDevices() { - if (VDBG) log("getConnectedDevices()"); - final IBluetoothPan service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getConnectedDevices(mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - * @hide - */ - @Override - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { - if (VDBG) log("getDevicesMatchingStates()"); - final IBluetoothPan service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * {@inheritDoc} - * @hide - */ - @SystemApi - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public int getConnectionState(@NonNull BluetoothDevice device) { - if (VDBG) log("getState(" + device + ")"); - final IBluetoothPan service = getService(); - final int defaultValue = BluetoothProfile.STATE_DISCONNECTED; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionState(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Turns on/off bluetooth tethering - * - * @param value is whether to enable or disable bluetooth tethering - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - android.Manifest.permission.TETHER_PRIVILEGED, - }) - public void setBluetoothTethering(boolean value) { - String pkgName = mContext.getOpPackageName(); - if (DBG) log("setBluetoothTethering(" + value + "), calling package:" + pkgName); - final IBluetoothPan service = getService(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver recv = new SynchronousResultReceiver(); - service.setBluetoothTethering(value, mAttributionSource, recv); - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - } - - /** - * Determines whether tethering is enabled - * - * @return true if tethering is on, false if not or some error occurred - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean isTetheringOn() { - if (VDBG) log("isTetheringOn()"); - final IBluetoothPan service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.isTetheringOn(mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - @UnsupportedAppUsage - private boolean isEnabled() { - return mAdapter.getState() == BluetoothAdapter.STATE_ON; - } - - @UnsupportedAppUsage - private static boolean isValidDevice(BluetoothDevice device) { - return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); - } - - @UnsupportedAppUsage - private static void log(String msg) { - Log.d(TAG, msg); - } -} diff --git a/core/java/android/bluetooth/BluetoothPbap.java b/core/java/android/bluetooth/BluetoothPbap.java deleted file mode 100644 index de2db9c2ca86..000000000000 --- a/core/java/android/bluetooth/BluetoothPbap.java +++ /dev/null @@ -1,317 +0,0 @@ -/* - * Copyright (C) 2008 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.bluetooth; - -import android.Manifest; -import android.annotation.NonNull; -import android.annotation.RequiresPermission; -import android.annotation.SdkConstant; -import android.annotation.SuppressLint; -import android.annotation.SystemApi; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.compat.annotation.UnsupportedAppUsage; -import android.content.AttributionSource; -import android.content.Context; -import android.os.Build; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.Log; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** - * Public API for controlling the Bluetooth Pbap Service. This includes - * Bluetooth Phone book Access profile. - * BluetoothPbap is a proxy object for controlling the Bluetooth Pbap - * Service via IPC. - * - * Creating a BluetoothPbap object will create a binding with the - * BluetoothPbap service. Users of this object should call close() when they - * are finished with the BluetoothPbap, so that this proxy object can unbind - * from the service. - * - * This BluetoothPbap object is not immediately bound to the - * BluetoothPbap service. Use the ServiceListener interface to obtain a - * notification when it is bound, this is especially important if you wish to - * immediately call methods on BluetoothPbap after construction. - * - * To get an instance of the BluetoothPbap class, you can call - * {@link BluetoothAdapter#getProfileProxy(Context, ServiceListener, int)} with the final param - * being {@link BluetoothProfile#PBAP}. The ServiceListener should be able to get the instance of - * BluetoothPbap in {@link android.bluetooth.BluetoothProfile.ServiceListener#onServiceConnected}. - * - * Android only supports one connected Bluetooth Pce at a time. - * - * @hide - */ -@SystemApi -public class BluetoothPbap implements BluetoothProfile { - - private static final String TAG = "BluetoothPbap"; - private static final boolean DBG = false; - - /** - * Intent used to broadcast the change in connection state of the PBAP - * profile. - * - * <p>This intent will have 3 extras: - * <ul> - * <li> {@link BluetoothProfile#EXTRA_STATE} - The current state of the profile. </li> - * <li> {@link BluetoothProfile#EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> - * </ul> - * <p>{@link BluetoothProfile#EXTRA_STATE} or {@link BluetoothProfile#EXTRA_PREVIOUS_STATE} - * can be any of {@link BluetoothProfile#STATE_DISCONNECTED}, - * {@link BluetoothProfile#STATE_CONNECTING}, {@link BluetoothProfile#STATE_CONNECTED}, - * {@link BluetoothProfile#STATE_DISCONNECTING}. - * - * @hide - */ - @SuppressLint("ActionValue") - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_CONNECTION_STATE_CHANGED = - "android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED"; - - private final AttributionSource mAttributionSource; - - /** @hide */ - public static final int RESULT_FAILURE = 0; - /** @hide */ - public static final int RESULT_SUCCESS = 1; - /** - * Connection canceled before completion. - * - * @hide - */ - public static final int RESULT_CANCELED = 2; - - private BluetoothAdapter mAdapter; - private final BluetoothProfileConnector<IBluetoothPbap> mProfileConnector = - new BluetoothProfileConnector(this, BluetoothProfile.PBAP, "BluetoothPbap", - IBluetoothPbap.class.getName()) { - @Override - public IBluetoothPbap getServiceInterface(IBinder service) { - return IBluetoothPbap.Stub.asInterface(service); - } - }; - - /** - * Create a BluetoothPbap proxy object. - * - * @hide - */ - public BluetoothPbap(Context context, ServiceListener listener, BluetoothAdapter adapter) { - mAdapter = adapter; - mAttributionSource = adapter.getAttributionSource(); - mProfileConnector.connect(context, listener); - } - - /** @hide */ - protected void finalize() throws Throwable { - try { - close(); - } finally { - super.finalize(); - } - } - - /** - * Close the connection to the backing service. - * Other public functions of BluetoothPbap will return default error - * results once close() has been called. Multiple invocations of close() - * are ok. - * - * @hide - */ - public synchronized void close() { - mProfileConnector.disconnect(); - } - - private IBluetoothPbap getService() { - return (IBluetoothPbap) mProfileConnector.getService(); - } - - /** - * {@inheritDoc} - * - * @hide - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getConnectedDevices() { - log("getConnectedDevices()"); - final IBluetoothPbap service = getService(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - return new ArrayList<BluetoothDevice>(); - } - try { - return Attributable.setAttributionSource( - service.getConnectedDevices(mAttributionSource), mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, e.toString()); - } - return new ArrayList<BluetoothDevice>(); - } - - /** - * {@inheritDoc} - * - * @hide - */ - @SystemApi - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public @BtProfileState int getConnectionState(@NonNull BluetoothDevice device) { - log("getConnectionState: device=" + device); - try { - final IBluetoothPbap service = getService(); - if (service != null && isEnabled() && isValidDevice(device)) { - return service.getConnectionState(device, mAttributionSource); - } - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - } - return BluetoothProfile.STATE_DISCONNECTED; - } catch (RemoteException e) { - Log.e(TAG, e.toString()); - } - return BluetoothProfile.STATE_DISCONNECTED; - } - - /** - * {@inheritDoc} - * - * @hide - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { - log("getDevicesMatchingConnectionStates: states=" + Arrays.toString(states)); - final IBluetoothPbap service = getService(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - return new ArrayList<BluetoothDevice>(); - } - try { - return Attributable.setAttributionSource( - service.getDevicesMatchingConnectionStates(states, mAttributionSource), - mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, e.toString()); - } - return new ArrayList<BluetoothDevice>(); - } - - /** - * Set connection policy of the profile and tries to disconnect it if connectionPolicy is - * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN} - * - * <p> The device should already be paired. - * Connection policy can be one of: - * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED}, - * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}, - * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN} - * - * @param device Paired bluetooth device - * @param connectionPolicy is the connection policy to set to for this profile - * @return true if connectionPolicy is set, false on error - * - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setConnectionPolicy(@NonNull BluetoothDevice device, - @ConnectionPolicy int connectionPolicy) { - if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); - try { - final IBluetoothPbap service = getService(); - if (service != null && isEnabled() - && isValidDevice(device)) { - if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN - && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - return false; - } - return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource); - } - if (service == null) Log.w(TAG, "Proxy not attached to service"); - return false; - } catch (RemoteException e) { - Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); - return false; - } - } - - /** - * Disconnects the current Pbap client (PCE). Currently this call blocks, - * it may soon be made asynchronous. Returns false if this proxy object is - * not currently connected to the Pbap service. - * - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean disconnect(BluetoothDevice device) { - log("disconnect()"); - final IBluetoothPbap service = getService(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - return false; - } - try { - service.disconnect(device, mAttributionSource); - return true; - } catch (RemoteException e) { - Log.e(TAG, e.toString()); - } - return false; - } - - private boolean isEnabled() { - if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true; - return false; - } - - private boolean isValidDevice(BluetoothDevice device) { - if (device == null) return false; - - if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; - return false; - } - - private static void log(String msg) { - if (DBG) { - Log.d(TAG, msg); - } - } -} diff --git a/core/java/android/bluetooth/BluetoothPbapClient.java b/core/java/android/bluetooth/BluetoothPbapClient.java deleted file mode 100644 index e096de8cb829..000000000000 --- a/core/java/android/bluetooth/BluetoothPbapClient.java +++ /dev/null @@ -1,405 +0,0 @@ -/* - * Copyright (C) 2016 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.bluetooth; - -import static android.bluetooth.BluetoothUtils.getSyncTimeout; - -import android.Manifest; -import android.annotation.NonNull; -import android.annotation.RequiresPermission; -import android.annotation.SdkConstant; -import android.annotation.SdkConstant.SdkConstantType; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.content.AttributionSource; -import android.content.Context; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.Log; - -import com.android.modules.utils.SynchronousResultReceiver; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeoutException; - -/** - * This class provides the APIs to control the Bluetooth PBAP Client Profile. - * - * @hide - */ -public final class BluetoothPbapClient implements BluetoothProfile { - - private static final String TAG = "BluetoothPbapClient"; - private static final boolean DBG = false; - private static final boolean VDBG = false; - - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_CONNECTION_STATE_CHANGED = - "android.bluetooth.pbapclient.profile.action.CONNECTION_STATE_CHANGED"; - - /** There was an error trying to obtain the state */ - public static final int STATE_ERROR = -1; - - public static final int RESULT_FAILURE = 0; - public static final int RESULT_SUCCESS = 1; - /** Connection canceled before completion. */ - public static final int RESULT_CANCELED = 2; - - private final BluetoothAdapter mAdapter; - private final AttributionSource mAttributionSource; - private final BluetoothProfileConnector<IBluetoothPbapClient> mProfileConnector = - new BluetoothProfileConnector(this, BluetoothProfile.PBAP_CLIENT, - "BluetoothPbapClient", IBluetoothPbapClient.class.getName()) { - @Override - public IBluetoothPbapClient getServiceInterface(IBinder service) { - return IBluetoothPbapClient.Stub.asInterface(service); - } - }; - - /** - * Create a BluetoothPbapClient proxy object. - */ - BluetoothPbapClient(Context context, ServiceListener listener, BluetoothAdapter adapter) { - if (DBG) { - Log.d(TAG, "Create BluetoothPbapClient proxy object"); - } - mAdapter = adapter; - mAttributionSource = adapter.getAttributionSource(); - mProfileConnector.connect(context, listener); - } - - protected void finalize() throws Throwable { - try { - close(); - } finally { - super.finalize(); - } - } - - /** - * Close the connection to the backing service. - * Other public functions of BluetoothPbapClient will return default error - * results once close() has been called. Multiple invocations of close() - * are ok. - */ - public synchronized void close() { - mProfileConnector.disconnect(); - } - - private IBluetoothPbapClient getService() { - return mProfileConnector.getService(); - } - - /** - * Initiate connection. - * Upon successful connection to remote PBAP server the Client will - * attempt to automatically download the users phonebook and call log. - * - * @param device a remote device we want connect to - * @return <code>true</code> if command has been issued successfully; <code>false</code> - * otherwise; - * - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean connect(BluetoothDevice device) { - if (DBG) { - log("connect(" + device + ") for PBAP Client."); - } - final IBluetoothPbapClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.connect(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Initiate disconnect. - * - * @param device Remote Bluetooth Device - * @return false on error, true otherwise - * - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean disconnect(BluetoothDevice device) { - if (DBG) { - log("disconnect(" + device + ")" + new Exception()); - } - final IBluetoothPbapClient service = getService(); - final boolean defaultValue = true; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.disconnect(device, mAttributionSource, recv); - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - return true; - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the list of connected devices. - * Currently at most one. - * - * @return list of connected devices - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getConnectedDevices() { - if (DBG) { - log("getConnectedDevices()"); - } - final IBluetoothPbapClient service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getConnectedDevices(mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the list of devices matching specified states. Currently at most one. - * - * @return list of matching devices - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { - if (DBG) { - log("getDevicesMatchingStates()"); - } - final IBluetoothPbapClient service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get connection state of device - * - * @return device connection state - */ - @Override - @RequiresBluetoothConnectPermission - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - public int getConnectionState(BluetoothDevice device) { - if (DBG) { - log("getConnectionState(" + device + ")"); - } - final IBluetoothPbapClient service = getService(); - final int defaultValue = BluetoothProfile.STATE_DISCONNECTED; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionState(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - private static void log(String msg) { - Log.d(TAG, msg); - } - - private boolean isEnabled() { - return mAdapter.isEnabled(); - } - - private static boolean isValidDevice(BluetoothDevice device) { - return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); - } - - /** - * Set priority of the profile - * - * <p> The device should already be paired. - * Priority can be one of {@link #PRIORITY_ON} or {@link #PRIORITY_OFF}, - * - * @param device Paired bluetooth device - * @param priority - * @return true if priority is set, false on error - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setPriority(BluetoothDevice device, int priority) { - if (DBG) log("setPriority(" + device + ", " + priority + ")"); - return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority)); - } - - /** - * Set connection policy of the profile - * - * <p> The device should already be paired. - * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED}, - * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Paired bluetooth device - * @param connectionPolicy is the connection policy to set to for this profile - * @return true if connectionPolicy is set, false on error - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setConnectionPolicy(@NonNull BluetoothDevice device, - @ConnectionPolicy int connectionPolicy) { - if (DBG) { - log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); - } - final IBluetoothPbapClient service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device) - && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN - || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the priority of the profile. - * - * <p> The priority can be any of: - * {@link #PRIORITY_OFF}, {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED} - * - * @param device Bluetooth device - * @return priority of the device - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public int getPriority(BluetoothDevice device) { - if (VDBG) log("getPriority(" + device + ")"); - return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device)); - } - - /** - * Get the connection policy of the profile. - * - * <p> The connection policy can be any of: - * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, - * {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Bluetooth device - * @return connection policy of the device - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) { - if (VDBG) { - log("getConnectionPolicy(" + device + ")"); - } - final IBluetoothPbapClient service = getService(); - final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionPolicy(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } -} diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java deleted file mode 100644 index d0f74e985729..000000000000 --- a/core/java/android/bluetooth/BluetoothProfile.java +++ /dev/null @@ -1,459 +0,0 @@ -/* - * Copyright (C) 2010-2016 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.bluetooth; - -import android.annotation.IntDef; -import android.annotation.RequiresNoPermission; -import android.annotation.SuppressLint; -import android.annotation.SystemApi; -import android.compat.annotation.UnsupportedAppUsage; -import android.os.Build; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.List; - -/** - * Public APIs for the Bluetooth Profiles. - * - * <p> Clients should call {@link BluetoothAdapter#getProfileProxy}, - * to get the Profile Proxy. Each public profile implements this - * interface. - */ -public interface BluetoothProfile { - - /** - * Extra for the connection state intents of the individual profiles. - * - * This extra represents the current connection state of the profile of the - * Bluetooth device. - */ - @SuppressLint("ActionValue") - String EXTRA_STATE = "android.bluetooth.profile.extra.STATE"; - - /** - * Extra for the connection state intents of the individual profiles. - * - * This extra represents the previous connection state of the profile of the - * Bluetooth device. - */ - @SuppressLint("ActionValue") - String EXTRA_PREVIOUS_STATE = - "android.bluetooth.profile.extra.PREVIOUS_STATE"; - - /** The profile is in disconnected state */ - int STATE_DISCONNECTED = 0; - /** The profile is in connecting state */ - int STATE_CONNECTING = 1; - /** The profile is in connected state */ - int STATE_CONNECTED = 2; - /** The profile is in disconnecting state */ - int STATE_DISCONNECTING = 3; - - /** @hide */ - @IntDef({ - STATE_DISCONNECTED, - STATE_CONNECTING, - STATE_CONNECTED, - STATE_DISCONNECTING, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface BtProfileState {} - - /** - * Headset and Handsfree profile - */ - int HEADSET = 1; - - /** - * A2DP profile. - */ - int A2DP = 2; - - /** - * Health Profile - * - * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New - * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt}, - * {@link BluetoothAdapter#listenUsingL2capChannel()}, or - * {@link BluetoothDevice#createL2capChannel(int)} - */ - @Deprecated - int HEALTH = 3; - - /** - * HID Host - * - * @hide - */ - int HID_HOST = 4; - - /** - * PAN Profile - * - * @hide - */ - @SystemApi - int PAN = 5; - - /** - * PBAP - * - * @hide - */ - int PBAP = 6; - - /** - * GATT - */ - int GATT = 7; - - /** - * GATT_SERVER - */ - int GATT_SERVER = 8; - - /** - * MAP Profile - * - * @hide - */ - int MAP = 9; - - /* - * SAP Profile - * @hide - */ - int SAP = 10; - - /** - * A2DP Sink Profile - * - * @hide - */ - @SystemApi - int A2DP_SINK = 11; - - /** - * AVRCP Controller Profile - * - * @hide - */ - @SystemApi - int AVRCP_CONTROLLER = 12; - - /** - * AVRCP Target Profile - * - * @hide - */ - int AVRCP = 13; - - /** - * Headset Client - HFP HF Role - * - * @hide - */ - @SystemApi - int HEADSET_CLIENT = 16; - - /** - * PBAP Client - * - * @hide - */ - @SystemApi - int PBAP_CLIENT = 17; - - /** - * MAP Messaging Client Equipment (MCE) - * - * @hide - */ - @SystemApi - int MAP_CLIENT = 18; - - /** - * HID Device - */ - int HID_DEVICE = 19; - - /** - * Object Push Profile (OPP) - * - * @hide - */ - int OPP = 20; - - /** - * Hearing Aid Device - * - */ - int HEARING_AID = 21; - - /** - * LE Audio Device - * - */ - int LE_AUDIO = 22; - - /** - * Volume Control profile - * - * @hide - */ - @SystemApi - int VOLUME_CONTROL = 23; - - /** - * @hide - * Media Control Profile server - * - */ - int MCP_SERVER = 24; - - /** - * Coordinated Set Identification Profile set coordinator - * - */ - int CSIP_SET_COORDINATOR = 25; - - /** - * LE Audio Broadcast Source - * - * @hide - */ - int LE_AUDIO_BROADCAST = 26; - - /** - * @hide - * Telephone Bearer Service from Call Control Profile - * - */ - int LE_CALL_CONTROL = 27; - - /** - * Max profile ID. This value should be updated whenever a new profile is added to match - * the largest value assigned to a profile. - * - * @hide - */ - int MAX_PROFILE_ID = 27; - - /** - * Default priority for devices that we try to auto-connect to and - * and allow incoming connections for the profile - * - * @hide - **/ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - int PRIORITY_AUTO_CONNECT = 1000; - - /** - * Default priority for devices that allow incoming - * and outgoing connections for the profile - * - * @hide - * @deprecated Replaced with {@link #CONNECTION_POLICY_ALLOWED} - **/ - @Deprecated - @SystemApi - int PRIORITY_ON = 100; - - /** - * Default priority for devices that does not allow incoming - * connections and outgoing connections for the profile. - * - * @hide - * @deprecated Replaced with {@link #CONNECTION_POLICY_FORBIDDEN} - **/ - @Deprecated - @SystemApi - int PRIORITY_OFF = 0; - - /** - * Default priority when not set or when the device is unpaired - * - * @hide - */ - @UnsupportedAppUsage - int PRIORITY_UNDEFINED = -1; - - /** @hide */ - @IntDef(prefix = "CONNECTION_POLICY_", value = {CONNECTION_POLICY_ALLOWED, - CONNECTION_POLICY_FORBIDDEN, CONNECTION_POLICY_UNKNOWN}) - @Retention(RetentionPolicy.SOURCE) - public @interface ConnectionPolicy{} - - /** - * Default connection policy for devices that allow incoming and outgoing connections - * for the profile - * - * @hide - **/ - @SystemApi - int CONNECTION_POLICY_ALLOWED = 100; - - /** - * Default connection policy for devices that do not allow incoming or outgoing connections - * for the profile. - * - * @hide - **/ - @SystemApi - int CONNECTION_POLICY_FORBIDDEN = 0; - - /** - * Default connection policy when not set or when the device is unpaired - * - * @hide - */ - @SystemApi - int CONNECTION_POLICY_UNKNOWN = -1; - - /** - * Get connected devices for this specific profile. - * - * <p> Return the set of devices which are in state {@link #STATE_CONNECTED} - * - * @return List of devices. The list will be empty on error. - */ - public List<BluetoothDevice> getConnectedDevices(); - - /** - * Get a list of devices that match any of the given connection - * states. - * - * <p> If none of the devices match any of the given states, - * an empty list will be returned. - * - * @param states Array of states. States can be one of {@link #STATE_CONNECTED}, {@link - * #STATE_CONNECTING}, {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING}, - * @return List of devices. The list will be empty on error. - */ - public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states); - - /** - * Get the current connection state of the profile - * - * @param device Remote bluetooth device. - * @return State of the profile connection. One of {@link #STATE_CONNECTED}, {@link - * #STATE_CONNECTING}, {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING} - */ - @BtProfileState int getConnectionState(BluetoothDevice device); - - /** - * An interface for notifying BluetoothProfile IPC clients when they have - * been connected or disconnected to the service. - */ - public interface ServiceListener { - /** - * Called to notify the client when the proxy object has been - * connected to the service. - * - * @param profile - One of {@link #HEADSET} or {@link #A2DP} - * @param proxy - One of {@link BluetoothHeadset} or {@link BluetoothA2dp} - */ - @RequiresNoPermission - public void onServiceConnected(int profile, BluetoothProfile proxy); - - /** - * Called to notify the client that this proxy object has been - * disconnected from the service. - * - * @param profile - One of {@link #HEADSET} or {@link #A2DP} - */ - @RequiresNoPermission - public void onServiceDisconnected(int profile); - } - - /** - * Convert an integer value of connection state into human readable string - * - * @param connectionState - One of {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, - * {@link #STATE_CONNECTED}, or {@link #STATE_DISCONNECTED} - * @return a string representation of the connection state, STATE_UNKNOWN if the state - * is not defined - * @hide - */ - static String getConnectionStateName(int connectionState) { - switch (connectionState) { - case STATE_DISCONNECTED: - return "STATE_DISCONNECTED"; - case STATE_CONNECTING: - return "STATE_CONNECTING"; - case STATE_CONNECTED: - return "STATE_CONNECTED"; - case STATE_DISCONNECTING: - return "STATE_DISCONNECTING"; - default: - return "STATE_UNKNOWN"; - } - } - - /** - * Convert an integer value of profile ID into human readable string - * - * @param profile profile ID - * @return profile name as String, UNKOWN_PROFILE if the profile ID is not defined. - * @hide - */ - static String getProfileName(int profile) { - switch(profile) { - case HEADSET: - return "HEADSET"; - case A2DP: - return "A2DP"; - case HID_HOST: - return "HID_HOST"; - case PAN: - return "PAN"; - case PBAP: - return "PBAP"; - case GATT: - return "GATT"; - case GATT_SERVER: - return "GATT_SERVER"; - case MAP: - return "MAP"; - case SAP: - return "SAP"; - case A2DP_SINK: - return "A2DP_SINK"; - case AVRCP_CONTROLLER: - return "AVRCP_CONTROLLER"; - case AVRCP: - return "AVRCP"; - case HEADSET_CLIENT: - return "HEADSET_CLIENT"; - case PBAP_CLIENT: - return "PBAP_CLIENT"; - case MAP_CLIENT: - return "MAP_CLIENT"; - case HID_DEVICE: - return "HID_DEVICE"; - case OPP: - return "OPP"; - case HEARING_AID: - return "HEARING_AID"; - case LE_AUDIO: - return "LE_AUDIO"; - default: - return "UNKNOWN_PROFILE"; - } - } -} diff --git a/core/java/android/bluetooth/BluetoothProfileConnector.java b/core/java/android/bluetooth/BluetoothProfileConnector.java deleted file mode 100644 index a457679716d3..000000000000 --- a/core/java/android/bluetooth/BluetoothProfileConnector.java +++ /dev/null @@ -1,220 +0,0 @@ -/* - * Copyright 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.bluetooth; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.SuppressLint; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.os.Build; -import android.os.IBinder; -import android.os.RemoteException; -import android.os.UserHandle; -import android.util.CloseGuard; -import android.util.Log; - -import java.util.List; -/** - * Connector for Bluetooth profile proxies to bind manager service and - * profile services - * @param <T> The Bluetooth profile interface for this connection. - * @hide - */ -@SuppressLint("AndroidFrameworkBluetoothPermission") -public abstract class BluetoothProfileConnector<T> { - private final CloseGuard mCloseGuard = new CloseGuard(); - private final int mProfileId; - private BluetoothProfile.ServiceListener mServiceListener; - private final BluetoothProfile mProfileProxy; - private Context mContext; - private final String mProfileName; - private final String mServiceName; - private volatile T mService; - - private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback = - new IBluetoothStateChangeCallback.Stub() { - public void onBluetoothStateChange(boolean up) { - if (up) { - doBind(); - } else { - doUnbind(); - } - } - }; - - private @Nullable ComponentName resolveSystemService(@NonNull Intent intent, - @NonNull PackageManager pm) { - List<ResolveInfo> results = pm.queryIntentServices(intent, - PackageManager.ResolveInfoFlags.of(0)); - if (results == null) { - return null; - } - ComponentName comp = null; - for (int i = 0; i < results.size(); i++) { - ResolveInfo ri = results.get(i); - if ((ri.serviceInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { - continue; - } - ComponentName foundComp = new ComponentName(ri.serviceInfo.applicationInfo.packageName, - ri.serviceInfo.name); - if (comp != null) { - throw new IllegalStateException("Multiple system services handle " + intent - + ": " + comp + ", " + foundComp); - } - comp = foundComp; - } - return comp; - } - - private final ServiceConnection mConnection = new ServiceConnection() { - public void onServiceConnected(ComponentName className, IBinder service) { - logDebug("Proxy object connected"); - mService = getServiceInterface(service); - - if (mServiceListener != null) { - mServiceListener.onServiceConnected(mProfileId, mProfileProxy); - } - } - - public void onServiceDisconnected(ComponentName className) { - logDebug("Proxy object disconnected"); - doUnbind(); - if (mServiceListener != null) { - mServiceListener.onServiceDisconnected(mProfileId); - } - } - }; - - BluetoothProfileConnector(BluetoothProfile profile, int profileId, String profileName, - String serviceName) { - mProfileId = profileId; - mProfileProxy = profile; - mProfileName = profileName; - mServiceName = serviceName; - } - - /** {@hide} */ - @Override - public void finalize() { - mCloseGuard.warnIfOpen(); - doUnbind(); - } - - @SuppressLint("AndroidFrameworkRequiresPermission") - private boolean doBind() { - synchronized (mConnection) { - if (mService == null) { - logDebug("Binding service..."); - mCloseGuard.open("doUnbind"); - try { - Intent intent = new Intent(mServiceName); - ComponentName comp = resolveSystemService(intent, mContext.getPackageManager()); - intent.setComponent(comp); - if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0, - UserHandle.CURRENT)) { - logError("Could not bind to Bluetooth Service with " + intent); - return false; - } - } catch (SecurityException se) { - logError("Failed to bind service. " + se); - return false; - } - } - } - return true; - } - - private void doUnbind() { - synchronized (mConnection) { - if (mService != null) { - logDebug("Unbinding service..."); - mCloseGuard.close(); - try { - mContext.unbindService(mConnection); - } catch (IllegalArgumentException ie) { - logError("Unable to unbind service: " + ie); - } finally { - mService = null; - } - } - } - } - - void connect(Context context, BluetoothProfile.ServiceListener listener) { - mContext = context; - mServiceListener = listener; - IBluetoothManager mgr = BluetoothAdapter.getDefaultAdapter().getBluetoothManager(); - - // Preserve legacy compatibility where apps were depending on - // registerStateChangeCallback() performing a permissions check which - // has been relaxed in modern platform versions - if (context.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.R - && context.checkSelfPermission(android.Manifest.permission.BLUETOOTH) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Need BLUETOOTH permission"); - } - - if (mgr != null) { - try { - mgr.registerStateChangeCallback(mBluetoothStateChangeCallback); - } catch (RemoteException re) { - logError("Failed to register state change callback. " + re); - } - } - doBind(); - } - - void disconnect() { - mServiceListener = null; - IBluetoothManager mgr = BluetoothAdapter.getDefaultAdapter().getBluetoothManager(); - if (mgr != null) { - try { - mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback); - } catch (RemoteException re) { - logError("Failed to unregister state change callback" + re); - } - } - doUnbind(); - } - - T getService() { - return mService; - } - - /** - * This abstract function is used to implement method to get the - * connected Bluetooth service interface. - * @param service the connected binder service. - * @return T the binder interface of {@code service}. - * @hide - */ - public abstract T getServiceInterface(IBinder service); - - private void logDebug(String log) { - Log.d(mProfileName, log); - } - - private void logError(String log) { - Log.e(mProfileName, log); - } -} diff --git a/core/java/android/bluetooth/BluetoothSap.java b/core/java/android/bluetooth/BluetoothSap.java deleted file mode 100644 index 808fa3913316..000000000000 --- a/core/java/android/bluetooth/BluetoothSap.java +++ /dev/null @@ -1,491 +0,0 @@ -/* - * Copyright (C) 2008 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.bluetooth; - -import static android.bluetooth.BluetoothUtils.getSyncTimeout; - -import android.Manifest; -import android.annotation.RequiresNoPermission; -import android.annotation.RequiresPermission; -import android.annotation.SdkConstant; -import android.annotation.SdkConstant.SdkConstantType; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; -import android.compat.annotation.UnsupportedAppUsage; -import android.content.AttributionSource; -import android.content.Context; -import android.os.Build; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.Log; - -import com.android.modules.utils.SynchronousResultReceiver; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeoutException; - -/** - * This class provides the APIs to control the Bluetooth SIM - * Access Profile (SAP). - * - * <p>BluetoothSap is a proxy object for controlling the Bluetooth - * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get - * the BluetoothSap proxy object. - * - * <p>Each method is protected with its appropriate permission. - * - * @hide - */ -public final class BluetoothSap implements BluetoothProfile { - - private static final String TAG = "BluetoothSap"; - private static final boolean DBG = true; - private static final boolean VDBG = false; - - /** - * Intent used to broadcast the change in connection state of the profile. - * - * <p>This intent will have 4 extras: - * <ul> - * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> - * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> - * </ul> - * - * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of - * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, - * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. - * - * @hide - */ - @RequiresLegacyBluetoothPermission - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_CONNECTION_STATE_CHANGED = - "android.bluetooth.sap.profile.action.CONNECTION_STATE_CHANGED"; - - /** - * There was an error trying to obtain the state. - * - * @hide - */ - public static final int STATE_ERROR = -1; - - /** - * Connection state change succceeded. - * - * @hide - */ - public static final int RESULT_SUCCESS = 1; - - /** - * Connection canceled before completion. - * - * @hide - */ - public static final int RESULT_CANCELED = 2; - - private final BluetoothAdapter mAdapter; - private final AttributionSource mAttributionSource; - private final BluetoothProfileConnector<IBluetoothSap> mProfileConnector = - new BluetoothProfileConnector(this, BluetoothProfile.SAP, - "BluetoothSap", IBluetoothSap.class.getName()) { - @Override - public IBluetoothSap getServiceInterface(IBinder service) { - return IBluetoothSap.Stub.asInterface(service); - } - }; - - /** - * Create a BluetoothSap proxy object. - */ - /* package */ BluetoothSap(Context context, ServiceListener listener, - BluetoothAdapter adapter) { - if (DBG) Log.d(TAG, "Create BluetoothSap proxy object"); - mAdapter = adapter; - mAttributionSource = adapter.getAttributionSource(); - mProfileConnector.connect(context, listener); - } - - protected void finalize() throws Throwable { - try { - close(); - } finally { - super.finalize(); - } - } - - /** - * Close the connection to the backing service. - * Other public functions of BluetoothSap will return default error - * results once close() has been called. Multiple invocations of close() - * are ok. - * - * @hide - */ - public synchronized void close() { - mProfileConnector.disconnect(); - } - - private IBluetoothSap getService() { - return mProfileConnector.getService(); - } - - /** - * Get the current state of the BluetoothSap service. - * - * @return One of the STATE_ return codes, or STATE_ERROR if this proxy object is currently not - * connected to the Sap service. - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public int getState() { - if (VDBG) log("getState()"); - final IBluetoothSap service = getService(); - final int defaultValue = BluetoothSap.STATE_ERROR; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getState(mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the currently connected remote Bluetooth device (PCE). - * - * @return The remote Bluetooth device, or null if not in connected or connecting state, or if - * this proxy object is not connected to the Sap service. - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public BluetoothDevice getClient() { - if (VDBG) log("getClient()"); - final IBluetoothSap service = getService(); - final BluetoothDevice defaultValue = null; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<BluetoothDevice> recv = - new SynchronousResultReceiver(); - service.getClient(mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Returns true if the specified Bluetooth device is connected. - * Returns false if not connected, or if this proxy object is not - * currently connected to the Sap service. - * - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean isConnected(BluetoothDevice device) { - if (VDBG) log("isConnected(" + device + ")"); - final IBluetoothSap service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.isConnected(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Initiate connection. Initiation of outgoing connections is not - * supported for SAP server. - * - * @hide - */ - @RequiresNoPermission - public boolean connect(BluetoothDevice device) { - if (DBG) log("connect(" + device + ")" + "not supported for SAPS"); - return false; - } - - /** - * Initiate disconnect. - * - * @param device Remote Bluetooth Device - * @return false on error, true otherwise - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public boolean disconnect(BluetoothDevice device) { - if (DBG) log("disconnect(" + device + ")"); - final IBluetoothSap service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.disconnect(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the list of connected devices. Currently at most one. - * - * @return list of connected devices - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getConnectedDevices() { - if (DBG) log("getConnectedDevices()"); - final IBluetoothSap service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getConnectedDevices(mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the list of devices matching specified states. Currently at most one. - * - * @return list of matching devices - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { - if (DBG) log("getDevicesMatchingStates()"); - final IBluetoothSap service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get connection state of device - * - * @return device connection state - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - public int getConnectionState(BluetoothDevice device) { - if (DBG) log("getConnectionState(" + device + ")"); - final IBluetoothSap service = getService(); - final int defaultValue = BluetoothProfile.STATE_DISCONNECTED; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionState(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Set priority of the profile - * - * <p> The device should already be paired. - * Priority can be one of {@link #PRIORITY_ON} or {@link #PRIORITY_OFF}, - * - * @param device Paired bluetooth device - * @param priority - * @return true if priority is set, false on error - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setPriority(BluetoothDevice device, int priority) { - if (DBG) log("setPriority(" + device + ", " + priority + ")"); - return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority)); - } - - /** - * Set connection policy of the profile - * - * <p> The device should already be paired. - * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED}, - * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Paired bluetooth device - * @param connectionPolicy is the connection policy to set to for this profile - * @return true if connectionPolicy is set, false on error - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setConnectionPolicy(BluetoothDevice device, - @ConnectionPolicy int connectionPolicy) { - if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); - final IBluetoothSap service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device) - && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN - || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the priority of the profile. - * - * <p> The priority can be any of: - * {@link #PRIORITY_OFF}, {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED} - * - * @param device Bluetooth device - * @return priority of the device - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public int getPriority(BluetoothDevice device) { - if (VDBG) log("getPriority(" + device + ")"); - return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device)); - } - - /** - * Get the connection policy of the profile. - * - * <p> The connection policy can be any of: - * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, - * {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Bluetooth device - * @return connection policy of the device - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public @ConnectionPolicy int getConnectionPolicy(BluetoothDevice device) { - if (VDBG) log("getConnectionPolicy(" + device + ")"); - final IBluetoothSap service = getService(); - final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionPolicy(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - private static void log(String msg) { - Log.d(TAG, msg); - } - - private boolean isEnabled() { - return mAdapter.isEnabled(); - } - - private static boolean isValidDevice(BluetoothDevice device) { - return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); - } -} diff --git a/core/java/android/bluetooth/BluetoothServerSocket.java b/core/java/android/bluetooth/BluetoothServerSocket.java deleted file mode 100644 index bb4e35483fea..000000000000 --- a/core/java/android/bluetooth/BluetoothServerSocket.java +++ /dev/null @@ -1,266 +0,0 @@ -/* - * Copyright (C) 2009 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.bluetooth; - -import android.annotation.SuppressLint; -import android.compat.annotation.UnsupportedAppUsage; -import android.os.Handler; -import android.os.ParcelUuid; -import android.util.Log; - -import java.io.Closeable; -import java.io.IOException; - -/** - * A listening Bluetooth socket. - * - * <p>The interface for Bluetooth Sockets is similar to that of TCP sockets: - * {@link java.net.Socket} and {@link java.net.ServerSocket}. On the server - * side, use a {@link BluetoothServerSocket} to create a listening server - * socket. When a connection is accepted by the {@link BluetoothServerSocket}, - * it will return a new {@link BluetoothSocket} to manage the connection. - * On the client side, use a single {@link BluetoothSocket} to both initiate - * an outgoing connection and to manage the connection. - * - * <p>For Bluetooth BR/EDR, the most common type of socket is RFCOMM, which is the type supported by - * the Android APIs. RFCOMM is a connection-oriented, streaming transport over Bluetooth BR/EDR. It - * is also known as the Serial Port Profile (SPP). To create a listening - * {@link BluetoothServerSocket} that's ready for incoming Bluetooth BR/EDR connections, use {@link - * BluetoothAdapter#listenUsingRfcommWithServiceRecord - * BluetoothAdapter.listenUsingRfcommWithServiceRecord()}. - * - * <p>For Bluetooth LE, the socket uses LE Connection-oriented Channel (CoC). LE CoC is a - * connection-oriented, streaming transport over Bluetooth LE and has a credit-based flow control. - * Correspondingly, use {@link BluetoothAdapter#listenUsingL2capChannel - * BluetoothAdapter.listenUsingL2capChannel()} to create a listening {@link BluetoothServerSocket} - * that's ready for incoming Bluetooth LE CoC connections. For LE CoC, you can use {@link #getPsm()} - * to get the protocol/service multiplexer (PSM) value that the peer needs to use to connect to your - * socket. - * - * <p> After the listening {@link BluetoothServerSocket} is created, call {@link #accept()} to - * listen for incoming connection requests. This call will block until a connection is established, - * at which point, it will return a {@link BluetoothSocket} to manage the connection. Once the - * {@link BluetoothSocket} is acquired, it's a good idea to call {@link #close()} on the {@link - * BluetoothServerSocket} when it's no longer needed for accepting - * connections. Closing the {@link BluetoothServerSocket} will <em>not</em> close the returned - * {@link BluetoothSocket}. - * - * <p>{@link BluetoothServerSocket} is thread - * safe. In particular, {@link #close} will always immediately abort ongoing - * operations and close the server socket. - * - * <div class="special reference"> - * <h3>Developer Guides</h3> - * <p>For more information about using Bluetooth, read the - * <a href="{@docRoot}guide/topics/connectivity/bluetooth.html">Bluetooth</a> developer guide.</p> - * </div> - * - * {@see BluetoothSocket} - */ -@SuppressLint("AndroidFrameworkBluetoothPermission") -public final class BluetoothServerSocket implements Closeable { - - private static final String TAG = "BluetoothServerSocket"; - private static final boolean DBG = false; - @UnsupportedAppUsage(publicAlternatives = "Use public {@link BluetoothServerSocket} API " - + "instead.") - /*package*/ final BluetoothSocket mSocket; - private Handler mHandler; - private int mMessage; - private int mChannel; - - /** - * Construct a socket for incoming connections. - * - * @param type type of socket - * @param auth require the remote device to be authenticated - * @param encrypt require the connection to be encrypted - * @param port remote port - * @throws IOException On error, for example Bluetooth not available, or insufficient - * privileges - */ - /*package*/ BluetoothServerSocket(int type, boolean auth, boolean encrypt, int port) - throws IOException { - mChannel = port; - mSocket = new BluetoothSocket(type, -1, auth, encrypt, null, port, null); - if (port == BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) { - mSocket.setExcludeSdp(true); - } - } - - /** - * Construct a socket for incoming connections. - * - * @param type type of socket - * @param auth require the remote device to be authenticated - * @param encrypt require the connection to be encrypted - * @param port remote port - * @param mitm enforce person-in-the-middle protection for authentication. - * @param min16DigitPin enforce a minimum length of 16 digits for a sec mode 2 connection - * @throws IOException On error, for example Bluetooth not available, or insufficient - * privileges - */ - /*package*/ BluetoothServerSocket(int type, boolean auth, boolean encrypt, int port, - boolean mitm, boolean min16DigitPin) - throws IOException { - mChannel = port; - mSocket = new BluetoothSocket(type, -1, auth, encrypt, null, port, null, mitm, - min16DigitPin); - if (port == BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) { - mSocket.setExcludeSdp(true); - } - } - - /** - * Construct a socket for incoming connections. - * - * @param type type of socket - * @param auth require the remote device to be authenticated - * @param encrypt require the connection to be encrypted - * @param uuid uuid - * @throws IOException On error, for example Bluetooth not available, or insufficient - * privileges - */ - /*package*/ BluetoothServerSocket(int type, boolean auth, boolean encrypt, ParcelUuid uuid) - throws IOException { - mSocket = new BluetoothSocket(type, -1, auth, encrypt, null, -1, uuid); - // TODO: This is the same as mChannel = -1 - is this intentional? - mChannel = mSocket.getPort(); - } - - - /** - * Block until a connection is established. - * <p>Returns a connected {@link BluetoothSocket} on successful connection. - * <p>Once this call returns, it can be called again to accept subsequent - * incoming connections. - * <p>{@link #close} can be used to abort this call from another thread. - * - * @return a connected {@link BluetoothSocket} - * @throws IOException on error, for example this call was aborted, or timeout - */ - public BluetoothSocket accept() throws IOException { - return accept(-1); - } - - /** - * Block until a connection is established, with timeout. - * <p>Returns a connected {@link BluetoothSocket} on successful connection. - * <p>Once this call returns, it can be called again to accept subsequent - * incoming connections. - * <p>{@link #close} can be used to abort this call from another thread. - * - * @return a connected {@link BluetoothSocket} - * @throws IOException on error, for example this call was aborted, or timeout - */ - public BluetoothSocket accept(int timeout) throws IOException { - return mSocket.accept(timeout); - } - - /** - * Immediately close this socket, and release all associated resources. - * <p>Causes blocked calls on this socket in other threads to immediately - * throw an IOException. - * <p>Closing the {@link BluetoothServerSocket} will <em>not</em> - * close any {@link BluetoothSocket} received from {@link #accept()}. - */ - public void close() throws IOException { - if (DBG) Log.d(TAG, "BluetoothServerSocket:close() called. mChannel=" + mChannel); - synchronized (this) { - if (mHandler != null) { - mHandler.obtainMessage(mMessage).sendToTarget(); - } - } - mSocket.close(); - } - - /*package*/ - synchronized void setCloseHandler(Handler handler, int message) { - mHandler = handler; - mMessage = message; - } - - /*package*/ void setServiceName(String serviceName) { - mSocket.setServiceName(serviceName); - } - - /** - * Returns the channel on which this socket is bound. - * - * @hide - */ - public int getChannel() { - return mChannel; - } - - /** - * Returns the assigned dynamic protocol/service multiplexer (PSM) value for the listening L2CAP - * Connection-oriented Channel (CoC) server socket. This server socket must be returned by the - * {@link BluetoothAdapter#listenUsingL2capChannel()} or {@link - * BluetoothAdapter#listenUsingInsecureL2capChannel()}. The returned value is undefined if this - * method is called on non-L2CAP server sockets. - * - * @return the assigned PSM or LE_PSM value depending on transport - */ - public int getPsm() { - return mChannel; - } - - /** - * Sets the channel on which future sockets are bound. - * Currently used only when a channel is auto generated. - */ - /*package*/ void setChannel(int newChannel) { - /* TODO: From a design/architecture perspective this is wrong. - * The bind operation should be conducted through this class - * and the resulting port should be kept in mChannel, and - * not set from BluetoothAdapter. */ - if (mSocket != null) { - if (mSocket.getPort() != newChannel) { - Log.w(TAG, "The port set is different that the underlying port. mSocket.getPort(): " - + mSocket.getPort() + " requested newChannel: " + newChannel); - } - } - mChannel = newChannel; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("ServerSocket: Type: "); - switch (mSocket.getConnectionType()) { - case BluetoothSocket.TYPE_RFCOMM: { - sb.append("TYPE_RFCOMM"); - break; - } - case BluetoothSocket.TYPE_L2CAP: { - sb.append("TYPE_L2CAP"); - break; - } - case BluetoothSocket.TYPE_L2CAP_LE: { - sb.append("TYPE_L2CAP_LE"); - break; - } - case BluetoothSocket.TYPE_SCO: { - sb.append("TYPE_SCO"); - break; - } - } - sb.append(" Channel: ").append(mChannel); - return sb.toString(); - } -} diff --git a/core/java/android/bluetooth/BluetoothSocket.java b/core/java/android/bluetooth/BluetoothSocket.java deleted file mode 100644 index db5b75148e88..000000000000 --- a/core/java/android/bluetooth/BluetoothSocket.java +++ /dev/null @@ -1,809 +0,0 @@ -/* - * Copyright (C) 2012 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.bluetooth; - -import android.annotation.RequiresNoPermission; -import android.annotation.RequiresPermission; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.compat.annotation.UnsupportedAppUsage; -import android.net.LocalSocket; -import android.os.Build; -import android.os.ParcelFileDescriptor; -import android.os.ParcelUuid; -import android.os.RemoteException; -import android.util.Log; - -import java.io.Closeable; -import java.io.FileDescriptor; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Arrays; -import java.util.Locale; -import java.util.UUID; - -/** - * A connected or connecting Bluetooth socket. - * - * <p>The interface for Bluetooth Sockets is similar to that of TCP sockets: - * {@link java.net.Socket} and {@link java.net.ServerSocket}. On the server - * side, use a {@link BluetoothServerSocket} to create a listening server - * socket. When a connection is accepted by the {@link BluetoothServerSocket}, - * it will return a new {@link BluetoothSocket} to manage the connection. - * On the client side, use a single {@link BluetoothSocket} to both initiate - * an outgoing connection and to manage the connection. - * - * <p>The most common type of Bluetooth socket is RFCOMM, which is the type - * supported by the Android APIs. RFCOMM is a connection-oriented, streaming - * transport over Bluetooth. It is also known as the Serial Port Profile (SPP). - * - * <p>To create a {@link BluetoothSocket} for connecting to a known device, use - * {@link BluetoothDevice#createRfcommSocketToServiceRecord - * BluetoothDevice.createRfcommSocketToServiceRecord()}. - * Then call {@link #connect()} to attempt a connection to the remote device. - * This call will block until a connection is established or the connection - * fails. - * - * <p>To create a {@link BluetoothSocket} as a server (or "host"), see the - * {@link BluetoothServerSocket} documentation. - * - * <p>Once the socket is connected, whether initiated as a client or accepted - * as a server, open the IO streams by calling {@link #getInputStream} and - * {@link #getOutputStream} in order to retrieve {@link java.io.InputStream} - * and {@link java.io.OutputStream} objects, respectively, which are - * automatically connected to the socket. - * - * <p>{@link BluetoothSocket} is thread - * safe. In particular, {@link #close} will always immediately abort ongoing - * operations and close the socket. - * - * <div class="special reference"> - * <h3>Developer Guides</h3> - * <p>For more information about using Bluetooth, read the - * <a href="{@docRoot}guide/topics/connectivity/bluetooth.html">Bluetooth</a> developer guide.</p> - * </div> - * - * {@see BluetoothServerSocket} - * {@see java.io.InputStream} - * {@see java.io.OutputStream} - */ -public final class BluetoothSocket implements Closeable { - private static final String TAG = "BluetoothSocket"; - private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); - private static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE); - - /** @hide */ - public static final int MAX_RFCOMM_CHANNEL = 30; - /*package*/ static final int MAX_L2CAP_PACKAGE_SIZE = 0xFFFF; - - /** RFCOMM socket */ - public static final int TYPE_RFCOMM = 1; - - /** SCO socket */ - public static final int TYPE_SCO = 2; - - /** L2CAP socket */ - public static final int TYPE_L2CAP = 3; - - /** L2CAP socket on BR/EDR transport - * @hide - */ - public static final int TYPE_L2CAP_BREDR = TYPE_L2CAP; - - /** L2CAP socket on LE transport - * @hide - */ - public static final int TYPE_L2CAP_LE = 4; - - /*package*/ static final int EBADFD = 77; - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - /*package*/ static final int EADDRINUSE = 98; - - /*package*/ static final int SEC_FLAG_ENCRYPT = 1; - /*package*/ static final int SEC_FLAG_AUTH = 1 << 1; - /*package*/ static final int BTSOCK_FLAG_NO_SDP = 1 << 2; - /*package*/ static final int SEC_FLAG_AUTH_MITM = 1 << 3; - /*package*/ static final int SEC_FLAG_AUTH_16_DIGIT = 1 << 4; - - private final int mType; /* one of TYPE_RFCOMM etc */ - private BluetoothDevice mDevice; /* remote device */ - private String mAddress; /* remote address */ - private final boolean mAuth; - private final boolean mEncrypt; - private final BluetoothInputStream mInputStream; - private final BluetoothOutputStream mOutputStream; - private final ParcelUuid mUuid; - /** when true no SPP SDP record will be created */ - private boolean mExcludeSdp = false; - /** when true Person-in-the-middle protection will be enabled */ - private boolean mAuthMitm = false; - /** Minimum 16 digit pin for sec mode 2 connections */ - private boolean mMin16DigitPin = false; - @UnsupportedAppUsage(publicAlternatives = "Use {@link BluetoothSocket} public API instead.") - private ParcelFileDescriptor mPfd; - @UnsupportedAppUsage - private LocalSocket mSocket; - private InputStream mSocketIS; - private OutputStream mSocketOS; - @UnsupportedAppUsage - private int mPort; /* RFCOMM channel or L2CAP psm */ - private int mFd; - private String mServiceName; - private static final int PROXY_CONNECTION_TIMEOUT = 5000; - - private static final int SOCK_SIGNAL_SIZE = 20; - - private ByteBuffer mL2capBuffer = null; - private int mMaxTxPacketSize = 0; // The l2cap maximum packet size supported by the peer. - private int mMaxRxPacketSize = 0; // The l2cap maximum packet size that can be received. - - private enum SocketState { - INIT, - CONNECTED, - LISTENING, - CLOSED, - } - - /** prevents all native calls after destroyNative() */ - private volatile SocketState mSocketState; - - /** protects mSocketState */ - //private final ReentrantReadWriteLock mLock; - - /** - * Construct a BluetoothSocket. - * - * @param type type of socket - * @param fd fd to use for connected socket, or -1 for a new socket - * @param auth require the remote device to be authenticated - * @param encrypt require the connection to be encrypted - * @param device remote device that this socket can connect to - * @param port remote port - * @param uuid SDP uuid - * @throws IOException On error, for example Bluetooth not available, or insufficient - * privileges - */ - /*package*/ BluetoothSocket(int type, int fd, boolean auth, boolean encrypt, - BluetoothDevice device, int port, ParcelUuid uuid) throws IOException { - this(type, fd, auth, encrypt, device, port, uuid, false, false); - } - - /** - * Construct a BluetoothSocket. - * - * @param type type of socket - * @param fd fd to use for connected socket, or -1 for a new socket - * @param auth require the remote device to be authenticated - * @param encrypt require the connection to be encrypted - * @param device remote device that this socket can connect to - * @param port remote port - * @param uuid SDP uuid - * @param mitm enforce person-in-the-middle protection. - * @param min16DigitPin enforce a minimum length of 16 digits for a sec mode 2 connection - * @throws IOException On error, for example Bluetooth not available, or insufficient - * privileges - */ - /*package*/ BluetoothSocket(int type, int fd, boolean auth, boolean encrypt, - BluetoothDevice device, int port, ParcelUuid uuid, boolean mitm, boolean min16DigitPin) - throws IOException { - if (VDBG) Log.d(TAG, "Creating new BluetoothSocket of type: " + type); - if (type == BluetoothSocket.TYPE_RFCOMM && uuid == null && fd == -1 - && port != BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) { - if (port < 1 || port > MAX_RFCOMM_CHANNEL) { - throw new IOException("Invalid RFCOMM channel: " + port); - } - } - if (uuid != null) { - mUuid = uuid; - } else { - mUuid = new ParcelUuid(new UUID(0, 0)); - } - mType = type; - mAuth = auth; - mAuthMitm = mitm; - mMin16DigitPin = min16DigitPin; - mEncrypt = encrypt; - mDevice = device; - mPort = port; - mFd = fd; - - mSocketState = SocketState.INIT; - - if (device == null) { - // Server socket - mAddress = BluetoothAdapter.getDefaultAdapter().getAddress(); - } else { - // Remote socket - mAddress = device.getAddress(); - } - mInputStream = new BluetoothInputStream(this); - mOutputStream = new BluetoothOutputStream(this); - } - - private BluetoothSocket(BluetoothSocket s) { - if (VDBG) Log.d(TAG, "Creating new Private BluetoothSocket of type: " + s.mType); - mUuid = s.mUuid; - mType = s.mType; - mAuth = s.mAuth; - mEncrypt = s.mEncrypt; - mPort = s.mPort; - mInputStream = new BluetoothInputStream(this); - mOutputStream = new BluetoothOutputStream(this); - mMaxRxPacketSize = s.mMaxRxPacketSize; - mMaxTxPacketSize = s.mMaxTxPacketSize; - - mServiceName = s.mServiceName; - mExcludeSdp = s.mExcludeSdp; - mAuthMitm = s.mAuthMitm; - mMin16DigitPin = s.mMin16DigitPin; - } - - private BluetoothSocket acceptSocket(String remoteAddr) throws IOException { - BluetoothSocket as = new BluetoothSocket(this); - as.mSocketState = SocketState.CONNECTED; - FileDescriptor[] fds = mSocket.getAncillaryFileDescriptors(); - if (DBG) Log.d(TAG, "socket fd passed by stack fds: " + Arrays.toString(fds)); - if (fds == null || fds.length != 1) { - Log.e(TAG, "socket fd passed from stack failed, fds: " + Arrays.toString(fds)); - as.close(); - throw new IOException("bt socket acept failed"); - } - - as.mPfd = ParcelFileDescriptor.dup(fds[0]); - as.mSocket = LocalSocket.createConnectedLocalSocket(fds[0]); - as.mSocketIS = as.mSocket.getInputStream(); - as.mSocketOS = as.mSocket.getOutputStream(); - as.mAddress = remoteAddr; - as.mDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(remoteAddr); - return as; - } - - /** - * Construct a BluetoothSocket from address. Used by native code. - * - * @param type type of socket - * @param fd fd to use for connected socket, or -1 for a new socket - * @param auth require the remote device to be authenticated - * @param encrypt require the connection to be encrypted - * @param address remote device that this socket can connect to - * @param port remote port - * @throws IOException On error, for example Bluetooth not available, or insufficient - * privileges - */ - private BluetoothSocket(int type, int fd, boolean auth, boolean encrypt, String address, - int port) throws IOException { - this(type, fd, auth, encrypt, new BluetoothDevice(address), port, null, false, false); - } - - /** @hide */ - @Override - protected void finalize() throws Throwable { - try { - close(); - } finally { - super.finalize(); - } - } - - private int getSecurityFlags() { - int flags = 0; - if (mAuth) { - flags |= SEC_FLAG_AUTH; - } - if (mEncrypt) { - flags |= SEC_FLAG_ENCRYPT; - } - if (mExcludeSdp) { - flags |= BTSOCK_FLAG_NO_SDP; - } - if (mAuthMitm) { - flags |= SEC_FLAG_AUTH_MITM; - } - if (mMin16DigitPin) { - flags |= SEC_FLAG_AUTH_16_DIGIT; - } - return flags; - } - - /** - * Get the remote device this socket is connecting, or connected, to. - * - * @return remote device - */ - @RequiresNoPermission - public BluetoothDevice getRemoteDevice() { - return mDevice; - } - - /** - * Get the input stream associated with this socket. - * <p>The input stream will be returned even if the socket is not yet - * connected, but operations on that stream will throw IOException until - * the associated socket is connected. - * - * @return InputStream - */ - @RequiresNoPermission - public InputStream getInputStream() throws IOException { - return mInputStream; - } - - /** - * Get the output stream associated with this socket. - * <p>The output stream will be returned even if the socket is not yet - * connected, but operations on that stream will throw IOException until - * the associated socket is connected. - * - * @return OutputStream - */ - @RequiresNoPermission - public OutputStream getOutputStream() throws IOException { - return mOutputStream; - } - - /** - * Get the connection status of this socket, ie, whether there is an active connection with - * remote device. - * - * @return true if connected false if not connected - */ - @RequiresNoPermission - public boolean isConnected() { - return mSocketState == SocketState.CONNECTED; - } - - /*package*/ void setServiceName(String name) { - mServiceName = name; - } - - /** - * Attempt to connect to a remote device. - * <p>This method will block until a connection is made or the connection - * fails. If this method returns without an exception then this socket - * is now connected. - * <p>Creating new connections to - * remote Bluetooth devices should not be attempted while device discovery - * is in progress. Device discovery is a heavyweight procedure on the - * Bluetooth adapter and will significantly slow a device connection. - * Use {@link BluetoothAdapter#cancelDiscovery()} to cancel an ongoing - * discovery. Discovery is not managed by the Activity, - * but is run as a system service, so an application should always call - * {@link BluetoothAdapter#cancelDiscovery()} even if it - * did not directly request a discovery, just to be sure. - * <p>{@link #close} can be used to abort this call from another thread. - * - * @throws IOException on error, for example connection failure - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public void connect() throws IOException { - if (mDevice == null) throw new IOException("Connect is called on null device"); - - try { - if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed"); - IBluetooth bluetoothProxy = - BluetoothAdapter.getDefaultAdapter().getBluetoothService(); - if (bluetoothProxy == null) throw new IOException("Bluetooth is off"); - mPfd = bluetoothProxy.getSocketManager().connectSocket(mDevice, mType, - mUuid, mPort, getSecurityFlags()); - synchronized (this) { - if (DBG) Log.d(TAG, "connect(), SocketState: " + mSocketState + ", mPfd: " + mPfd); - if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed"); - if (mPfd == null) throw new IOException("bt socket connect failed"); - FileDescriptor fd = mPfd.getFileDescriptor(); - mSocket = LocalSocket.createConnectedLocalSocket(fd); - mSocketIS = mSocket.getInputStream(); - mSocketOS = mSocket.getOutputStream(); - } - int channel = readInt(mSocketIS); - if (channel <= 0) { - throw new IOException("bt socket connect failed"); - } - mPort = channel; - waitSocketSignal(mSocketIS); - synchronized (this) { - if (mSocketState == SocketState.CLOSED) { - throw new IOException("bt socket closed"); - } - mSocketState = SocketState.CONNECTED; - } - } catch (RemoteException e) { - Log.e(TAG, Log.getStackTraceString(new Throwable())); - throw new IOException("unable to send RPC: " + e.getMessage()); - } - } - - /** - * Currently returns unix errno instead of throwing IOException, - * so that BluetoothAdapter can check the error code for EADDRINUSE - */ - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - /*package*/ int bindListen() { - int ret; - if (mSocketState == SocketState.CLOSED) return EBADFD; - IBluetooth bluetoothProxy = BluetoothAdapter.getDefaultAdapter().getBluetoothService(); - if (bluetoothProxy == null) { - Log.e(TAG, "bindListen fail, reason: bluetooth is off"); - return -1; - } - try { - if (DBG) Log.d(TAG, "bindListen(): mPort=" + mPort + ", mType=" + mType); - mPfd = bluetoothProxy.getSocketManager().createSocketChannel(mType, mServiceName, - mUuid, mPort, getSecurityFlags()); - } catch (RemoteException e) { - Log.e(TAG, Log.getStackTraceString(new Throwable())); - return -1; - } - - // read out port number - try { - synchronized (this) { - if (DBG) { - Log.d(TAG, "bindListen(), SocketState: " + mSocketState + ", mPfd: " + mPfd); - } - if (mSocketState != SocketState.INIT) return EBADFD; - if (mPfd == null) return -1; - FileDescriptor fd = mPfd.getFileDescriptor(); - if (fd == null) { - Log.e(TAG, "bindListen(), null file descriptor"); - return -1; - } - - if (DBG) Log.d(TAG, "bindListen(), Create LocalSocket"); - mSocket = LocalSocket.createConnectedLocalSocket(fd); - if (DBG) Log.d(TAG, "bindListen(), new LocalSocket.getInputStream()"); - mSocketIS = mSocket.getInputStream(); - mSocketOS = mSocket.getOutputStream(); - } - if (DBG) Log.d(TAG, "bindListen(), readInt mSocketIS: " + mSocketIS); - int channel = readInt(mSocketIS); - synchronized (this) { - if (mSocketState == SocketState.INIT) { - mSocketState = SocketState.LISTENING; - } - } - if (DBG) Log.d(TAG, "bindListen(): channel=" + channel + ", mPort=" + mPort); - if (mPort <= -1) { - mPort = channel; - } // else ASSERT(mPort == channel) - ret = 0; - } catch (IOException e) { - if (mPfd != null) { - try { - mPfd.close(); - } catch (IOException e1) { - Log.e(TAG, "bindListen, close mPfd: " + e1); - } - mPfd = null; - } - Log.e(TAG, "bindListen, fail to get port number, exception: " + e); - return -1; - } - return ret; - } - - /*package*/ BluetoothSocket accept(int timeout) throws IOException { - BluetoothSocket acceptedSocket; - if (mSocketState != SocketState.LISTENING) { - throw new IOException("bt socket is not in listen state"); - } - if (timeout > 0) { - Log.d(TAG, "accept() set timeout (ms):" + timeout); - mSocket.setSoTimeout(timeout); - } - String RemoteAddr = waitSocketSignal(mSocketIS); - if (timeout > 0) { - mSocket.setSoTimeout(0); - } - synchronized (this) { - if (mSocketState != SocketState.LISTENING) { - throw new IOException("bt socket is not in listen state"); - } - acceptedSocket = acceptSocket(RemoteAddr); - //quick drop the reference of the file handle - } - return acceptedSocket; - } - - /*package*/ int available() throws IOException { - if (VDBG) Log.d(TAG, "available: " + mSocketIS); - return mSocketIS.available(); - } - - /*package*/ int read(byte[] b, int offset, int length) throws IOException { - int ret = 0; - if (VDBG) Log.d(TAG, "read in: " + mSocketIS + " len: " + length); - if ((mType == TYPE_L2CAP) || (mType == TYPE_L2CAP_LE)) { - int bytesToRead = length; - if (VDBG) { - Log.v(TAG, "l2cap: read(): offset: " + offset + " length:" + length - + "mL2capBuffer= " + mL2capBuffer); - } - if (mL2capBuffer == null) { - createL2capRxBuffer(); - } - if (mL2capBuffer.remaining() == 0) { - if (VDBG) Log.v(TAG, "l2cap buffer empty, refilling..."); - if (fillL2capRxBuffer() == -1) { - return -1; - } - } - if (bytesToRead > mL2capBuffer.remaining()) { - bytesToRead = mL2capBuffer.remaining(); - } - if (VDBG) { - Log.v(TAG, "get(): offset: " + offset - + " bytesToRead: " + bytesToRead); - } - mL2capBuffer.get(b, offset, bytesToRead); - ret = bytesToRead; - } else { - if (VDBG) Log.v(TAG, "default: read(): offset: " + offset + " length:" + length); - ret = mSocketIS.read(b, offset, length); - } - if (ret < 0) { - throw new IOException("bt socket closed, read return: " + ret); - } - if (VDBG) Log.d(TAG, "read out: " + mSocketIS + " ret: " + ret); - return ret; - } - - /*package*/ int write(byte[] b, int offset, int length) throws IOException { - - //TODO: Since bindings can exist between the SDU size and the - // protocol, we might need to throw an exception instead of just - // splitting the write into multiple smaller writes. - // Rfcomm uses dynamic allocation, and should not have any bindings - // to the actual message length. - if (VDBG) Log.d(TAG, "write: " + mSocketOS + " length: " + length); - if ((mType == TYPE_L2CAP) || (mType == TYPE_L2CAP_LE)) { - if (length <= mMaxTxPacketSize) { - mSocketOS.write(b, offset, length); - } else { - if (DBG) { - Log.w(TAG, "WARNING: Write buffer larger than L2CAP packet size!\n" - + "Packet will be divided into SDU packets of size " - + mMaxTxPacketSize); - } - int tmpOffset = offset; - int bytesToWrite = length; - while (bytesToWrite > 0) { - int tmpLength = (bytesToWrite > mMaxTxPacketSize) - ? mMaxTxPacketSize - : bytesToWrite; - mSocketOS.write(b, tmpOffset, tmpLength); - tmpOffset += tmpLength; - bytesToWrite -= tmpLength; - } - } - } else { - mSocketOS.write(b, offset, length); - } - // There is no good way to confirm since the entire process is asynchronous anyway - if (VDBG) Log.d(TAG, "write out: " + mSocketOS + " length: " + length); - return length; - } - - @Override - public void close() throws IOException { - Log.d(TAG, "close() this: " + this + ", channel: " + mPort + ", mSocketIS: " + mSocketIS - + ", mSocketOS: " + mSocketOS + "mSocket: " + mSocket + ", mSocketState: " - + mSocketState); - if (mSocketState == SocketState.CLOSED) { - return; - } else { - synchronized (this) { - if (mSocketState == SocketState.CLOSED) { - return; - } - mSocketState = SocketState.CLOSED; - if (mSocket != null) { - if (DBG) Log.d(TAG, "Closing mSocket: " + mSocket); - mSocket.shutdownInput(); - mSocket.shutdownOutput(); - mSocket.close(); - mSocket = null; - } - if (mPfd != null) { - mPfd.close(); - mPfd = null; - } - } - } - } - - /*package */ void removeChannel() { - } - - /*package */ int getPort() { - return mPort; - } - - /** - * Get the maximum supported Transmit packet size for the underlying transport. - * Use this to optimize the writes done to the output socket, to avoid sending - * half full packets. - * - * @return the maximum supported Transmit packet size for the underlying transport. - */ - @RequiresNoPermission - public int getMaxTransmitPacketSize() { - return mMaxTxPacketSize; - } - - /** - * Get the maximum supported Receive packet size for the underlying transport. - * Use this to optimize the reads done on the input stream, as any call to read - * will return a maximum of this amount of bytes - or for some transports a - * multiple of this value. - * - * @return the maximum supported Receive packet size for the underlying transport. - */ - @RequiresNoPermission - public int getMaxReceivePacketSize() { - return mMaxRxPacketSize; - } - - /** - * Get the type of the underlying connection. - * - * @return one of {@link #TYPE_RFCOMM}, {@link #TYPE_SCO} or {@link #TYPE_L2CAP} - */ - @RequiresNoPermission - public int getConnectionType() { - if (mType == TYPE_L2CAP_LE) { - // Treat the LE CoC to be the same type as L2CAP. - return TYPE_L2CAP; - } - return mType; - } - - /** - * Change if a SDP entry should be automatically created. - * Must be called before calling .bind, for the call to have any effect. - * - * @param excludeSdp <li>TRUE - do not auto generate SDP record. <li>FALSE - default - auto - * generate SPP SDP record. - * @hide - */ - @RequiresNoPermission - public void setExcludeSdp(boolean excludeSdp) { - mExcludeSdp = excludeSdp; - } - - /** - * Set the LE Transmit Data Length to be the maximum that the BT Controller is capable of. This - * parameter is used by the BT Controller to set the maximum transmission packet size on this - * connection. This function is currently used for testing only. - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public void requestMaximumTxDataLength() throws IOException { - if (mDevice == null) { - throw new IOException("requestMaximumTxDataLength is called on null device"); - } - - try { - if (mSocketState == SocketState.CLOSED) { - throw new IOException("socket closed"); - } - IBluetooth bluetoothProxy = - BluetoothAdapter.getDefaultAdapter().getBluetoothService(); - if (bluetoothProxy == null) { - throw new IOException("Bluetooth is off"); - } - - if (DBG) Log.d(TAG, "requestMaximumTxDataLength"); - bluetoothProxy.getSocketManager().requestMaximumTxDataLength(mDevice); - } catch (RemoteException e) { - Log.e(TAG, Log.getStackTraceString(new Throwable())); - throw new IOException("unable to send RPC: " + e.getMessage()); - } - } - - private String convertAddr(final byte[] addr) { - return String.format(Locale.US, "%02X:%02X:%02X:%02X:%02X:%02X", - addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]); - } - - private String waitSocketSignal(InputStream is) throws IOException { - byte[] sig = new byte[SOCK_SIGNAL_SIZE]; - int ret = readAll(is, sig); - if (VDBG) { - Log.d(TAG, "waitSocketSignal read " + SOCK_SIGNAL_SIZE + " bytes signal ret: " + ret); - } - ByteBuffer bb = ByteBuffer.wrap(sig); - /* the struct in native is decorated with __attribute__((packed)), hence this is possible */ - bb.order(ByteOrder.nativeOrder()); - int size = bb.getShort(); - if (size != SOCK_SIGNAL_SIZE) { - throw new IOException("Connection failure, wrong signal size: " + size); - } - byte[] addr = new byte[6]; - bb.get(addr); - int channel = bb.getInt(); - int status = bb.getInt(); - mMaxTxPacketSize = (bb.getShort() & 0xffff); // Convert to unsigned value - mMaxRxPacketSize = (bb.getShort() & 0xffff); // Convert to unsigned value - String RemoteAddr = convertAddr(addr); - if (VDBG) { - Log.d(TAG, "waitSocketSignal: sig size: " + size + ", remote addr: " - + RemoteAddr + ", channel: " + channel + ", status: " + status - + " MaxRxPktSize: " + mMaxRxPacketSize + " MaxTxPktSize: " + mMaxTxPacketSize); - } - if (status != 0) { - throw new IOException("Connection failure, status: " + status); - } - return RemoteAddr; - } - - private void createL2capRxBuffer() { - if ((mType == TYPE_L2CAP) || (mType == TYPE_L2CAP_LE)) { - // Allocate the buffer to use for reads. - if (VDBG) Log.v(TAG, " Creating mL2capBuffer: mMaxPacketSize: " + mMaxRxPacketSize); - mL2capBuffer = ByteBuffer.wrap(new byte[mMaxRxPacketSize]); - if (VDBG) Log.v(TAG, "mL2capBuffer.remaining()" + mL2capBuffer.remaining()); - mL2capBuffer.limit(0); // Ensure we do a real read at the first read-request - if (VDBG) { - Log.v(TAG, "mL2capBuffer.remaining() after limit(0):" + mL2capBuffer.remaining()); - } - } - } - - private int readAll(InputStream is, byte[] b) throws IOException { - int left = b.length; - while (left > 0) { - int ret = is.read(b, b.length - left, left); - if (ret <= 0) { - throw new IOException("read failed, socket might closed or timeout, read ret: " - + ret); - } - left -= ret; - if (left != 0) { - Log.w(TAG, "readAll() looping, read partial size: " + (b.length - left) - + ", expect size: " + b.length); - } - } - return b.length; - } - - private int readInt(InputStream is) throws IOException { - byte[] ibytes = new byte[4]; - int ret = readAll(is, ibytes); - if (VDBG) Log.d(TAG, "inputStream.read ret: " + ret); - ByteBuffer bb = ByteBuffer.wrap(ibytes); - bb.order(ByteOrder.nativeOrder()); - return bb.getInt(); - } - - private int fillL2capRxBuffer() throws IOException { - mL2capBuffer.rewind(); - int ret = mSocketIS.read(mL2capBuffer.array()); - if (ret == -1) { - // reached end of stream - return -1 - mL2capBuffer.limit(0); - return -1; - } - mL2capBuffer.limit(ret); - return ret; - } - - -} diff --git a/core/java/android/bluetooth/BluetoothStatusCodes.java b/core/java/android/bluetooth/BluetoothStatusCodes.java deleted file mode 100644 index a8ce4b411881..000000000000 --- a/core/java/android/bluetooth/BluetoothStatusCodes.java +++ /dev/null @@ -1,361 +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 android.bluetooth; - -import android.annotation.SystemApi; - -/** - * A class with constants representing possible return values for Bluetooth APIs. General return - * values occupy the range 0 to 199. Profile-specific return values occupy the range 200-999. - * API-specific return values start at 1000. The exception to this is the "UNKNOWN" error code which - * occupies the max integer value. - */ -public final class BluetoothStatusCodes { - - private BluetoothStatusCodes() {} - - /** - * Indicates that the API call was successful. - */ - public static final int SUCCESS = 0; - - /** - * Error code indicating that Bluetooth is not enabled. - */ - public static final int ERROR_BLUETOOTH_NOT_ENABLED = 1; - - /** - * Error code indicating that the API call was initiated by neither the system nor the active - * user. - */ - public static final int ERROR_BLUETOOTH_NOT_ALLOWED = 2; - - /** - * Error code indicating that the Bluetooth Device specified is not bonded. - */ - public static final int ERROR_DEVICE_NOT_BONDED = 3; - - /** - * Error code indicating that the Bluetooth Device specified is not connected, but is bonded. - * - * @hide - */ - public static final int ERROR_DEVICE_NOT_CONNECTED = 4; - - /** - * Error code indicating that the caller does not have the - * {@link android.Manifest.permission#BLUETOOTH_ADVERTISE} permission. - * - * @hide - */ - public static final int ERROR_MISSING_BLUETOOTH_ADVERTISE_PERMISSION = 5; - - /** - * Error code indicating that the caller does not have the - * {@link android.Manifest.permission#BLUETOOTH_CONNECT} permission. - */ - public static final int ERROR_MISSING_BLUETOOTH_CONNECT_PERMISSION = 6; - - /** - * Error code indicating that the caller does not have the - * {@link android.Manifest.permission#BLUETOOTH_SCAN} permission. - * - * @hide - */ - public static final int ERROR_MISSING_BLUETOOTH_SCAN_PERMISSION = 7; - - /** - * Error code indicating that the caller does not have the - * {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED} permission. - */ - public static final int ERROR_MISSING_BLUETOOTH_PRIVILEGED_PERMISSION = 8; - - /** - * Error code indicating that the profile service is not bound. You can bind a profile service - * by calling {@link BluetoothAdapter#getProfileProxy}. - */ - public static final int ERROR_PROFILE_SERVICE_NOT_BOUND = 9; - - /** - * Indicates that the feature is supported. - */ - public static final int FEATURE_SUPPORTED = 10; - - /** - * Indicates that the feature is not supported. - */ - public static final int FEATURE_NOT_SUPPORTED = 11; - - /** - * Error code indicating that the device is not the active device for this profile. - * - * @hide - */ - @SystemApi - public static final int ERROR_NOT_ACTIVE_DEVICE = 12; - - /** - * Error code indicating that there are no active devices for the profile. - * - * @hide - */ - @SystemApi - public static final int ERROR_NO_ACTIVE_DEVICES = 13; - - /** - * Indicates that the Bluetooth profile is not connected to this device. - * - * @hide - */ - @SystemApi - public static final int ERROR_PROFILE_NOT_CONNECTED = 14; - - /** - * Error code indicating that the requested operation timed out. - * - * @hide - */ - @SystemApi - public static final int ERROR_TIMEOUT = 15; - - /** - * A GATT writeCharacteristic request is not permitted on the remote device. - */ - public static final int ERROR_GATT_WRITE_NOT_ALLOWED = 200; - - /** - * A GATT writeCharacteristic request is issued to a busy remote device. - */ - public static final int ERROR_GATT_WRITE_REQUEST_BUSY = 201; - - /** - * If another application has already requested {@link OobData} then another fetch will be - * disallowed until the callback is removed. - * - * @hide - */ - @SystemApi - public static final int ERROR_ANOTHER_ACTIVE_OOB_REQUEST = 1000; - - /** - * Indicates that the ACL disconnected due to an explicit request from the local device. - * <p> - * Example cause: This is a normal disconnect reason, e.g., user/app initiates - * disconnection. - * - * @hide - */ - public static final int ERROR_DISCONNECT_REASON_LOCAL_REQUEST = 1100; - - /** - * Indicates that the ACL disconnected due to an explicit request from the remote device. - * <p> - * Example cause: This is a normal disconnect reason, e.g., user/app initiates - * disconnection. - * <p> - * Example solution: The app can also prompt the user to check their remote device. - * - * @hide - */ - public static final int ERROR_DISCONNECT_REASON_REMOTE_REQUEST = 1101; - - /** - * Generic disconnect reason indicating the ACL disconnected due to an error on the local - * device. - * <p> - * Example solution: Prompt the user to check their local device (e.g., phone, car - * headunit). - * - * @hide - */ - public static final int ERROR_DISCONNECT_REASON_LOCAL = 1102; - - /** - * Generic disconnect reason indicating the ACL disconnected due to an error on the remote - * device. - * <p> - * Example solution: Prompt the user to check their remote device (e.g., headset, car - * headunit, watch). - * - * @hide - */ - public static final int ERROR_DISCONNECT_REASON_REMOTE = 1103; - - /** - * Indicates that the ACL disconnected due to a timeout. - * <p> - * Example cause: remote device might be out of range. - * <p> - * Example solution: Prompt user to verify their remote device is on or in - * connection/pairing mode. - * - * @hide - */ - public static final int ERROR_DISCONNECT_REASON_TIMEOUT = 1104; - - /** - * Indicates that the ACL disconnected due to link key issues. - * <p> - * Example cause: Devices are either unpaired or remote device is refusing our pairing - * request. - * <p> - * Example solution: Prompt user to unpair and pair again. - * - * @hide - */ - public static final int ERROR_DISCONNECT_REASON_SECURITY = 1105; - - /** - * Indicates that the ACL disconnected due to the local device's system policy. - * <p> - * Example cause: privacy policy, power management policy, permissions, etc. - * <p> - * Example solution: Prompt the user to check settings, or check with their system - * administrator (e.g. some corp-managed devices do not allow OPP connection). - * - * @hide - */ - public static final int ERROR_DISCONNECT_REASON_SYSTEM_POLICY = 1106; - - /** - * Indicates that the ACL disconnected due to resource constraints, either on the local - * device or the remote device. - * <p> - * Example cause: controller is busy, memory limit reached, maximum number of connections - * reached. - * <p> - * Example solution: The app should wait and try again. If still failing, prompt the user - * to disconnect some devices, or toggle Bluetooth on the local and/or the remote device. - * - * @hide - */ - public static final int ERROR_DISCONNECT_REASON_RESOURCE_LIMIT_REACHED = 1107; - - /** - * Indicates that the ACL disconnected because another ACL connection already exists. - * - * @hide - */ - public static final int ERROR_DISCONNECT_REASON_CONNECTION_ALREADY_EXISTS = 1108; - - /** - * Indicates that the ACL disconnected due to incorrect parameters passed in from the app. - * <p> - * Example solution: Change parameters and try again. If error persists, the app can report - * telemetry and/or log the error in a bugreport. - * - * @hide - */ - public static final int ERROR_DISCONNECT_REASON_BAD_PARAMETERS = 1109; - - /** - * Indicates that setting the LE Audio Broadcast mode failed. - * <p> - * Example solution: Change parameters and try again. If error persists, the app can report - * telemetry and/or log the error in a bugreport. - * - * @hide - */ - public static final int ERROR_LE_AUDIO_BROADCAST_SOURCE_SET_BROADCAST_MODE_FAILED = 1110; - - /** - * Indicates that setting a new encryption key for Bluetooth LE Audio Broadcast Source failed. - * <p> - * Example solution: Change parameters and try again. If error persists, the app can report - * telemetry and/or log the error in a bugreport. - * - * @hide - */ - public static final int ERROR_LE_AUDIO_BROADCAST_SOURCE_SET_ENCRYPTION_KEY_FAILED = 1111; - - /** - * Indicates that connecting to a remote Broadcast Audio Scan Service failed. - * <p> - * Example solution: Change parameters and try again. If error persists, the app can report - * telemetry and/or log the error in a bugreport. - * - * @hide - */ - public static final int ERROR_LE_AUDIO_BROADCAST_AUDIO_SCAN_SERVICE_CONNECT_FAILED = 1112; - - /** - * Indicates that disconnecting from a remote Broadcast Audio Scan Service failed. - * <p> - * Example solution: Change parameters and try again. If error persists, the app can report - * telemetry and/or log the error in a bugreport. - * - * @hide - */ - public static final int ERROR_LE_AUDIO_BROADCAST_AUDIO_SCAN_SERVICE_DISCONNECT_FAILED = 1113; - - /** - * Indicates that enabling LE Audio Broadcast encryption failed - * <p> - * Example solution: Change parameters and try again. If error persists, the app can report - * telemetry and/or log the error in a bugreport. - * - * @hide - */ - public static final int ERROR_LE_AUDIO_BROADCAST_SOURCE_ENABLE_ENCRYPTION_FAILED = 1114; - - /** - * Indicates that disabling LE Audio Broadcast encryption failed - * <p> - * Example solution: Change parameters and try again. If error persists, the app can report - * telemetry and/or log the error in a bugreport. - * - * @hide - */ - public static final int ERROR_LE_AUDIO_BROADCAST_SOURCE_DISABLE_ENCRYPTION_FAILED = 1115; - - /** - * Indicates that there is already one device for which SCO audio is connected or connecting. - * - * @hide - */ - @SystemApi - public static final int ERROR_AUDIO_DEVICE_ALREADY_CONNECTED = 1116; - - /** - * Indicates that SCO audio was already not connected for this device. - * - * @hide - */ - @SystemApi - public static final int ERROR_AUDIO_DEVICE_ALREADY_DISCONNECTED = 1117; - - /** - * Indicates that there audio route is currently blocked by the system. - * - * @hide - */ - @SystemApi - public static final int ERROR_AUDIO_ROUTE_BLOCKED = 1118; - - /** - * Indicates that there is an active call preventing this operation from succeeding. - * - * @hide - */ - @SystemApi - public static final int ERROR_CALL_ACTIVE = 1119; - - /** - * Indicates that an unknown error has occurred has occurred. - */ - public static final int ERROR_UNKNOWN = Integer.MAX_VALUE; -} diff --git a/core/java/android/bluetooth/BluetoothUuid.java b/core/java/android/bluetooth/BluetoothUuid.java deleted file mode 100644 index 2a8ff5185085..000000000000 --- a/core/java/android/bluetooth/BluetoothUuid.java +++ /dev/null @@ -1,394 +0,0 @@ -/* - * Copyright (C) 2009 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.bluetooth; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.SuppressLint; -import android.annotation.SystemApi; -import android.compat.annotation.UnsupportedAppUsage; -import android.os.ParcelUuid; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Arrays; -import java.util.HashSet; -import java.util.UUID; - -/** - * Static helper methods and constants to decode the ParcelUuid of remote devices. - * - * @hide - */ -@SystemApi -@SuppressLint("AndroidFrameworkBluetoothPermission") -public final class BluetoothUuid { - - /* See Bluetooth Assigned Numbers document - SDP section, to get the values of UUIDs - * for the various services. - * - * The following 128 bit values are calculated as: - * uuid * 2^96 + BASE_UUID - */ - - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid A2DP_SINK = - ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid A2DP_SOURCE = - ParcelUuid.fromString("0000110A-0000-1000-8000-00805F9B34FB"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid ADV_AUDIO_DIST = - ParcelUuid.fromString("0000110D-0000-1000-8000-00805F9B34FB"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid HSP = - ParcelUuid.fromString("00001108-0000-1000-8000-00805F9B34FB"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid HSP_AG = - ParcelUuid.fromString("00001112-0000-1000-8000-00805F9B34FB"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid HFP = - ParcelUuid.fromString("0000111E-0000-1000-8000-00805F9B34FB"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid HFP_AG = - ParcelUuid.fromString("0000111F-0000-1000-8000-00805F9B34FB"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid AVRCP_CONTROLLER = - ParcelUuid.fromString("0000110E-0000-1000-8000-00805F9B34FB"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid AVRCP_TARGET = - ParcelUuid.fromString("0000110C-0000-1000-8000-00805F9B34FB"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid OBEX_OBJECT_PUSH = - ParcelUuid.fromString("00001105-0000-1000-8000-00805f9b34fb"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid HID = - ParcelUuid.fromString("00001124-0000-1000-8000-00805f9b34fb"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid HOGP = - ParcelUuid.fromString("00001812-0000-1000-8000-00805f9b34fb"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid PANU = - ParcelUuid.fromString("00001115-0000-1000-8000-00805F9B34FB"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid NAP = - ParcelUuid.fromString("00001116-0000-1000-8000-00805F9B34FB"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid BNEP = - ParcelUuid.fromString("0000000f-0000-1000-8000-00805F9B34FB"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid PBAP_PCE = - ParcelUuid.fromString("0000112e-0000-1000-8000-00805F9B34FB"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid PBAP_PSE = - ParcelUuid.fromString("0000112f-0000-1000-8000-00805F9B34FB"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid MAP = - ParcelUuid.fromString("00001134-0000-1000-8000-00805F9B34FB"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid MNS = - ParcelUuid.fromString("00001133-0000-1000-8000-00805F9B34FB"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid MAS = - ParcelUuid.fromString("00001132-0000-1000-8000-00805F9B34FB"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid SAP = - ParcelUuid.fromString("0000112D-0000-1000-8000-00805F9B34FB"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid HEARING_AID = - ParcelUuid.fromString("0000FDF0-0000-1000-8000-00805f9b34fb"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid LE_AUDIO = - ParcelUuid.fromString("0000184E-0000-1000-8000-00805F9B34FB"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid DIP = - ParcelUuid.fromString("00001200-0000-1000-8000-00805F9B34FB"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid VOLUME_CONTROL = - ParcelUuid.fromString("00001844-0000-1000-8000-00805F9B34FB"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid GENERIC_MEDIA_CONTROL = - ParcelUuid.fromString("00001849-0000-1000-8000-00805F9B34FB"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid MEDIA_CONTROL = - ParcelUuid.fromString("00001848-0000-1000-8000-00805F9B34FB"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid COORDINATED_SET = - ParcelUuid.fromString("00001846-0000-1000-8000-00805F9B34FB"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid CAP = - ParcelUuid.fromString("00001853-0000-1000-8000-00805F9B34FB"); - /** @hide */ - @NonNull - @SystemApi - public static final ParcelUuid BASE_UUID = - ParcelUuid.fromString("00000000-0000-1000-8000-00805F9B34FB"); - - /** - * Length of bytes for 16 bit UUID - * - * @hide - */ - @SystemApi - public static final int UUID_BYTES_16_BIT = 2; - /** - * Length of bytes for 32 bit UUID - * - * @hide - */ - @SystemApi - public static final int UUID_BYTES_32_BIT = 4; - /** - * Length of bytes for 128 bit UUID - * - * @hide - */ - @SystemApi - public static final int UUID_BYTES_128_BIT = 16; - - /** - * Returns true if there any common ParcelUuids in uuidA and uuidB. - * - * @param uuidA - List of ParcelUuids - * @param uuidB - List of ParcelUuids - * - * @hide - */ - @SystemApi - public static boolean containsAnyUuid(@Nullable ParcelUuid[] uuidA, - @Nullable ParcelUuid[] uuidB) { - if (uuidA == null && uuidB == null) return true; - - if (uuidA == null) { - return uuidB.length == 0; - } - - if (uuidB == null) { - return uuidA.length == 0; - } - - HashSet<ParcelUuid> uuidSet = new HashSet<ParcelUuid>(Arrays.asList(uuidA)); - for (ParcelUuid uuid : uuidB) { - if (uuidSet.contains(uuid)) return true; - } - return false; - } - - /** - * Extract the Service Identifier or the actual uuid from the Parcel Uuid. - * For example, if 0000110B-0000-1000-8000-00805F9B34FB is the parcel Uuid, - * this function will return 110B - * - * @param parcelUuid - * @return the service identifier. - */ - private static int getServiceIdentifierFromParcelUuid(ParcelUuid parcelUuid) { - UUID uuid = parcelUuid.getUuid(); - long value = (uuid.getMostSignificantBits() & 0xFFFFFFFF00000000L) >>> 32; - return (int) value; - } - - /** - * Parse UUID from bytes. The {@code uuidBytes} can represent a 16-bit, 32-bit or 128-bit UUID, - * but the returned UUID is always in 128-bit format. - * Note UUID is little endian in Bluetooth. - * - * @param uuidBytes Byte representation of uuid. - * @return {@link ParcelUuid} parsed from bytes. - * @throws IllegalArgumentException If the {@code uuidBytes} cannot be parsed. - * - * @hide - */ - @NonNull - @SystemApi - public static ParcelUuid parseUuidFrom(@Nullable byte[] uuidBytes) { - if (uuidBytes == null) { - throw new IllegalArgumentException("uuidBytes cannot be null"); - } - int length = uuidBytes.length; - if (length != UUID_BYTES_16_BIT && length != UUID_BYTES_32_BIT - && length != UUID_BYTES_128_BIT) { - throw new IllegalArgumentException("uuidBytes length invalid - " + length); - } - - // Construct a 128 bit UUID. - if (length == UUID_BYTES_128_BIT) { - ByteBuffer buf = ByteBuffer.wrap(uuidBytes).order(ByteOrder.LITTLE_ENDIAN); - long msb = buf.getLong(8); - long lsb = buf.getLong(0); - return new ParcelUuid(new UUID(msb, lsb)); - } - - // For 16 bit and 32 bit UUID we need to convert them to 128 bit value. - // 128_bit_value = uuid * 2^96 + BASE_UUID - long shortUuid; - if (length == UUID_BYTES_16_BIT) { - shortUuid = uuidBytes[0] & 0xFF; - shortUuid += (uuidBytes[1] & 0xFF) << 8; - } else { - shortUuid = uuidBytes[0] & 0xFF; - shortUuid += (uuidBytes[1] & 0xFF) << 8; - shortUuid += (uuidBytes[2] & 0xFF) << 16; - shortUuid += (uuidBytes[3] & 0xFF) << 24; - } - long msb = BASE_UUID.getUuid().getMostSignificantBits() + (shortUuid << 32); - long lsb = BASE_UUID.getUuid().getLeastSignificantBits(); - return new ParcelUuid(new UUID(msb, lsb)); - } - - /** - * Parse UUID to bytes. The returned value is shortest representation, a 16-bit, 32-bit or - * 128-bit UUID, Note returned value is little endian (Bluetooth). - * - * @param uuid uuid to parse. - * @return shortest representation of {@code uuid} as bytes. - * @throws IllegalArgumentException If the {@code uuid} is null. - * - * @hide - */ - public static byte[] uuidToBytes(ParcelUuid uuid) { - if (uuid == null) { - throw new IllegalArgumentException("uuid cannot be null"); - } - - if (is16BitUuid(uuid)) { - byte[] uuidBytes = new byte[UUID_BYTES_16_BIT]; - int uuidVal = getServiceIdentifierFromParcelUuid(uuid); - uuidBytes[0] = (byte) (uuidVal & 0xFF); - uuidBytes[1] = (byte) ((uuidVal & 0xFF00) >> 8); - return uuidBytes; - } - - if (is32BitUuid(uuid)) { - byte[] uuidBytes = new byte[UUID_BYTES_32_BIT]; - int uuidVal = getServiceIdentifierFromParcelUuid(uuid); - uuidBytes[0] = (byte) (uuidVal & 0xFF); - uuidBytes[1] = (byte) ((uuidVal & 0xFF00) >> 8); - uuidBytes[2] = (byte) ((uuidVal & 0xFF0000) >> 16); - uuidBytes[3] = (byte) ((uuidVal & 0xFF000000) >> 24); - return uuidBytes; - } - - // Construct a 128 bit UUID. - long msb = uuid.getUuid().getMostSignificantBits(); - long lsb = uuid.getUuid().getLeastSignificantBits(); - - byte[] uuidBytes = new byte[UUID_BYTES_128_BIT]; - ByteBuffer buf = ByteBuffer.wrap(uuidBytes).order(ByteOrder.LITTLE_ENDIAN); - buf.putLong(8, msb); - buf.putLong(0, lsb); - return uuidBytes; - } - - /** - * Check whether the given parcelUuid can be converted to 16 bit bluetooth uuid. - * - * @param parcelUuid - * @return true if the parcelUuid can be converted to 16 bit uuid, false otherwise. - * - * @hide - */ - @UnsupportedAppUsage - public static boolean is16BitUuid(ParcelUuid parcelUuid) { - UUID uuid = parcelUuid.getUuid(); - if (uuid.getLeastSignificantBits() != BASE_UUID.getUuid().getLeastSignificantBits()) { - return false; - } - return ((uuid.getMostSignificantBits() & 0xFFFF0000FFFFFFFFL) == 0x1000L); - } - - - /** - * Check whether the given parcelUuid can be converted to 32 bit bluetooth uuid. - * - * @param parcelUuid - * @return true if the parcelUuid can be converted to 32 bit uuid, false otherwise. - * - * @hide - */ - @UnsupportedAppUsage - public static boolean is32BitUuid(ParcelUuid parcelUuid) { - UUID uuid = parcelUuid.getUuid(); - if (uuid.getLeastSignificantBits() != BASE_UUID.getUuid().getLeastSignificantBits()) { - return false; - } - if (is16BitUuid(parcelUuid)) { - return false; - } - return ((uuid.getMostSignificantBits() & 0xFFFFFFFFL) == 0x1000L); - } - - private BluetoothUuid() {} -} diff --git a/core/java/android/bluetooth/BluetoothVolumeControl.java b/core/java/android/bluetooth/BluetoothVolumeControl.java deleted file mode 100644 index 27532aabc3fc..000000000000 --- a/core/java/android/bluetooth/BluetoothVolumeControl.java +++ /dev/null @@ -1,337 +0,0 @@ -/* - * Copyright 2021 HIMSA II K/S - www.himsa.com. - * Represented by EHIMA - www.ehima.com - * - * 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.bluetooth; - -import static android.bluetooth.BluetoothUtils.getSyncTimeout; - -import android.Manifest; -import android.annotation.IntRange; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.RequiresPermission; -import android.annotation.SdkConstant; -import android.annotation.SdkConstant.SdkConstantType; -import android.annotation.SuppressLint; -import android.annotation.SystemApi; -import android.bluetooth.annotations.RequiresBluetoothConnectPermission; -import android.content.AttributionSource; -import android.content.Context; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.CloseGuard; -import android.util.Log; - -import com.android.modules.utils.SynchronousResultReceiver; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeoutException; - -/** - * This class provides the public APIs to control the Bluetooth Volume Control service. - * - * <p>BluetoothVolumeControl is a proxy object for controlling the Bluetooth VC - * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get - * the BluetoothVolumeControl proxy object. - * @hide - */ -@SystemApi -public final class BluetoothVolumeControl implements BluetoothProfile, AutoCloseable { - private static final String TAG = "BluetoothVolumeControl"; - private static final boolean DBG = true; - private static final boolean VDBG = false; - - private CloseGuard mCloseGuard; - - /** - * Intent used to broadcast the change in connection state of the Volume Control - * profile. - * - * <p>This intent will have 3 extras: - * <ul> - * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> - * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> - * </ul> - * - * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of - * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, - * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. - * - * @hide - */ - @SystemApi - @SuppressLint("ActionValue") - @RequiresBluetoothConnectPermission - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_CONNECTION_STATE_CHANGED = - "android.bluetooth.volume-control.profile.action.CONNECTION_STATE_CHANGED"; - - private BluetoothAdapter mAdapter; - private final AttributionSource mAttributionSource; - private final BluetoothProfileConnector<IBluetoothVolumeControl> mProfileConnector = - new BluetoothProfileConnector(this, BluetoothProfile.VOLUME_CONTROL, TAG, - IBluetoothVolumeControl.class.getName()) { - @Override - public IBluetoothVolumeControl getServiceInterface(IBinder service) { - return IBluetoothVolumeControl.Stub.asInterface(service); - } - }; - - /** - * Create a BluetoothVolumeControl proxy object for interacting with the local - * Bluetooth Volume Control service. - */ - /*package*/ BluetoothVolumeControl(Context context, ServiceListener listener, - BluetoothAdapter adapter) { - mAdapter = adapter; - mAttributionSource = adapter.getAttributionSource(); - mProfileConnector.connect(context, listener); - mCloseGuard = new CloseGuard(); - mCloseGuard.open("close"); - } - - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) - protected void finalize() { - if (mCloseGuard != null) { - mCloseGuard.warnIfOpen(); - } - close(); - } - - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) - public void close() { - mProfileConnector.disconnect(); - } - - private IBluetoothVolumeControl getService() { return mProfileConnector.getService(); } - - /** - * Get the list of connected devices. Currently at most one. - * - * @return list of connected devices - * - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public @NonNull List<BluetoothDevice> getConnectedDevices() { - if (DBG) log("getConnectedDevices()"); - final IBluetoothVolumeControl service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getConnectedDevices(mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the list of devices matching specified states. Currently at most one. - * - * @return list of matching devices - * - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { - if (DBG) log("getDevicesMatchingStates()"); - final IBluetoothVolumeControl service = getService(); - final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver<List<BluetoothDevice>> recv = - new SynchronousResultReceiver(); - service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv); - return Attributable.setAttributionSource( - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), - mAttributionSource); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get connection state of device - * - * @return device connection state - * - * @hide - */ - @RequiresBluetoothConnectPermission - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - public int getConnectionState(BluetoothDevice device) { - if (DBG) log("getConnectionState(" + device + ")"); - final IBluetoothVolumeControl service = getService(); - final int defaultValue = BluetoothProfile.STATE_DISCONNECTED; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionState(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Tells remote device to set an absolute volume. - * - * @param volume Absolute volume to be set on remote device. - * Minimum value is 0 and maximum value is 255 - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public void setVolume(@Nullable BluetoothDevice device, - @IntRange(from = 0, to = 255) int volume) { - if (DBG) log("setVolume(" + volume + ")"); - final IBluetoothVolumeControl service = getService(); - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled()) { - try { - final SynchronousResultReceiver recv = new SynchronousResultReceiver(); - service.setVolume(device, volume, mAttributionSource, recv); - recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - } - - /** - * Set connection policy of the profile - * - * <p> The device should already be paired. - * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED}, - * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Paired bluetooth device - * @param connectionPolicy is the connection policy to set to for this profile - * @return true if connectionPolicy is set, false on error - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public boolean setConnectionPolicy(@NonNull BluetoothDevice device, - @ConnectionPolicy int connectionPolicy) { - if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); - final IBluetoothVolumeControl service = getService(); - final boolean defaultValue = false; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device) - && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN - || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) { - try { - final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver(); - service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - /** - * Get the connection policy of the profile. - * - * <p> The connection policy can be any of: - * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, - * {@link #CONNECTION_POLICY_UNKNOWN} - * - * @param device Bluetooth device - * @return connection policy of the device - * @hide - */ - @SystemApi - @RequiresBluetoothConnectPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) { - if (VDBG) log("getConnectionPolicy(" + device + ")"); - final IBluetoothVolumeControl service = getService(); - final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; - if (service == null) { - Log.w(TAG, "Proxy not attached to service"); - if (DBG) log(Log.getStackTraceString(new Throwable())); - } else if (isEnabled() && isValidDevice(device)) { - try { - final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver(); - service.getConnectionPolicy(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { - Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); - } - } - return defaultValue; - } - - private boolean isEnabled() { - return mAdapter.getState() == BluetoothAdapter.STATE_ON; - } - - private static boolean isValidDevice(@Nullable BluetoothDevice device) { - return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); - } - - private static void log(String msg) { - Log.d(TAG, msg); - } -} diff --git a/core/java/android/bluetooth/BufferConstraint.java b/core/java/android/bluetooth/BufferConstraint.java deleted file mode 100644 index cbffc788c35d..000000000000 --- a/core/java/android/bluetooth/BufferConstraint.java +++ /dev/null @@ -1,105 +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 android.bluetooth; - -import android.annotation.NonNull; -import android.annotation.SystemApi; -import android.os.Parcel; -import android.os.Parcelable; - -/** - * Stores a codec's constraints on buffering length in milliseconds. - * - * {@hide} - */ -@SystemApi -public final class BufferConstraint implements Parcelable { - - private static final String TAG = "BufferConstraint"; - private int mDefaultMillis; - private int mMaxMillis; - private int mMinMillis; - - public BufferConstraint(int defaultMillis, int maxMillis, - int minMillis) { - mDefaultMillis = defaultMillis; - mMaxMillis = maxMillis; - mMinMillis = minMillis; - } - - BufferConstraint(Parcel in) { - mDefaultMillis = in.readInt(); - mMaxMillis = in.readInt(); - mMinMillis = in.readInt(); - } - - public static final @NonNull Parcelable.Creator<BufferConstraint> CREATOR = - new Parcelable.Creator<BufferConstraint>() { - public BufferConstraint createFromParcel(Parcel in) { - return new BufferConstraint(in); - } - - public BufferConstraint[] newArray(int size) { - return new BufferConstraint[size]; - } - }; - - @Override - public void writeToParcel(@NonNull Parcel out, int flags) { - out.writeInt(mDefaultMillis); - out.writeInt(mMaxMillis); - out.writeInt(mMinMillis); - } - - @Override - public int describeContents() { - return 0; - } - - /** - * Get the default buffer millis - * - * @return default buffer millis - * @hide - */ - @SystemApi - public int getDefaultMillis() { - return mDefaultMillis; - } - - /** - * Get the maximum buffer millis - * - * @return maximum buffer millis - * @hide - */ - @SystemApi - public int getMaxMillis() { - return mMaxMillis; - } - - /** - * Get the minimum buffer millis - * - * @return minimum buffer millis - * @hide - */ - @SystemApi - public int getMinMillis() { - return mMinMillis; - } -} diff --git a/core/java/android/bluetooth/BufferConstraints.java b/core/java/android/bluetooth/BufferConstraints.java deleted file mode 100644 index 97d97232b7a6..000000000000 --- a/core/java/android/bluetooth/BufferConstraints.java +++ /dev/null @@ -1,96 +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 android.bluetooth; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.SystemApi; -import android.os.Parcel; -import android.os.Parcelable; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - - -/** - * A parcelable collection of buffer constraints by codec type. - * - * {@hide} - */ -@SystemApi -public final class BufferConstraints implements Parcelable { - public static final int BUFFER_CODEC_MAX_NUM = 32; - - private static final String TAG = "BufferConstraints"; - - private Map<Integer, BufferConstraint> mBufferConstraints; - private List<BufferConstraint> mBufferConstraintList; - - public BufferConstraints(@NonNull List<BufferConstraint> - bufferConstraintList) { - - mBufferConstraintList = new ArrayList<BufferConstraint>(bufferConstraintList); - mBufferConstraints = new HashMap<Integer, BufferConstraint>(); - for (int i = 0; i < BUFFER_CODEC_MAX_NUM; i++) { - mBufferConstraints.put(i, bufferConstraintList.get(i)); - } - } - - BufferConstraints(Parcel in) { - mBufferConstraintList = new ArrayList<BufferConstraint>(); - mBufferConstraints = new HashMap<Integer, BufferConstraint>(); - in.readList(mBufferConstraintList, BufferConstraint.class.getClassLoader()); - for (int i = 0; i < mBufferConstraintList.size(); i++) { - mBufferConstraints.put(i, mBufferConstraintList.get(i)); - } - } - - public static final @NonNull Parcelable.Creator<BufferConstraints> CREATOR = - new Parcelable.Creator<BufferConstraints>() { - public BufferConstraints createFromParcel(Parcel in) { - return new BufferConstraints(in); - } - - public BufferConstraints[] newArray(int size) { - return new BufferConstraints[size]; - } - }; - - @Override - public void writeToParcel(@NonNull Parcel out, int flags) { - out.writeList(mBufferConstraintList); - } - - @Override - public int describeContents() { - return 0; - } - - /** - * Get the buffer constraints by codec type. - * - * @param codec Audio codec - * @return buffer constraints by codec type. - * @hide - */ - @SystemApi - public @Nullable BufferConstraint forCodec(@BluetoothCodecConfig.SourceCodecType int codec) { - return mBufferConstraints.get(codec); - } -} diff --git a/core/java/android/bluetooth/OWNERS b/core/java/android/bluetooth/OWNERS deleted file mode 100644 index 8e9d7b74bf09..000000000000 --- a/core/java/android/bluetooth/OWNERS +++ /dev/null @@ -1,4 +0,0 @@ -# Bug component: 27441 - -sattiraju@google.com -baligh@google.com diff --git a/core/java/android/bluetooth/OobData.java b/core/java/android/bluetooth/OobData.java deleted file mode 100644 index bb0b95649b17..000000000000 --- a/core/java/android/bluetooth/OobData.java +++ /dev/null @@ -1,958 +0,0 @@ -/** - * Copyright (C) 2016 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.bluetooth; - -import static java.util.Objects.requireNonNull; - -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.SystemApi; -import android.os.Parcel; -import android.os.Parcelable; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * Out Of Band Data for Bluetooth device pairing. - * - * <p>This object represents optional data obtained from a remote device through - * an out-of-band channel (eg. NFC, QR). - * - * <p>References: - * NFC AD Forum SSP 1.1 (AD) - * {@link https://members.nfc-forum.org//apps/group_public/download.php/24620/NFCForum-AD-BTSSP_1_1.pdf} - * Core Specification Supplement (CSS) V9 - * - * <p>There are several BR/EDR Examples - * - * <p>Negotiated Handover: - * Bluetooth Carrier Configuration Record: - * - OOB Data Length - * - Device Address - * - Class of Device - * - Simple Pairing Hash C - * - Simple Pairing Randomizer R - * - Service Class UUID - * - Bluetooth Local Name - * - * <p>Static Handover: - * Bluetooth Carrier Configuration Record: - * - OOB Data Length - * - Device Address - * - Class of Device - * - Service Class UUID - * - Bluetooth Local Name - * - * <p>Simplified Tag Format for Single BT Carrier: - * Bluetooth OOB Data Record: - * - OOB Data Length - * - Device Address - * - Class of Device - * - Service Class UUID - * - Bluetooth Local Name - * - * @hide - */ -@SystemApi -public final class OobData implements Parcelable { - - private static final String TAG = "OobData"; - /** The {@link OobData#mClassicLength} may be. (AD 3.1.1) (CSS 1.6.2) @hide */ - @SystemApi - public static final int OOB_LENGTH_OCTETS = 2; - /** - * The length for the {@link OobData#mDeviceAddressWithType}(6) and Address Type(1). - * (AD 3.1.2) (CSS 1.6.2) - * @hide - */ - @SystemApi - public static final int DEVICE_ADDRESS_OCTETS = 7; - /** The Class of Device is 3 octets. (AD 3.1.3) (CSS 1.6.2) @hide */ - @SystemApi - public static final int CLASS_OF_DEVICE_OCTETS = 3; - /** The Confirmation data must be 16 octets. (AD 3.2.2) (CSS 1.6.2) @hide */ - @SystemApi - public static final int CONFIRMATION_OCTETS = 16; - /** The Randomizer data must be 16 octets. (AD 3.2.3) (CSS 1.6.2) @hide */ - @SystemApi - public static final int RANDOMIZER_OCTETS = 16; - /** The LE Device Role length is 1 octet. (AD 3.3.2) (CSS 1.17) @hide */ - @SystemApi - public static final int LE_DEVICE_ROLE_OCTETS = 1; - /** The {@link OobData#mLeTemporaryKey} length. (3.4.1) @hide */ - @SystemApi - public static final int LE_TK_OCTETS = 16; - /** The {@link OobData#mLeAppearance} length. (3.4.1) @hide */ - @SystemApi - public static final int LE_APPEARANCE_OCTETS = 2; - /** The {@link OobData#mLeFlags} length. (3.4.1) @hide */ - @SystemApi - public static final int LE_DEVICE_FLAG_OCTETS = 1; // 1 octet to hold the 0-4 value. - - // Le Roles - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef( - prefix = { "LE_DEVICE_ROLE_" }, - value = { - LE_DEVICE_ROLE_PERIPHERAL_ONLY, - LE_DEVICE_ROLE_CENTRAL_ONLY, - LE_DEVICE_ROLE_BOTH_PREFER_PERIPHERAL, - LE_DEVICE_ROLE_BOTH_PREFER_CENTRAL - } - ) - public @interface LeRole {} - - /** @hide */ - @SystemApi - public static final int LE_DEVICE_ROLE_PERIPHERAL_ONLY = 0x00; - /** @hide */ - @SystemApi - public static final int LE_DEVICE_ROLE_CENTRAL_ONLY = 0x01; - /** @hide */ - @SystemApi - public static final int LE_DEVICE_ROLE_BOTH_PREFER_PERIPHERAL = 0x02; - /** @hide */ - @SystemApi - public static final int LE_DEVICE_ROLE_BOTH_PREFER_CENTRAL = 0x03; - - // Le Flags - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef( - prefix = { "LE_FLAG_" }, - value = { - LE_FLAG_LIMITED_DISCOVERY_MODE, - LE_FLAG_GENERAL_DISCOVERY_MODE, - LE_FLAG_BREDR_NOT_SUPPORTED, - LE_FLAG_SIMULTANEOUS_CONTROLLER, - LE_FLAG_SIMULTANEOUS_HOST - } - ) - public @interface LeFlag {} - - /** @hide */ - @SystemApi - public static final int LE_FLAG_LIMITED_DISCOVERY_MODE = 0x00; - /** @hide */ - @SystemApi - public static final int LE_FLAG_GENERAL_DISCOVERY_MODE = 0x01; - /** @hide */ - @SystemApi - public static final int LE_FLAG_BREDR_NOT_SUPPORTED = 0x02; - /** @hide */ - @SystemApi - public static final int LE_FLAG_SIMULTANEOUS_CONTROLLER = 0x03; - /** @hide */ - @SystemApi - public static final int LE_FLAG_SIMULTANEOUS_HOST = 0x04; - - /** - * Builds an {@link OobData} object and validates that the required combination - * of values are present to create the LE specific OobData type. - * - * @hide - */ - @SystemApi - public static final class LeBuilder { - - /** - * It is recommended that this Hash C is generated anew for each - * pairing. - * - * <p>It should be noted that on passive NFC this isn't possible as the data is static - * and immutable. - */ - private byte[] mConfirmationHash = null; - - /** - * Optional, but adds more validity to the pairing. - * - * <p>If not present a value of 0 is assumed. - */ - private byte[] mRandomizerHash = new byte[] { - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - }; - - /** - * The Bluetooth Device user-friendly name presented over Bluetooth Technology. - * - * <p>This is the name that may be displayed to the device user as part of the UI. - */ - private byte[] mDeviceName = null; - - /** - * Sets the Bluetooth Device name to be used for UI purposes. - * - * <p>Optional attribute. - * - * @param deviceName byte array representing the name, may be 0 in length, not null. - * - * @return {@link OobData#ClassicBuilder} - * - * @throws NullPointerException if deviceName is null. - * - * @hide - */ - @NonNull - @SystemApi - public LeBuilder setDeviceName(@NonNull byte[] deviceName) { - requireNonNull(deviceName); - this.mDeviceName = deviceName; - return this; - } - - /** - * The Bluetooth Device Address is the address to which the OOB data belongs. - * - * <p>The length MUST be {@link OobData#DEVICE_ADDRESS_OCTETS} octets. - * - * <p> Address is encoded in Little Endian order. - * - * <p>e.g. 00:01:02:03:04:05 would be x05x04x03x02x01x00 - */ - private final byte[] mDeviceAddressWithType; - - /** - * During an LE connection establishment, one must be in the Peripheral mode and the other - * in the Central role. - * - * <p>Possible Values: - * {@link LE_DEVICE_ROLE_PERIPHERAL_ONLY} Only Peripheral supported - * {@link LE_DEVICE_ROLE_CENTRAL_ONLY} Only Central supported - * {@link LE_DEVICE_ROLE_BOTH_PREFER_PERIPHERAL} Central & Peripheral supported; - * Peripheral Preferred - * {@link LE_DEVICE_ROLE_BOTH_PREFER_CENTRAL} Only peripheral supported; Central Preferred - * 0x04 - 0xFF Reserved - */ - private final @LeRole int mLeDeviceRole; - - /** - * Temporary key value from the Security Manager. - * - * <p> Must be {@link LE_TK_OCTETS} in size - */ - private byte[] mLeTemporaryKey = null; - - /** - * Defines the representation of the external appearance of the device. - * - * <p>For example, a mouse, remote control, or keyboard. - * - * <p>Used for visual on discovering device to represent icon/string/etc... - */ - private byte[] mLeAppearance = null; - - /** - * Contains which discoverable mode to use, BR/EDR support and capability. - * - * <p>Possible LE Flags: - * {@link LE_FLAG_LIMITED_DISCOVERY_MODE} LE Limited Discoverable Mode. - * {@link LE_FLAG_GENERAL_DISCOVERY_MODE} LE General Discoverable Mode. - * {@link LE_FLAG_BREDR_NOT_SUPPORTED} BR/EDR Not Supported. Bit 37 of - * LMP Feature Mask Definitions. - * {@link LE_FLAG_SIMULTANEOUS_CONTROLLER} Simultaneous LE and BR/EDR to - * Same Device Capable (Controller). - * Bit 49 of LMP Feature Mask Definitions. - * {@link LE_FLAG_SIMULTANEOUS_HOST} Simultaneous LE and BR/EDR to - * Same Device Capable (Host). - * Bit 55 of LMP Feature Mask Definitions. - * <b>0x05- 0x07 Reserved</b> - */ - private @LeFlag int mLeFlags = LE_FLAG_GENERAL_DISCOVERY_MODE; // Invalid default - - /** - * Main creation method for creating a LE version of {@link OobData}. - * - * <p>This object will allow the caller to call {@link LeBuilder#build()} - * to build the data object or add any option information to the builder. - * - * @param deviceAddressWithType the LE device address plus the address type (7 octets); - * not null. - * @param leDeviceRole whether the device supports Peripheral, Central, - * Both including preference; not null. (1 octet) - * @param confirmationHash Array consisting of {@link OobData#CONFIRMATION_OCTETS} octets - * of data. Data is derived from controller/host stack and is - * required for pairing OOB. - * - * <p>Possible Values: - * {@link LE_DEVICE_ROLE_PERIPHERAL_ONLY} Only Peripheral supported - * {@link LE_DEVICE_ROLE_CENTRAL_ONLY} Only Central supported - * {@link LE_DEVICE_ROLE_BOTH_PREFER_PERIPHERAL} Central & Peripheral supported; - * Peripheral Preferred - * {@link LE_DEVICE_ROLE_BOTH_PREFER_CENTRAL} Only peripheral supported; Central Preferred - * 0x04 - 0xFF Reserved - * - * @throws IllegalArgumentException if any of the values fail to be set. - * @throws NullPointerException if any argument is null. - * - * @hide - */ - @SystemApi - public LeBuilder(@NonNull byte[] confirmationHash, @NonNull byte[] deviceAddressWithType, - @LeRole int leDeviceRole) { - requireNonNull(confirmationHash); - requireNonNull(deviceAddressWithType); - if (confirmationHash.length != OobData.CONFIRMATION_OCTETS) { - throw new IllegalArgumentException("confirmationHash must be " - + OobData.CONFIRMATION_OCTETS + " octets in length."); - } - this.mConfirmationHash = confirmationHash; - if (deviceAddressWithType.length != OobData.DEVICE_ADDRESS_OCTETS) { - throw new IllegalArgumentException("confirmationHash must be " - + OobData.DEVICE_ADDRESS_OCTETS+ " octets in length."); - } - this.mDeviceAddressWithType = deviceAddressWithType; - if (leDeviceRole < LE_DEVICE_ROLE_PERIPHERAL_ONLY - || leDeviceRole > LE_DEVICE_ROLE_BOTH_PREFER_CENTRAL) { - throw new IllegalArgumentException("leDeviceRole must be a valid value."); - } - this.mLeDeviceRole = leDeviceRole; - } - - /** - * Sets the Temporary Key value to be used by the LE Security Manager during - * LE pairing. - * - * @param leTemporaryKey byte array that shall be 16 bytes. Please see Bluetooth CSSv6, - * Part A 1.8 for a detailed description. - * - * @return {@link OobData#Builder} - * - * @throws IllegalArgumentException if the leTemporaryKey is an invalid format. - * @throws NullinterException if leTemporaryKey is null. - * - * @hide - */ - @NonNull - @SystemApi - public LeBuilder setLeTemporaryKey(@NonNull byte[] leTemporaryKey) { - requireNonNull(leTemporaryKey); - if (leTemporaryKey.length != LE_TK_OCTETS) { - throw new IllegalArgumentException("leTemporaryKey must be " - + LE_TK_OCTETS + " octets in length."); - } - this.mLeTemporaryKey = leTemporaryKey; - return this; - } - - /** - * @param randomizerHash byte array consisting of {@link OobData#RANDOMIZER_OCTETS} octets - * of data. Data is derived from controller/host stack and is required for pairing OOB. - * Also, randomizerHash may be all 0s or null in which case it becomes all 0s. - * - * @throws IllegalArgumentException if null or incorrect length randomizerHash was passed. - * @throws NullPointerException if randomizerHash is null. - * - * @hide - */ - @NonNull - @SystemApi - public LeBuilder setRandomizerHash(@NonNull byte[] randomizerHash) { - requireNonNull(randomizerHash); - if (randomizerHash.length != OobData.RANDOMIZER_OCTETS) { - throw new IllegalArgumentException("randomizerHash must be " - + OobData.RANDOMIZER_OCTETS + " octets in length."); - } - this.mRandomizerHash = randomizerHash; - return this; - } - - /** - * Sets the LE Flags necessary for the pairing scenario or discovery mode. - * - * @param leFlags enum value representing the 1 octet of data about discovery modes. - * - * <p>Possible LE Flags: - * {@link LE_FLAG_LIMITED_DISCOVERY_MODE} LE Limited Discoverable Mode. - * {@link LE_FLAG_GENERAL_DISCOVERY_MODE} LE General Discoverable Mode. - * {@link LE_FLAG_BREDR_NOT_SUPPORTED} BR/EDR Not Supported. Bit 37 of - * LMP Feature Mask Definitions. - * {@link LE_FLAG_SIMULTANEOUS_CONTROLLER} Simultaneous LE and BR/EDR to - * Same Device Capable (Controller) Bit 49 of LMP Feature Mask Definitions. - * {@link LE_FLAG_SIMULTANEOUS_HOST} Simultaneous LE and BR/EDR to - * Same Device Capable (Host). - * Bit 55 of LMP Feature Mask Definitions. - * 0x05- 0x07 Reserved - * - * @throws IllegalArgumentException for invalid flag - * @hide - */ - @NonNull - @SystemApi - public LeBuilder setLeFlags(@LeFlag int leFlags) { - if (leFlags < LE_FLAG_LIMITED_DISCOVERY_MODE || leFlags > LE_FLAG_SIMULTANEOUS_HOST) { - throw new IllegalArgumentException("leFlags must be a valid value."); - } - this.mLeFlags = leFlags; - return this; - } - - /** - * Validates and builds the {@link OobData} object for LE Security. - * - * @return {@link OobData} with given builder values - * - * @throws IllegalStateException if either of the 2 required fields were not set. - * - * @hide - */ - @NonNull - @SystemApi - public OobData build() { - final OobData oob = - new OobData(this.mDeviceAddressWithType, this.mLeDeviceRole, - this.mConfirmationHash); - - // If we have values, set them, otherwise use default - oob.mLeTemporaryKey = - (this.mLeTemporaryKey != null) ? this.mLeTemporaryKey : oob.mLeTemporaryKey; - oob.mLeAppearance = (this.mLeAppearance != null) - ? this.mLeAppearance : oob.mLeAppearance; - oob.mLeFlags = (this.mLeFlags != 0xF) ? this.mLeFlags : oob.mLeFlags; - oob.mDeviceName = (this.mDeviceName != null) ? this.mDeviceName : oob.mDeviceName; - oob.mRandomizerHash = this.mRandomizerHash; - return oob; - } - } - - /** - * Builds an {@link OobData} object and validates that the required combination - * of values are present to create the Classic specific OobData type. - * - * @hide - */ - @SystemApi - public static final class ClassicBuilder { - // Used by both Classic and LE - /** - * It is recommended that this Hash C is generated anew for each - * pairing. - * - * <p>It should be noted that on passive NFC this isn't possible as the data is static - * and immutable. - * - * @hide - */ - private byte[] mConfirmationHash = null; - - /** - * Optional, but adds more validity to the pairing. - * - * <p>If not present a value of 0 is assumed. - * - * @hide - */ - private byte[] mRandomizerHash = new byte[] { - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - }; - - /** - * The Bluetooth Device user-friendly name presented over Bluetooth Technology. - * - * <p>This is the name that may be displayed to the device user as part of the UI. - * - * @hide - */ - private byte[] mDeviceName = null; - - /** - * This length value provides the absolute length of total OOB data block used for - * Bluetooth BR/EDR - * - * <p>OOB communication, which includes the length field itself and the Bluetooth - * Device Address. - * - * <p>The minimum length that may be represented in this field is 8. - * - * @hide - */ - private final byte[] mClassicLength; - - /** - * The Bluetooth Device Address is the address to which the OOB data belongs. - * - * <p>The length MUST be {@link OobData#DEVICE_ADDRESS_OCTETS} octets. - * - * <p> Address is encoded in Little Endian order. - * - * <p>e.g. 00:01:02:03:04:05 would be x05x04x03x02x01x00 - * - * @hide - */ - private final byte[] mDeviceAddressWithType; - - /** - * Class of Device information is to be used to provide a graphical representation - * to the user as part of UI involving operations. - * - * <p>This is not to be used to determine a particular service can be used. - * - * <p>The length MUST be {@link OobData#CLASS_OF_DEVICE_OCTETS} octets. - * - * @hide - */ - private byte[] mClassOfDevice = null; - - /** - * Main creation method for creating a Classic version of {@link OobData}. - * - * <p>This object will allow the caller to call {@link ClassicBuilder#build()} - * to build the data object or add any option information to the builder. - * - * @param confirmationHash byte array consisting of {@link OobData#CONFIRMATION_OCTETS} - * octets of data. Data is derived from controller/host stack and is required for pairing - * OOB. - * @param classicLength byte array representing the length of data from 8-65535 across 2 - * octets (0xXXXX). - * @param deviceAddressWithType byte array representing the Bluetooth Address of the device - * that owns the OOB data. (i.e. the originator) [6 octets] - * - * @throws IllegalArgumentException if any of the values fail to be set. - * @throws NullPointerException if any argument is null. - * - * @hide - */ - @SystemApi - public ClassicBuilder(@NonNull byte[] confirmationHash, @NonNull byte[] classicLength, - @NonNull byte[] deviceAddressWithType) { - requireNonNull(confirmationHash); - requireNonNull(classicLength); - requireNonNull(deviceAddressWithType); - if (confirmationHash.length != OobData.CONFIRMATION_OCTETS) { - throw new IllegalArgumentException("confirmationHash must be " - + OobData.CONFIRMATION_OCTETS + " octets in length."); - } - this.mConfirmationHash = confirmationHash; - if (classicLength.length != OOB_LENGTH_OCTETS) { - throw new IllegalArgumentException("classicLength must be " - + OOB_LENGTH_OCTETS + " octets in length."); - } - this.mClassicLength = classicLength; - if (deviceAddressWithType.length != DEVICE_ADDRESS_OCTETS) { - throw new IllegalArgumentException("deviceAddressWithType must be " - + DEVICE_ADDRESS_OCTETS + " octets in length."); - } - this.mDeviceAddressWithType = deviceAddressWithType; - } - - /** - * @param randomizerHash byte array consisting of {@link OobData#RANDOMIZER_OCTETS} octets - * of data. Data is derived from controller/host stack and is required for pairing OOB. - * Also, randomizerHash may be all 0s or null in which case it becomes all 0s. - * - * @throws IllegalArgumentException if null or incorrect length randomizerHash was passed. - * @throws NullPointerException if randomizerHash is null. - * - * @hide - */ - @NonNull - @SystemApi - public ClassicBuilder setRandomizerHash(@NonNull byte[] randomizerHash) { - requireNonNull(randomizerHash); - if (randomizerHash.length != OobData.RANDOMIZER_OCTETS) { - throw new IllegalArgumentException("randomizerHash must be " - + OobData.RANDOMIZER_OCTETS + " octets in length."); - } - this.mRandomizerHash = randomizerHash; - return this; - } - - /** - * Sets the Bluetooth Device name to be used for UI purposes. - * - * <p>Optional attribute. - * - * @param deviceName byte array representing the name, may be 0 in length, not null. - * - * @return {@link OobData#ClassicBuilder} - * - * @throws NullPointerException if deviceName is null - * - * @hide - */ - @NonNull - @SystemApi - public ClassicBuilder setDeviceName(@NonNull byte[] deviceName) { - requireNonNull(deviceName); - this.mDeviceName = deviceName; - return this; - } - - /** - * Sets the Bluetooth Class of Device; used for UI purposes only. - * - * <p>Not an indicator of available services! - * - * <p>Optional attribute. - * - * @param classOfDevice byte array of {@link OobData#CLASS_OF_DEVICE_OCTETS} octets. - * - * @return {@link OobData#ClassicBuilder} - * - * @throws IllegalArgumentException if length is not equal to - * {@link OobData#CLASS_OF_DEVICE_OCTETS} octets. - * @throws NullPointerException if classOfDevice is null. - * - * @hide - */ - @NonNull - @SystemApi - public ClassicBuilder setClassOfDevice(@NonNull byte[] classOfDevice) { - requireNonNull(classOfDevice); - if (classOfDevice.length != OobData.CLASS_OF_DEVICE_OCTETS) { - throw new IllegalArgumentException("classOfDevice must be " - + OobData.CLASS_OF_DEVICE_OCTETS + " octets in length."); - } - this.mClassOfDevice = classOfDevice; - return this; - } - - /** - * Validates and builds the {@link OobDat object for Classic Security. - * - * @return {@link OobData} with previously given builder values. - * - * @hide - */ - @NonNull - @SystemApi - public OobData build() { - final OobData oob = - new OobData(this.mClassicLength, this.mDeviceAddressWithType, - this.mConfirmationHash); - // If we have values, set them, otherwise use default - oob.mDeviceName = (this.mDeviceName != null) ? this.mDeviceName : oob.mDeviceName; - oob.mClassOfDevice = (this.mClassOfDevice != null) - ? this.mClassOfDevice : oob.mClassOfDevice; - oob.mRandomizerHash = this.mRandomizerHash; - return oob; - } - } - - // Members (Defaults for Optionals must be set or Parceling fails on NPE) - // Both - private final byte[] mDeviceAddressWithType; - private final byte[] mConfirmationHash; - private byte[] mRandomizerHash = new byte[] { - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - }; - // Default the name to "Bluetooth Device" - private byte[] mDeviceName = new byte[] { - // Bluetooth - 0x42, 0x6c, 0x75, 0x65, 0x74, 0x6f, 0x6f, 0x74, 0x68, - // <space>Device - 0x20, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65 - }; - - // Classic - private final byte[] mClassicLength; - private byte[] mClassOfDevice = new byte[CLASS_OF_DEVICE_OCTETS]; - - // LE - private final @LeRole int mLeDeviceRole; - private byte[] mLeTemporaryKey = new byte[LE_TK_OCTETS]; - private byte[] mLeAppearance = new byte[LE_APPEARANCE_OCTETS]; - private @LeFlag int mLeFlags = LE_FLAG_LIMITED_DISCOVERY_MODE; - - /** - * @return byte array representing the MAC address of a bluetooth device. - * The Address is 6 octets long with a 1 octet address type associated with the address. - * - * <p>For classic this will be 6 byte address plus the default of PUBLIC_ADDRESS Address Type. - * For LE there are more choices for Address Type. - * - * @hide - */ - @NonNull - @SystemApi - public byte[] getDeviceAddressWithType() { - return mDeviceAddressWithType; - } - - /** - * @return byte array representing the confirmationHash value - * which is used to confirm the identity to the controller. - * - * @hide - */ - @NonNull - @SystemApi - public byte[] getConfirmationHash() { - return mConfirmationHash; - } - - /** - * @return byte array representing the randomizerHash value - * which is used to verify the identity of the controller. - * - * @hide - */ - @NonNull - @SystemApi - public byte[] getRandomizerHash() { - return mRandomizerHash; - } - - /** - * @return Device Name used for displaying name in UI. - * - * <p>Also, this will be populated with the LE Local Name if the data is for LE. - * - * @hide - */ - @Nullable - @SystemApi - public byte[] getDeviceName() { - return mDeviceName; - } - - /** - * @return byte array representing the oob data length which is the length - * of all of the data including these octets. - * - * @hide - */ - @NonNull - @SystemApi - public byte[] getClassicLength() { - return mClassicLength; - } - - /** - * @return byte array representing the class of device for UI display. - * - * <p>Does not indicate services available; for display only. - * - * @hide - */ - @NonNull - @SystemApi - public byte[] getClassOfDevice() { - return mClassOfDevice; - } - - /** - * @return Temporary Key used for LE pairing. - * - * @hide - */ - @Nullable - @SystemApi - public byte[] getLeTemporaryKey() { - return mLeTemporaryKey; - } - - /** - * @return Appearance used for LE pairing. For use in UI situations - * when determining what sort of icons or text to display regarding - * the device. - * - * @hide - */ - @Nullable - @SystemApi - public byte[] getLeAppearance() { - return mLeAppearance; - } - - /** - * @return Flags used to determing discoverable mode to use, BR/EDR Support, and Capability. - * - * <p>Possible LE Flags: - * {@link LE_FLAG_LIMITED_DISCOVERY_MODE} LE Limited Discoverable Mode. - * {@link LE_FLAG_GENERAL_DISCOVERY_MODE} LE General Discoverable Mode. - * {@link LE_FLAG_BREDR_NOT_SUPPORTED} BR/EDR Not Supported. Bit 37 of - * LMP Feature Mask Definitions. - * {@link LE_FLAG_SIMULTANEOUS_CONTROLLER} Simultaneous LE and BR/EDR to - * Same Device Capable (Controller). - * Bit 49 of LMP Feature Mask Definitions. - * {@link LE_FLAG_SIMULTANEOUS_HOST} Simultaneous LE and BR/EDR to - * Same Device Capable (Host). - * Bit 55 of LMP Feature Mask Definitions. - * <b>0x05- 0x07 Reserved</b> - * - * @hide - */ - @NonNull - @SystemApi - @LeFlag - public int getLeFlags() { - return mLeFlags; - } - - /** - * @return the supported and preferred roles of the LE device. - * - * <p>Possible Values: - * {@link LE_DEVICE_ROLE_PERIPHERAL_ONLY} Only Peripheral supported - * {@link LE_DEVICE_ROLE_CENTRAL_ONLY} Only Central supported - * {@link LE_DEVICE_ROLE_BOTH_PREFER_PERIPHERAL} Central & Peripheral supported; - * Peripheral Preferred - * {@link LE_DEVICE_ROLE_BOTH_PREFER_CENTRAL} Only peripheral supported; Central Preferred - * 0x04 - 0xFF Reserved - * - * @hide - */ - @NonNull - @SystemApi - @LeRole - public int getLeDeviceRole() { - return mLeDeviceRole; - } - - /** - * Classic Security Constructor - */ - private OobData(@NonNull byte[] classicLength, @NonNull byte[] deviceAddressWithType, - @NonNull byte[] confirmationHash) { - mClassicLength = classicLength; - mDeviceAddressWithType = deviceAddressWithType; - mConfirmationHash = confirmationHash; - mLeDeviceRole = -1; // Satisfy final - } - - /** - * LE Security Constructor - */ - private OobData(@NonNull byte[] deviceAddressWithType, @LeRole int leDeviceRole, - @NonNull byte[] confirmationHash) { - mDeviceAddressWithType = deviceAddressWithType; - mLeDeviceRole = leDeviceRole; - mConfirmationHash = confirmationHash; - mClassicLength = new byte[OOB_LENGTH_OCTETS]; // Satisfy final - } - - private OobData(Parcel in) { - // Both - mDeviceAddressWithType = in.createByteArray(); - mConfirmationHash = in.createByteArray(); - mRandomizerHash = in.createByteArray(); - mDeviceName = in.createByteArray(); - - // Classic - mClassicLength = in.createByteArray(); - mClassOfDevice = in.createByteArray(); - - // LE - mLeDeviceRole = in.readInt(); - mLeTemporaryKey = in.createByteArray(); - mLeAppearance = in.createByteArray(); - mLeFlags = in.readInt(); - } - - /** - * @hide - */ - @Override - public int describeContents() { - return 0; - } - - /** - * @hide - */ - @Override - public void writeToParcel(@NonNull Parcel out, int flags) { - // Both - // Required - out.writeByteArray(mDeviceAddressWithType); - // Required - out.writeByteArray(mConfirmationHash); - // Optional - out.writeByteArray(mRandomizerHash); - // Optional - out.writeByteArray(mDeviceName); - - // Classic - // Required - out.writeByteArray(mClassicLength); - // Optional - out.writeByteArray(mClassOfDevice); - - // LE - // Required - out.writeInt(mLeDeviceRole); - // Required - out.writeByteArray(mLeTemporaryKey); - // Optional - out.writeByteArray(mLeAppearance); - // Optional - out.writeInt(mLeFlags); - } - - // For Parcelable - public static final @android.annotation.NonNull Parcelable.Creator<OobData> CREATOR = - new Parcelable.Creator<OobData>() { - public OobData createFromParcel(Parcel in) { - return new OobData(in); - } - - public OobData[] newArray(int size) { - return new OobData[size]; - } - }; - - /** - * @return a {@link String} representation of the OobData object. - * - * @hide - */ - @Override - @NonNull - public String toString() { - return "OobData: \n\t" - // Both - + "Device Address With Type: " + toHexString(mDeviceAddressWithType) + "\n\t" - + "Confirmation: " + toHexString(mConfirmationHash) + "\n\t" - + "Randomizer: " + toHexString(mRandomizerHash) + "\n\t" - + "Device Name: " + toHexString(mDeviceName) + "\n\t" - // Classic - + "OobData Length: " + toHexString(mClassicLength) + "\n\t" - + "Class of Device: " + toHexString(mClassOfDevice) + "\n\t" - // LE - + "LE Device Role: " + toHexString(mLeDeviceRole) + "\n\t" - + "LE Temporary Key: " + toHexString(mLeTemporaryKey) + "\n\t" - + "LE Appearance: " + toHexString(mLeAppearance) + "\n\t" - + "LE Flags: " + toHexString(mLeFlags) + "\n\t"; - } - - @NonNull - private String toHexString(int b) { - return toHexString(new byte[] {(byte) b}); - } - - @NonNull - private String toHexString(byte b) { - return toHexString(new byte[] {b}); - } - - @NonNull - private String toHexString(byte[] array) { - if (array == null) return "null"; - StringBuilder builder = new StringBuilder(array.length * 2); - for (byte b: array) { - builder.append(String.format("%02x", b)); - } - return builder.toString(); - } -} diff --git a/core/java/android/bluetooth/SdpDipRecord.java b/core/java/android/bluetooth/SdpDipRecord.java deleted file mode 100644 index 84b0eef0593e..000000000000 --- a/core/java/android/bluetooth/SdpDipRecord.java +++ /dev/null @@ -1,104 +0,0 @@ -/* -* Copyright (C) 2015 Samsung System LSI -* 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.bluetooth; - -import java.util.Arrays; - -import android.os.Parcel; -import android.os.Parcelable; - -/** - * Data representation of a Object Push Profile Server side SDP record. - */ -/** @hide */ -public class SdpDipRecord implements Parcelable { - private final int mSpecificationId; - private final int mVendorId; - private final int mVendorIdSource; - private final int mProductId; - private final int mVersion; - private final boolean mPrimaryRecord; - - public SdpDipRecord(int specificationId, - int vendorId, int vendorIdSource, - int productId, int version, - boolean primaryRecord) { - super(); - this.mSpecificationId = specificationId; - this.mVendorId = vendorId; - this.mVendorIdSource = vendorIdSource; - this.mProductId = productId; - this.mVersion = version; - this.mPrimaryRecord = primaryRecord; - } - - public SdpDipRecord(Parcel in) { - this.mSpecificationId = in.readInt(); - this.mVendorId = in.readInt(); - this.mVendorIdSource = in.readInt(); - this.mProductId = in.readInt(); - this.mVersion = in.readInt(); - this.mPrimaryRecord = in.readBoolean(); - } - - public int getSpecificationId() { - return mSpecificationId; - } - - public int getVendorId() { - return mVendorId; - } - - public int getVendorIdSource() { - return mVendorIdSource; - } - - public int getProductId() { - return mProductId; - } - - public int getVersion() { - return mVersion; - } - - public boolean getPrimaryRecord() { - return mPrimaryRecord; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mSpecificationId); - dest.writeInt(mVendorId); - dest.writeInt(mVendorIdSource); - dest.writeInt(mProductId); - dest.writeInt(mVersion); - dest.writeBoolean(mPrimaryRecord); - } - - @Override - public int describeContents() { - /* No special objects */ - return 0; - } - - public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { - public SdpDipRecord createFromParcel(Parcel in) { - return new SdpDipRecord(in); - } - public SdpDipRecord[] newArray(int size) { - return new SdpDipRecord[size]; - } - }; -} diff --git a/core/java/android/bluetooth/SdpMasRecord.java b/core/java/android/bluetooth/SdpMasRecord.java deleted file mode 100644 index 72d49380b713..000000000000 --- a/core/java/android/bluetooth/SdpMasRecord.java +++ /dev/null @@ -1,150 +0,0 @@ -/* -* Copyright (C) 2015 Samsung System LSI -* 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.bluetooth; - -import android.os.Parcel; -import android.os.Parcelable; - -/** @hide */ -public class SdpMasRecord implements Parcelable { - private final int mMasInstanceId; - private final int mL2capPsm; - private final int mRfcommChannelNumber; - private final int mProfileVersion; - private final int mSupportedFeatures; - private final int mSupportedMessageTypes; - private final String mServiceName; - - /** Message type */ - public static final class MessageType { - public static final int EMAIL = 0x01; - public static final int SMS_GSM = 0x02; - public static final int SMS_CDMA = 0x04; - public static final int MMS = 0x08; - } - - public SdpMasRecord(int masInstanceId, - int l2capPsm, - int rfcommChannelNumber, - int profileVersion, - int supportedFeatures, - int supportedMessageTypes, - String serviceName) { - mMasInstanceId = masInstanceId; - mL2capPsm = l2capPsm; - mRfcommChannelNumber = rfcommChannelNumber; - mProfileVersion = profileVersion; - mSupportedFeatures = supportedFeatures; - mSupportedMessageTypes = supportedMessageTypes; - mServiceName = serviceName; - } - - public SdpMasRecord(Parcel in) { - mMasInstanceId = in.readInt(); - mL2capPsm = in.readInt(); - mRfcommChannelNumber = in.readInt(); - mProfileVersion = in.readInt(); - mSupportedFeatures = in.readInt(); - mSupportedMessageTypes = in.readInt(); - mServiceName = in.readString(); - } - - @Override - public int describeContents() { - // TODO Auto-generated method stub - return 0; - } - - public int getMasInstanceId() { - return mMasInstanceId; - } - - public int getL2capPsm() { - return mL2capPsm; - } - - public int getRfcommCannelNumber() { - return mRfcommChannelNumber; - } - - public int getProfileVersion() { - return mProfileVersion; - } - - public int getSupportedFeatures() { - return mSupportedFeatures; - } - - public int getSupportedMessageTypes() { - return mSupportedMessageTypes; - } - - public boolean msgSupported(int msg) { - return (mSupportedMessageTypes & msg) != 0; - } - - public String getServiceName() { - return mServiceName; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mMasInstanceId); - dest.writeInt(mL2capPsm); - dest.writeInt(mRfcommChannelNumber); - dest.writeInt(mProfileVersion); - dest.writeInt(mSupportedFeatures); - dest.writeInt(mSupportedMessageTypes); - dest.writeString(mServiceName); - } - - @Override - public String toString() { - String ret = "Bluetooth MAS SDP Record:\n"; - - if (mMasInstanceId != -1) { - ret += "Mas Instance Id: " + mMasInstanceId + "\n"; - } - if (mRfcommChannelNumber != -1) { - ret += "RFCOMM Chan Number: " + mRfcommChannelNumber + "\n"; - } - if (mL2capPsm != -1) { - ret += "L2CAP PSM: " + mL2capPsm + "\n"; - } - if (mServiceName != null) { - ret += "Service Name: " + mServiceName + "\n"; - } - if (mProfileVersion != -1) { - ret += "Profile version: " + mProfileVersion + "\n"; - } - if (mSupportedMessageTypes != -1) { - ret += "Supported msg types: " + mSupportedMessageTypes + "\n"; - } - if (mSupportedFeatures != -1) { - ret += "Supported features: " + mSupportedFeatures + "\n"; - } - return ret; - } - - public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { - public SdpMasRecord createFromParcel(Parcel in) { - return new SdpMasRecord(in); - } - - public SdpRecord[] newArray(int size) { - return new SdpRecord[size]; - } - }; -} diff --git a/core/java/android/bluetooth/SdpMnsRecord.java b/core/java/android/bluetooth/SdpMnsRecord.java deleted file mode 100644 index a781d5df7dd0..000000000000 --- a/core/java/android/bluetooth/SdpMnsRecord.java +++ /dev/null @@ -1,114 +0,0 @@ -/* -* Copyright (C) 2015 Samsung System LSI -* 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.bluetooth; - -import android.os.Parcel; -import android.os.Parcelable; - -/** @hide */ -public class SdpMnsRecord implements Parcelable { - private final int mL2capPsm; - private final int mRfcommChannelNumber; - private final int mSupportedFeatures; - private final int mProfileVersion; - private final String mServiceName; - - public SdpMnsRecord(int l2capPsm, - int rfcommChannelNumber, - int profileVersion, - int supportedFeatures, - String serviceName) { - mL2capPsm = l2capPsm; - mRfcommChannelNumber = rfcommChannelNumber; - mSupportedFeatures = supportedFeatures; - mServiceName = serviceName; - mProfileVersion = profileVersion; - } - - public SdpMnsRecord(Parcel in) { - mRfcommChannelNumber = in.readInt(); - mL2capPsm = in.readInt(); - mServiceName = in.readString(); - mSupportedFeatures = in.readInt(); - mProfileVersion = in.readInt(); - } - - @Override - public int describeContents() { - // TODO Auto-generated method stub - return 0; - } - - - public int getL2capPsm() { - return mL2capPsm; - } - - public int getRfcommChannelNumber() { - return mRfcommChannelNumber; - } - - public int getSupportedFeatures() { - return mSupportedFeatures; - } - - public String getServiceName() { - return mServiceName; - } - - public int getProfileVersion() { - return mProfileVersion; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mRfcommChannelNumber); - dest.writeInt(mL2capPsm); - dest.writeString(mServiceName); - dest.writeInt(mSupportedFeatures); - dest.writeInt(mProfileVersion); - } - - public String toString() { - String ret = "Bluetooth MNS SDP Record:\n"; - - if (mRfcommChannelNumber != -1) { - ret += "RFCOMM Chan Number: " + mRfcommChannelNumber + "\n"; - } - if (mL2capPsm != -1) { - ret += "L2CAP PSM: " + mL2capPsm + "\n"; - } - if (mServiceName != null) { - ret += "Service Name: " + mServiceName + "\n"; - } - if (mSupportedFeatures != -1) { - ret += "Supported features: " + mSupportedFeatures + "\n"; - } - if (mProfileVersion != -1) { - ret += "Profile_version: " + mProfileVersion + "\n"; - } - return ret; - } - - public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { - public SdpMnsRecord createFromParcel(Parcel in) { - return new SdpMnsRecord(in); - } - - public SdpMnsRecord[] newArray(int size) { - return new SdpMnsRecord[size]; - } - }; -} diff --git a/core/java/android/bluetooth/SdpOppOpsRecord.java b/core/java/android/bluetooth/SdpOppOpsRecord.java deleted file mode 100644 index e30745b89821..000000000000 --- a/core/java/android/bluetooth/SdpOppOpsRecord.java +++ /dev/null @@ -1,121 +0,0 @@ -/* -* Copyright (C) 2015 Samsung System LSI -* 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.bluetooth; - -import android.os.Parcel; -import android.os.Parcelable; - -import java.util.Arrays; - -/** - * Data representation of a Object Push Profile Server side SDP record. - */ - -/** @hide */ -public class SdpOppOpsRecord implements Parcelable { - - private final String mServiceName; - private final int mRfcommChannel; - private final int mL2capPsm; - private final int mProfileVersion; - private final byte[] mFormatsList; - - public SdpOppOpsRecord(String serviceName, int rfcommChannel, - int l2capPsm, int version, byte[] formatsList) { - super(); - mServiceName = serviceName; - mRfcommChannel = rfcommChannel; - mL2capPsm = l2capPsm; - mProfileVersion = version; - mFormatsList = formatsList; - } - - public String getServiceName() { - return mServiceName; - } - - public int getRfcommChannel() { - return mRfcommChannel; - } - - public int getL2capPsm() { - return mL2capPsm; - } - - public int getProfileVersion() { - return mProfileVersion; - } - - public byte[] getFormatsList() { - return mFormatsList; - } - - @Override - public int describeContents() { - /* No special objects */ - return 0; - } - - public SdpOppOpsRecord(Parcel in) { - mRfcommChannel = in.readInt(); - mL2capPsm = in.readInt(); - mProfileVersion = in.readInt(); - mServiceName = in.readString(); - int arrayLength = in.readInt(); - if (arrayLength > 0) { - byte[] bytes = new byte[arrayLength]; - in.readByteArray(bytes); - mFormatsList = bytes; - } else { - mFormatsList = null; - } - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mRfcommChannel); - dest.writeInt(mL2capPsm); - dest.writeInt(mProfileVersion); - dest.writeString(mServiceName); - if (mFormatsList != null && mFormatsList.length > 0) { - dest.writeInt(mFormatsList.length); - dest.writeByteArray(mFormatsList); - } else { - dest.writeInt(0); - } - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder("Bluetooth OPP Server SDP Record:\n"); - sb.append(" RFCOMM Chan Number: ").append(mRfcommChannel); - sb.append("\n L2CAP PSM: ").append(mL2capPsm); - sb.append("\n Profile version: ").append(mProfileVersion); - sb.append("\n Service Name: ").append(mServiceName); - sb.append("\n Formats List: ").append(Arrays.toString(mFormatsList)); - return sb.toString(); - } - - public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { - public SdpOppOpsRecord createFromParcel(Parcel in) { - return new SdpOppOpsRecord(in); - } - - public SdpOppOpsRecord[] newArray(int size) { - return new SdpOppOpsRecord[size]; - } - }; - -} diff --git a/core/java/android/bluetooth/SdpPseRecord.java b/core/java/android/bluetooth/SdpPseRecord.java deleted file mode 100644 index 72249d0585c6..000000000000 --- a/core/java/android/bluetooth/SdpPseRecord.java +++ /dev/null @@ -1,129 +0,0 @@ -/* -* Copyright (C) 2015 Samsung System LSI -* 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.bluetooth; - -import android.os.Parcel; -import android.os.Parcelable; - -/** @hide */ -public class SdpPseRecord implements Parcelable { - private final int mL2capPsm; - private final int mRfcommChannelNumber; - private final int mProfileVersion; - private final int mSupportedFeatures; - private final int mSupportedRepositories; - private final String mServiceName; - - public SdpPseRecord(int l2capPsm, - int rfcommChannelNumber, - int profileVersion, - int supportedFeatures, - int supportedRepositories, - String serviceName) { - mL2capPsm = l2capPsm; - mRfcommChannelNumber = rfcommChannelNumber; - mProfileVersion = profileVersion; - mSupportedFeatures = supportedFeatures; - mSupportedRepositories = supportedRepositories; - mServiceName = serviceName; - } - - public SdpPseRecord(Parcel in) { - mRfcommChannelNumber = in.readInt(); - mL2capPsm = in.readInt(); - mProfileVersion = in.readInt(); - mSupportedFeatures = in.readInt(); - mSupportedRepositories = in.readInt(); - mServiceName = in.readString(); - } - - @Override - public int describeContents() { - // TODO Auto-generated method stub - return 0; - } - - public int getL2capPsm() { - return mL2capPsm; - } - - public int getRfcommChannelNumber() { - return mRfcommChannelNumber; - } - - public int getSupportedFeatures() { - return mSupportedFeatures; - } - - public String getServiceName() { - return mServiceName; - } - - public int getProfileVersion() { - return mProfileVersion; - } - - public int getSupportedRepositories() { - return mSupportedRepositories; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mRfcommChannelNumber); - dest.writeInt(mL2capPsm); - dest.writeInt(mProfileVersion); - dest.writeInt(mSupportedFeatures); - dest.writeInt(mSupportedRepositories); - dest.writeString(mServiceName); - - } - - @Override - public String toString() { - String ret = "Bluetooth MNS SDP Record:\n"; - - if (mRfcommChannelNumber != -1) { - ret += "RFCOMM Chan Number: " + mRfcommChannelNumber + "\n"; - } - if (mL2capPsm != -1) { - ret += "L2CAP PSM: " + mL2capPsm + "\n"; - } - if (mProfileVersion != -1) { - ret += "profile version: " + mProfileVersion + "\n"; - } - if (mServiceName != null) { - ret += "Service Name: " + mServiceName + "\n"; - } - if (mSupportedFeatures != -1) { - ret += "Supported features: " + mSupportedFeatures + "\n"; - } - if (mSupportedRepositories != -1) { - ret += "Supported repositories: " + mSupportedRepositories + "\n"; - } - - return ret; - } - - public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { - public SdpPseRecord createFromParcel(Parcel in) { - return new SdpPseRecord(in); - } - - public SdpPseRecord[] newArray(int size) { - return new SdpPseRecord[size]; - } - }; -} diff --git a/core/java/android/bluetooth/SdpRecord.java b/core/java/android/bluetooth/SdpRecord.java deleted file mode 100644 index 730862ec6f91..000000000000 --- a/core/java/android/bluetooth/SdpRecord.java +++ /dev/null @@ -1,77 +0,0 @@ -/* -* Copyright (C) 2015 Samsung System LSI -* 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.bluetooth; - -import android.os.Parcel; -import android.os.Parcelable; - -import java.util.Arrays; - -/** @hide */ -public class SdpRecord implements Parcelable { - - private final byte[] mRawData; - private final int mRawSize; - - @Override - public String toString() { - return "BluetoothSdpRecord [rawData=" + Arrays.toString(mRawData) - + ", rawSize=" + mRawSize + "]"; - } - - public SdpRecord(int sizeRecord, byte[] record) { - mRawData = record; - mRawSize = sizeRecord; - } - - public SdpRecord(Parcel in) { - mRawSize = in.readInt(); - mRawData = new byte[mRawSize]; - in.readByteArray(mRawData); - - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mRawSize); - dest.writeByteArray(mRawData); - - - } - - public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { - public SdpRecord createFromParcel(Parcel in) { - return new SdpRecord(in); - } - - public SdpRecord[] newArray(int size) { - return new SdpRecord[size]; - } - }; - - public byte[] getRawData() { - return mRawData; - } - - public int getRawSize() { - return mRawSize; - } -} diff --git a/core/java/android/bluetooth/SdpSapsRecord.java b/core/java/android/bluetooth/SdpSapsRecord.java deleted file mode 100644 index a1e2f7b51f35..000000000000 --- a/core/java/android/bluetooth/SdpSapsRecord.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2015 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.bluetooth; - -import android.os.Parcel; -import android.os.Parcelable; - -/** @hide */ -public class SdpSapsRecord implements Parcelable { - private final int mRfcommChannelNumber; - private final int mProfileVersion; - private final String mServiceName; - - public SdpSapsRecord(int rfcommChannelNumber, int profileVersion, String serviceName) { - mRfcommChannelNumber = rfcommChannelNumber; - mProfileVersion = profileVersion; - mServiceName = serviceName; - } - - public SdpSapsRecord(Parcel in) { - mRfcommChannelNumber = in.readInt(); - mProfileVersion = in.readInt(); - mServiceName = in.readString(); - } - - @Override - public int describeContents() { - return 0; - } - - public int getRfcommCannelNumber() { - return mRfcommChannelNumber; - } - - public int getProfileVersion() { - return mProfileVersion; - } - - public String getServiceName() { - return mServiceName; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mRfcommChannelNumber); - dest.writeInt(mProfileVersion); - dest.writeString(mServiceName); - - } - - @Override - public String toString() { - String ret = "Bluetooth MAS SDP Record:\n"; - - if (mRfcommChannelNumber != -1) { - ret += "RFCOMM Chan Number: " + mRfcommChannelNumber + "\n"; - } - if (mServiceName != null) { - ret += "Service Name: " + mServiceName + "\n"; - } - if (mProfileVersion != -1) { - ret += "Profile version: " + mProfileVersion + "\n"; - } - return ret; - } - - public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { - public SdpSapsRecord createFromParcel(Parcel in) { - return new SdpSapsRecord(in); - } - - public SdpRecord[] newArray(int size) { - return new SdpRecord[size]; - } - }; -} diff --git a/core/java/android/bluetooth/UidTraffic.java b/core/java/android/bluetooth/UidTraffic.java deleted file mode 100644 index 9982fa6121e4..000000000000 --- a/core/java/android/bluetooth/UidTraffic.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (C) 2015 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.bluetooth; - -import android.annotation.SystemApi; -import android.os.Parcel; -import android.os.Parcelable; - -/** - * Record of data traffic (in bytes) by an application identified by its UID. - * - * @hide - */ -@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) -public final class UidTraffic implements Cloneable, Parcelable { - private final int mAppUid; - private long mRxBytes; - private long mTxBytes; - - /** @hide */ - public UidTraffic(int appUid, long rx, long tx) { - mAppUid = appUid; - mRxBytes = rx; - mTxBytes = tx; - } - - /** @hide */ - private UidTraffic(Parcel in) { - mAppUid = in.readInt(); - mRxBytes = in.readLong(); - mTxBytes = in.readLong(); - } - - /** @hide */ - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mAppUid); - dest.writeLong(mRxBytes); - dest.writeLong(mTxBytes); - } - - /** @hide */ - public void setRxBytes(long bytes) { - mRxBytes = bytes; - } - - /** @hide */ - public void setTxBytes(long bytes) { - mTxBytes = bytes; - } - - /** @hide */ - public void addRxBytes(long bytes) { - mRxBytes += bytes; - } - - /** @hide */ - public void addTxBytes(long bytes) { - mTxBytes += bytes; - } - - /** - * @return corresponding app Uid - */ - public int getUid() { - return mAppUid; - } - - /** - * @return rx bytes count - */ - public long getRxBytes() { - return mRxBytes; - } - - /** - * @return tx bytes count - */ - public long getTxBytes() { - return mTxBytes; - } - - /** @hide */ - @Override - public int describeContents() { - return 0; - } - - /** @hide */ - @Override - public UidTraffic clone() { - return new UidTraffic(mAppUid, mRxBytes, mTxBytes); - } - - /** @hide */ - @Override - public String toString() { - return "UidTraffic{mAppUid=" + mAppUid + ", mRxBytes=" + mRxBytes + ", mTxBytes=" - + mTxBytes + '}'; - } - - public static final @android.annotation.NonNull Creator<UidTraffic> CREATOR = new Creator<UidTraffic>() { - @Override - public UidTraffic createFromParcel(Parcel source) { - return new UidTraffic(source); - } - - @Override - public UidTraffic[] newArray(int size) { - return new UidTraffic[size]; - } - }; -} diff --git a/core/java/android/bluetooth/annotations/RequiresBluetoothAdvertisePermission.java b/core/java/android/bluetooth/annotations/RequiresBluetoothAdvertisePermission.java deleted file mode 100644 index c508c2c9ca0b..000000000000 --- a/core/java/android/bluetooth/annotations/RequiresBluetoothAdvertisePermission.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 android.bluetooth.annotations; - -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.RetentionPolicy.SOURCE; - -import android.Manifest; -import android.os.Build; - -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - -/** - * @memberDoc For apps targeting {@link Build.VERSION_CODES#S} or or higher, - * this requires the {@link Manifest.permission#BLUETOOTH_ADVERTISE} - * permission which can be gained with - * {@link android.app.Activity#requestPermissions(String[], int)}. - * @hide - */ -@Retention(SOURCE) -@Target({METHOD, FIELD}) -public @interface RequiresBluetoothAdvertisePermission { -} diff --git a/core/java/android/bluetooth/annotations/RequiresBluetoothConnectPermission.java b/core/java/android/bluetooth/annotations/RequiresBluetoothConnectPermission.java deleted file mode 100644 index e159eaafe2e4..000000000000 --- a/core/java/android/bluetooth/annotations/RequiresBluetoothConnectPermission.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 android.bluetooth.annotations; - -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.RetentionPolicy.SOURCE; - -import android.Manifest; -import android.os.Build; - -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - -/** - * @memberDoc For apps targeting {@link Build.VERSION_CODES#S} or or higher, - * this requires the {@link Manifest.permission#BLUETOOTH_CONNECT} - * permission which can be gained with - * {@link android.app.Activity#requestPermissions(String[], int)}. - * @hide - */ -@Retention(SOURCE) -@Target({METHOD, FIELD}) -public @interface RequiresBluetoothConnectPermission { -} diff --git a/core/java/android/bluetooth/annotations/RequiresBluetoothLocationPermission.java b/core/java/android/bluetooth/annotations/RequiresBluetoothLocationPermission.java deleted file mode 100644 index 2bb320413941..000000000000 --- a/core/java/android/bluetooth/annotations/RequiresBluetoothLocationPermission.java +++ /dev/null @@ -1,41 +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 android.bluetooth.annotations; - -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.RetentionPolicy.SOURCE; - -import android.Manifest; - -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - -/** - * @memberDoc In addition, this requires either the - * {@link Manifest.permission#ACCESS_FINE_LOCATION} - * permission or a strong assertion that you will never derive the - * physical location of the device. You can make this assertion by - * declaring {@code usesPermissionFlags="neverForLocation"} on the - * relevant {@code <uses-permission>} manifest tag, but it may - * restrict the types of Bluetooth devices you can interact with. - * @hide - */ -@Retention(SOURCE) -@Target({METHOD, FIELD}) -public @interface RequiresBluetoothLocationPermission { -} diff --git a/core/java/android/bluetooth/annotations/RequiresBluetoothScanPermission.java b/core/java/android/bluetooth/annotations/RequiresBluetoothScanPermission.java deleted file mode 100644 index 800ff39933f2..000000000000 --- a/core/java/android/bluetooth/annotations/RequiresBluetoothScanPermission.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 android.bluetooth.annotations; - -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.RetentionPolicy.SOURCE; - -import android.Manifest; -import android.os.Build; - -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - -/** - * @memberDoc For apps targeting {@link Build.VERSION_CODES#S} or or higher, - * this requires the {@link Manifest.permission#BLUETOOTH_SCAN} - * permission which can be gained with - * {@link android.app.Activity#requestPermissions(String[], int)}. - * @hide - */ -@Retention(SOURCE) -@Target({METHOD, FIELD}) -public @interface RequiresBluetoothScanPermission { -} diff --git a/core/java/android/bluetooth/annotations/RequiresLegacyBluetoothAdminPermission.java b/core/java/android/bluetooth/annotations/RequiresLegacyBluetoothAdminPermission.java deleted file mode 100644 index 9adf695cde0f..000000000000 --- a/core/java/android/bluetooth/annotations/RequiresLegacyBluetoothAdminPermission.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 android.bluetooth.annotations; - -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.RetentionPolicy.SOURCE; - -import android.Manifest; -import android.os.Build; - -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - -/** - * @memberDoc For apps targeting {@link Build.VERSION_CODES#R} or lower, this - * requires the {@link Manifest.permission#BLUETOOTH_ADMIN} - * permission which can be gained with a simple - * {@code <uses-permission>} manifest tag. - * @hide - */ -@Retention(SOURCE) -@Target({METHOD, FIELD}) -public @interface RequiresLegacyBluetoothAdminPermission { -} diff --git a/core/java/android/bluetooth/annotations/RequiresLegacyBluetoothPermission.java b/core/java/android/bluetooth/annotations/RequiresLegacyBluetoothPermission.java deleted file mode 100644 index 79621c366f59..000000000000 --- a/core/java/android/bluetooth/annotations/RequiresLegacyBluetoothPermission.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 android.bluetooth.annotations; - -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.RetentionPolicy.SOURCE; - -import android.Manifest; -import android.os.Build; - -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - -/** - * @memberDoc For apps targeting {@link Build.VERSION_CODES#R} or lower, this - * requires the {@link Manifest.permission#BLUETOOTH} permission - * which can be gained with a simple {@code <uses-permission>} - * manifest tag. - * @hide - */ -@Retention(SOURCE) -@Target({METHOD, FIELD}) -public @interface RequiresLegacyBluetoothPermission { -} diff --git a/core/java/android/bluetooth/le/AdvertiseCallback.java b/core/java/android/bluetooth/le/AdvertiseCallback.java deleted file mode 100644 index 4fa8c4f2f539..000000000000 --- a/core/java/android/bluetooth/le/AdvertiseCallback.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2014 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.bluetooth.le; - -/** - * Bluetooth LE advertising callbacks, used to deliver advertising operation status. - */ -public abstract class AdvertiseCallback { - - /** - * The requested operation was successful. - * - * @hide - */ - public static final int ADVERTISE_SUCCESS = 0; - - /** - * Failed to start advertising as the advertise data to be broadcasted is larger than 31 bytes. - */ - public static final int ADVERTISE_FAILED_DATA_TOO_LARGE = 1; - - /** - * Failed to start advertising because no advertising instance is available. - */ - public static final int ADVERTISE_FAILED_TOO_MANY_ADVERTISERS = 2; - - /** - * Failed to start advertising as the advertising is already started. - */ - public static final int ADVERTISE_FAILED_ALREADY_STARTED = 3; - - /** - * Operation failed due to an internal error. - */ - public static final int ADVERTISE_FAILED_INTERNAL_ERROR = 4; - - /** - * This feature is not supported on this platform. - */ - public static final int ADVERTISE_FAILED_FEATURE_UNSUPPORTED = 5; - - /** - * Callback triggered in response to {@link BluetoothLeAdvertiser#startAdvertising} indicating - * that the advertising has been started successfully. - * - * @param settingsInEffect The actual settings used for advertising, which may be different from - * what has been requested. - */ - public void onStartSuccess(AdvertiseSettings settingsInEffect) { - } - - /** - * Callback when advertising could not be started. - * - * @param errorCode Error code (see ADVERTISE_FAILED_* constants) for advertising start - * failures. - */ - public void onStartFailure(int errorCode) { - } -} diff --git a/core/java/android/bluetooth/le/AdvertiseData.java b/core/java/android/bluetooth/le/AdvertiseData.java deleted file mode 100644 index fdf62ec3a647..000000000000 --- a/core/java/android/bluetooth/le/AdvertiseData.java +++ /dev/null @@ -1,374 +0,0 @@ -/* - * Copyright (C) 2014 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.bluetooth.le; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.os.Parcel; -import android.os.ParcelUuid; -import android.os.Parcelable; -import android.util.ArrayMap; -import android.util.SparseArray; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Objects; - -/** - * Advertise data packet container for Bluetooth LE advertising. This represents the data to be - * advertised as well as the scan response data for active scans. - * <p> - * Use {@link AdvertiseData.Builder} to create an instance of {@link AdvertiseData} to be - * advertised. - * - * @see BluetoothLeAdvertiser - * @see ScanRecord - */ -public final class AdvertiseData implements Parcelable { - - @Nullable - private final List<ParcelUuid> mServiceUuids; - - @NonNull - private final List<ParcelUuid> mServiceSolicitationUuids; - - @Nullable - private final List<TransportDiscoveryData> mTransportDiscoveryData; - - private final SparseArray<byte[]> mManufacturerSpecificData; - private final Map<ParcelUuid, byte[]> mServiceData; - private final boolean mIncludeTxPowerLevel; - private final boolean mIncludeDeviceName; - - private AdvertiseData(List<ParcelUuid> serviceUuids, - List<ParcelUuid> serviceSolicitationUuids, - List<TransportDiscoveryData> transportDiscoveryData, - SparseArray<byte[]> manufacturerData, - Map<ParcelUuid, byte[]> serviceData, - boolean includeTxPowerLevel, - boolean includeDeviceName) { - mServiceUuids = serviceUuids; - mServiceSolicitationUuids = serviceSolicitationUuids; - mTransportDiscoveryData = transportDiscoveryData; - mManufacturerSpecificData = manufacturerData; - mServiceData = serviceData; - mIncludeTxPowerLevel = includeTxPowerLevel; - mIncludeDeviceName = includeDeviceName; - } - - /** - * Returns a list of service UUIDs within the advertisement that are used to identify the - * Bluetooth GATT services. - */ - public List<ParcelUuid> getServiceUuids() { - return mServiceUuids; - } - - /** - * Returns a list of service solicitation UUIDs within the advertisement that we invite to connect. - */ - @NonNull - public List<ParcelUuid> getServiceSolicitationUuids() { - return mServiceSolicitationUuids; - } - - /** - * Returns a list of {@link TransportDiscoveryData} within the advertisement. - */ - @NonNull - public List<TransportDiscoveryData> getTransportDiscoveryData() { - if (mTransportDiscoveryData == null) { - return Collections.emptyList(); - } - return mTransportDiscoveryData; - } - - /** - * Returns an array of manufacturer Id and the corresponding manufacturer specific data. The - * manufacturer id is a non-negative number assigned by Bluetooth SIG. - */ - public SparseArray<byte[]> getManufacturerSpecificData() { - return mManufacturerSpecificData; - } - - /** - * Returns a map of 16-bit UUID and its corresponding service data. - */ - public Map<ParcelUuid, byte[]> getServiceData() { - return mServiceData; - } - - /** - * Whether the transmission power level will be included in the advertisement packet. - */ - public boolean getIncludeTxPowerLevel() { - return mIncludeTxPowerLevel; - } - - /** - * Whether the device name will be included in the advertisement packet. - */ - public boolean getIncludeDeviceName() { - return mIncludeDeviceName; - } - - /** - * @hide - */ - @Override - public int hashCode() { - return Objects.hash(mServiceUuids, mServiceSolicitationUuids, mTransportDiscoveryData, - mManufacturerSpecificData, mServiceData, mIncludeDeviceName, mIncludeTxPowerLevel); - } - - /** - * @hide - */ - @Override - public boolean equals(@Nullable Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - AdvertiseData other = (AdvertiseData) obj; - return Objects.equals(mServiceUuids, other.mServiceUuids) - && Objects.equals(mServiceSolicitationUuids, other.mServiceSolicitationUuids) - && Objects.equals(mTransportDiscoveryData, other.mTransportDiscoveryData) - && BluetoothLeUtils.equals(mManufacturerSpecificData, - other.mManufacturerSpecificData) - && BluetoothLeUtils.equals(mServiceData, other.mServiceData) - && mIncludeDeviceName == other.mIncludeDeviceName - && mIncludeTxPowerLevel == other.mIncludeTxPowerLevel; - } - - @Override - public String toString() { - return "AdvertiseData [mServiceUuids=" + mServiceUuids + ", mServiceSolicitationUuids=" - + mServiceSolicitationUuids + ", mTransportDiscoveryData=" - + mTransportDiscoveryData + ", mManufacturerSpecificData=" - + BluetoothLeUtils.toString(mManufacturerSpecificData) + ", mServiceData=" - + BluetoothLeUtils.toString(mServiceData) - + ", mIncludeTxPowerLevel=" + mIncludeTxPowerLevel + ", mIncludeDeviceName=" - + mIncludeDeviceName + "]"; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeTypedArray(mServiceUuids.toArray(new ParcelUuid[mServiceUuids.size()]), flags); - dest.writeTypedArray(mServiceSolicitationUuids.toArray( - new ParcelUuid[mServiceSolicitationUuids.size()]), flags); - - dest.writeTypedList(mTransportDiscoveryData); - - // mManufacturerSpecificData could not be null. - dest.writeInt(mManufacturerSpecificData.size()); - for (int i = 0; i < mManufacturerSpecificData.size(); ++i) { - dest.writeInt(mManufacturerSpecificData.keyAt(i)); - dest.writeByteArray(mManufacturerSpecificData.valueAt(i)); - } - dest.writeInt(mServiceData.size()); - for (ParcelUuid uuid : mServiceData.keySet()) { - dest.writeTypedObject(uuid, flags); - dest.writeByteArray(mServiceData.get(uuid)); - } - dest.writeByte((byte) (getIncludeTxPowerLevel() ? 1 : 0)); - dest.writeByte((byte) (getIncludeDeviceName() ? 1 : 0)); - } - - public static final @android.annotation.NonNull Parcelable.Creator<AdvertiseData> CREATOR = - new Creator<AdvertiseData>() { - @Override - public AdvertiseData[] newArray(int size) { - return new AdvertiseData[size]; - } - - @Override - public AdvertiseData createFromParcel(Parcel in) { - Builder builder = new Builder(); - ArrayList<ParcelUuid> uuids = in.createTypedArrayList(ParcelUuid.CREATOR); - for (ParcelUuid uuid : uuids) { - builder.addServiceUuid(uuid); - } - - ArrayList<ParcelUuid> solicitationUuids = in.createTypedArrayList(ParcelUuid.CREATOR); - for (ParcelUuid uuid : solicitationUuids) { - builder.addServiceSolicitationUuid(uuid); - } - - List<TransportDiscoveryData> transportDiscoveryData = - in.createTypedArrayList(TransportDiscoveryData.CREATOR); - for (TransportDiscoveryData tdd : transportDiscoveryData) { - builder.addTransportDiscoveryData(tdd); - } - - int manufacturerSize = in.readInt(); - for (int i = 0; i < manufacturerSize; ++i) { - int manufacturerId = in.readInt(); - byte[] manufacturerData = in.createByteArray(); - builder.addManufacturerData(manufacturerId, manufacturerData); - } - int serviceDataSize = in.readInt(); - for (int i = 0; i < serviceDataSize; ++i) { - ParcelUuid serviceDataUuid = in.readTypedObject(ParcelUuid.CREATOR); - byte[] serviceData = in.createByteArray(); - builder.addServiceData(serviceDataUuid, serviceData); - } - builder.setIncludeTxPowerLevel(in.readByte() == 1); - builder.setIncludeDeviceName(in.readByte() == 1); - return builder.build(); - } - }; - - /** - * Builder for {@link AdvertiseData}. - */ - public static final class Builder { - @Nullable - private List<ParcelUuid> mServiceUuids = new ArrayList<ParcelUuid>(); - @NonNull - private List<ParcelUuid> mServiceSolicitationUuids = new ArrayList<ParcelUuid>(); - @Nullable - private List<TransportDiscoveryData> mTransportDiscoveryData = - new ArrayList<TransportDiscoveryData>(); - private SparseArray<byte[]> mManufacturerSpecificData = new SparseArray<byte[]>(); - private Map<ParcelUuid, byte[]> mServiceData = new ArrayMap<ParcelUuid, byte[]>(); - private boolean mIncludeTxPowerLevel; - private boolean mIncludeDeviceName; - - /** - * Add a service UUID to advertise data. - * - * @param serviceUuid A service UUID to be advertised. - * @throws IllegalArgumentException If the {@code serviceUuid} is null. - */ - public Builder addServiceUuid(ParcelUuid serviceUuid) { - if (serviceUuid == null) { - throw new IllegalArgumentException("serviceUuid is null"); - } - mServiceUuids.add(serviceUuid); - return this; - } - - /** - * Add a service solicitation UUID to advertise data. - * - * @param serviceSolicitationUuid A service solicitation UUID to be advertised. - * @throws IllegalArgumentException If the {@code serviceSolicitationUuid} is null. - */ - @NonNull - public Builder addServiceSolicitationUuid(@NonNull ParcelUuid serviceSolicitationUuid) { - if (serviceSolicitationUuid == null) { - throw new IllegalArgumentException("serviceSolicitationUuid is null"); - } - mServiceSolicitationUuids.add(serviceSolicitationUuid); - return this; - } - - /** - * Add service data to advertise data. - * - * @param serviceDataUuid 16-bit UUID of the service the data is associated with - * @param serviceData Service data - * @throws IllegalArgumentException If the {@code serviceDataUuid} or {@code serviceData} is - * empty. - */ - public Builder addServiceData(ParcelUuid serviceDataUuid, byte[] serviceData) { - if (serviceDataUuid == null || serviceData == null) { - throw new IllegalArgumentException( - "serviceDataUuid or serviceDataUuid is null"); - } - mServiceData.put(serviceDataUuid, serviceData); - return this; - } - - /** - * Add Transport Discovery Data to advertise data. - * - * @param transportDiscoveryData Transport Discovery Data, consisting of one or more - * Transport Blocks. Transport Discovery Data AD Type Code is already included. - * @throws IllegalArgumentException If the {@code transportDiscoveryData} is empty - */ - @NonNull - public Builder addTransportDiscoveryData( - @NonNull TransportDiscoveryData transportDiscoveryData) { - if (transportDiscoveryData == null) { - throw new IllegalArgumentException("transportDiscoveryData is null"); - } - mTransportDiscoveryData.add(transportDiscoveryData); - return this; - } - - /** - * Add manufacturer specific data. - * <p> - * Please refer to the Bluetooth Assigned Numbers document provided by the <a - * href="https://www.bluetooth.org">Bluetooth SIG</a> for a list of existing company - * identifiers. - * - * @param manufacturerId Manufacturer ID assigned by Bluetooth SIG. - * @param manufacturerSpecificData Manufacturer specific data - * @throws IllegalArgumentException If the {@code manufacturerId} is negative or {@code - * manufacturerSpecificData} is null. - */ - public Builder addManufacturerData(int manufacturerId, byte[] manufacturerSpecificData) { - if (manufacturerId < 0) { - throw new IllegalArgumentException( - "invalid manufacturerId - " + manufacturerId); - } - if (manufacturerSpecificData == null) { - throw new IllegalArgumentException("manufacturerSpecificData is null"); - } - mManufacturerSpecificData.put(manufacturerId, manufacturerSpecificData); - return this; - } - - /** - * Whether the transmission power level should be included in the advertise packet. Tx power - * level field takes 3 bytes in advertise packet. - */ - public Builder setIncludeTxPowerLevel(boolean includeTxPowerLevel) { - mIncludeTxPowerLevel = includeTxPowerLevel; - return this; - } - - /** - * Set whether the device name should be included in advertise packet. - */ - public Builder setIncludeDeviceName(boolean includeDeviceName) { - mIncludeDeviceName = includeDeviceName; - return this; - } - - /** - * Build the {@link AdvertiseData}. - */ - public AdvertiseData build() { - return new AdvertiseData(mServiceUuids, mServiceSolicitationUuids, - mTransportDiscoveryData, mManufacturerSpecificData, mServiceData, - mIncludeTxPowerLevel, mIncludeDeviceName); - } - } -} diff --git a/core/java/android/bluetooth/le/AdvertiseSettings.java b/core/java/android/bluetooth/le/AdvertiseSettings.java deleted file mode 100644 index c52a6ee33989..000000000000 --- a/core/java/android/bluetooth/le/AdvertiseSettings.java +++ /dev/null @@ -1,277 +0,0 @@ -/* - * Copyright (C) 2014 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.bluetooth.le; - -import android.annotation.NonNull; -import android.annotation.SystemApi; -import android.bluetooth.le.AdvertisingSetParameters.AddressTypeStatus; -import android.os.Parcel; -import android.os.Parcelable; - -/** - * The {@link AdvertiseSettings} provide a way to adjust advertising preferences for each - * Bluetooth LE advertisement instance. Use {@link AdvertiseSettings.Builder} to create an - * instance of this class. - */ -public final class AdvertiseSettings implements Parcelable { - /** - * Perform Bluetooth LE advertising in low power mode. This is the default and preferred - * advertising mode as it consumes the least power. - */ - public static final int ADVERTISE_MODE_LOW_POWER = 0; - - /** - * Perform Bluetooth LE advertising in balanced power mode. This is balanced between advertising - * frequency and power consumption. - */ - public static final int ADVERTISE_MODE_BALANCED = 1; - - /** - * Perform Bluetooth LE advertising in low latency, high power mode. This has the highest power - * consumption and should not be used for continuous background advertising. - */ - public static final int ADVERTISE_MODE_LOW_LATENCY = 2; - - /** - * Advertise using the lowest transmission (TX) power level. Low transmission power can be used - * to restrict the visibility range of advertising packets. - */ - public static final int ADVERTISE_TX_POWER_ULTRA_LOW = 0; - - /** - * Advertise using low TX power level. - */ - public static final int ADVERTISE_TX_POWER_LOW = 1; - - /** - * Advertise using medium TX power level. - */ - public static final int ADVERTISE_TX_POWER_MEDIUM = 2; - - /** - * Advertise using high TX power level. This corresponds to largest visibility range of the - * advertising packet. - */ - public static final int ADVERTISE_TX_POWER_HIGH = 3; - - /** - * The maximum limited advertisement duration as specified by the Bluetooth SIG - */ - private static final int LIMITED_ADVERTISING_MAX_MILLIS = 180 * 1000; - - - private final int mAdvertiseMode; - private final int mAdvertiseTxPowerLevel; - private final int mAdvertiseTimeoutMillis; - private final boolean mAdvertiseConnectable; - private final int mOwnAddressType; - - private AdvertiseSettings(int advertiseMode, int advertiseTxPowerLevel, - boolean advertiseConnectable, int advertiseTimeout, - @AddressTypeStatus int ownAddressType) { - mAdvertiseMode = advertiseMode; - mAdvertiseTxPowerLevel = advertiseTxPowerLevel; - mAdvertiseConnectable = advertiseConnectable; - mAdvertiseTimeoutMillis = advertiseTimeout; - mOwnAddressType = ownAddressType; - } - - private AdvertiseSettings(Parcel in) { - mAdvertiseMode = in.readInt(); - mAdvertiseTxPowerLevel = in.readInt(); - mAdvertiseConnectable = in.readInt() != 0; - mAdvertiseTimeoutMillis = in.readInt(); - mOwnAddressType = in.readInt(); - } - - /** - * Returns the advertise mode. - */ - public int getMode() { - return mAdvertiseMode; - } - - /** - * Returns the TX power level for advertising. - */ - public int getTxPowerLevel() { - return mAdvertiseTxPowerLevel; - } - - /** - * Returns whether the advertisement will indicate connectable. - */ - public boolean isConnectable() { - return mAdvertiseConnectable; - } - - /** - * Returns the advertising time limit in milliseconds. - */ - public int getTimeout() { - return mAdvertiseTimeoutMillis; - } - - /** - * @return the own address type for advertising - * - * @hide - */ - @SystemApi - public @AddressTypeStatus int getOwnAddressType() { - return mOwnAddressType; - } - - @Override - public String toString() { - return "Settings [mAdvertiseMode=" + mAdvertiseMode - + ", mAdvertiseTxPowerLevel=" + mAdvertiseTxPowerLevel - + ", mAdvertiseConnectable=" + mAdvertiseConnectable - + ", mAdvertiseTimeoutMillis=" + mAdvertiseTimeoutMillis - + ", mOwnAddressType=" + mOwnAddressType + "]"; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mAdvertiseMode); - dest.writeInt(mAdvertiseTxPowerLevel); - dest.writeInt(mAdvertiseConnectable ? 1 : 0); - dest.writeInt(mAdvertiseTimeoutMillis); - dest.writeInt(mOwnAddressType); - } - - public static final @android.annotation.NonNull Parcelable.Creator<AdvertiseSettings> CREATOR = - new Creator<AdvertiseSettings>() { - @Override - public AdvertiseSettings[] newArray(int size) { - return new AdvertiseSettings[size]; - } - - @Override - public AdvertiseSettings createFromParcel(Parcel in) { - return new AdvertiseSettings(in); - } - }; - - /** - * Builder class for {@link AdvertiseSettings}. - */ - public static final class Builder { - private int mMode = ADVERTISE_MODE_LOW_POWER; - private int mTxPowerLevel = ADVERTISE_TX_POWER_MEDIUM; - private int mTimeoutMillis = 0; - private boolean mConnectable = true; - private int mOwnAddressType = AdvertisingSetParameters.ADDRESS_TYPE_DEFAULT; - - /** - * Set advertise mode to control the advertising power and latency. - * - * @param advertiseMode Bluetooth LE Advertising mode, can only be one of {@link - * AdvertiseSettings#ADVERTISE_MODE_LOW_POWER}, - * {@link AdvertiseSettings#ADVERTISE_MODE_BALANCED}, - * or {@link AdvertiseSettings#ADVERTISE_MODE_LOW_LATENCY}. - * @throws IllegalArgumentException If the advertiseMode is invalid. - */ - public Builder setAdvertiseMode(int advertiseMode) { - if (advertiseMode < ADVERTISE_MODE_LOW_POWER - || advertiseMode > ADVERTISE_MODE_LOW_LATENCY) { - throw new IllegalArgumentException("unknown mode " + advertiseMode); - } - mMode = advertiseMode; - return this; - } - - /** - * Set advertise TX power level to control the transmission power level for the advertising. - * - * @param txPowerLevel Transmission power of Bluetooth LE Advertising, can only be one of - * {@link AdvertiseSettings#ADVERTISE_TX_POWER_ULTRA_LOW}, {@link - * AdvertiseSettings#ADVERTISE_TX_POWER_LOW}, - * {@link AdvertiseSettings#ADVERTISE_TX_POWER_MEDIUM} - * or {@link AdvertiseSettings#ADVERTISE_TX_POWER_HIGH}. - * @throws IllegalArgumentException If the {@code txPowerLevel} is invalid. - */ - public Builder setTxPowerLevel(int txPowerLevel) { - if (txPowerLevel < ADVERTISE_TX_POWER_ULTRA_LOW - || txPowerLevel > ADVERTISE_TX_POWER_HIGH) { - throw new IllegalArgumentException("unknown tx power level " + txPowerLevel); - } - mTxPowerLevel = txPowerLevel; - return this; - } - - /** - * Set whether the advertisement type should be connectable or non-connectable. - * - * @param connectable Controls whether the advertisment type will be connectable (true) or - * non-connectable (false). - */ - public Builder setConnectable(boolean connectable) { - mConnectable = connectable; - return this; - } - - /** - * Limit advertising to a given amount of time. - * - * @param timeoutMillis Advertising time limit. May not exceed 180000 milliseconds. A value - * of 0 will disable the time limit. - * @throws IllegalArgumentException If the provided timeout is over 180000 ms. - */ - public Builder setTimeout(int timeoutMillis) { - if (timeoutMillis < 0 || timeoutMillis > LIMITED_ADVERTISING_MAX_MILLIS) { - throw new IllegalArgumentException("timeoutMillis invalid (must be 0-" - + LIMITED_ADVERTISING_MAX_MILLIS + " milliseconds)"); - } - mTimeoutMillis = timeoutMillis; - return this; - } - - /** - * Set own address type for advertising to control public or privacy mode. If used to set - * address type anything other than {@link AdvertisingSetParameters#ADDRESS_TYPE_DEFAULT}, - * then it will require BLUETOOTH_PRIVILEGED permission and will be checked at the - * time of starting advertising. - * - * @throws IllegalArgumentException If the {@code ownAddressType} is invalid - * - * @hide - */ - @SystemApi - public @NonNull Builder setOwnAddressType(@AddressTypeStatus int ownAddressType) { - if (ownAddressType < AdvertisingSetParameters.ADDRESS_TYPE_DEFAULT - || ownAddressType > AdvertisingSetParameters.ADDRESS_TYPE_RANDOM) { - throw new IllegalArgumentException("unknown address type " + ownAddressType); - } - mOwnAddressType = ownAddressType; - return this; - } - - /** - * Build the {@link AdvertiseSettings} object. - */ - public AdvertiseSettings build() { - return new AdvertiseSettings(mMode, mTxPowerLevel, mConnectable, mTimeoutMillis, - mOwnAddressType); - } - } -} diff --git a/core/java/android/bluetooth/le/AdvertisingSet.java b/core/java/android/bluetooth/le/AdvertisingSet.java deleted file mode 100644 index bbdb6953afd1..000000000000 --- a/core/java/android/bluetooth/le/AdvertisingSet.java +++ /dev/null @@ -1,230 +0,0 @@ -/* - * 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 android.bluetooth.le; - -import android.annotation.RequiresNoPermission; -import android.annotation.RequiresPermission; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.IBluetoothGatt; -import android.bluetooth.IBluetoothManager; -import android.bluetooth.annotations.RequiresBluetoothAdvertisePermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission; -import android.content.AttributionSource; -import android.os.RemoteException; -import android.util.Log; - -/** - * This class provides a way to control single Bluetooth LE advertising instance. - * <p> - * To get an instance of {@link AdvertisingSet}, call the - * {@link BluetoothLeAdvertiser#startAdvertisingSet} method. - * - * @see AdvertiseData - */ -public final class AdvertisingSet { - private static final String TAG = "AdvertisingSet"; - - private final IBluetoothGatt mGatt; - private int mAdvertiserId; - private AttributionSource mAttributionSource; - - /* package */ AdvertisingSet(int advertiserId, IBluetoothManager bluetoothManager, - AttributionSource attributionSource) { - mAdvertiserId = advertiserId; - mAttributionSource = attributionSource; - try { - mGatt = bluetoothManager.getBluetoothGatt(); - } catch (RemoteException e) { - Log.e(TAG, "Failed to get Bluetooth gatt - ", e); - throw new IllegalStateException("Failed to get Bluetooth"); - } - } - - /* package */ void setAdvertiserId(int advertiserId) { - mAdvertiserId = advertiserId; - } - - /** - * Enables Advertising. This method returns immediately, the operation status is - * delivered through {@code callback.onAdvertisingEnabled()}. - * - * @param enable whether the advertising should be enabled (true), or disabled (false) - * @param duration advertising duration, in 10ms unit. Valid range is from 1 (10ms) to 65535 - * (655,350 ms) - * @param maxExtendedAdvertisingEvents maximum number of extended advertising events the - * controller shall attempt to send prior to terminating the extended advertising, even if the - * duration has not expired. Valid range is from 1 to 255. - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothAdvertisePermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) - public void enableAdvertising(boolean enable, int duration, - int maxExtendedAdvertisingEvents) { - try { - mGatt.enableAdvertisingSet(mAdvertiserId, enable, duration, - maxExtendedAdvertisingEvents, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "remote exception - ", e); - } - } - - /** - * Set/update data being Advertised. Make sure that data doesn't exceed the size limit for - * specified AdvertisingSetParameters. This method returns immediately, the operation status is - * delivered through {@code callback.onAdvertisingDataSet()}. - * <p> - * Advertising data must be empty if non-legacy scannable advertising is used. - * - * @param advertiseData Advertisement data to be broadcasted. Size must not exceed {@link - * BluetoothAdapter#getLeMaximumAdvertisingDataLength}. If the advertisement is connectable, - * three bytes will be added for flags. If the update takes place when the advertising set is - * enabled, the data can be maximum 251 bytes long. - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothAdvertisePermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) - public void setAdvertisingData(AdvertiseData advertiseData) { - try { - mGatt.setAdvertisingData(mAdvertiserId, advertiseData, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "remote exception - ", e); - } - } - - /** - * Set/update scan response data. Make sure that data doesn't exceed the size limit for - * specified AdvertisingSetParameters. This method returns immediately, the operation status - * is delivered through {@code callback.onScanResponseDataSet()}. - * - * @param scanResponse Scan response associated with the advertisement data. Size must not - * exceed {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength}. If the update takes place - * when the advertising set is enabled, the data can be maximum 251 bytes long. - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothAdvertisePermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) - public void setScanResponseData(AdvertiseData scanResponse) { - try { - mGatt.setScanResponseData(mAdvertiserId, scanResponse, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "remote exception - ", e); - } - } - - /** - * Update advertising parameters associated with this AdvertisingSet. Must be called when - * advertising is not active. This method returns immediately, the operation status is delivered - * through {@code callback.onAdvertisingParametersUpdated}. - * - * @param parameters advertising set parameters. - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothAdvertisePermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) - public void setAdvertisingParameters(AdvertisingSetParameters parameters) { - try { - mGatt.setAdvertisingParameters(mAdvertiserId, parameters, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "remote exception - ", e); - } - } - - /** - * Update periodic advertising parameters associated with this set. Must be called when - * periodic advertising is not enabled. This method returns immediately, the operation - * status is delivered through {@code callback.onPeriodicAdvertisingParametersUpdated()}. - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothAdvertisePermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) - public void setPeriodicAdvertisingParameters(PeriodicAdvertisingParameters parameters) { - try { - mGatt.setPeriodicAdvertisingParameters(mAdvertiserId, parameters, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "remote exception - ", e); - } - } - - /** - * Used to set periodic advertising data, must be called after setPeriodicAdvertisingParameters, - * or after advertising was started with periodic advertising data set. This method returns - * immediately, the operation status is delivered through - * {@code callback.onPeriodicAdvertisingDataSet()}. - * - * @param periodicData Periodic advertising data. Size must not exceed {@link - * BluetoothAdapter#getLeMaximumAdvertisingDataLength}. If the update takes place when the - * periodic advertising is enabled for this set, the data can be maximum 251 bytes long. - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothAdvertisePermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) - public void setPeriodicAdvertisingData(AdvertiseData periodicData) { - try { - mGatt.setPeriodicAdvertisingData(mAdvertiserId, periodicData, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "remote exception - ", e); - } - } - - /** - * Used to enable/disable periodic advertising. This method returns immediately, the operation - * status is delivered through {@code callback.onPeriodicAdvertisingEnable()}. - * - * @param enable whether the periodic advertising should be enabled (true), or disabled - * (false). - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothAdvertisePermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) - public void setPeriodicAdvertisingEnabled(boolean enable) { - try { - mGatt.setPeriodicAdvertisingEnable(mAdvertiserId, enable, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "remote exception - ", e); - } - } - - /** - * Returns address associated with this advertising set. - * This method is exposed only for Bluetooth PTS tests, no app or system service - * should ever use it. - * - * @hide - */ - @RequiresBluetoothAdvertisePermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_ADVERTISE, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - public void getOwnAddress() { - try { - mGatt.getOwnAddress(mAdvertiserId, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "remote exception - ", e); - } - } - - /** - * Returns advertiserId associated with this advertising set. - * - * @hide - */ - @RequiresNoPermission - public int getAdvertiserId() { - return mAdvertiserId; - } -} diff --git a/core/java/android/bluetooth/le/AdvertisingSetCallback.java b/core/java/android/bluetooth/le/AdvertisingSetCallback.java deleted file mode 100644 index 51324fdb01ff..000000000000 --- a/core/java/android/bluetooth/le/AdvertisingSetCallback.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * 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 android.bluetooth.le; - -/** - * Bluetooth LE advertising set callbacks, used to deliver advertising operation - * status. - */ -public abstract class AdvertisingSetCallback { - - /** - * The requested operation was successful. - */ - public static final int ADVERTISE_SUCCESS = 0; - - /** - * Failed to start advertising as the advertise data to be broadcasted is too - * large. - */ - public static final int ADVERTISE_FAILED_DATA_TOO_LARGE = 1; - - /** - * Failed to start advertising because no advertising instance is available. - */ - public static final int ADVERTISE_FAILED_TOO_MANY_ADVERTISERS = 2; - - /** - * Failed to start advertising as the advertising is already started. - */ - public static final int ADVERTISE_FAILED_ALREADY_STARTED = 3; - - /** - * Operation failed due to an internal error. - */ - public static final int ADVERTISE_FAILED_INTERNAL_ERROR = 4; - - /** - * This feature is not supported on this platform. - */ - public static final int ADVERTISE_FAILED_FEATURE_UNSUPPORTED = 5; - - /** - * Callback triggered in response to {@link BluetoothLeAdvertiser#startAdvertisingSet} - * indicating result of the operation. If status is ADVERTISE_SUCCESS, then advertisingSet - * contains the started set and it is advertising. If error occurred, advertisingSet is - * null, and status will be set to proper error code. - * - * @param advertisingSet The advertising set that was started or null if error. - * @param txPower tx power that will be used for this set. - * @param status Status of the operation. - */ - public void onAdvertisingSetStarted(AdvertisingSet advertisingSet, int txPower, int status) { - } - - /** - * Callback triggered in response to {@link BluetoothLeAdvertiser#stopAdvertisingSet} - * indicating advertising set is stopped. - * - * @param advertisingSet The advertising set. - */ - public void onAdvertisingSetStopped(AdvertisingSet advertisingSet) { - } - - /** - * Callback triggered in response to {@link BluetoothLeAdvertiser#startAdvertisingSet} - * indicating result of the operation. If status is ADVERTISE_SUCCESS, then advertising set is - * advertising. - * - * @param advertisingSet The advertising set. - * @param status Status of the operation. - */ - public void onAdvertisingEnabled(AdvertisingSet advertisingSet, boolean enable, int status) { - } - - /** - * Callback triggered in response to {@link AdvertisingSet#setAdvertisingData} indicating - * result of the operation. If status is ADVERTISE_SUCCESS, then data was changed. - * - * @param advertisingSet The advertising set. - * @param status Status of the operation. - */ - public void onAdvertisingDataSet(AdvertisingSet advertisingSet, int status) { - } - - /** - * Callback triggered in response to {@link AdvertisingSet#setAdvertisingData} indicating - * result of the operation. - * - * @param advertisingSet The advertising set. - * @param status Status of the operation. - */ - public void onScanResponseDataSet(AdvertisingSet advertisingSet, int status) { - } - - /** - * Callback triggered in response to {@link AdvertisingSet#setAdvertisingParameters} - * indicating result of the operation. - * - * @param advertisingSet The advertising set. - * @param txPower tx power that will be used for this set. - * @param status Status of the operation. - */ - public void onAdvertisingParametersUpdated(AdvertisingSet advertisingSet, - int txPower, int status) { - } - - /** - * Callback triggered in response to {@link AdvertisingSet#setPeriodicAdvertisingParameters} - * indicating result of the operation. - * - * @param advertisingSet The advertising set. - * @param status Status of the operation. - */ - public void onPeriodicAdvertisingParametersUpdated(AdvertisingSet advertisingSet, int status) { - } - - /** - * Callback triggered in response to {@link AdvertisingSet#setPeriodicAdvertisingData} - * indicating result of the operation. - * - * @param advertisingSet The advertising set. - * @param status Status of the operation. - */ - public void onPeriodicAdvertisingDataSet(AdvertisingSet advertisingSet, - int status) { - } - - /** - * Callback triggered in response to {@link AdvertisingSet#setPeriodicAdvertisingEnabled} - * indicating result of the operation. - * - * @param advertisingSet The advertising set. - * @param status Status of the operation. - */ - public void onPeriodicAdvertisingEnabled(AdvertisingSet advertisingSet, boolean enable, - int status) { - } - - /** - * Callback triggered in response to {@link AdvertisingSet#getOwnAddress()} - * indicating result of the operation. - * - * @param advertisingSet The advertising set. - * @param addressType type of address. - * @param address advertising set bluetooth address. - * @hide - */ - public void onOwnAddressRead(AdvertisingSet advertisingSet, int addressType, String address) { - } -} diff --git a/core/java/android/bluetooth/le/AdvertisingSetParameters.java b/core/java/android/bluetooth/le/AdvertisingSetParameters.java deleted file mode 100644 index 5c8fae65193d..000000000000 --- a/core/java/android/bluetooth/le/AdvertisingSetParameters.java +++ /dev/null @@ -1,513 +0,0 @@ -/* - * 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 android.bluetooth.le; - -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.SystemApi; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.os.Parcel; -import android.os.Parcelable; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * The {@link AdvertisingSetParameters} provide a way to adjust advertising - * preferences for each - * Bluetooth LE advertising set. Use {@link AdvertisingSetParameters.Builder} to - * create an - * instance of this class. - */ -public final class AdvertisingSetParameters implements Parcelable { - - /** - * Advertise on low frequency, around every 1000ms. This is the default and - * preferred advertising mode as it consumes the least power. - */ - public static final int INTERVAL_HIGH = 1600; - - /** - * Advertise on medium frequency, around every 250ms. This is balanced - * between advertising frequency and power consumption. - */ - public static final int INTERVAL_MEDIUM = 400; - - /** - * Perform high frequency, low latency advertising, around every 100ms. This - * has the highest power consumption and should not be used for continuous - * background advertising. - */ - public static final int INTERVAL_LOW = 160; - - /** - * Minimum value for advertising interval. - */ - public static final int INTERVAL_MIN = 160; - - /** - * Maximum value for advertising interval. - */ - public static final int INTERVAL_MAX = 16777215; - - /** - * Advertise using the lowest transmission (TX) power level. Low transmission - * power can be used to restrict the visibility range of advertising packets. - */ - public static final int TX_POWER_ULTRA_LOW = -21; - - /** - * Advertise using low TX power level. - */ - public static final int TX_POWER_LOW = -15; - - /** - * Advertise using medium TX power level. - */ - public static final int TX_POWER_MEDIUM = -7; - - /** - * Advertise using high TX power level. This corresponds to largest visibility - * range of the advertising packet. - */ - public static final int TX_POWER_HIGH = 1; - - /** - * Minimum value for TX power. - */ - public static final int TX_POWER_MIN = -127; - - /** - * Maximum value for TX power. - */ - public static final int TX_POWER_MAX = 1; - - /** - * The maximum limited advertisement duration as specified by the Bluetooth - * SIG - */ - private static final int LIMITED_ADVERTISING_MAX_MILLIS = 180 * 1000; - - /** @hide */ - @IntDef(prefix = "ADDRESS_TYPE_", value = { - ADDRESS_TYPE_DEFAULT, - ADDRESS_TYPE_PUBLIC, - ADDRESS_TYPE_RANDOM - }) - @Retention(RetentionPolicy.SOURCE) - public @interface AddressTypeStatus {} - - /** - * Advertise own address type that corresponds privacy settings of the device. - * - * @hide - */ - @SystemApi - public static final int ADDRESS_TYPE_DEFAULT = -1; - - /** - * Advertise own public address type. - * - * @hide - */ - @SystemApi - public static final int ADDRESS_TYPE_PUBLIC = 0; - - /** - * Generate and adverise own resolvable private address. - * - * @hide - */ - @SystemApi - public static final int ADDRESS_TYPE_RANDOM = 1; - - private final boolean mIsLegacy; - private final boolean mIsAnonymous; - private final boolean mIncludeTxPower; - private final int mPrimaryPhy; - private final int mSecondaryPhy; - private final boolean mConnectable; - private final boolean mScannable; - private final int mInterval; - private final int mTxPowerLevel; - private final int mOwnAddressType; - - private AdvertisingSetParameters(boolean connectable, boolean scannable, boolean isLegacy, - boolean isAnonymous, boolean includeTxPower, - int primaryPhy, int secondaryPhy, - int interval, int txPowerLevel, @AddressTypeStatus int ownAddressType) { - mConnectable = connectable; - mScannable = scannable; - mIsLegacy = isLegacy; - mIsAnonymous = isAnonymous; - mIncludeTxPower = includeTxPower; - mPrimaryPhy = primaryPhy; - mSecondaryPhy = secondaryPhy; - mInterval = interval; - mTxPowerLevel = txPowerLevel; - mOwnAddressType = ownAddressType; - } - - private AdvertisingSetParameters(Parcel in) { - mConnectable = in.readInt() != 0; - mScannable = in.readInt() != 0; - mIsLegacy = in.readInt() != 0; - mIsAnonymous = in.readInt() != 0; - mIncludeTxPower = in.readInt() != 0; - mPrimaryPhy = in.readInt(); - mSecondaryPhy = in.readInt(); - mInterval = in.readInt(); - mTxPowerLevel = in.readInt(); - mOwnAddressType = in.readInt(); - } - - /** - * Returns whether the advertisement will be connectable. - */ - public boolean isConnectable() { - return mConnectable; - } - - /** - * Returns whether the advertisement will be scannable. - */ - public boolean isScannable() { - return mScannable; - } - - /** - * Returns whether the legacy advertisement will be used. - */ - public boolean isLegacy() { - return mIsLegacy; - } - - /** - * Returns whether the advertisement will be anonymous. - */ - public boolean isAnonymous() { - return mIsAnonymous; - } - - /** - * Returns whether the TX Power will be included. - */ - public boolean includeTxPower() { - return mIncludeTxPower; - } - - /** - * Returns the primary advertising phy. - */ - public int getPrimaryPhy() { - return mPrimaryPhy; - } - - /** - * Returns the secondary advertising phy. - */ - public int getSecondaryPhy() { - return mSecondaryPhy; - } - - /** - * Returns the advertising interval. - */ - public int getInterval() { - return mInterval; - } - - /** - * Returns the TX power level for advertising. - */ - public int getTxPowerLevel() { - return mTxPowerLevel; - } - - /** - * @return the own address type for advertising - * - * @hide - */ - @SystemApi - public @AddressTypeStatus int getOwnAddressType() { - return mOwnAddressType; - } - - @Override - public String toString() { - return "AdvertisingSetParameters [connectable=" + mConnectable - + ", isLegacy=" + mIsLegacy - + ", isAnonymous=" + mIsAnonymous - + ", includeTxPower=" + mIncludeTxPower - + ", primaryPhy=" + mPrimaryPhy - + ", secondaryPhy=" + mSecondaryPhy - + ", interval=" + mInterval - + ", txPowerLevel=" + mTxPowerLevel - + ", ownAddressType=" + mOwnAddressType + "]"; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mConnectable ? 1 : 0); - dest.writeInt(mScannable ? 1 : 0); - dest.writeInt(mIsLegacy ? 1 : 0); - dest.writeInt(mIsAnonymous ? 1 : 0); - dest.writeInt(mIncludeTxPower ? 1 : 0); - dest.writeInt(mPrimaryPhy); - dest.writeInt(mSecondaryPhy); - dest.writeInt(mInterval); - dest.writeInt(mTxPowerLevel); - dest.writeInt(mOwnAddressType); - } - - public static final @android.annotation.NonNull Parcelable.Creator<AdvertisingSetParameters> CREATOR = - new Creator<AdvertisingSetParameters>() { - @Override - public AdvertisingSetParameters[] newArray(int size) { - return new AdvertisingSetParameters[size]; - } - - @Override - public AdvertisingSetParameters createFromParcel(Parcel in) { - return new AdvertisingSetParameters(in); - } - }; - - /** - * Builder class for {@link AdvertisingSetParameters}. - */ - public static final class Builder { - private boolean mConnectable = false; - private boolean mScannable = false; - private boolean mIsLegacy = false; - private boolean mIsAnonymous = false; - private boolean mIncludeTxPower = false; - private int mPrimaryPhy = BluetoothDevice.PHY_LE_1M; - private int mSecondaryPhy = BluetoothDevice.PHY_LE_1M; - private int mInterval = INTERVAL_LOW; - private int mTxPowerLevel = TX_POWER_MEDIUM; - private int mOwnAddressType = ADDRESS_TYPE_DEFAULT; - - /** - * Set whether the advertisement type should be connectable or - * non-connectable. - * Legacy advertisements can be both connectable and scannable. Non-legacy - * advertisements can be only scannable or only connectable. - * - * @param connectable Controls whether the advertisement type will be connectable (true) or - * non-connectable (false). - */ - public Builder setConnectable(boolean connectable) { - mConnectable = connectable; - return this; - } - - /** - * Set whether the advertisement type should be scannable. - * Legacy advertisements can be both connectable and scannable. Non-legacy - * advertisements can be only scannable or only connectable. - * - * @param scannable Controls whether the advertisement type will be scannable (true) or - * non-scannable (false). - */ - public Builder setScannable(boolean scannable) { - mScannable = scannable; - return this; - } - - /** - * When set to true, advertising set will advertise 4.x Spec compliant - * advertisements. - * - * @param isLegacy whether legacy advertising mode should be used. - */ - public Builder setLegacyMode(boolean isLegacy) { - mIsLegacy = isLegacy; - return this; - } - - /** - * Set whether advertiser address should be ommited from all packets. If this - * mode is used, periodic advertising can't be enabled for this set. - * - * This is used only if legacy mode is not used. - * - * @param isAnonymous whether anonymous advertising should be used. - */ - public Builder setAnonymous(boolean isAnonymous) { - mIsAnonymous = isAnonymous; - return this; - } - - /** - * Set whether TX power should be included in the extended header. - * - * This is used only if legacy mode is not used. - * - * @param includeTxPower whether TX power should be included in extended header - */ - public Builder setIncludeTxPower(boolean includeTxPower) { - mIncludeTxPower = includeTxPower; - return this; - } - - /** - * Set the primary physical channel used for this advertising set. - * - * This is used only if legacy mode is not used. - * - * Use {@link BluetoothAdapter#isLeCodedPhySupported} to determine if LE Coded PHY is - * supported on this device. - * - * @param primaryPhy Primary advertising physical channel, can only be {@link - * BluetoothDevice#PHY_LE_1M} or {@link BluetoothDevice#PHY_LE_CODED}. - * @throws IllegalArgumentException If the primaryPhy is invalid. - */ - public Builder setPrimaryPhy(int primaryPhy) { - if (primaryPhy != BluetoothDevice.PHY_LE_1M - && primaryPhy != BluetoothDevice.PHY_LE_CODED) { - throw new IllegalArgumentException("bad primaryPhy " + primaryPhy); - } - mPrimaryPhy = primaryPhy; - return this; - } - - /** - * Set the secondary physical channel used for this advertising set. - * - * This is used only if legacy mode is not used. - * - * Use {@link BluetoothAdapter#isLeCodedPhySupported} and - * {@link BluetoothAdapter#isLe2MPhySupported} to determine if LE Coded PHY or 2M PHY is - * supported on this device. - * - * @param secondaryPhy Secondary advertising physical channel, can only be one of {@link - * BluetoothDevice#PHY_LE_1M}, {@link BluetoothDevice#PHY_LE_2M} or {@link - * BluetoothDevice#PHY_LE_CODED}. - * @throws IllegalArgumentException If the secondaryPhy is invalid. - */ - public Builder setSecondaryPhy(int secondaryPhy) { - if (secondaryPhy != BluetoothDevice.PHY_LE_1M - && secondaryPhy != BluetoothDevice.PHY_LE_2M - && secondaryPhy != BluetoothDevice.PHY_LE_CODED) { - throw new IllegalArgumentException("bad secondaryPhy " + secondaryPhy); - } - mSecondaryPhy = secondaryPhy; - return this; - } - - /** - * Set advertising interval. - * - * @param interval Bluetooth LE Advertising interval, in 0.625ms unit. Valid range is from - * 160 (100ms) to 16777215 (10,485.759375 s). Recommended values are: {@link - * AdvertisingSetParameters#INTERVAL_LOW}, {@link AdvertisingSetParameters#INTERVAL_MEDIUM}, - * or {@link AdvertisingSetParameters#INTERVAL_HIGH}. - * @throws IllegalArgumentException If the interval is invalid. - */ - public Builder setInterval(int interval) { - if (interval < INTERVAL_MIN || interval > INTERVAL_MAX) { - throw new IllegalArgumentException("unknown interval " + interval); - } - mInterval = interval; - return this; - } - - /** - * Set the transmission power level for the advertising. - * - * @param txPowerLevel Transmission power of Bluetooth LE Advertising, in dBm. The valid - * range is [-127, 1] Recommended values are: - * {@link AdvertisingSetParameters#TX_POWER_ULTRA_LOW}, - * {@link AdvertisingSetParameters#TX_POWER_LOW}, - * {@link AdvertisingSetParameters#TX_POWER_MEDIUM}, - * or {@link AdvertisingSetParameters#TX_POWER_HIGH}. - * @throws IllegalArgumentException If the {@code txPowerLevel} is invalid. - */ - public Builder setTxPowerLevel(int txPowerLevel) { - if (txPowerLevel < TX_POWER_MIN || txPowerLevel > TX_POWER_MAX) { - throw new IllegalArgumentException("unknown txPowerLevel " + txPowerLevel); - } - mTxPowerLevel = txPowerLevel; - return this; - } - - /** - * Set own address type for advertising to control public or privacy mode. If used to set - * address type anything other than {@link AdvertisingSetParameters#ADDRESS_TYPE_DEFAULT}, - * then it will require BLUETOOTH_PRIVILEGED permission and will be checked at the - * time of starting advertising. - * - * @throws IllegalArgumentException If the {@code ownAddressType} is invalid - * - * @hide - */ - @SystemApi - public @NonNull Builder setOwnAddressType(@AddressTypeStatus int ownAddressType) { - if (ownAddressType < AdvertisingSetParameters.ADDRESS_TYPE_DEFAULT - || ownAddressType > AdvertisingSetParameters.ADDRESS_TYPE_RANDOM) { - throw new IllegalArgumentException("unknown address type " + ownAddressType); - } - mOwnAddressType = ownAddressType; - return this; - } - - /** - * Build the {@link AdvertisingSetParameters} object. - * - * @throws IllegalStateException if invalid combination of parameters is used. - */ - public AdvertisingSetParameters build() { - if (mIsLegacy) { - if (mIsAnonymous) { - throw new IllegalArgumentException("Legacy advertising can't be anonymous"); - } - - if (mConnectable && !mScannable) { - throw new IllegalStateException( - "Legacy advertisement can't be connectable and non-scannable"); - } - - if (mIncludeTxPower) { - throw new IllegalStateException( - "Legacy advertising can't include TX power level in header"); - } - } else { - if (mConnectable && mScannable) { - throw new IllegalStateException( - "Advertising can't be both connectable and scannable"); - } - - if (mIsAnonymous && mConnectable) { - throw new IllegalStateException( - "Advertising can't be both connectable and anonymous"); - } - } - - return new AdvertisingSetParameters(mConnectable, mScannable, mIsLegacy, mIsAnonymous, - mIncludeTxPower, mPrimaryPhy, mSecondaryPhy, mInterval, mTxPowerLevel, - mOwnAddressType); - } - } -} diff --git a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java deleted file mode 100644 index 879dceedaaec..000000000000 --- a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java +++ /dev/null @@ -1,756 +0,0 @@ -/* - * Copyright (C) 2014 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.bluetooth.le; - -import android.annotation.RequiresNoPermission; -import android.annotation.RequiresPermission; -import android.annotation.SuppressLint; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothUuid; -import android.bluetooth.IBluetoothGatt; -import android.bluetooth.IBluetoothManager; -import android.bluetooth.annotations.RequiresBluetoothAdvertisePermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission; -import android.content.AttributionSource; -import android.os.Handler; -import android.os.Looper; -import android.os.ParcelUuid; -import android.os.RemoteException; -import android.util.Log; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; - -/** - * This class provides a way to perform Bluetooth LE advertise operations, such as starting and - * stopping advertising. An advertiser can broadcast up to 31 bytes of advertisement data - * represented by {@link AdvertiseData}. - * <p> - * To get an instance of {@link BluetoothLeAdvertiser}, call the - * {@link BluetoothAdapter#getBluetoothLeAdvertiser()} method. - * - * @see AdvertiseData - */ -public final class BluetoothLeAdvertiser { - - private static final String TAG = "BluetoothLeAdvertiser"; - - private static final int MAX_ADVERTISING_DATA_BYTES = 1650; - private static final int MAX_LEGACY_ADVERTISING_DATA_BYTES = 31; - // Each fields need one byte for field length and another byte for field type. - private static final int OVERHEAD_BYTES_PER_FIELD = 2; - // Flags field will be set by system. - private static final int FLAGS_FIELD_BYTES = 3; - private static final int MANUFACTURER_SPECIFIC_DATA_LENGTH = 2; - - private final BluetoothAdapter mBluetoothAdapter; - private final IBluetoothManager mBluetoothManager; - private final AttributionSource mAttributionSource; - - private final Handler mHandler; - private final Map<AdvertiseCallback, AdvertisingSetCallback> - mLegacyAdvertisers = new HashMap<>(); - private final Map<AdvertisingSetCallback, IAdvertisingSetCallback> - mCallbackWrappers = Collections.synchronizedMap(new HashMap<>()); - private final Map<Integer, AdvertisingSet> - mAdvertisingSets = Collections.synchronizedMap(new HashMap<>()); - - /** - * Use BluetoothAdapter.getLeAdvertiser() instead. - * - * @param bluetoothManager BluetoothManager that conducts overall Bluetooth Management - * @hide - */ - public BluetoothLeAdvertiser(BluetoothAdapter bluetoothAdapter) { - mBluetoothAdapter = Objects.requireNonNull(bluetoothAdapter); - mBluetoothManager = mBluetoothAdapter.getBluetoothManager(); - mAttributionSource = mBluetoothAdapter.getAttributionSource(); - mHandler = new Handler(Looper.getMainLooper()); - } - - /** - * Start Bluetooth LE Advertising. On success, the {@code advertiseData} will be broadcasted. - * Returns immediately, the operation status is delivered through {@code callback}. - * - * @param settings Settings for Bluetooth LE advertising. - * @param advertiseData Advertisement data to be broadcasted. - * @param callback Callback for advertising status. - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothAdvertisePermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) - public void startAdvertising(AdvertiseSettings settings, - AdvertiseData advertiseData, final AdvertiseCallback callback) { - startAdvertising(settings, advertiseData, null, callback); - } - - /** - * Start Bluetooth LE Advertising. The {@code advertiseData} will be broadcasted if the - * operation succeeds. The {@code scanResponse} is returned when a scanning device sends an - * active scan request. This method returns immediately, the operation status is delivered - * through {@code callback}. - * - * @param settings Settings for Bluetooth LE advertising. - * @param advertiseData Advertisement data to be advertised in advertisement packet. - * @param scanResponse Scan response associated with the advertisement data. - * @param callback Callback for advertising status. - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothAdvertisePermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) - public void startAdvertising(AdvertiseSettings settings, - AdvertiseData advertiseData, AdvertiseData scanResponse, - final AdvertiseCallback callback) { - synchronized (mLegacyAdvertisers) { - BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter); - if (callback == null) { - throw new IllegalArgumentException("callback cannot be null"); - } - boolean isConnectable = settings.isConnectable(); - if (totalBytes(advertiseData, isConnectable) > MAX_LEGACY_ADVERTISING_DATA_BYTES - || totalBytes(scanResponse, false) > MAX_LEGACY_ADVERTISING_DATA_BYTES) { - postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE); - return; - } - if (mLegacyAdvertisers.containsKey(callback)) { - postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_ALREADY_STARTED); - return; - } - - AdvertisingSetParameters.Builder parameters = new AdvertisingSetParameters.Builder(); - parameters.setLegacyMode(true); - parameters.setConnectable(isConnectable); - parameters.setScannable(true); // legacy advertisements we support are always scannable - parameters.setOwnAddressType(settings.getOwnAddressType()); - if (settings.getMode() == AdvertiseSettings.ADVERTISE_MODE_LOW_POWER) { - parameters.setInterval(1600); // 1s - } else if (settings.getMode() == AdvertiseSettings.ADVERTISE_MODE_BALANCED) { - parameters.setInterval(400); // 250ms - } else if (settings.getMode() == AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY) { - parameters.setInterval(160); // 100ms - } - - if (settings.getTxPowerLevel() == AdvertiseSettings.ADVERTISE_TX_POWER_ULTRA_LOW) { - parameters.setTxPowerLevel(-21); - } else if (settings.getTxPowerLevel() == AdvertiseSettings.ADVERTISE_TX_POWER_LOW) { - parameters.setTxPowerLevel(-15); - } else if (settings.getTxPowerLevel() == AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM) { - parameters.setTxPowerLevel(-7); - } else if (settings.getTxPowerLevel() == AdvertiseSettings.ADVERTISE_TX_POWER_HIGH) { - parameters.setTxPowerLevel(1); - } - - int duration = 0; - int timeoutMillis = settings.getTimeout(); - if (timeoutMillis > 0) { - duration = (timeoutMillis < 10) ? 1 : timeoutMillis / 10; - } - - AdvertisingSetCallback wrapped = wrapOldCallback(callback, settings); - mLegacyAdvertisers.put(callback, wrapped); - startAdvertisingSet(parameters.build(), advertiseData, scanResponse, null, null, - duration, 0, wrapped); - } - } - - @SuppressLint({ - "AndroidFrameworkBluetoothPermission", - "AndroidFrameworkRequiresPermission", - }) - AdvertisingSetCallback wrapOldCallback(AdvertiseCallback callback, AdvertiseSettings settings) { - return new AdvertisingSetCallback() { - @Override - public void onAdvertisingSetStarted(AdvertisingSet advertisingSet, int txPower, - int status) { - if (status != AdvertisingSetCallback.ADVERTISE_SUCCESS) { - postStartFailure(callback, status); - return; - } - - postStartSuccess(callback, settings); - } - - /* Legacy advertiser is disabled on timeout */ - @Override - public void onAdvertisingEnabled(AdvertisingSet advertisingSet, boolean enabled, - int status) { - if (enabled) { - Log.e(TAG, "Legacy advertiser should be only disabled on timeout," - + " but was enabled!"); - return; - } - - stopAdvertising(callback); - } - - }; - } - - /** - * Stop Bluetooth LE advertising. The {@code callback} must be the same one use in - * {@link BluetoothLeAdvertiser#startAdvertising}. - * - * @param callback {@link AdvertiseCallback} identifies the advertising instance to stop. - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothAdvertisePermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) - public void stopAdvertising(final AdvertiseCallback callback) { - synchronized (mLegacyAdvertisers) { - if (callback == null) { - throw new IllegalArgumentException("callback cannot be null"); - } - AdvertisingSetCallback wrapper = mLegacyAdvertisers.get(callback); - if (wrapper == null) return; - - stopAdvertisingSet(wrapper); - - mLegacyAdvertisers.remove(callback); - } - } - - /** - * Creates a new advertising set. If operation succeed, device will start advertising. This - * method returns immediately, the operation status is delivered through - * {@code callback.onAdvertisingSetStarted()}. - * <p> - * - * @param parameters advertising set parameters. - * @param advertiseData Advertisement data to be broadcasted. Size must not exceed {@link - * BluetoothAdapter#getLeMaximumAdvertisingDataLength}. If the advertisement is connectable, - * three bytes will be added for flags. - * @param scanResponse Scan response associated with the advertisement data. Size must not - * exceed {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength}. - * @param periodicParameters periodic advertisng parameters. If null, periodic advertising will - * not be started. - * @param periodicData Periodic advertising data. Size must not exceed {@link - * BluetoothAdapter#getLeMaximumAdvertisingDataLength}. - * @param callback Callback for advertising set. - * @throws IllegalArgumentException when any of the data parameter exceed the maximum allowable - * size, or unsupported advertising PHY is selected, or when attempt to use Periodic Advertising - * feature is made when it's not supported by the controller. - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothAdvertisePermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) - public void startAdvertisingSet(AdvertisingSetParameters parameters, - AdvertiseData advertiseData, AdvertiseData scanResponse, - PeriodicAdvertisingParameters periodicParameters, - AdvertiseData periodicData, AdvertisingSetCallback callback) { - startAdvertisingSet(parameters, advertiseData, scanResponse, periodicParameters, - periodicData, 0, 0, callback, new Handler(Looper.getMainLooper())); - } - - /** - * Creates a new advertising set. If operation succeed, device will start advertising. This - * method returns immediately, the operation status is delivered through - * {@code callback.onAdvertisingSetStarted()}. - * <p> - * - * @param parameters advertising set parameters. - * @param advertiseData Advertisement data to be broadcasted. Size must not exceed {@link - * BluetoothAdapter#getLeMaximumAdvertisingDataLength}. If the advertisement is connectable, - * three bytes will be added for flags. - * @param scanResponse Scan response associated with the advertisement data. Size must not - * exceed {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength}. - * @param periodicParameters periodic advertisng parameters. If null, periodic advertising will - * not be started. - * @param periodicData Periodic advertising data. Size must not exceed {@link - * BluetoothAdapter#getLeMaximumAdvertisingDataLength}. - * @param callback Callback for advertising set. - * @param handler thread upon which the callbacks will be invoked. - * @throws IllegalArgumentException when any of the data parameter exceed the maximum allowable - * size, or unsupported advertising PHY is selected, or when attempt to use Periodic Advertising - * feature is made when it's not supported by the controller. - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothAdvertisePermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) - public void startAdvertisingSet(AdvertisingSetParameters parameters, - AdvertiseData advertiseData, AdvertiseData scanResponse, - PeriodicAdvertisingParameters periodicParameters, - AdvertiseData periodicData, AdvertisingSetCallback callback, - Handler handler) { - startAdvertisingSet(parameters, advertiseData, scanResponse, periodicParameters, - periodicData, 0, 0, callback, handler); - } - - /** - * Creates a new advertising set. If operation succeed, device will start advertising. This - * method returns immediately, the operation status is delivered through - * {@code callback.onAdvertisingSetStarted()}. - * <p> - * - * @param parameters advertising set parameters. - * @param advertiseData Advertisement data to be broadcasted. Size must not exceed {@link - * BluetoothAdapter#getLeMaximumAdvertisingDataLength}. If the advertisement is connectable, - * three bytes will be added for flags. - * @param scanResponse Scan response associated with the advertisement data. Size must not - * exceed {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength}. - * @param periodicParameters periodic advertisng parameters. If null, periodic advertising will - * not be started. - * @param periodicData Periodic advertising data. Size must not exceed {@link - * BluetoothAdapter#getLeMaximumAdvertisingDataLength}. - * @param duration advertising duration, in 10ms unit. Valid range is from 1 (10ms) to 65535 - * (655,350 ms). 0 means advertising should continue until stopped. - * @param maxExtendedAdvertisingEvents maximum number of extended advertising events the - * controller shall attempt to send prior to terminating the extended advertising, even if the - * duration has not expired. Valid range is from 1 to 255. 0 means no maximum. - * @param callback Callback for advertising set. - * @throws IllegalArgumentException when any of the data parameter exceed the maximum allowable - * size, or unsupported advertising PHY is selected, or when attempt to use Periodic Advertising - * feature is made when it's not supported by the controller. - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothAdvertisePermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) - public void startAdvertisingSet(AdvertisingSetParameters parameters, - AdvertiseData advertiseData, AdvertiseData scanResponse, - PeriodicAdvertisingParameters periodicParameters, - AdvertiseData periodicData, int duration, - int maxExtendedAdvertisingEvents, - AdvertisingSetCallback callback) { - startAdvertisingSet(parameters, advertiseData, scanResponse, periodicParameters, - periodicData, duration, maxExtendedAdvertisingEvents, callback, - new Handler(Looper.getMainLooper())); - } - - /** - * Creates a new advertising set. If operation succeed, device will start advertising. This - * method returns immediately, the operation status is delivered through - * {@code callback.onAdvertisingSetStarted()}. - * <p> - * - * @param parameters Advertising set parameters. - * @param advertiseData Advertisement data to be broadcasted. Size must not exceed {@link - * BluetoothAdapter#getLeMaximumAdvertisingDataLength}. If the advertisement is connectable, - * three bytes will be added for flags. - * @param scanResponse Scan response associated with the advertisement data. Size must not - * exceed {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength} - * @param periodicParameters Periodic advertisng parameters. If null, periodic advertising will - * not be started. - * @param periodicData Periodic advertising data. Size must not exceed {@link - * BluetoothAdapter#getLeMaximumAdvertisingDataLength} - * @param duration advertising duration, in 10ms unit. Valid range is from 1 (10ms) to 65535 - * (655,350 ms). 0 means advertising should continue until stopped. - * @param maxExtendedAdvertisingEvents maximum number of extended advertising events the - * controller shall attempt to send prior to terminating the extended advertising, even if the - * duration has not expired. Valid range is from 1 to 255. 0 means no maximum. - * @param callback Callback for advertising set. - * @param handler Thread upon which the callbacks will be invoked. - * @throws IllegalArgumentException When any of the data parameter exceed the maximum allowable - * size, or unsupported advertising PHY is selected, or when attempt to use Periodic Advertising - * feature is made when it's not supported by the controller, or when - * maxExtendedAdvertisingEvents is used on a controller that doesn't support the LE Extended - * Advertising - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothAdvertisePermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) - public void startAdvertisingSet(AdvertisingSetParameters parameters, - AdvertiseData advertiseData, AdvertiseData scanResponse, - PeriodicAdvertisingParameters periodicParameters, - AdvertiseData periodicData, int duration, - int maxExtendedAdvertisingEvents, AdvertisingSetCallback callback, - Handler handler) { - BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter); - if (callback == null) { - throw new IllegalArgumentException("callback cannot be null"); - } - - boolean isConnectable = parameters.isConnectable(); - if (parameters.isLegacy()) { - if (totalBytes(advertiseData, isConnectable) > MAX_LEGACY_ADVERTISING_DATA_BYTES) { - throw new IllegalArgumentException("Legacy advertising data too big"); - } - - if (totalBytes(scanResponse, false) > MAX_LEGACY_ADVERTISING_DATA_BYTES) { - throw new IllegalArgumentException("Legacy scan response data too big"); - } - } else { - boolean supportCodedPhy = mBluetoothAdapter.isLeCodedPhySupported(); - boolean support2MPhy = mBluetoothAdapter.isLe2MPhySupported(); - int pphy = parameters.getPrimaryPhy(); - int sphy = parameters.getSecondaryPhy(); - if (pphy == BluetoothDevice.PHY_LE_CODED && !supportCodedPhy) { - throw new IllegalArgumentException("Unsupported primary PHY selected"); - } - - if ((sphy == BluetoothDevice.PHY_LE_CODED && !supportCodedPhy) - || (sphy == BluetoothDevice.PHY_LE_2M && !support2MPhy)) { - throw new IllegalArgumentException("Unsupported secondary PHY selected"); - } - - int maxData = mBluetoothAdapter.getLeMaximumAdvertisingDataLength(); - if (totalBytes(advertiseData, isConnectable) > maxData) { - throw new IllegalArgumentException("Advertising data too big"); - } - - if (totalBytes(scanResponse, false) > maxData) { - throw new IllegalArgumentException("Scan response data too big"); - } - - if (totalBytes(periodicData, false) > maxData) { - throw new IllegalArgumentException("Periodic advertising data too big"); - } - - boolean supportPeriodic = mBluetoothAdapter.isLePeriodicAdvertisingSupported(); - if (periodicParameters != null && !supportPeriodic) { - throw new IllegalArgumentException( - "Controller does not support LE Periodic Advertising"); - } - } - - if (maxExtendedAdvertisingEvents < 0 || maxExtendedAdvertisingEvents > 255) { - throw new IllegalArgumentException( - "maxExtendedAdvertisingEvents out of range: " + maxExtendedAdvertisingEvents); - } - - if (maxExtendedAdvertisingEvents != 0 - && !mBluetoothAdapter.isLePeriodicAdvertisingSupported()) { - throw new IllegalArgumentException( - "Can't use maxExtendedAdvertisingEvents with controller that don't support " - + "LE Extended Advertising"); - } - - if (duration < 0 || duration > 65535) { - throw new IllegalArgumentException("duration out of range: " + duration); - } - - IBluetoothGatt gatt; - try { - gatt = mBluetoothManager.getBluetoothGatt(); - } catch (RemoteException e) { - Log.e(TAG, "Failed to get Bluetooth GATT - ", e); - postStartSetFailure(handler, callback, - AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR); - return; - } - - if (gatt == null) { - Log.e(TAG, "Bluetooth GATT is null"); - postStartSetFailure(handler, callback, - AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR); - return; - } - - IAdvertisingSetCallback wrapped = wrap(callback, handler); - if (mCallbackWrappers.putIfAbsent(callback, wrapped) != null) { - throw new IllegalArgumentException( - "callback instance already associated with advertising"); - } - - try { - gatt.startAdvertisingSet(parameters, advertiseData, scanResponse, periodicParameters, - periodicData, duration, maxExtendedAdvertisingEvents, wrapped, - mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "Failed to start advertising set - ", e); - postStartSetFailure(handler, callback, - AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR); - return; - } - } - - /** - * Used to dispose of a {@link AdvertisingSet} object, obtained with {@link - * BluetoothLeAdvertiser#startAdvertisingSet}. - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothAdvertisePermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) - public void stopAdvertisingSet(AdvertisingSetCallback callback) { - if (callback == null) { - throw new IllegalArgumentException("callback cannot be null"); - } - - IAdvertisingSetCallback wrapped = mCallbackWrappers.remove(callback); - if (wrapped == null) { - return; - } - - IBluetoothGatt gatt; - try { - gatt = mBluetoothManager.getBluetoothGatt(); - gatt.stopAdvertisingSet(wrapped, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "Failed to stop advertising - ", e); - } - } - - /** - * Cleans up advertisers. Should be called when bluetooth is down. - * - * @hide - */ - @RequiresNoPermission - public void cleanup() { - mLegacyAdvertisers.clear(); - mCallbackWrappers.clear(); - mAdvertisingSets.clear(); - } - - // Compute the size of advertisement data or scan resp - @RequiresBluetoothAdvertisePermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) - private int totalBytes(AdvertiseData data, boolean isFlagsIncluded) { - if (data == null) return 0; - // Flags field is omitted if the advertising is not connectable. - int size = (isFlagsIncluded) ? FLAGS_FIELD_BYTES : 0; - if (data.getServiceUuids() != null) { - int num16BitUuids = 0; - int num32BitUuids = 0; - int num128BitUuids = 0; - for (ParcelUuid uuid : data.getServiceUuids()) { - if (BluetoothUuid.is16BitUuid(uuid)) { - ++num16BitUuids; - } else if (BluetoothUuid.is32BitUuid(uuid)) { - ++num32BitUuids; - } else { - ++num128BitUuids; - } - } - // 16 bit service uuids are grouped into one field when doing advertising. - if (num16BitUuids != 0) { - size += OVERHEAD_BYTES_PER_FIELD + num16BitUuids * BluetoothUuid.UUID_BYTES_16_BIT; - } - // 32 bit service uuids are grouped into one field when doing advertising. - if (num32BitUuids != 0) { - size += OVERHEAD_BYTES_PER_FIELD + num32BitUuids * BluetoothUuid.UUID_BYTES_32_BIT; - } - // 128 bit service uuids are grouped into one field when doing advertising. - if (num128BitUuids != 0) { - size += OVERHEAD_BYTES_PER_FIELD - + num128BitUuids * BluetoothUuid.UUID_BYTES_128_BIT; - } - } - if (data.getServiceSolicitationUuids() != null) { - int num16BitUuids = 0; - int num32BitUuids = 0; - int num128BitUuids = 0; - for (ParcelUuid uuid : data.getServiceSolicitationUuids()) { - if (BluetoothUuid.is16BitUuid(uuid)) { - ++num16BitUuids; - } else if (BluetoothUuid.is32BitUuid(uuid)) { - ++num32BitUuids; - } else { - ++num128BitUuids; - } - } - // 16 bit service uuids are grouped into one field when doing advertising. - if (num16BitUuids != 0) { - size += OVERHEAD_BYTES_PER_FIELD + num16BitUuids * BluetoothUuid.UUID_BYTES_16_BIT; - } - // 32 bit service uuids are grouped into one field when doing advertising. - if (num32BitUuids != 0) { - size += OVERHEAD_BYTES_PER_FIELD + num32BitUuids * BluetoothUuid.UUID_BYTES_32_BIT; - } - // 128 bit service uuids are grouped into one field when doing advertising. - if (num128BitUuids != 0) { - size += OVERHEAD_BYTES_PER_FIELD - + num128BitUuids * BluetoothUuid.UUID_BYTES_128_BIT; - } - } - for (TransportDiscoveryData transportDiscoveryData : data.getTransportDiscoveryData()) { - size += OVERHEAD_BYTES_PER_FIELD + transportDiscoveryData.totalBytes(); - } - for (ParcelUuid uuid : data.getServiceData().keySet()) { - int uuidLen = BluetoothUuid.uuidToBytes(uuid).length; - size += OVERHEAD_BYTES_PER_FIELD + uuidLen - + byteLength(data.getServiceData().get(uuid)); - } - for (int i = 0; i < data.getManufacturerSpecificData().size(); ++i) { - size += OVERHEAD_BYTES_PER_FIELD + MANUFACTURER_SPECIFIC_DATA_LENGTH - + byteLength(data.getManufacturerSpecificData().valueAt(i)); - } - if (data.getIncludeTxPowerLevel()) { - size += OVERHEAD_BYTES_PER_FIELD + 1; // tx power level value is one byte. - } - if (data.getIncludeDeviceName()) { - final int length = mBluetoothAdapter.getNameLengthForAdvertise(); - if (length >= 0) { - size += OVERHEAD_BYTES_PER_FIELD + length; - } - } - return size; - } - - private int byteLength(byte[] array) { - return array == null ? 0 : array.length; - } - - @SuppressLint("AndroidFrameworkBluetoothPermission") - IAdvertisingSetCallback wrap(AdvertisingSetCallback callback, Handler handler) { - return new IAdvertisingSetCallback.Stub() { - @Override - public void onAdvertisingSetStarted(int advertiserId, int txPower, int status) { - handler.post(new Runnable() { - @Override - public void run() { - if (status != AdvertisingSetCallback.ADVERTISE_SUCCESS) { - callback.onAdvertisingSetStarted(null, 0, status); - mCallbackWrappers.remove(callback); - return; - } - - AdvertisingSet advertisingSet = new AdvertisingSet( - advertiserId, mBluetoothManager, mAttributionSource); - mAdvertisingSets.put(advertiserId, advertisingSet); - callback.onAdvertisingSetStarted(advertisingSet, txPower, status); - } - }); - } - - @Override - public void onOwnAddressRead(int advertiserId, int addressType, String address) { - handler.post(new Runnable() { - @Override - public void run() { - AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); - callback.onOwnAddressRead(advertisingSet, addressType, address); - } - }); - } - - @Override - public void onAdvertisingSetStopped(int advertiserId) { - handler.post(new Runnable() { - @Override - public void run() { - AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); - callback.onAdvertisingSetStopped(advertisingSet); - mAdvertisingSets.remove(advertiserId); - mCallbackWrappers.remove(callback); - } - }); - } - - @Override - public void onAdvertisingEnabled(int advertiserId, boolean enabled, int status) { - handler.post(new Runnable() { - @Override - public void run() { - AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); - callback.onAdvertisingEnabled(advertisingSet, enabled, status); - } - }); - } - - @Override - public void onAdvertisingDataSet(int advertiserId, int status) { - handler.post(new Runnable() { - @Override - public void run() { - AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); - callback.onAdvertisingDataSet(advertisingSet, status); - } - }); - } - - @Override - public void onScanResponseDataSet(int advertiserId, int status) { - handler.post(new Runnable() { - @Override - public void run() { - AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); - callback.onScanResponseDataSet(advertisingSet, status); - } - }); - } - - @Override - public void onAdvertisingParametersUpdated(int advertiserId, int txPower, int status) { - handler.post(new Runnable() { - @Override - public void run() { - AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); - callback.onAdvertisingParametersUpdated(advertisingSet, txPower, status); - } - }); - } - - @Override - public void onPeriodicAdvertisingParametersUpdated(int advertiserId, int status) { - handler.post(new Runnable() { - @Override - public void run() { - AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); - callback.onPeriodicAdvertisingParametersUpdated(advertisingSet, status); - } - }); - } - - @Override - public void onPeriodicAdvertisingDataSet(int advertiserId, int status) { - handler.post(new Runnable() { - @Override - public void run() { - AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); - callback.onPeriodicAdvertisingDataSet(advertisingSet, status); - } - }); - } - - @Override - public void onPeriodicAdvertisingEnabled(int advertiserId, boolean enable, int status) { - handler.post(new Runnable() { - @Override - public void run() { - AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); - callback.onPeriodicAdvertisingEnabled(advertisingSet, enable, status); - } - }); - } - }; - } - - @SuppressLint("AndroidFrameworkBluetoothPermission") - private void postStartSetFailure(Handler handler, final AdvertisingSetCallback callback, - final int error) { - handler.post(new Runnable() { - @Override - public void run() { - callback.onAdvertisingSetStarted(null, 0, error); - } - }); - } - - @SuppressLint("AndroidFrameworkBluetoothPermission") - private void postStartFailure(final AdvertiseCallback callback, final int error) { - mHandler.post(new Runnable() { - @Override - public void run() { - callback.onStartFailure(error); - } - }); - } - - @SuppressLint("AndroidFrameworkBluetoothPermission") - private void postStartSuccess(final AdvertiseCallback callback, - final AdvertiseSettings settings) { - mHandler.post(new Runnable() { - - @Override - public void run() { - callback.onStartSuccess(settings); - } - }); - } -} diff --git a/core/java/android/bluetooth/le/BluetoothLeScanner.java b/core/java/android/bluetooth/le/BluetoothLeScanner.java deleted file mode 100644 index 540e5a778c27..000000000000 --- a/core/java/android/bluetooth/le/BluetoothLeScanner.java +++ /dev/null @@ -1,658 +0,0 @@ -/* - * Copyright (C) 2014 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.bluetooth.le; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.RequiresNoPermission; -import android.annotation.RequiresPermission; -import android.annotation.SuppressLint; -import android.annotation.SystemApi; -import android.app.PendingIntent; -import android.bluetooth.Attributable; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothGatt; -import android.bluetooth.IBluetoothGatt; -import android.bluetooth.IBluetoothManager; -import android.bluetooth.annotations.RequiresBluetoothLocationPermission; -import android.bluetooth.annotations.RequiresBluetoothScanPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission; -import android.content.AttributionSource; -import android.os.Handler; -import android.os.Looper; -import android.os.RemoteException; -import android.os.WorkSource; -import android.util.Log; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; - -/** - * This class provides methods to perform scan related operations for Bluetooth LE devices. An - * application can scan for a particular type of Bluetooth LE devices using {@link ScanFilter}. It - * can also request different types of callbacks for delivering the result. - * <p> - * Use {@link BluetoothAdapter#getBluetoothLeScanner()} to get an instance of - * {@link BluetoothLeScanner}. - * - * @see ScanFilter - */ -public final class BluetoothLeScanner { - - private static final String TAG = "BluetoothLeScanner"; - private static final boolean DBG = true; - private static final boolean VDBG = false; - - /** - * Extra containing a list of ScanResults. It can have one or more results if there was no - * error. In case of error, {@link #EXTRA_ERROR_CODE} will contain the error code and this - * extra will not be available. - */ - public static final String EXTRA_LIST_SCAN_RESULT = - "android.bluetooth.le.extra.LIST_SCAN_RESULT"; - - /** - * Optional extra indicating the error code, if any. The error code will be one of the - * SCAN_FAILED_* codes in {@link ScanCallback}. - */ - public static final String EXTRA_ERROR_CODE = "android.bluetooth.le.extra.ERROR_CODE"; - - /** - * Optional extra indicating the callback type, which will be one of - * CALLBACK_TYPE_* constants in {@link ScanSettings}. - * - * @see ScanCallback#onScanResult(int, ScanResult) - */ - public static final String EXTRA_CALLBACK_TYPE = "android.bluetooth.le.extra.CALLBACK_TYPE"; - - private final BluetoothAdapter mBluetoothAdapter; - private final IBluetoothManager mBluetoothManager; - private final AttributionSource mAttributionSource; - - private final Handler mHandler; - private final Map<ScanCallback, BleScanCallbackWrapper> mLeScanClients; - - /** - * Use {@link BluetoothAdapter#getBluetoothLeScanner()} instead. - * - * @param bluetoothManager BluetoothManager that conducts overall Bluetooth Management. - * @param opPackageName The opPackageName of the context this object was created from - * @param featureId The featureId of the context this object was created from - * @hide - */ - public BluetoothLeScanner(BluetoothAdapter bluetoothAdapter) { - mBluetoothAdapter = Objects.requireNonNull(bluetoothAdapter); - mBluetoothManager = mBluetoothAdapter.getBluetoothManager(); - mAttributionSource = mBluetoothAdapter.getAttributionSource(); - mHandler = new Handler(Looper.getMainLooper()); - mLeScanClients = new HashMap<ScanCallback, BleScanCallbackWrapper>(); - } - - /** - * Start Bluetooth LE scan with default parameters and no filters. The scan results will be - * delivered through {@code callback}. For unfiltered scans, scanning is stopped on screen - * off to save power. Scanning is resumed when screen is turned on again. To avoid this, use - * {@link #startScan(List, ScanSettings, ScanCallback)} with desired {@link ScanFilter}. - * <p> - * An app must have - * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} permission - * in order to get results. An App targeting Android Q or later must have - * {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} permission - * in order to get results. - * - * @param callback Callback used to deliver scan results. - * @throws IllegalArgumentException If {@code callback} is null. - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothScanPermission - @RequiresBluetoothLocationPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - public void startScan(final ScanCallback callback) { - startScan(null, new ScanSettings.Builder().build(), callback); - } - - /** - * Start Bluetooth LE scan. The scan results will be delivered through {@code callback}. - * For unfiltered scans, scanning is stopped on screen off to save power. Scanning is - * resumed when screen is turned on again. To avoid this, do filetered scanning by - * using proper {@link ScanFilter}. - * <p> - * An app must have - * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} permission - * in order to get results. An App targeting Android Q or later must have - * {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} permission - * in order to get results. - * - * @param filters {@link ScanFilter}s for finding exact BLE devices. - * @param settings Settings for the scan. - * @param callback Callback used to deliver scan results. - * @throws IllegalArgumentException If {@code settings} or {@code callback} is null. - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothScanPermission - @RequiresBluetoothLocationPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - public void startScan(List<ScanFilter> filters, ScanSettings settings, - final ScanCallback callback) { - startScan(filters, settings, null, callback, /*callbackIntent=*/ null); - } - - /** - * Start Bluetooth LE scan using a {@link PendingIntent}. The scan results will be delivered via - * the PendingIntent. Use this method of scanning if your process is not always running and it - * should be started when scan results are available. - * <p> - * An app must have - * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} permission - * in order to get results. An App targeting Android Q or later must have - * {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} permission - * in order to get results. - * <p> - * When the PendingIntent is delivered, the Intent passed to the receiver or activity - * will contain one or more of the extras {@link #EXTRA_CALLBACK_TYPE}, - * {@link #EXTRA_ERROR_CODE} and {@link #EXTRA_LIST_SCAN_RESULT} to indicate the result of - * the scan. - * - * @param filters Optional list of ScanFilters for finding exact BLE devices. - * @param settings Optional settings for the scan. - * @param callbackIntent The PendingIntent to deliver the result to. - * @return Returns 0 for success or an error code from {@link ScanCallback} if the scan request - * could not be sent. - * @see #stopScan(PendingIntent) - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothScanPermission - @RequiresBluetoothLocationPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - public int startScan(@Nullable List<ScanFilter> filters, @Nullable ScanSettings settings, - @NonNull PendingIntent callbackIntent) { - return startScan(filters, - settings != null ? settings : new ScanSettings.Builder().build(), - null, null, callbackIntent); - } - - /** - * Start Bluetooth LE scan. Same as {@link #startScan(ScanCallback)} but allows the caller to - * specify on behalf of which application(s) the work is being done. - * - * @param workSource {@link WorkSource} identifying the application(s) for which to blame for - * the scan. - * @param callback Callback used to deliver scan results. - * @hide - */ - @SystemApi - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothScanPermission - @RequiresBluetoothLocationPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_SCAN, - android.Manifest.permission.UPDATE_DEVICE_STATS - }) - public void startScanFromSource(final WorkSource workSource, final ScanCallback callback) { - startScanFromSource(null, new ScanSettings.Builder().build(), workSource, callback); - } - - /** - * Start Bluetooth LE scan. Same as {@link #startScan(List, ScanSettings, ScanCallback)} but - * allows the caller to specify on behalf of which application(s) the work is being done. - * - * @param filters {@link ScanFilter}s for finding exact BLE devices. - * @param settings Settings for the scan. - * @param workSource {@link WorkSource} identifying the application(s) for which to blame for - * the scan. - * @param callback Callback used to deliver scan results. - * @hide - */ - @SystemApi - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothScanPermission - @RequiresBluetoothLocationPermission - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_SCAN, - android.Manifest.permission.UPDATE_DEVICE_STATS - }) - @SuppressLint("AndroidFrameworkRequiresPermission") - public void startScanFromSource(List<ScanFilter> filters, ScanSettings settings, - final WorkSource workSource, final ScanCallback callback) { - startScan(filters, settings, workSource, callback, null); - } - - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - private int startScan(List<ScanFilter> filters, ScanSettings settings, - final WorkSource workSource, final ScanCallback callback, - final PendingIntent callbackIntent) { - BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter); - if (callback == null && callbackIntent == null) { - throw new IllegalArgumentException("callback is null"); - } - if (settings == null) { - throw new IllegalArgumentException("settings is null"); - } - synchronized (mLeScanClients) { - if (callback != null && mLeScanClients.containsKey(callback)) { - return postCallbackErrorOrReturn(callback, - ScanCallback.SCAN_FAILED_ALREADY_STARTED); - } - IBluetoothGatt gatt; - try { - gatt = mBluetoothManager.getBluetoothGatt(); - } catch (RemoteException e) { - gatt = null; - } - if (gatt == null) { - return postCallbackErrorOrReturn(callback, ScanCallback.SCAN_FAILED_INTERNAL_ERROR); - } - if (!isSettingsConfigAllowedForScan(settings)) { - return postCallbackErrorOrReturn(callback, - ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED); - } - if (!isHardwareResourcesAvailableForScan(settings)) { - return postCallbackErrorOrReturn(callback, - ScanCallback.SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES); - } - if (!isSettingsAndFilterComboAllowed(settings, filters)) { - return postCallbackErrorOrReturn(callback, - ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED); - } - if (callback != null) { - BleScanCallbackWrapper wrapper = new BleScanCallbackWrapper(gatt, filters, - settings, workSource, callback); - wrapper.startRegistration(); - } else { - try { - gatt.startScanForIntent(callbackIntent, settings, filters, - mAttributionSource); - } catch (RemoteException e) { - return ScanCallback.SCAN_FAILED_INTERNAL_ERROR; - } - } - } - return ScanCallback.NO_ERROR; - } - - /** - * Stops an ongoing Bluetooth LE scan. - * - * @param callback - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothScanPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - public void stopScan(ScanCallback callback) { - BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter); - synchronized (mLeScanClients) { - BleScanCallbackWrapper wrapper = mLeScanClients.remove(callback); - if (wrapper == null) { - if (DBG) Log.d(TAG, "could not find callback wrapper"); - return; - } - wrapper.stopLeScan(); - } - } - - /** - * Stops an ongoing Bluetooth LE scan started using a PendingIntent. When creating the - * PendingIntent parameter, please do not use the FLAG_CANCEL_CURRENT flag. Otherwise, the stop - * scan may have no effect. - * - * @param callbackIntent The PendingIntent that was used to start the scan. - * @see #startScan(List, ScanSettings, PendingIntent) - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothScanPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - public void stopScan(PendingIntent callbackIntent) { - BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter); - IBluetoothGatt gatt; - try { - gatt = mBluetoothManager.getBluetoothGatt(); - gatt.stopScanForIntent(callbackIntent, mAttributionSource); - } catch (RemoteException e) { - } - } - - /** - * Flush pending batch scan results stored in Bluetooth controller. This will return Bluetooth - * LE scan results batched on bluetooth controller. Returns immediately, batch scan results data - * will be delivered through the {@code callback}. - * - * @param callback Callback of the Bluetooth LE Scan, it has to be the same instance as the one - * used to start scan. - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothScanPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - public void flushPendingScanResults(ScanCallback callback) { - BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter); - if (callback == null) { - throw new IllegalArgumentException("callback cannot be null!"); - } - synchronized (mLeScanClients) { - BleScanCallbackWrapper wrapper = mLeScanClients.get(callback); - if (wrapper == null) { - return; - } - wrapper.flushPendingBatchResults(); - } - } - - /** - * Start truncated scan. - * - * @deprecated this is not used anywhere - * - * @hide - */ - @Deprecated - @SystemApi - @RequiresBluetoothScanPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - public void startTruncatedScan(List<TruncatedFilter> truncatedFilters, ScanSettings settings, - final ScanCallback callback) { - int filterSize = truncatedFilters.size(); - List<ScanFilter> scanFilters = new ArrayList<ScanFilter>(filterSize); - for (TruncatedFilter filter : truncatedFilters) { - scanFilters.add(filter.getFilter()); - } - startScan(scanFilters, settings, null, callback, null); - } - - /** - * Cleans up scan clients. Should be called when bluetooth is down. - * - * @hide - */ - @RequiresNoPermission - public void cleanup() { - mLeScanClients.clear(); - } - - /** - * Bluetooth GATT interface callbacks - */ - @SuppressLint("AndroidFrameworkRequiresPermission") - private class BleScanCallbackWrapper extends IScannerCallback.Stub { - private static final int REGISTRATION_CALLBACK_TIMEOUT_MILLIS = 2000; - - private final ScanCallback mScanCallback; - private final List<ScanFilter> mFilters; - private final WorkSource mWorkSource; - private ScanSettings mSettings; - private IBluetoothGatt mBluetoothGatt; - - // mLeHandle 0: not registered - // -2: registration failed because app is scanning to frequently - // -1: scan stopped or registration failed - // > 0: registered and scan started - private int mScannerId; - - public BleScanCallbackWrapper(IBluetoothGatt bluetoothGatt, - List<ScanFilter> filters, ScanSettings settings, - WorkSource workSource, ScanCallback scanCallback) { - mBluetoothGatt = bluetoothGatt; - mFilters = filters; - mSettings = settings; - mWorkSource = workSource; - mScanCallback = scanCallback; - mScannerId = 0; - } - - public void startRegistration() { - synchronized (this) { - // Scan stopped. - if (mScannerId == -1 || mScannerId == -2) return; - try { - mBluetoothGatt.registerScanner(this, mWorkSource, mAttributionSource); - wait(REGISTRATION_CALLBACK_TIMEOUT_MILLIS); - } catch (InterruptedException | RemoteException e) { - Log.e(TAG, "application registeration exception", e); - postCallbackError(mScanCallback, ScanCallback.SCAN_FAILED_INTERNAL_ERROR); - } - if (mScannerId > 0) { - mLeScanClients.put(mScanCallback, this); - } else { - // Registration timed out or got exception, reset RscannerId to -1 so no - // subsequent operations can proceed. - if (mScannerId == 0) mScannerId = -1; - - // If scanning too frequently, don't report anything to the app. - if (mScannerId == -2) return; - - postCallbackError(mScanCallback, - ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED); - } - } - } - - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - public void stopLeScan() { - synchronized (this) { - if (mScannerId <= 0) { - Log.e(TAG, "Error state, mLeHandle: " + mScannerId); - return; - } - try { - mBluetoothGatt.stopScan(mScannerId, mAttributionSource); - mBluetoothGatt.unregisterScanner(mScannerId, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "Failed to stop scan and unregister", e); - } - mScannerId = -1; - } - } - - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - void flushPendingBatchResults() { - synchronized (this) { - if (mScannerId <= 0) { - Log.e(TAG, "Error state, mLeHandle: " + mScannerId); - return; - } - try { - mBluetoothGatt.flushPendingBatchResults(mScannerId, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "Failed to get pending scan results", e); - } - } - } - - /** - * Application interface registered - app is ready to go - */ - @Override - public void onScannerRegistered(int status, int scannerId) { - Log.d(TAG, "onScannerRegistered() - status=" + status - + " scannerId=" + scannerId + " mScannerId=" + mScannerId); - synchronized (this) { - if (status == BluetoothGatt.GATT_SUCCESS) { - try { - if (mScannerId == -1) { - // Registration succeeds after timeout, unregister scanner. - mBluetoothGatt.unregisterScanner(scannerId, mAttributionSource); - } else { - mScannerId = scannerId; - mBluetoothGatt.startScan(mScannerId, mSettings, mFilters, - mAttributionSource); - } - } catch (RemoteException e) { - Log.e(TAG, "fail to start le scan: " + e); - mScannerId = -1; - } - } else if (status == ScanCallback.SCAN_FAILED_SCANNING_TOO_FREQUENTLY) { - // applicaiton was scanning too frequently - mScannerId = -2; - } else { - // registration failed - mScannerId = -1; - } - notifyAll(); - } - } - - /** - * Callback reporting an LE scan result. - * - * @hide - */ - @Override - public void onScanResult(final ScanResult scanResult) { - Attributable.setAttributionSource(scanResult, mAttributionSource); - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "onScanResult() - mScannerId=" + mScannerId); - } - if (VDBG) Log.d(TAG, "onScanResult() - " + scanResult.toString()); - - // Check null in case the scan has been stopped - synchronized (this) { - if (mScannerId <= 0) { - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "Ignoring result as scan stopped."); - } - return; - }; - } - Handler handler = new Handler(Looper.getMainLooper()); - handler.post(new Runnable() { - @Override - public void run() { - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "onScanResult() - handler run"); - } - mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_ALL_MATCHES, scanResult); - } - }); - } - - @Override - public void onBatchScanResults(final List<ScanResult> results) { - Attributable.setAttributionSource(results, mAttributionSource); - Handler handler = new Handler(Looper.getMainLooper()); - handler.post(new Runnable() { - @Override - public void run() { - mScanCallback.onBatchScanResults(results); - } - }); - } - - @Override - public void onFoundOrLost(final boolean onFound, final ScanResult scanResult) { - Attributable.setAttributionSource(scanResult, mAttributionSource); - if (VDBG) { - Log.d(TAG, "onFoundOrLost() - onFound = " + onFound + " " + scanResult.toString()); - } - - // Check null in case the scan has been stopped - synchronized (this) { - if (mScannerId <= 0) { - return; - } - } - Handler handler = new Handler(Looper.getMainLooper()); - handler.post(new Runnable() { - @Override - public void run() { - if (onFound) { - mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_FIRST_MATCH, - scanResult); - } else { - mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_MATCH_LOST, - scanResult); - } - } - }); - } - - @Override - public void onScanManagerErrorCallback(final int errorCode) { - if (VDBG) { - Log.d(TAG, "onScanManagerErrorCallback() - errorCode = " + errorCode); - } - synchronized (this) { - if (mScannerId <= 0) { - return; - } - } - postCallbackError(mScanCallback, errorCode); - } - } - - private int postCallbackErrorOrReturn(final ScanCallback callback, final int errorCode) { - if (callback == null) { - return errorCode; - } else { - postCallbackError(callback, errorCode); - return ScanCallback.NO_ERROR; - } - } - - @SuppressLint("AndroidFrameworkBluetoothPermission") - private void postCallbackError(final ScanCallback callback, final int errorCode) { - mHandler.post(new Runnable() { - @Override - public void run() { - callback.onScanFailed(errorCode); - } - }); - } - - private boolean isSettingsConfigAllowedForScan(ScanSettings settings) { - if (mBluetoothAdapter.isOffloadedFilteringSupported()) { - return true; - } - final int callbackType = settings.getCallbackType(); - // Only support regular scan if no offloaded filter support. - if (callbackType == ScanSettings.CALLBACK_TYPE_ALL_MATCHES - && settings.getReportDelayMillis() == 0) { - return true; - } - return false; - } - - private boolean isSettingsAndFilterComboAllowed(ScanSettings settings, - List<ScanFilter> filterList) { - final int callbackType = settings.getCallbackType(); - // If onlost/onfound is requested, a non-empty filter is expected - if ((callbackType & (ScanSettings.CALLBACK_TYPE_FIRST_MATCH - | ScanSettings.CALLBACK_TYPE_MATCH_LOST)) != 0) { - if (filterList == null) { - return false; - } - for (ScanFilter filter : filterList) { - if (filter.isAllFieldsEmpty()) { - return false; - } - } - } - return true; - } - - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - private boolean isHardwareResourcesAvailableForScan(ScanSettings settings) { - final int callbackType = settings.getCallbackType(); - if ((callbackType & ScanSettings.CALLBACK_TYPE_FIRST_MATCH) != 0 - || (callbackType & ScanSettings.CALLBACK_TYPE_MATCH_LOST) != 0) { - // For onlost/onfound, we required hw support be available - return (mBluetoothAdapter.isOffloadedFilteringSupported() - && mBluetoothAdapter.isHardwareTrackingFiltersAvailable()); - } - return true; - } -} diff --git a/core/java/android/bluetooth/le/BluetoothLeUtils.java b/core/java/android/bluetooth/le/BluetoothLeUtils.java deleted file mode 100644 index ed50b09597bb..000000000000 --- a/core/java/android/bluetooth/le/BluetoothLeUtils.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright (C) 2014 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.bluetooth.le; - -import android.bluetooth.BluetoothAdapter; -import android.util.SparseArray; - -import java.util.Arrays; -import java.util.Iterator; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.UUID; - -/** - * Helper class for Bluetooth LE utils. - * - * @hide - */ -public class BluetoothLeUtils { - - /** - * Returns a string composed from a {@link SparseArray}. - */ - static String toString(SparseArray<byte[]> array) { - if (array == null) { - return "null"; - } - if (array.size() == 0) { - return "{}"; - } - StringBuilder buffer = new StringBuilder(); - buffer.append('{'); - for (int i = 0; i < array.size(); ++i) { - buffer.append(array.keyAt(i)).append("=").append(Arrays.toString(array.valueAt(i))); - } - buffer.append('}'); - return buffer.toString(); - } - - /** - * Returns a string composed from a {@link Map}. - */ - static <T> String toString(Map<T, byte[]> map) { - if (map == null) { - return "null"; - } - if (map.isEmpty()) { - return "{}"; - } - StringBuilder buffer = new StringBuilder(); - buffer.append('{'); - Iterator<Map.Entry<T, byte[]>> it = map.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry<T, byte[]> entry = it.next(); - Object key = entry.getKey(); - buffer.append(key).append("=").append(Arrays.toString(map.get(key))); - if (it.hasNext()) { - buffer.append(", "); - } - } - buffer.append('}'); - return buffer.toString(); - } - - /** - * Check whether two {@link SparseArray} equal. - */ - static boolean equals(SparseArray<byte[]> array, SparseArray<byte[]> otherArray) { - if (array == otherArray) { - return true; - } - if (array == null || otherArray == null) { - return false; - } - if (array.size() != otherArray.size()) { - return false; - } - - // Keys are guaranteed in ascending order when indices are in ascending order. - for (int i = 0; i < array.size(); ++i) { - if (array.keyAt(i) != otherArray.keyAt(i) - || !Arrays.equals(array.valueAt(i), otherArray.valueAt(i))) { - return false; - } - } - return true; - } - - /** - * Check whether two {@link Map} equal. - */ - static <T> boolean equals(Map<T, byte[]> map, Map<T, byte[]> otherMap) { - if (map == otherMap) { - return true; - } - if (map == null || otherMap == null) { - return false; - } - if (map.size() != otherMap.size()) { - return false; - } - Set<T> keys = map.keySet(); - if (!keys.equals(otherMap.keySet())) { - return false; - } - for (T key : keys) { - if (!Objects.deepEquals(map.get(key), otherMap.get(key))) { - return false; - } - } - return true; - } - - /** - * Ensure Bluetooth is turned on. - * - * @throws IllegalStateException If {@code adapter} is null or Bluetooth state is not {@link - * BluetoothAdapter#STATE_ON}. - */ - static void checkAdapterStateOn(BluetoothAdapter adapter) { - if (adapter == null || !adapter.isLeEnabled()) { - throw new IllegalStateException("BT Adapter is not turned ON"); - } - } - - /** - * Compares two UUIDs with a UUID mask. - * - * @param data first {@link #UUID} to compare. - * @param uuid second {@link #UUID} to compare. - * @param mask mask {@link #UUID}. - * @return true if both UUIDs are equals when masked, false otherwise. - */ - static boolean maskedEquals(UUID data, UUID uuid, UUID mask) { - if (mask == null) { - return Objects.equals(data, uuid); - } - return (data.getLeastSignificantBits() & mask.getLeastSignificantBits()) - == (uuid.getLeastSignificantBits() & mask.getLeastSignificantBits()) - && (data.getMostSignificantBits() & mask.getMostSignificantBits()) - == (uuid.getMostSignificantBits() & mask.getMostSignificantBits()); - } -} diff --git a/core/java/android/bluetooth/le/OWNERS b/core/java/android/bluetooth/le/OWNERS deleted file mode 100644 index 3523ee0640ab..000000000000 --- a/core/java/android/bluetooth/le/OWNERS +++ /dev/null @@ -1,4 +0,0 @@ -# Bug component: 27441 - -zachoverflow@google.com -siyuanh@google.com diff --git a/core/java/android/bluetooth/le/PeriodicAdvertisingCallback.java b/core/java/android/bluetooth/le/PeriodicAdvertisingCallback.java deleted file mode 100644 index 14ac911fcb7f..000000000000 --- a/core/java/android/bluetooth/le/PeriodicAdvertisingCallback.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * 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 android.bluetooth.le; - -import android.bluetooth.BluetoothDevice; - -/** - * Bluetooth LE periodic advertising callbacks, used to deliver periodic - * advertising operation status. - * - * @hide - * @see PeriodicAdvertisingManager#createSync - */ -public abstract class PeriodicAdvertisingCallback { - - /** - * The requested operation was successful. - * - * @hide - */ - public static final int SYNC_SUCCESS = 0; - - /** - * Sync failed to be established because remote device did not respond. - */ - public static final int SYNC_NO_RESPONSE = 1; - - /** - * Sync failed to be established because controller can't support more syncs. - */ - public static final int SYNC_NO_RESOURCES = 2; - - - /** - * Callback when synchronization was established. - * - * @param syncHandle handle used to identify this synchronization. - * @param device remote device. - * @param advertisingSid synchronized advertising set id. - * @param skip The number of periodic advertising packets that can be skipped after a successful - * receive in force. @see PeriodicAdvertisingManager#createSync - * @param timeout Synchronization timeout for the periodic advertising in force. One unit is - * 10ms. @see PeriodicAdvertisingManager#createSync - * @param timeout - * @param status operation status. - */ - public void onSyncEstablished(int syncHandle, BluetoothDevice device, - int advertisingSid, int skip, int timeout, - int status) { - } - - /** - * Callback when periodic advertising report is received. - * - * @param report periodic advertising report. - */ - public void onPeriodicAdvertisingReport(PeriodicAdvertisingReport report) { - } - - /** - * Callback when periodic advertising synchronization was lost. - * - * @param syncHandle handle used to identify this synchronization. - */ - public void onSyncLost(int syncHandle) { - } -} diff --git a/core/java/android/bluetooth/le/PeriodicAdvertisingManager.java b/core/java/android/bluetooth/le/PeriodicAdvertisingManager.java deleted file mode 100644 index bbd31170bb41..000000000000 --- a/core/java/android/bluetooth/le/PeriodicAdvertisingManager.java +++ /dev/null @@ -1,264 +0,0 @@ -/* - * 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 android.bluetooth.le; - -import android.annotation.RequiresPermission; -import android.annotation.SuppressLint; -import android.bluetooth.Attributable; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.IBluetoothGatt; -import android.bluetooth.IBluetoothManager; -import android.bluetooth.annotations.RequiresBluetoothLocationPermission; -import android.bluetooth.annotations.RequiresBluetoothScanPermission; -import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission; -import android.content.AttributionSource; -import android.os.Handler; -import android.os.Looper; -import android.os.RemoteException; -import android.util.Log; - -import java.util.IdentityHashMap; -import java.util.Map; -import java.util.Objects; - -/** - * This class provides methods to perform periodic advertising related - * operations. An application can register for periodic advertisements using - * {@link PeriodicAdvertisingManager#registerSync}. - * <p> - * Use {@link BluetoothAdapter#getPeriodicAdvertisingManager()} to get an - * instance of {@link PeriodicAdvertisingManager}. - * - * @hide - */ -public final class PeriodicAdvertisingManager { - - private static final String TAG = "PeriodicAdvertisingManager"; - - private static final int SKIP_MIN = 0; - private static final int SKIP_MAX = 499; - private static final int TIMEOUT_MIN = 10; - private static final int TIMEOUT_MAX = 16384; - - private static final int SYNC_STARTING = -1; - - private final BluetoothAdapter mBluetoothAdapter; - private final IBluetoothManager mBluetoothManager; - private final AttributionSource mAttributionSource; - - /* maps callback, to callback wrapper and sync handle */ - Map<PeriodicAdvertisingCallback, - IPeriodicAdvertisingCallback /* callbackWrapper */> mCallbackWrappers; - - /** - * Use {@link BluetoothAdapter#getBluetoothLeScanner()} instead. - * - * @param bluetoothManager BluetoothManager that conducts overall Bluetooth Management. - * @hide - */ - public PeriodicAdvertisingManager(BluetoothAdapter bluetoothAdapter) { - mBluetoothAdapter = Objects.requireNonNull(bluetoothAdapter); - mBluetoothManager = mBluetoothAdapter.getBluetoothManager(); - mAttributionSource = mBluetoothAdapter.getAttributionSource(); - mCallbackWrappers = new IdentityHashMap<>(); - } - - /** - * Synchronize with periodic advertising pointed to by the {@code scanResult}. - * The {@code scanResult} used must contain a valid advertisingSid. First - * call to registerSync will use the {@code skip} and {@code timeout} provided. - * Subsequent calls from other apps, trying to sync with same set will reuse - * existing sync, thus {@code skip} and {@code timeout} values will not take - * effect. The values in effect will be returned in - * {@link PeriodicAdvertisingCallback#onSyncEstablished}. - * - * @param scanResult Scan result containing advertisingSid. - * @param skip The number of periodic advertising packets that can be skipped after a successful - * receive. Must be between 0 and 499. - * @param timeout Synchronization timeout for the periodic advertising. One unit is 10ms. Must - * be between 10 (100ms) and 16384 (163.84s). - * @param callback Callback used to deliver all operations status. - * @throws IllegalArgumentException if {@code scanResult} is null or {@code skip} is invalid or - * {@code timeout} is invalid or {@code callback} is null. - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothScanPermission - @RequiresBluetoothLocationPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - public void registerSync(ScanResult scanResult, int skip, int timeout, - PeriodicAdvertisingCallback callback) { - registerSync(scanResult, skip, timeout, callback, null); - } - - /** - * Synchronize with periodic advertising pointed to by the {@code scanResult}. - * The {@code scanResult} used must contain a valid advertisingSid. First - * call to registerSync will use the {@code skip} and {@code timeout} provided. - * Subsequent calls from other apps, trying to sync with same set will reuse - * existing sync, thus {@code skip} and {@code timeout} values will not take - * effect. The values in effect will be returned in - * {@link PeriodicAdvertisingCallback#onSyncEstablished}. - * - * @param scanResult Scan result containing advertisingSid. - * @param skip The number of periodic advertising packets that can be skipped after a successful - * receive. Must be between 0 and 499. - * @param timeout Synchronization timeout for the periodic advertising. One unit is 10ms. Must - * be between 10 (100ms) and 16384 (163.84s). - * @param callback Callback used to deliver all operations status. - * @param handler thread upon which the callbacks will be invoked. - * @throws IllegalArgumentException if {@code scanResult} is null or {@code skip} is invalid or - * {@code timeout} is invalid or {@code callback} is null. - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothScanPermission - @RequiresBluetoothLocationPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - public void registerSync(ScanResult scanResult, int skip, int timeout, - PeriodicAdvertisingCallback callback, Handler handler) { - if (callback == null) { - throw new IllegalArgumentException("callback can't be null"); - } - - if (scanResult == null) { - throw new IllegalArgumentException("scanResult can't be null"); - } - - if (scanResult.getAdvertisingSid() == ScanResult.SID_NOT_PRESENT) { - throw new IllegalArgumentException("scanResult must contain a valid sid"); - } - - if (skip < SKIP_MIN || skip > SKIP_MAX) { - throw new IllegalArgumentException( - "timeout must be between " + TIMEOUT_MIN + " and " + TIMEOUT_MAX); - } - - if (timeout < TIMEOUT_MIN || timeout > TIMEOUT_MAX) { - throw new IllegalArgumentException( - "timeout must be between " + TIMEOUT_MIN + " and " + TIMEOUT_MAX); - } - - IBluetoothGatt gatt; - try { - gatt = mBluetoothManager.getBluetoothGatt(); - } catch (RemoteException e) { - Log.e(TAG, "Failed to get Bluetooth gatt - ", e); - callback.onSyncEstablished(0, scanResult.getDevice(), scanResult.getAdvertisingSid(), - skip, timeout, - PeriodicAdvertisingCallback.SYNC_NO_RESOURCES); - return; - } - - if (handler == null) { - handler = new Handler(Looper.getMainLooper()); - } - - IPeriodicAdvertisingCallback wrapped = wrap(callback, handler); - mCallbackWrappers.put(callback, wrapped); - - try { - gatt.registerSync( - scanResult, skip, timeout, wrapped, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "Failed to register sync - ", e); - return; - } - } - - /** - * Cancel pending attempt to create sync, or terminate existing sync. - * - * @param callback Callback used to deliver all operations status. - * @throws IllegalArgumentException if {@code callback} is null, or not a properly registered - * callback. - */ - @RequiresLegacyBluetoothAdminPermission - @RequiresBluetoothScanPermission - @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) - public void unregisterSync(PeriodicAdvertisingCallback callback) { - if (callback == null) { - throw new IllegalArgumentException("callback can't be null"); - } - - IBluetoothGatt gatt; - try { - gatt = mBluetoothManager.getBluetoothGatt(); - } catch (RemoteException e) { - Log.e(TAG, "Failed to get Bluetooth gatt - ", e); - return; - } - - IPeriodicAdvertisingCallback wrapper = mCallbackWrappers.remove(callback); - if (wrapper == null) { - throw new IllegalArgumentException("callback was not properly registered"); - } - - try { - gatt.unregisterSync(wrapper, mAttributionSource); - } catch (RemoteException e) { - Log.e(TAG, "Failed to cancel sync creation - ", e); - return; - } - } - - @SuppressLint("AndroidFrameworkBluetoothPermission") - private IPeriodicAdvertisingCallback wrap(PeriodicAdvertisingCallback callback, - Handler handler) { - return new IPeriodicAdvertisingCallback.Stub() { - public void onSyncEstablished(int syncHandle, BluetoothDevice device, - int advertisingSid, int skip, int timeout, int status) { - Attributable.setAttributionSource(device, mAttributionSource); - handler.post(new Runnable() { - @Override - public void run() { - callback.onSyncEstablished(syncHandle, device, advertisingSid, skip, - timeout, - status); - - if (status != PeriodicAdvertisingCallback.SYNC_SUCCESS) { - // App can still unregister the sync until notified it failed. Remove - // callback - // after app was notifed. - mCallbackWrappers.remove(callback); - } - } - }); - } - - public void onPeriodicAdvertisingReport(PeriodicAdvertisingReport report) { - handler.post(new Runnable() { - @Override - public void run() { - callback.onPeriodicAdvertisingReport(report); - } - }); - } - - public void onSyncLost(int syncHandle) { - handler.post(new Runnable() { - @Override - public void run() { - callback.onSyncLost(syncHandle); - // App can still unregister the sync until notified it's lost. - // Remove callback after app was notifed. - mCallbackWrappers.remove(callback); - } - }); - } - }; - } -} diff --git a/core/java/android/bluetooth/le/PeriodicAdvertisingParameters.java b/core/java/android/bluetooth/le/PeriodicAdvertisingParameters.java deleted file mode 100644 index 4e64dbed7017..000000000000 --- a/core/java/android/bluetooth/le/PeriodicAdvertisingParameters.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * 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 android.bluetooth.le; - -import android.os.Parcel; -import android.os.Parcelable; - -/** - * The {@link PeriodicAdvertisingParameters} provide a way to adjust periodic - * advertising preferences for each Bluetooth LE advertising set. Use {@link - * PeriodicAdvertisingParameters.Builder} to create an instance of this class. - */ -public final class PeriodicAdvertisingParameters implements Parcelable { - - private static final int INTERVAL_MIN = 80; - private static final int INTERVAL_MAX = 65519; - - private final boolean mIncludeTxPower; - private final int mInterval; - - private PeriodicAdvertisingParameters(boolean includeTxPower, int interval) { - mIncludeTxPower = includeTxPower; - mInterval = interval; - } - - private PeriodicAdvertisingParameters(Parcel in) { - mIncludeTxPower = in.readInt() != 0; - mInterval = in.readInt(); - } - - /** - * Returns whether the TX Power will be included. - */ - public boolean getIncludeTxPower() { - return mIncludeTxPower; - } - - /** - * Returns the periodic advertising interval, in 1.25ms unit. - * Valid values are from 80 (100ms) to 65519 (81.89875s). - */ - public int getInterval() { - return mInterval; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mIncludeTxPower ? 1 : 0); - dest.writeInt(mInterval); - } - - public static final Parcelable - .Creator<PeriodicAdvertisingParameters> CREATOR = - new Creator<PeriodicAdvertisingParameters>() { - @Override - public PeriodicAdvertisingParameters[] newArray(int size) { - return new PeriodicAdvertisingParameters[size]; - } - - @Override - public PeriodicAdvertisingParameters createFromParcel(Parcel in) { - return new PeriodicAdvertisingParameters(in); - } - }; - - public static final class Builder { - private boolean mIncludeTxPower = false; - private int mInterval = INTERVAL_MAX; - - /** - * Whether the transmission power level should be included in the periodic - * packet. - */ - public Builder setIncludeTxPower(boolean includeTxPower) { - mIncludeTxPower = includeTxPower; - return this; - } - - /** - * Set advertising interval for periodic advertising, in 1.25ms unit. - * Valid values are from 80 (100ms) to 65519 (81.89875s). - * Value from range [interval, interval+20ms] will be picked as the actual value. - * - * @throws IllegalArgumentException If the interval is invalid. - */ - public Builder setInterval(int interval) { - if (interval < INTERVAL_MIN || interval > INTERVAL_MAX) { - throw new IllegalArgumentException("Invalid interval (must be " + INTERVAL_MIN - + "-" + INTERVAL_MAX + ")"); - } - mInterval = interval; - return this; - } - - /** - * Build the {@link AdvertisingSetParameters} object. - */ - public PeriodicAdvertisingParameters build() { - return new PeriodicAdvertisingParameters(mIncludeTxPower, mInterval); - } - } -} diff --git a/core/java/android/bluetooth/le/PeriodicAdvertisingReport.java b/core/java/android/bluetooth/le/PeriodicAdvertisingReport.java deleted file mode 100644 index 54b953c25c27..000000000000 --- a/core/java/android/bluetooth/le/PeriodicAdvertisingReport.java +++ /dev/null @@ -1,186 +0,0 @@ -/* - * 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 android.bluetooth.le; - -import android.annotation.Nullable; -import android.os.Parcel; -import android.os.Parcelable; - -import java.util.Objects; - -/** - * PeriodicAdvertisingReport for Bluetooth LE synchronized advertising. - * - * @hide - */ -public final class PeriodicAdvertisingReport implements Parcelable { - - /** - * The data returned is complete - */ - public static final int DATA_COMPLETE = 0; - - /** - * The data returned is incomplete. The controller was unsuccessfull to - * receive all chained packets, returning only partial data. - */ - public static final int DATA_INCOMPLETE_TRUNCATED = 2; - - private int mSyncHandle; - private int mTxPower; - private int mRssi; - private int mDataStatus; - - // periodic advertising data. - @Nullable - private ScanRecord mData; - - // Device timestamp when the result was last seen. - private long mTimestampNanos; - - /** - * Constructor of periodic advertising result. - */ - public PeriodicAdvertisingReport(int syncHandle, int txPower, int rssi, - int dataStatus, ScanRecord data) { - mSyncHandle = syncHandle; - mTxPower = txPower; - mRssi = rssi; - mDataStatus = dataStatus; - mData = data; - } - - private PeriodicAdvertisingReport(Parcel in) { - readFromParcel(in); - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mSyncHandle); - dest.writeInt(mTxPower); - dest.writeInt(mRssi); - dest.writeInt(mDataStatus); - if (mData != null) { - dest.writeInt(1); - dest.writeByteArray(mData.getBytes()); - } else { - dest.writeInt(0); - } - } - - private void readFromParcel(Parcel in) { - mSyncHandle = in.readInt(); - mTxPower = in.readInt(); - mRssi = in.readInt(); - mDataStatus = in.readInt(); - if (in.readInt() == 1) { - mData = ScanRecord.parseFromBytes(in.createByteArray()); - } - } - - @Override - public int describeContents() { - return 0; - } - - /** - * Returns the synchronization handle. - */ - public int getSyncHandle() { - return mSyncHandle; - } - - /** - * Returns the transmit power in dBm. The valid range is [-127, 126]. Value - * of 127 means information was not available. - */ - public int getTxPower() { - return mTxPower; - } - - /** - * Returns the received signal strength in dBm. The valid range is [-127, 20]. - */ - public int getRssi() { - return mRssi; - } - - /** - * Returns the data status. Can be one of {@link PeriodicAdvertisingReport#DATA_COMPLETE} - * or {@link PeriodicAdvertisingReport#DATA_INCOMPLETE_TRUNCATED}. - */ - public int getDataStatus() { - return mDataStatus; - } - - /** - * Returns the data contained in this periodic advertising report. - */ - @Nullable - public ScanRecord getData() { - return mData; - } - - /** - * Returns timestamp since boot when the scan record was observed. - */ - public long getTimestampNanos() { - return mTimestampNanos; - } - - @Override - public int hashCode() { - return Objects.hash(mSyncHandle, mTxPower, mRssi, mDataStatus, mData, mTimestampNanos); - } - - @Override - public boolean equals(@Nullable Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - PeriodicAdvertisingReport other = (PeriodicAdvertisingReport) obj; - return (mSyncHandle == other.mSyncHandle) - && (mTxPower == other.mTxPower) - && (mRssi == other.mRssi) - && (mDataStatus == other.mDataStatus) - && Objects.equals(mData, other.mData) - && (mTimestampNanos == other.mTimestampNanos); - } - - @Override - public String toString() { - return "PeriodicAdvertisingReport{syncHandle=" + mSyncHandle - + ", txPower=" + mTxPower + ", rssi=" + mRssi + ", dataStatus=" + mDataStatus - + ", data=" + Objects.toString(mData) + ", timestampNanos=" + mTimestampNanos + '}'; - } - - public static final @android.annotation.NonNull Parcelable.Creator<PeriodicAdvertisingReport> CREATOR = - new Creator<PeriodicAdvertisingReport>() { - @Override - public PeriodicAdvertisingReport createFromParcel(Parcel source) { - return new PeriodicAdvertisingReport(source); - } - - @Override - public PeriodicAdvertisingReport[] newArray(int size) { - return new PeriodicAdvertisingReport[size]; - } - }; -} diff --git a/core/java/android/bluetooth/le/ResultStorageDescriptor.java b/core/java/android/bluetooth/le/ResultStorageDescriptor.java deleted file mode 100644 index f65048975deb..000000000000 --- a/core/java/android/bluetooth/le/ResultStorageDescriptor.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2014 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.bluetooth.le; - -import android.annotation.SystemApi; -import android.os.Parcel; -import android.os.Parcelable; - -/** - * Describes the way to store scan result. - * - * @deprecated this is not used anywhere - * - * @hide - */ -@Deprecated -@SystemApi -public final class ResultStorageDescriptor implements Parcelable { - private int mType; - private int mOffset; - private int mLength; - - public int getType() { - return mType; - } - - public int getOffset() { - return mOffset; - } - - public int getLength() { - return mLength; - } - - /** - * Constructor of {@link ResultStorageDescriptor} - * - * @param type Type of the data. - * @param offset Offset from start of the advertise packet payload. - * @param length Byte length of the data - */ - public ResultStorageDescriptor(int type, int offset, int length) { - mType = type; - mOffset = offset; - mLength = length; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mType); - dest.writeInt(mOffset); - dest.writeInt(mLength); - } - - private ResultStorageDescriptor(Parcel in) { - ReadFromParcel(in); - } - - private void ReadFromParcel(Parcel in) { - mType = in.readInt(); - mOffset = in.readInt(); - mLength = in.readInt(); - } - - public static final @android.annotation.NonNull Parcelable.Creator<ResultStorageDescriptor> CREATOR = - new Creator<ResultStorageDescriptor>() { - @Override - public ResultStorageDescriptor createFromParcel(Parcel source) { - return new ResultStorageDescriptor(source); - } - - @Override - public ResultStorageDescriptor[] newArray(int size) { - return new ResultStorageDescriptor[size]; - } - }; -} diff --git a/core/java/android/bluetooth/le/ScanCallback.java b/core/java/android/bluetooth/le/ScanCallback.java deleted file mode 100644 index 53d9310a1236..000000000000 --- a/core/java/android/bluetooth/le/ScanCallback.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2014 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.bluetooth.le; - -import java.util.List; - -/** - * Bluetooth LE scan callbacks. Scan results are reported using these callbacks. - * - * @see BluetoothLeScanner#startScan - */ -public abstract class ScanCallback { - /** - * Fails to start scan as BLE scan with the same settings is already started by the app. - */ - public static final int SCAN_FAILED_ALREADY_STARTED = 1; - - /** - * Fails to start scan as app cannot be registered. - */ - public static final int SCAN_FAILED_APPLICATION_REGISTRATION_FAILED = 2; - - /** - * Fails to start scan due an internal error - */ - public static final int SCAN_FAILED_INTERNAL_ERROR = 3; - - /** - * Fails to start power optimized scan as this feature is not supported. - */ - public static final int SCAN_FAILED_FEATURE_UNSUPPORTED = 4; - - /** - * Fails to start scan as it is out of hardware resources. - * - * @hide - */ - public static final int SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES = 5; - - /** - * Fails to start scan as application tries to scan too frequently. - * @hide - */ - public static final int SCAN_FAILED_SCANNING_TOO_FREQUENTLY = 6; - - static final int NO_ERROR = 0; - - /** - * Callback when a BLE advertisement has been found. - * - * @param callbackType Determines how this callback was triggered. Could be one of {@link - * ScanSettings#CALLBACK_TYPE_ALL_MATCHES}, {@link ScanSettings#CALLBACK_TYPE_FIRST_MATCH} or - * {@link ScanSettings#CALLBACK_TYPE_MATCH_LOST} - * @param result A Bluetooth LE scan result. - */ - public void onScanResult(int callbackType, ScanResult result) { - } - - /** - * Callback when batch results are delivered. - * - * @param results List of scan results that are previously scanned. - */ - public void onBatchScanResults(List<ScanResult> results) { - } - - /** - * Callback when scan could not be started. - * - * @param errorCode Error code (one of SCAN_FAILED_*) for scan failure. - */ - public void onScanFailed(int errorCode) { - } -} diff --git a/core/java/android/bluetooth/le/ScanFilter.java b/core/java/android/bluetooth/le/ScanFilter.java deleted file mode 100644 index b059193ae03f..000000000000 --- a/core/java/android/bluetooth/le/ScanFilter.java +++ /dev/null @@ -1,910 +0,0 @@ -/* - * Copyright (C) 2014 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.bluetooth.le; - -import static java.util.Objects.requireNonNull; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.SystemApi; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothDevice.AddressType; -import android.os.Parcel; -import android.os.ParcelUuid; -import android.os.Parcelable; - -import java.util.Arrays; -import java.util.List; -import java.util.Objects; -import java.util.UUID; - -/** - * Criteria for filtering result from Bluetooth LE scans. A {@link ScanFilter} allows clients to - * restrict scan results to only those that are of interest to them. - * <p> - * Current filtering on the following fields are supported: - * <li>Service UUIDs which identify the bluetooth gatt services running on the device. - * <li>Name of remote Bluetooth LE device. - * <li>Mac address of the remote device. - * <li>Service data which is the data associated with a service. - * <li>Manufacturer specific data which is the data associated with a particular manufacturer. - * - * @see ScanResult - * @see BluetoothLeScanner - */ -public final class ScanFilter implements Parcelable { - - @Nullable - private final String mDeviceName; - - @Nullable - private final String mDeviceAddress; - - private final @AddressType int mAddressType; - - @Nullable - private final byte[] mIrk; - - @Nullable - private final ParcelUuid mServiceUuid; - @Nullable - private final ParcelUuid mServiceUuidMask; - - @Nullable - private final ParcelUuid mServiceSolicitationUuid; - @Nullable - private final ParcelUuid mServiceSolicitationUuidMask; - - @Nullable - private final ParcelUuid mServiceDataUuid; - @Nullable - private final byte[] mServiceData; - @Nullable - private final byte[] mServiceDataMask; - - private final int mManufacturerId; - @Nullable - private final byte[] mManufacturerData; - @Nullable - private final byte[] mManufacturerDataMask; - - /** @hide */ - public static final ScanFilter EMPTY = new ScanFilter.Builder().build(); - - private ScanFilter(String name, String deviceAddress, ParcelUuid uuid, - ParcelUuid uuidMask, ParcelUuid solicitationUuid, - ParcelUuid solicitationUuidMask, ParcelUuid serviceDataUuid, - byte[] serviceData, byte[] serviceDataMask, - int manufacturerId, byte[] manufacturerData, byte[] manufacturerDataMask, - @AddressType int addressType, @Nullable byte[] irk) { - mDeviceName = name; - mServiceUuid = uuid; - mServiceUuidMask = uuidMask; - mServiceSolicitationUuid = solicitationUuid; - mServiceSolicitationUuidMask = solicitationUuidMask; - mDeviceAddress = deviceAddress; - mServiceDataUuid = serviceDataUuid; - mServiceData = serviceData; - mServiceDataMask = serviceDataMask; - mManufacturerId = manufacturerId; - mManufacturerData = manufacturerData; - mManufacturerDataMask = manufacturerDataMask; - mAddressType = addressType; - mIrk = irk; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mDeviceName == null ? 0 : 1); - if (mDeviceName != null) { - dest.writeString(mDeviceName); - } - dest.writeInt(mDeviceAddress == null ? 0 : 1); - if (mDeviceAddress != null) { - dest.writeString(mDeviceAddress); - } - dest.writeInt(mServiceUuid == null ? 0 : 1); - if (mServiceUuid != null) { - dest.writeParcelable(mServiceUuid, flags); - dest.writeInt(mServiceUuidMask == null ? 0 : 1); - if (mServiceUuidMask != null) { - dest.writeParcelable(mServiceUuidMask, flags); - } - } - dest.writeInt(mServiceSolicitationUuid == null ? 0 : 1); - if (mServiceSolicitationUuid != null) { - dest.writeParcelable(mServiceSolicitationUuid, flags); - dest.writeInt(mServiceSolicitationUuidMask == null ? 0 : 1); - if (mServiceSolicitationUuidMask != null) { - dest.writeParcelable(mServiceSolicitationUuidMask, flags); - } - } - dest.writeInt(mServiceDataUuid == null ? 0 : 1); - if (mServiceDataUuid != null) { - dest.writeParcelable(mServiceDataUuid, flags); - dest.writeInt(mServiceData == null ? 0 : 1); - if (mServiceData != null) { - dest.writeInt(mServiceData.length); - dest.writeByteArray(mServiceData); - - dest.writeInt(mServiceDataMask == null ? 0 : 1); - if (mServiceDataMask != null) { - dest.writeInt(mServiceDataMask.length); - dest.writeByteArray(mServiceDataMask); - } - } - } - dest.writeInt(mManufacturerId); - dest.writeInt(mManufacturerData == null ? 0 : 1); - if (mManufacturerData != null) { - dest.writeInt(mManufacturerData.length); - dest.writeByteArray(mManufacturerData); - - dest.writeInt(mManufacturerDataMask == null ? 0 : 1); - if (mManufacturerDataMask != null) { - dest.writeInt(mManufacturerDataMask.length); - dest.writeByteArray(mManufacturerDataMask); - } - } - - // IRK - if (mDeviceAddress != null) { - dest.writeInt(mAddressType); - dest.writeInt(mIrk == null ? 0 : 1); - if (mIrk != null) { - dest.writeByteArray(mIrk); - } - } - } - - /** - * A {@link android.os.Parcelable.Creator} to create {@link ScanFilter} from parcel. - */ - public static final @android.annotation.NonNull Creator<ScanFilter> CREATOR = - new Creator<ScanFilter>() { - - @Override - public ScanFilter[] newArray(int size) { - return new ScanFilter[size]; - } - - @Override - public ScanFilter createFromParcel(Parcel in) { - Builder builder = new Builder(); - if (in.readInt() == 1) { - builder.setDeviceName(in.readString()); - } - String address = null; - // If we have a non-null address - if (in.readInt() == 1) { - address = in.readString(); - } - if (in.readInt() == 1) { - ParcelUuid uuid = in.readParcelable(ParcelUuid.class.getClassLoader()); - builder.setServiceUuid(uuid); - if (in.readInt() == 1) { - ParcelUuid uuidMask = in.readParcelable( - ParcelUuid.class.getClassLoader()); - builder.setServiceUuid(uuid, uuidMask); - } - } - if (in.readInt() == 1) { - ParcelUuid solicitationUuid = in.readParcelable( - ParcelUuid.class.getClassLoader()); - builder.setServiceSolicitationUuid(solicitationUuid); - if (in.readInt() == 1) { - ParcelUuid solicitationUuidMask = in.readParcelable( - ParcelUuid.class.getClassLoader()); - builder.setServiceSolicitationUuid(solicitationUuid, - solicitationUuidMask); - } - } - if (in.readInt() == 1) { - ParcelUuid servcieDataUuid = - in.readParcelable(ParcelUuid.class.getClassLoader()); - if (in.readInt() == 1) { - int serviceDataLength = in.readInt(); - byte[] serviceData = new byte[serviceDataLength]; - in.readByteArray(serviceData); - if (in.readInt() == 0) { - builder.setServiceData(servcieDataUuid, serviceData); - } else { - int serviceDataMaskLength = in.readInt(); - byte[] serviceDataMask = new byte[serviceDataMaskLength]; - in.readByteArray(serviceDataMask); - builder.setServiceData( - servcieDataUuid, serviceData, serviceDataMask); - } - } - } - - int manufacturerId = in.readInt(); - if (in.readInt() == 1) { - int manufacturerDataLength = in.readInt(); - byte[] manufacturerData = new byte[manufacturerDataLength]; - in.readByteArray(manufacturerData); - if (in.readInt() == 0) { - builder.setManufacturerData(manufacturerId, manufacturerData); - } else { - int manufacturerDataMaskLength = in.readInt(); - byte[] manufacturerDataMask = new byte[manufacturerDataMaskLength]; - in.readByteArray(manufacturerDataMask); - builder.setManufacturerData(manufacturerId, manufacturerData, - manufacturerDataMask); - } - } - - // IRK - if (address != null) { - final int addressType = in.readInt(); - if (in.readInt() == 1) { - final byte[] irk = new byte[16]; - in.readByteArray(irk); - builder.setDeviceAddress(address, addressType, irk); - } else { - builder.setDeviceAddress(address, addressType); - } - } - return builder.build(); - } - }; - - /** - * Returns the filter set the device name field of Bluetooth advertisement data. - */ - @Nullable - public String getDeviceName() { - return mDeviceName; - } - - /** - * Returns the filter set on the service uuid. - */ - @Nullable - public ParcelUuid getServiceUuid() { - return mServiceUuid; - } - - @Nullable - public ParcelUuid getServiceUuidMask() { - return mServiceUuidMask; - } - - /** - * Returns the filter set on the service Solicitation uuid. - */ - @Nullable - public ParcelUuid getServiceSolicitationUuid() { - return mServiceSolicitationUuid; - } - - /** - * Returns the filter set on the service Solicitation uuid mask. - */ - @Nullable - public ParcelUuid getServiceSolicitationUuidMask() { - return mServiceSolicitationUuidMask; - } - - @Nullable - public String getDeviceAddress() { - return mDeviceAddress; - } - - /** - * @hide - */ - @SystemApi - public @AddressType int getAddressType() { - return mAddressType; - } - - /** - * @hide - */ - @SystemApi - @Nullable - public byte[] getIrk() { - return mIrk; - } - - @Nullable - public byte[] getServiceData() { - return mServiceData; - } - - @Nullable - public byte[] getServiceDataMask() { - return mServiceDataMask; - } - - @Nullable - public ParcelUuid getServiceDataUuid() { - return mServiceDataUuid; - } - - /** - * Returns the manufacturer id. -1 if the manufacturer filter is not set. - */ - public int getManufacturerId() { - return mManufacturerId; - } - - @Nullable - public byte[] getManufacturerData() { - return mManufacturerData; - } - - @Nullable - public byte[] getManufacturerDataMask() { - return mManufacturerDataMask; - } - - /** - * Check if the scan filter matches a {@code scanResult}. A scan result is considered as a match - * if it matches all the field filters. - */ - public boolean matches(ScanResult scanResult) { - if (scanResult == null) { - return false; - } - BluetoothDevice device = scanResult.getDevice(); - // Device match. - if (mDeviceAddress != null - && (device == null || !mDeviceAddress.equals(device.getAddress()))) { - return false; - } - - ScanRecord scanRecord = scanResult.getScanRecord(); - - // Scan record is null but there exist filters on it. - if (scanRecord == null - && (mDeviceName != null || mServiceUuid != null || mManufacturerData != null - || mServiceData != null || mServiceSolicitationUuid != null)) { - return false; - } - - // Local name match. - if (mDeviceName != null && !mDeviceName.equals(scanRecord.getDeviceName())) { - return false; - } - - // UUID match. - if (mServiceUuid != null && !matchesServiceUuids(mServiceUuid, mServiceUuidMask, - scanRecord.getServiceUuids())) { - return false; - } - - // solicitation UUID match. - if (mServiceSolicitationUuid != null && !matchesServiceSolicitationUuids( - mServiceSolicitationUuid, mServiceSolicitationUuidMask, - scanRecord.getServiceSolicitationUuids())) { - return false; - } - - // Service data match - if (mServiceDataUuid != null) { - if (!matchesPartialData(mServiceData, mServiceDataMask, - scanRecord.getServiceData(mServiceDataUuid))) { - return false; - } - } - - // Manufacturer data match. - if (mManufacturerId >= 0) { - if (!matchesPartialData(mManufacturerData, mManufacturerDataMask, - scanRecord.getManufacturerSpecificData(mManufacturerId))) { - return false; - } - } - // All filters match. - return true; - } - - /** - * Check if the uuid pattern is contained in a list of parcel uuids. - * - * @hide - */ - public static boolean matchesServiceUuids(ParcelUuid uuid, ParcelUuid parcelUuidMask, - List<ParcelUuid> uuids) { - if (uuid == null) { - return true; - } - if (uuids == null) { - return false; - } - - for (ParcelUuid parcelUuid : uuids) { - UUID uuidMask = parcelUuidMask == null ? null : parcelUuidMask.getUuid(); - if (matchesServiceUuid(uuid.getUuid(), uuidMask, parcelUuid.getUuid())) { - return true; - } - } - return false; - } - - // Check if the uuid pattern matches the particular service uuid. - private static boolean matchesServiceUuid(UUID uuid, UUID mask, UUID data) { - return BluetoothLeUtils.maskedEquals(data, uuid, mask); - } - - /** - * Check if the solicitation uuid pattern is contained in a list of parcel uuids. - * - */ - private static boolean matchesServiceSolicitationUuids(ParcelUuid solicitationUuid, - ParcelUuid parcelSolicitationUuidMask, List<ParcelUuid> solicitationUuids) { - if (solicitationUuid == null) { - return true; - } - if (solicitationUuids == null) { - return false; - } - - for (ParcelUuid parcelSolicitationUuid : solicitationUuids) { - UUID solicitationUuidMask = parcelSolicitationUuidMask == null - ? null : parcelSolicitationUuidMask.getUuid(); - if (matchesServiceUuid(solicitationUuid.getUuid(), solicitationUuidMask, - parcelSolicitationUuid.getUuid())) { - return true; - } - } - return false; - } - - // Check if the solicitation uuid pattern matches the particular service solicitation uuid. - private static boolean matchesServiceSolicitationUuid(UUID solicitationUuid, - UUID solicitationUuidMask, UUID data) { - return BluetoothLeUtils.maskedEquals(data, solicitationUuid, solicitationUuidMask); - } - - // Check whether the data pattern matches the parsed data. - private boolean matchesPartialData(byte[] data, byte[] dataMask, byte[] parsedData) { - if (parsedData == null || parsedData.length < data.length) { - return false; - } - if (dataMask == null) { - for (int i = 0; i < data.length; ++i) { - if (parsedData[i] != data[i]) { - return false; - } - } - return true; - } - for (int i = 0; i < data.length; ++i) { - if ((dataMask[i] & parsedData[i]) != (dataMask[i] & data[i])) { - return false; - } - } - return true; - } - - @Override - public String toString() { - return "BluetoothLeScanFilter [mDeviceName=" + mDeviceName + ", mDeviceAddress=" - + mDeviceAddress - + ", mUuid=" + mServiceUuid + ", mUuidMask=" + mServiceUuidMask - + ", mServiceSolicitationUuid=" + mServiceSolicitationUuid - + ", mServiceSolicitationUuidMask=" + mServiceSolicitationUuidMask - + ", mServiceDataUuid=" + Objects.toString(mServiceDataUuid) + ", mServiceData=" - + Arrays.toString(mServiceData) + ", mServiceDataMask=" - + Arrays.toString(mServiceDataMask) + ", mManufacturerId=" + mManufacturerId - + ", mManufacturerData=" + Arrays.toString(mManufacturerData) - + ", mManufacturerDataMask=" + Arrays.toString(mManufacturerDataMask) + "]"; - } - - @Override - public int hashCode() { - return Objects.hash(mDeviceName, mDeviceAddress, mManufacturerId, - Arrays.hashCode(mManufacturerData), - Arrays.hashCode(mManufacturerDataMask), - mServiceDataUuid, - Arrays.hashCode(mServiceData), - Arrays.hashCode(mServiceDataMask), - mServiceUuid, mServiceUuidMask, - mServiceSolicitationUuid, mServiceSolicitationUuidMask); - } - - @Override - public boolean equals(@Nullable Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - ScanFilter other = (ScanFilter) obj; - return Objects.equals(mDeviceName, other.mDeviceName) - && Objects.equals(mDeviceAddress, other.mDeviceAddress) - && mManufacturerId == other.mManufacturerId - && Objects.deepEquals(mManufacturerData, other.mManufacturerData) - && Objects.deepEquals(mManufacturerDataMask, other.mManufacturerDataMask) - && Objects.equals(mServiceDataUuid, other.mServiceDataUuid) - && Objects.deepEquals(mServiceData, other.mServiceData) - && Objects.deepEquals(mServiceDataMask, other.mServiceDataMask) - && Objects.equals(mServiceUuid, other.mServiceUuid) - && Objects.equals(mServiceUuidMask, other.mServiceUuidMask) - && Objects.equals(mServiceSolicitationUuid, other.mServiceSolicitationUuid) - && Objects.equals(mServiceSolicitationUuidMask, - other.mServiceSolicitationUuidMask); - } - - /** - * Checks if the scanfilter is empty - * - * @hide - */ - public boolean isAllFieldsEmpty() { - return EMPTY.equals(this); - } - - /** - * Builder class for {@link ScanFilter}. - */ - public static final class Builder { - - /** - * @hide - */ - @SystemApi - public static final int LEN_IRK_OCTETS = 16; - - private String mDeviceName; - private String mDeviceAddress; - private @AddressType int mAddressType = BluetoothDevice.ADDRESS_TYPE_PUBLIC; - private byte[] mIrk; - - private ParcelUuid mServiceUuid; - private ParcelUuid mUuidMask; - - private ParcelUuid mServiceSolicitationUuid; - private ParcelUuid mServiceSolicitationUuidMask; - - private ParcelUuid mServiceDataUuid; - private byte[] mServiceData; - private byte[] mServiceDataMask; - - private int mManufacturerId = -1; - private byte[] mManufacturerData; - private byte[] mManufacturerDataMask; - - /** - * Set filter on device name. - */ - public Builder setDeviceName(String deviceName) { - mDeviceName = deviceName; - return this; - } - - /** - * Set filter on device address. - * - * @param deviceAddress The device Bluetooth address for the filter. It needs to be in the - * format of "01:02:03:AB:CD:EF". The device address can be validated using {@link - * BluetoothAdapter#checkBluetoothAddress}. The @AddressType is defaulted to {@link - * BluetoothDevice#ADDRESS_TYPE_PUBLIC} - * @throws IllegalArgumentException If the {@code deviceAddress} is invalid. - */ - public Builder setDeviceAddress(String deviceAddress) { - if (deviceAddress == null) { - mDeviceAddress = deviceAddress; - return this; - } - return setDeviceAddress(deviceAddress, BluetoothDevice.ADDRESS_TYPE_PUBLIC); - } - - /** - * Set filter on Address with AddressType - * - * <p>This key is used to resolve a private address from a public address. - * - * @param deviceAddress The device Bluetooth address for the filter. It needs to be in the - * format of "01:02:03:AB:CD:EF". The device address can be validated using {@link - * BluetoothAdapter#checkBluetoothAddress}. May be any type of address. - * @param addressType indication of the type of address - * e.g. {@link BluetoothDevice#ADDRESS_TYPE_PUBLIC} - * or {@link BluetoothDevice#ADDRESS_TYPE_RANDOM} - * - * @throws IllegalArgumentException If the {@code deviceAddress} is invalid. - * @throws IllegalArgumentException If the {@code addressType} is invalid length - * @throws NullPointerException if {@code deviceAddress} is null. - * - * @hide - */ - @NonNull - @SystemApi - public Builder setDeviceAddress(@NonNull String deviceAddress, - @AddressType int addressType) { - return setDeviceAddressInternal(deviceAddress, addressType, null); - } - - /** - * Set filter on Address with AddressType and the Identity Resolving Key (IRK). - * - * <p>The IRK is used to resolve a {@link BluetoothDevice#ADDRESS_TYPE_PUBLIC} from - * a PRIVATE_ADDRESS type. - * - * @param deviceAddress The device Bluetooth address for the filter. It needs to be in the - * format of "01:02:03:AB:CD:EF". The device address can be validated using {@link - * BluetoothAdapter#checkBluetoothAddress}. This Address type must only be PUBLIC OR RANDOM - * STATIC. - * @param addressType indication of the type of address - * e.g. {@link BluetoothDevice#ADDRESS_TYPE_PUBLIC} - * or {@link BluetoothDevice#ADDRESS_TYPE_RANDOM} - * @param irk non-null byte array representing the Identity Resolving Key - * - * @throws IllegalArgumentException If the {@code deviceAddress} is invalid. - * @throws IllegalArgumentException if the {@code irk} is invalid length. - * @throws IllegalArgumentException If the {@code addressType} is invalid length or is not - * PUBLIC or RANDOM STATIC when an IRK is present. - * @throws NullPointerException if {@code deviceAddress} or {@code irk} is null. - * - * @hide - */ - @NonNull - @SystemApi - public Builder setDeviceAddress(@NonNull String deviceAddress, - @AddressType int addressType, - @NonNull byte[] irk) { - requireNonNull(irk); - if (irk.length != LEN_IRK_OCTETS) { - throw new IllegalArgumentException("'irk' is invalid length!"); - } - return setDeviceAddressInternal(deviceAddress, addressType, irk); - } - - /** - * Set filter on Address with AddressType and the Identity Resolving Key (IRK). - * - * <p>Internal setter for the device address - * - * @param deviceAddress The device Bluetooth address for the filter. It needs to be in the - * format of "01:02:03:AB:CD:EF". The device address can be validated using {@link - * BluetoothAdapter#checkBluetoothAddress}. - * @param addressType indication of the type of address - * e.g. {@link BluetoothDevice#ADDRESS_TYPE_PUBLIC} - * @param irk non-null byte array representing the Identity Resolving Address; nullable - * internally. - * - * @throws IllegalArgumentException If the {@code deviceAddress} is invalid. - * @throws IllegalArgumentException If the {@code addressType} is invalid length. - * @throws NullPointerException if {@code deviceAddress} is null. - * - * @hide - */ - @NonNull - private Builder setDeviceAddressInternal(@NonNull String deviceAddress, - @AddressType int addressType, - @Nullable byte[] irk) { - - // Make sure our deviceAddress is valid! - requireNonNull(deviceAddress); - if (!BluetoothAdapter.checkBluetoothAddress(deviceAddress)) { - throw new IllegalArgumentException("invalid device address " + deviceAddress); - } - - // Verify type range - if (addressType < BluetoothDevice.ADDRESS_TYPE_PUBLIC - || addressType > BluetoothDevice.ADDRESS_TYPE_RANDOM) { - throw new IllegalArgumentException("'addressType' is invalid!"); - } - - // IRK can only be used for a PUBLIC or RANDOM (STATIC) Address. - if (addressType == BluetoothDevice.ADDRESS_TYPE_RANDOM) { - // Don't want a bad combination of address and irk! - if (irk != null) { - // Since there are 3 possible RANDOM subtypes we must check to make sure - // the correct type of address is used. - if (!BluetoothAdapter.isAddressRandomStatic(deviceAddress)) { - throw new IllegalArgumentException( - "Invalid combination: IRK requires either a PUBLIC or " - + "RANDOM (STATIC) Address"); - } - } - } - - // PUBLIC doesn't require extra work - // Without an IRK any address may be accepted - - mDeviceAddress = deviceAddress; - mAddressType = addressType; - mIrk = irk; - return this; - } - - /** - * Set filter on service uuid. - */ - public Builder setServiceUuid(ParcelUuid serviceUuid) { - mServiceUuid = serviceUuid; - mUuidMask = null; // clear uuid mask - return this; - } - - /** - * Set filter on partial service uuid. The {@code uuidMask} is the bit mask for the - * {@code serviceUuid}. Set any bit in the mask to 1 to indicate a match is needed for the - * bit in {@code serviceUuid}, and 0 to ignore that bit. - * - * @throws IllegalArgumentException If {@code serviceUuid} is {@code null} but {@code - * uuidMask} is not {@code null}. - */ - public Builder setServiceUuid(ParcelUuid serviceUuid, ParcelUuid uuidMask) { - if (mUuidMask != null && mServiceUuid == null) { - throw new IllegalArgumentException("uuid is null while uuidMask is not null!"); - } - mServiceUuid = serviceUuid; - mUuidMask = uuidMask; - return this; - } - - - /** - * Set filter on service solicitation uuid. - */ - public @NonNull Builder setServiceSolicitationUuid( - @Nullable ParcelUuid serviceSolicitationUuid) { - mServiceSolicitationUuid = serviceSolicitationUuid; - if (serviceSolicitationUuid == null) { - mServiceSolicitationUuidMask = null; - } - return this; - } - - - /** - * Set filter on partial service Solicitation uuid. The {@code SolicitationUuidMask} is the - * bit mask for the {@code serviceSolicitationUuid}. Set any bit in the mask to 1 to - * indicate a match is needed for the bit in {@code serviceSolicitationUuid}, and 0 to - * ignore that bit. - * - * @param serviceSolicitationUuid can only be null if solicitationUuidMask is null. - * @param solicitationUuidMask can be null or a mask with no restriction. - * - * @throws IllegalArgumentException If {@code serviceSolicitationUuid} is {@code null} but - * {@code serviceSolicitationUuidMask} is not {@code null}. - */ - public @NonNull Builder setServiceSolicitationUuid( - @Nullable ParcelUuid serviceSolicitationUuid, - @Nullable ParcelUuid solicitationUuidMask) { - if (solicitationUuidMask != null && serviceSolicitationUuid == null) { - throw new IllegalArgumentException( - "SolicitationUuid is null while SolicitationUuidMask is not null!"); - } - mServiceSolicitationUuid = serviceSolicitationUuid; - mServiceSolicitationUuidMask = solicitationUuidMask; - return this; - } - - /** - * Set filtering on service data. - * - * @throws IllegalArgumentException If {@code serviceDataUuid} is null. - */ - public Builder setServiceData(ParcelUuid serviceDataUuid, byte[] serviceData) { - if (serviceDataUuid == null) { - throw new IllegalArgumentException("serviceDataUuid is null"); - } - mServiceDataUuid = serviceDataUuid; - mServiceData = serviceData; - mServiceDataMask = null; // clear service data mask - return this; - } - - /** - * Set partial filter on service data. For any bit in the mask, set it to 1 if it needs to - * match the one in service data, otherwise set it to 0 to ignore that bit. - * <p> - * The {@code serviceDataMask} must have the same length of the {@code serviceData}. - * - * @throws IllegalArgumentException If {@code serviceDataUuid} is null or {@code - * serviceDataMask} is {@code null} while {@code serviceData} is not or {@code - * serviceDataMask} and {@code serviceData} has different length. - */ - public Builder setServiceData(ParcelUuid serviceDataUuid, - byte[] serviceData, byte[] serviceDataMask) { - if (serviceDataUuid == null) { - throw new IllegalArgumentException("serviceDataUuid is null"); - } - if (mServiceDataMask != null) { - if (mServiceData == null) { - throw new IllegalArgumentException( - "serviceData is null while serviceDataMask is not null"); - } - // Since the mServiceDataMask is a bit mask for mServiceData, the lengths of the two - // byte array need to be the same. - if (mServiceData.length != mServiceDataMask.length) { - throw new IllegalArgumentException( - "size mismatch for service data and service data mask"); - } - } - mServiceDataUuid = serviceDataUuid; - mServiceData = serviceData; - mServiceDataMask = serviceDataMask; - return this; - } - - /** - * Set filter on on manufacturerData. A negative manufacturerId is considered as invalid id. - * - * @throws IllegalArgumentException If the {@code manufacturerId} is invalid. - */ - public Builder setManufacturerData(int manufacturerId, byte[] manufacturerData) { - if (manufacturerData != null && manufacturerId < 0) { - throw new IllegalArgumentException("invalid manufacture id"); - } - mManufacturerId = manufacturerId; - mManufacturerData = manufacturerData; - mManufacturerDataMask = null; // clear manufacturer data mask - return this; - } - - /** - * Set filter on partial manufacture data. For any bit in the mask, set it the 1 if it needs - * to match the one in manufacturer data, otherwise set it to 0. - * <p> - * The {@code manufacturerDataMask} must have the same length of {@code manufacturerData}. - * - * @throws IllegalArgumentException If the {@code manufacturerId} is invalid, or {@code - * manufacturerData} is null while {@code manufacturerDataMask} is not, or {@code - * manufacturerData} and {@code manufacturerDataMask} have different length. - */ - public Builder setManufacturerData(int manufacturerId, byte[] manufacturerData, - byte[] manufacturerDataMask) { - if (manufacturerData != null && manufacturerId < 0) { - throw new IllegalArgumentException("invalid manufacture id"); - } - if (mManufacturerDataMask != null) { - if (mManufacturerData == null) { - throw new IllegalArgumentException( - "manufacturerData is null while manufacturerDataMask is not null"); - } - // Since the mManufacturerDataMask is a bit mask for mManufacturerData, the lengths - // of the two byte array need to be the same. - if (mManufacturerData.length != mManufacturerDataMask.length) { - throw new IllegalArgumentException( - "size mismatch for manufacturerData and manufacturerDataMask"); - } - } - mManufacturerId = manufacturerId; - mManufacturerData = manufacturerData; - mManufacturerDataMask = manufacturerDataMask; - return this; - } - - /** - * Build {@link ScanFilter}. - * - * @throws IllegalArgumentException If the filter cannot be built. - */ - public ScanFilter build() { - return new ScanFilter(mDeviceName, mDeviceAddress, - mServiceUuid, mUuidMask, mServiceSolicitationUuid, - mServiceSolicitationUuidMask, - mServiceDataUuid, mServiceData, mServiceDataMask, - mManufacturerId, mManufacturerData, mManufacturerDataMask, - mAddressType, mIrk); - } - } -} diff --git a/core/java/android/bluetooth/le/ScanRecord.java b/core/java/android/bluetooth/le/ScanRecord.java deleted file mode 100644 index 9b8c2eaf4d19..000000000000 --- a/core/java/android/bluetooth/le/ScanRecord.java +++ /dev/null @@ -1,378 +0,0 @@ -/* - * Copyright (C) 2014 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.bluetooth.le; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.SuppressLint; -import android.bluetooth.BluetoothUuid; -import android.compat.annotation.UnsupportedAppUsage; -import android.os.ParcelUuid; -import android.util.ArrayMap; -import android.util.Log; -import android.util.SparseArray; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.function.Predicate; - -/** - * Represents a scan record from Bluetooth LE scan. - */ -@SuppressLint("AndroidFrameworkBluetoothPermission") -public final class ScanRecord { - - private static final String TAG = "ScanRecord"; - - // The following data type values are assigned by Bluetooth SIG. - // For more details refer to Bluetooth 4.1 specification, Volume 3, Part C, Section 18. - private static final int DATA_TYPE_FLAGS = 0x01; - private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL = 0x02; - private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE = 0x03; - private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL = 0x04; - private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE = 0x05; - private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL = 0x06; - private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE = 0x07; - private static final int DATA_TYPE_LOCAL_NAME_SHORT = 0x08; - private static final int DATA_TYPE_LOCAL_NAME_COMPLETE = 0x09; - private static final int DATA_TYPE_TX_POWER_LEVEL = 0x0A; - private static final int DATA_TYPE_SERVICE_DATA_16_BIT = 0x16; - private static final int DATA_TYPE_SERVICE_DATA_32_BIT = 0x20; - private static final int DATA_TYPE_SERVICE_DATA_128_BIT = 0x21; - private static final int DATA_TYPE_SERVICE_SOLICITATION_UUIDS_16_BIT = 0x14; - private static final int DATA_TYPE_SERVICE_SOLICITATION_UUIDS_32_BIT = 0x1F; - private static final int DATA_TYPE_SERVICE_SOLICITATION_UUIDS_128_BIT = 0x15; - private static final int DATA_TYPE_MANUFACTURER_SPECIFIC_DATA = 0xFF; - - // Flags of the advertising data. - private final int mAdvertiseFlags; - - @Nullable - private final List<ParcelUuid> mServiceUuids; - @Nullable - private final List<ParcelUuid> mServiceSolicitationUuids; - - private final SparseArray<byte[]> mManufacturerSpecificData; - - private final Map<ParcelUuid, byte[]> mServiceData; - - // Transmission power level(in dB). - private final int mTxPowerLevel; - - // Local name of the Bluetooth LE device. - private final String mDeviceName; - - // Raw bytes of scan record. - private final byte[] mBytes; - - /** - * Returns the advertising flags indicating the discoverable mode and capability of the device. - * Returns -1 if the flag field is not set. - */ - public int getAdvertiseFlags() { - return mAdvertiseFlags; - } - - /** - * Returns a list of service UUIDs within the advertisement that are used to identify the - * bluetooth GATT services. - */ - public List<ParcelUuid> getServiceUuids() { - return mServiceUuids; - } - - /** - * Returns a list of service solicitation UUIDs within the advertisement that are used to - * identify the Bluetooth GATT services. - */ - @NonNull - public List<ParcelUuid> getServiceSolicitationUuids() { - return mServiceSolicitationUuids; - } - - /** - * Returns a sparse array of manufacturer identifier and its corresponding manufacturer specific - * data. - */ - public SparseArray<byte[]> getManufacturerSpecificData() { - return mManufacturerSpecificData; - } - - /** - * Returns the manufacturer specific data associated with the manufacturer id. Returns - * {@code null} if the {@code manufacturerId} is not found. - */ - @Nullable - public byte[] getManufacturerSpecificData(int manufacturerId) { - if (mManufacturerSpecificData == null) { - return null; - } - return mManufacturerSpecificData.get(manufacturerId); - } - - /** - * Returns a map of service UUID and its corresponding service data. - */ - public Map<ParcelUuid, byte[]> getServiceData() { - return mServiceData; - } - - /** - * Returns the service data byte array associated with the {@code serviceUuid}. Returns - * {@code null} if the {@code serviceDataUuid} is not found. - */ - @Nullable - public byte[] getServiceData(ParcelUuid serviceDataUuid) { - if (serviceDataUuid == null || mServiceData == null) { - return null; - } - return mServiceData.get(serviceDataUuid); - } - - /** - * Returns the transmission power level of the packet in dBm. Returns {@link Integer#MIN_VALUE} - * if the field is not set. This value can be used to calculate the path loss of a received - * packet using the following equation: - * <p> - * <code>pathloss = txPowerLevel - rssi</code> - */ - public int getTxPowerLevel() { - return mTxPowerLevel; - } - - /** - * Returns the local name of the BLE device. This is a UTF-8 encoded string. - */ - @Nullable - public String getDeviceName() { - return mDeviceName; - } - - /** - * Returns raw bytes of scan record. - */ - public byte[] getBytes() { - return mBytes; - } - - /** - * Test if any fields contained inside this scan record are matched by the - * given matcher. - * - * @hide - */ - public boolean matchesAnyField(@NonNull Predicate<byte[]> matcher) { - int pos = 0; - while (pos < mBytes.length) { - final int length = mBytes[pos] & 0xFF; - if (length == 0) { - break; - } - if (matcher.test(Arrays.copyOfRange(mBytes, pos, pos + length + 1))) { - return true; - } - pos += length + 1; - } - return false; - } - - private ScanRecord(List<ParcelUuid> serviceUuids, - List<ParcelUuid> serviceSolicitationUuids, - SparseArray<byte[]> manufacturerData, - Map<ParcelUuid, byte[]> serviceData, - int advertiseFlags, int txPowerLevel, - String localName, byte[] bytes) { - mServiceSolicitationUuids = serviceSolicitationUuids; - mServiceUuids = serviceUuids; - mManufacturerSpecificData = manufacturerData; - mServiceData = serviceData; - mDeviceName = localName; - mAdvertiseFlags = advertiseFlags; - mTxPowerLevel = txPowerLevel; - mBytes = bytes; - } - - /** - * Parse scan record bytes to {@link ScanRecord}. - * <p> - * The format is defined in Bluetooth 4.1 specification, Volume 3, Part C, Section 11 and 18. - * <p> - * All numerical multi-byte entities and values shall use little-endian <strong>byte</strong> - * order. - * - * @param scanRecord The scan record of Bluetooth LE advertisement and/or scan response. - * @hide - */ - @UnsupportedAppUsage - public static ScanRecord parseFromBytes(byte[] scanRecord) { - if (scanRecord == null) { - return null; - } - - int currentPos = 0; - int advertiseFlag = -1; - List<ParcelUuid> serviceUuids = new ArrayList<ParcelUuid>(); - List<ParcelUuid> serviceSolicitationUuids = new ArrayList<ParcelUuid>(); - String localName = null; - int txPowerLevel = Integer.MIN_VALUE; - - SparseArray<byte[]> manufacturerData = new SparseArray<byte[]>(); - Map<ParcelUuid, byte[]> serviceData = new ArrayMap<ParcelUuid, byte[]>(); - - try { - while (currentPos < scanRecord.length) { - // length is unsigned int. - int length = scanRecord[currentPos++] & 0xFF; - if (length == 0) { - break; - } - // Note the length includes the length of the field type itself. - int dataLength = length - 1; - // fieldType is unsigned int. - int fieldType = scanRecord[currentPos++] & 0xFF; - switch (fieldType) { - case DATA_TYPE_FLAGS: - advertiseFlag = scanRecord[currentPos] & 0xFF; - break; - case DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL: - case DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE: - parseServiceUuid(scanRecord, currentPos, - dataLength, BluetoothUuid.UUID_BYTES_16_BIT, serviceUuids); - break; - case DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL: - case DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE: - parseServiceUuid(scanRecord, currentPos, dataLength, - BluetoothUuid.UUID_BYTES_32_BIT, serviceUuids); - break; - case DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL: - case DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE: - parseServiceUuid(scanRecord, currentPos, dataLength, - BluetoothUuid.UUID_BYTES_128_BIT, serviceUuids); - break; - case DATA_TYPE_SERVICE_SOLICITATION_UUIDS_16_BIT: - parseServiceSolicitationUuid(scanRecord, currentPos, dataLength, - BluetoothUuid.UUID_BYTES_16_BIT, serviceSolicitationUuids); - break; - case DATA_TYPE_SERVICE_SOLICITATION_UUIDS_32_BIT: - parseServiceSolicitationUuid(scanRecord, currentPos, dataLength, - BluetoothUuid.UUID_BYTES_32_BIT, serviceSolicitationUuids); - break; - case DATA_TYPE_SERVICE_SOLICITATION_UUIDS_128_BIT: - parseServiceSolicitationUuid(scanRecord, currentPos, dataLength, - BluetoothUuid.UUID_BYTES_128_BIT, serviceSolicitationUuids); - break; - case DATA_TYPE_LOCAL_NAME_SHORT: - case DATA_TYPE_LOCAL_NAME_COMPLETE: - localName = new String( - extractBytes(scanRecord, currentPos, dataLength)); - break; - case DATA_TYPE_TX_POWER_LEVEL: - txPowerLevel = scanRecord[currentPos]; - break; - case DATA_TYPE_SERVICE_DATA_16_BIT: - case DATA_TYPE_SERVICE_DATA_32_BIT: - case DATA_TYPE_SERVICE_DATA_128_BIT: - int serviceUuidLength = BluetoothUuid.UUID_BYTES_16_BIT; - if (fieldType == DATA_TYPE_SERVICE_DATA_32_BIT) { - serviceUuidLength = BluetoothUuid.UUID_BYTES_32_BIT; - } else if (fieldType == DATA_TYPE_SERVICE_DATA_128_BIT) { - serviceUuidLength = BluetoothUuid.UUID_BYTES_128_BIT; - } - - byte[] serviceDataUuidBytes = extractBytes(scanRecord, currentPos, - serviceUuidLength); - ParcelUuid serviceDataUuid = BluetoothUuid.parseUuidFrom( - serviceDataUuidBytes); - byte[] serviceDataArray = extractBytes(scanRecord, - currentPos + serviceUuidLength, dataLength - serviceUuidLength); - serviceData.put(serviceDataUuid, serviceDataArray); - break; - case DATA_TYPE_MANUFACTURER_SPECIFIC_DATA: - // The first two bytes of the manufacturer specific data are - // manufacturer ids in little endian. - int manufacturerId = ((scanRecord[currentPos + 1] & 0xFF) << 8) - + (scanRecord[currentPos] & 0xFF); - byte[] manufacturerDataBytes = extractBytes(scanRecord, currentPos + 2, - dataLength - 2); - manufacturerData.put(manufacturerId, manufacturerDataBytes); - break; - default: - // Just ignore, we don't handle such data type. - break; - } - currentPos += dataLength; - } - - if (serviceUuids.isEmpty()) { - serviceUuids = null; - } - return new ScanRecord(serviceUuids, serviceSolicitationUuids, manufacturerData, - serviceData, advertiseFlag, txPowerLevel, localName, scanRecord); - } catch (Exception e) { - Log.e(TAG, "unable to parse scan record: " + Arrays.toString(scanRecord)); - // As the record is invalid, ignore all the parsed results for this packet - // and return an empty record with raw scanRecord bytes in results - return new ScanRecord(null, null, null, null, -1, Integer.MIN_VALUE, null, scanRecord); - } - } - - @Override - public String toString() { - return "ScanRecord [mAdvertiseFlags=" + mAdvertiseFlags + ", mServiceUuids=" + mServiceUuids - + ", mServiceSolicitationUuids=" + mServiceSolicitationUuids - + ", mManufacturerSpecificData=" + BluetoothLeUtils.toString( - mManufacturerSpecificData) - + ", mServiceData=" + BluetoothLeUtils.toString(mServiceData) - + ", mTxPowerLevel=" + mTxPowerLevel + ", mDeviceName=" + mDeviceName + "]"; - } - - // Parse service UUIDs. - private static int parseServiceUuid(byte[] scanRecord, int currentPos, int dataLength, - int uuidLength, List<ParcelUuid> serviceUuids) { - while (dataLength > 0) { - byte[] uuidBytes = extractBytes(scanRecord, currentPos, - uuidLength); - serviceUuids.add(BluetoothUuid.parseUuidFrom(uuidBytes)); - dataLength -= uuidLength; - currentPos += uuidLength; - } - return currentPos; - } - - /** - * Parse service Solicitation UUIDs. - */ - private static int parseServiceSolicitationUuid(byte[] scanRecord, int currentPos, - int dataLength, int uuidLength, List<ParcelUuid> serviceSolicitationUuids) { - while (dataLength > 0) { - byte[] uuidBytes = extractBytes(scanRecord, currentPos, uuidLength); - serviceSolicitationUuids.add(BluetoothUuid.parseUuidFrom(uuidBytes)); - dataLength -= uuidLength; - currentPos += uuidLength; - } - return currentPos; - } - - // Helper method to extract bytes from byte array. - private static byte[] extractBytes(byte[] scanRecord, int start, int length) { - byte[] bytes = new byte[length]; - System.arraycopy(scanRecord, start, bytes, 0, length); - return bytes; - } -} diff --git a/core/java/android/bluetooth/le/ScanResult.java b/core/java/android/bluetooth/le/ScanResult.java deleted file mode 100644 index f437d867ea37..000000000000 --- a/core/java/android/bluetooth/le/ScanResult.java +++ /dev/null @@ -1,361 +0,0 @@ -/* - * Copyright (C) 2014 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.bluetooth.le; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.bluetooth.Attributable; -import android.bluetooth.BluetoothDevice; -import android.content.AttributionSource; -import android.os.Parcel; -import android.os.Parcelable; - -import java.util.Objects; - -/** - * ScanResult for Bluetooth LE scan. - */ -public final class ScanResult implements Parcelable, Attributable { - - /** - * For chained advertisements, inidcates tha the data contained in this - * scan result is complete. - */ - public static final int DATA_COMPLETE = 0x00; - - /** - * For chained advertisements, indicates that the controller was - * unable to receive all chained packets and the scan result contains - * incomplete truncated data. - */ - public static final int DATA_TRUNCATED = 0x02; - - /** - * Indicates that the secondary physical layer was not used. - */ - public static final int PHY_UNUSED = 0x00; - - /** - * Advertising Set ID is not present in the packet. - */ - public static final int SID_NOT_PRESENT = 0xFF; - - /** - * TX power is not present in the packet. - */ - public static final int TX_POWER_NOT_PRESENT = 0x7F; - - /** - * Periodic advertising interval is not present in the packet. - */ - public static final int PERIODIC_INTERVAL_NOT_PRESENT = 0x00; - - /** - * Mask for checking whether event type represents legacy advertisement. - */ - private static final int ET_LEGACY_MASK = 0x10; - - /** - * Mask for checking whether event type represents connectable advertisement. - */ - private static final int ET_CONNECTABLE_MASK = 0x01; - - // Remote Bluetooth device. - private BluetoothDevice mDevice; - - // Scan record, including advertising data and scan response data. - @Nullable - private ScanRecord mScanRecord; - - // Received signal strength. - private int mRssi; - - // Device timestamp when the result was last seen. - private long mTimestampNanos; - - private int mEventType; - private int mPrimaryPhy; - private int mSecondaryPhy; - private int mAdvertisingSid; - private int mTxPower; - private int mPeriodicAdvertisingInterval; - - /** - * Constructs a new ScanResult. - * - * @param device Remote Bluetooth device found. - * @param scanRecord Scan record including both advertising data and scan response data. - * @param rssi Received signal strength. - * @param timestampNanos Timestamp at which the scan result was observed. - * @deprecated use {@link #ScanResult(BluetoothDevice, int, int, int, int, int, int, int, - * ScanRecord, long)} - */ - @Deprecated - public ScanResult(BluetoothDevice device, ScanRecord scanRecord, int rssi, - long timestampNanos) { - mDevice = device; - mScanRecord = scanRecord; - mRssi = rssi; - mTimestampNanos = timestampNanos; - mEventType = (DATA_COMPLETE << 5) | ET_LEGACY_MASK | ET_CONNECTABLE_MASK; - mPrimaryPhy = BluetoothDevice.PHY_LE_1M; - mSecondaryPhy = PHY_UNUSED; - mAdvertisingSid = SID_NOT_PRESENT; - mTxPower = 127; - mPeriodicAdvertisingInterval = 0; - } - - /** - * Constructs a new ScanResult. - * - * @param device Remote Bluetooth device found. - * @param eventType Event type. - * @param primaryPhy Primary advertising phy. - * @param secondaryPhy Secondary advertising phy. - * @param advertisingSid Advertising set ID. - * @param txPower Transmit power. - * @param rssi Received signal strength. - * @param periodicAdvertisingInterval Periodic advertising interval. - * @param scanRecord Scan record including both advertising data and scan response data. - * @param timestampNanos Timestamp at which the scan result was observed. - */ - public ScanResult(BluetoothDevice device, int eventType, int primaryPhy, int secondaryPhy, - int advertisingSid, int txPower, int rssi, int periodicAdvertisingInterval, - ScanRecord scanRecord, long timestampNanos) { - mDevice = device; - mEventType = eventType; - mPrimaryPhy = primaryPhy; - mSecondaryPhy = secondaryPhy; - mAdvertisingSid = advertisingSid; - mTxPower = txPower; - mRssi = rssi; - mPeriodicAdvertisingInterval = periodicAdvertisingInterval; - mScanRecord = scanRecord; - mTimestampNanos = timestampNanos; - } - - private ScanResult(Parcel in) { - readFromParcel(in); - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - if (mDevice != null) { - dest.writeInt(1); - mDevice.writeToParcel(dest, flags); - } else { - dest.writeInt(0); - } - if (mScanRecord != null) { - dest.writeInt(1); - dest.writeByteArray(mScanRecord.getBytes()); - } else { - dest.writeInt(0); - } - dest.writeInt(mRssi); - dest.writeLong(mTimestampNanos); - dest.writeInt(mEventType); - dest.writeInt(mPrimaryPhy); - dest.writeInt(mSecondaryPhy); - dest.writeInt(mAdvertisingSid); - dest.writeInt(mTxPower); - dest.writeInt(mPeriodicAdvertisingInterval); - } - - private void readFromParcel(Parcel in) { - if (in.readInt() == 1) { - mDevice = BluetoothDevice.CREATOR.createFromParcel(in); - } - if (in.readInt() == 1) { - mScanRecord = ScanRecord.parseFromBytes(in.createByteArray()); - } - mRssi = in.readInt(); - mTimestampNanos = in.readLong(); - mEventType = in.readInt(); - mPrimaryPhy = in.readInt(); - mSecondaryPhy = in.readInt(); - mAdvertisingSid = in.readInt(); - mTxPower = in.readInt(); - mPeriodicAdvertisingInterval = in.readInt(); - } - - @Override - public int describeContents() { - return 0; - } - - /** {@hide} */ - public void setAttributionSource(@NonNull AttributionSource attributionSource) { - Attributable.setAttributionSource(mDevice, attributionSource); - } - - /** - * Returns the remote Bluetooth device identified by the Bluetooth device address. - */ - public BluetoothDevice getDevice() { - return mDevice; - } - - /** - * Returns the scan record, which is a combination of advertisement and scan response. - */ - @Nullable - public ScanRecord getScanRecord() { - return mScanRecord; - } - - /** - * Returns the received signal strength in dBm. The valid range is [-127, 126]. - */ - public int getRssi() { - return mRssi; - } - - /** - * Returns timestamp since boot when the scan record was observed. - */ - public long getTimestampNanos() { - return mTimestampNanos; - } - - /** - * Returns true if this object represents legacy scan result. - * Legacy scan results do not contain advanced advertising information - * as specified in the Bluetooth Core Specification v5. - */ - public boolean isLegacy() { - return (mEventType & ET_LEGACY_MASK) != 0; - } - - /** - * Returns true if this object represents connectable scan result. - */ - public boolean isConnectable() { - return (mEventType & ET_CONNECTABLE_MASK) != 0; - } - - /** - * Returns the data status. - * Can be one of {@link ScanResult#DATA_COMPLETE} or - * {@link ScanResult#DATA_TRUNCATED}. - */ - public int getDataStatus() { - // return bit 5 and 6 - return (mEventType >> 5) & 0x03; - } - - /** - * Returns the primary Physical Layer - * on which this advertisment was received. - * Can be one of {@link BluetoothDevice#PHY_LE_1M} or - * {@link BluetoothDevice#PHY_LE_CODED}. - */ - public int getPrimaryPhy() { - return mPrimaryPhy; - } - - /** - * Returns the secondary Physical Layer - * on which this advertisment was received. - * Can be one of {@link BluetoothDevice#PHY_LE_1M}, - * {@link BluetoothDevice#PHY_LE_2M}, {@link BluetoothDevice#PHY_LE_CODED} - * or {@link ScanResult#PHY_UNUSED} - if the advertisement - * was not received on a secondary physical channel. - */ - public int getSecondaryPhy() { - return mSecondaryPhy; - } - - /** - * Returns the advertising set id. - * May return {@link ScanResult#SID_NOT_PRESENT} if - * no set id was is present. - */ - public int getAdvertisingSid() { - return mAdvertisingSid; - } - - /** - * Returns the transmit power in dBm. - * Valid range is [-127, 126]. A value of {@link ScanResult#TX_POWER_NOT_PRESENT} - * indicates that the TX power is not present. - */ - public int getTxPower() { - return mTxPower; - } - - /** - * Returns the periodic advertising interval in units of 1.25ms. - * Valid range is 6 (7.5ms) to 65536 (81918.75ms). A value of - * {@link ScanResult#PERIODIC_INTERVAL_NOT_PRESENT} means periodic - * advertising interval is not present. - */ - public int getPeriodicAdvertisingInterval() { - return mPeriodicAdvertisingInterval; - } - - @Override - public int hashCode() { - return Objects.hash(mDevice, mRssi, mScanRecord, mTimestampNanos, - mEventType, mPrimaryPhy, mSecondaryPhy, - mAdvertisingSid, mTxPower, - mPeriodicAdvertisingInterval); - } - - @Override - public boolean equals(@Nullable Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - ScanResult other = (ScanResult) obj; - return Objects.equals(mDevice, other.mDevice) && (mRssi == other.mRssi) - && Objects.equals(mScanRecord, other.mScanRecord) - && (mTimestampNanos == other.mTimestampNanos) - && mEventType == other.mEventType - && mPrimaryPhy == other.mPrimaryPhy - && mSecondaryPhy == other.mSecondaryPhy - && mAdvertisingSid == other.mAdvertisingSid - && mTxPower == other.mTxPower - && mPeriodicAdvertisingInterval == other.mPeriodicAdvertisingInterval; - } - - @Override - public String toString() { - return "ScanResult{" + "device=" + mDevice + ", scanRecord=" - + Objects.toString(mScanRecord) + ", rssi=" + mRssi - + ", timestampNanos=" + mTimestampNanos + ", eventType=" + mEventType - + ", primaryPhy=" + mPrimaryPhy + ", secondaryPhy=" + mSecondaryPhy - + ", advertisingSid=" + mAdvertisingSid + ", txPower=" + mTxPower - + ", periodicAdvertisingInterval=" + mPeriodicAdvertisingInterval + '}'; - } - - public static final @android.annotation.NonNull Parcelable.Creator<ScanResult> CREATOR = new Creator<ScanResult>() { - @Override - public ScanResult createFromParcel(Parcel source) { - return new ScanResult(source); - } - - @Override - public ScanResult[] newArray(int size) { - return new ScanResult[size]; - } - }; - -} diff --git a/core/java/android/bluetooth/le/ScanSettings.java b/core/java/android/bluetooth/le/ScanSettings.java deleted file mode 100644 index 1aa7cb5111ce..000000000000 --- a/core/java/android/bluetooth/le/ScanSettings.java +++ /dev/null @@ -1,438 +0,0 @@ -/* - * Copyright (C) 2014 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.bluetooth.le; - -import android.annotation.SystemApi; -import android.bluetooth.BluetoothDevice; -import android.os.Parcel; -import android.os.Parcelable; - -/** - * Bluetooth LE scan settings are passed to {@link BluetoothLeScanner#startScan} to define the - * parameters for the scan. - */ -public final class ScanSettings implements Parcelable { - - /** - * A special Bluetooth LE scan mode. Applications using this scan mode will passively listen for - * other scan results without starting BLE scans themselves. - */ - public static final int SCAN_MODE_OPPORTUNISTIC = -1; - - /** - * Perform Bluetooth LE scan in low power mode. This is the default scan mode as it consumes the - * least power. This mode is enforced if the scanning application is not in foreground. - */ - public static final int SCAN_MODE_LOW_POWER = 0; - - /** - * Perform Bluetooth LE scan in balanced power mode. Scan results are returned at a rate that - * provides a good trade-off between scan frequency and power consumption. - */ - public static final int SCAN_MODE_BALANCED = 1; - - /** - * Scan using highest duty cycle. It's recommended to only use this mode when the application is - * running in the foreground. - */ - public static final int SCAN_MODE_LOW_LATENCY = 2; - - /** - * Perform Bluetooth LE scan in ambient discovery mode. This mode has lower duty cycle and more - * aggressive scan interval than balanced mode that provides a good trade-off between scan - * latency and power consumption. - * - * @hide - */ - @SystemApi - public static final int SCAN_MODE_AMBIENT_DISCOVERY = 3; - - /** - * Trigger a callback for every Bluetooth advertisement found that matches the filter criteria. - * If no filter is active, all advertisement packets are reported. - */ - public static final int CALLBACK_TYPE_ALL_MATCHES = 1; - - /** - * A result callback is only triggered for the first advertisement packet received that matches - * the filter criteria. - */ - public static final int CALLBACK_TYPE_FIRST_MATCH = 2; - - /** - * Receive a callback when advertisements are no longer received from a device that has been - * previously reported by a first match callback. - */ - public static final int CALLBACK_TYPE_MATCH_LOST = 4; - - - /** - * Determines how many advertisements to match per filter, as this is scarce hw resource - */ - /** - * Match one advertisement per filter - */ - public static final int MATCH_NUM_ONE_ADVERTISEMENT = 1; - - /** - * Match few advertisement per filter, depends on current capability and availibility of - * the resources in hw - */ - public static final int MATCH_NUM_FEW_ADVERTISEMENT = 2; - - /** - * Match as many advertisement per filter as hw could allow, depends on current - * capability and availibility of the resources in hw - */ - public static final int MATCH_NUM_MAX_ADVERTISEMENT = 3; - - - /** - * In Aggressive mode, hw will determine a match sooner even with feeble signal strength - * and few number of sightings/match in a duration. - */ - public static final int MATCH_MODE_AGGRESSIVE = 1; - - /** - * For sticky mode, higher threshold of signal strength and sightings is required - * before reporting by hw - */ - public static final int MATCH_MODE_STICKY = 2; - - /** - * Request full scan results which contain the device, rssi, advertising data, scan response - * as well as the scan timestamp. - * - * @hide - */ - @SystemApi - public static final int SCAN_RESULT_TYPE_FULL = 0; - - /** - * Request abbreviated scan results which contain the device, rssi and scan timestamp. - * <p> - * <b>Note:</b> It is possible for an application to get more scan results than it asked for, if - * there are multiple apps using this type. - * - * @hide - */ - @SystemApi - public static final int SCAN_RESULT_TYPE_ABBREVIATED = 1; - - /** - * Use all supported PHYs for scanning. - * This will check the controller capabilities, and start - * the scan on 1Mbit and LE Coded PHYs if supported, or on - * the 1Mbit PHY only. - */ - public static final int PHY_LE_ALL_SUPPORTED = 255; - - // Bluetooth LE scan mode. - private int mScanMode; - - // Bluetooth LE scan callback type - private int mCallbackType; - - // Bluetooth LE scan result type - private int mScanResultType; - - // Time of delay for reporting the scan result - private long mReportDelayMillis; - - private int mMatchMode; - - private int mNumOfMatchesPerFilter; - - // Include only legacy advertising results - private boolean mLegacy; - - private int mPhy; - - public int getScanMode() { - return mScanMode; - } - - public int getCallbackType() { - return mCallbackType; - } - - public int getScanResultType() { - return mScanResultType; - } - - /** - * @hide - */ - public int getMatchMode() { - return mMatchMode; - } - - /** - * @hide - */ - public int getNumOfMatches() { - return mNumOfMatchesPerFilter; - } - - /** - * Returns whether only legacy advertisements will be returned. - * Legacy advertisements include advertisements as specified - * by the Bluetooth core specification 4.2 and below. - */ - public boolean getLegacy() { - return mLegacy; - } - - /** - * Returns the physical layer used during a scan. - */ - public int getPhy() { - return mPhy; - } - - /** - * Returns report delay timestamp based on the device clock. - */ - public long getReportDelayMillis() { - return mReportDelayMillis; - } - - private ScanSettings(int scanMode, int callbackType, int scanResultType, - long reportDelayMillis, int matchMode, - int numOfMatchesPerFilter, boolean legacy, int phy) { - mScanMode = scanMode; - mCallbackType = callbackType; - mScanResultType = scanResultType; - mReportDelayMillis = reportDelayMillis; - mNumOfMatchesPerFilter = numOfMatchesPerFilter; - mMatchMode = matchMode; - mLegacy = legacy; - mPhy = phy; - } - - private ScanSettings(Parcel in) { - mScanMode = in.readInt(); - mCallbackType = in.readInt(); - mScanResultType = in.readInt(); - mReportDelayMillis = in.readLong(); - mMatchMode = in.readInt(); - mNumOfMatchesPerFilter = in.readInt(); - mLegacy = in.readInt() != 0; - mPhy = in.readInt(); - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mScanMode); - dest.writeInt(mCallbackType); - dest.writeInt(mScanResultType); - dest.writeLong(mReportDelayMillis); - dest.writeInt(mMatchMode); - dest.writeInt(mNumOfMatchesPerFilter); - dest.writeInt(mLegacy ? 1 : 0); - dest.writeInt(mPhy); - } - - @Override - public int describeContents() { - return 0; - } - - public static final @android.annotation.NonNull Parcelable.Creator<ScanSettings> CREATOR = - new Creator<ScanSettings>() { - @Override - public ScanSettings[] newArray(int size) { - return new ScanSettings[size]; - } - - @Override - public ScanSettings createFromParcel(Parcel in) { - return new ScanSettings(in); - } - }; - - /** - * Builder for {@link ScanSettings}. - */ - public static final class Builder { - private int mScanMode = SCAN_MODE_LOW_POWER; - private int mCallbackType = CALLBACK_TYPE_ALL_MATCHES; - private int mScanResultType = SCAN_RESULT_TYPE_FULL; - private long mReportDelayMillis = 0; - private int mMatchMode = MATCH_MODE_AGGRESSIVE; - private int mNumOfMatchesPerFilter = MATCH_NUM_MAX_ADVERTISEMENT; - private boolean mLegacy = true; - private int mPhy = PHY_LE_ALL_SUPPORTED; - - /** - * Set scan mode for Bluetooth LE scan. - * - * @param scanMode The scan mode can be one of {@link ScanSettings#SCAN_MODE_LOW_POWER}, - * {@link ScanSettings#SCAN_MODE_BALANCED} or {@link ScanSettings#SCAN_MODE_LOW_LATENCY}. - * @throws IllegalArgumentException If the {@code scanMode} is invalid. - */ - public Builder setScanMode(int scanMode) { - switch (scanMode) { - case SCAN_MODE_OPPORTUNISTIC: - case SCAN_MODE_LOW_POWER: - case SCAN_MODE_BALANCED: - case SCAN_MODE_LOW_LATENCY: - case SCAN_MODE_AMBIENT_DISCOVERY: - mScanMode = scanMode; - break; - default: - throw new IllegalArgumentException("invalid scan mode " + scanMode); - } - return this; - } - - /** - * Set callback type for Bluetooth LE scan. - * - * @param callbackType The callback type flags for the scan. - * @throws IllegalArgumentException If the {@code callbackType} is invalid. - */ - public Builder setCallbackType(int callbackType) { - - if (!isValidCallbackType(callbackType)) { - throw new IllegalArgumentException("invalid callback type - " + callbackType); - } - mCallbackType = callbackType; - return this; - } - - // Returns true if the callbackType is valid. - private boolean isValidCallbackType(int callbackType) { - if (callbackType == CALLBACK_TYPE_ALL_MATCHES - || callbackType == CALLBACK_TYPE_FIRST_MATCH - || callbackType == CALLBACK_TYPE_MATCH_LOST) { - return true; - } - return callbackType == (CALLBACK_TYPE_FIRST_MATCH | CALLBACK_TYPE_MATCH_LOST); - } - - /** - * Set scan result type for Bluetooth LE scan. - * - * @param scanResultType Type for scan result, could be either {@link - * ScanSettings#SCAN_RESULT_TYPE_FULL} or {@link ScanSettings#SCAN_RESULT_TYPE_ABBREVIATED}. - * @throws IllegalArgumentException If the {@code scanResultType} is invalid. - * @hide - */ - @SystemApi - public Builder setScanResultType(int scanResultType) { - if (scanResultType < SCAN_RESULT_TYPE_FULL - || scanResultType > SCAN_RESULT_TYPE_ABBREVIATED) { - throw new IllegalArgumentException( - "invalid scanResultType - " + scanResultType); - } - mScanResultType = scanResultType; - return this; - } - - /** - * Set report delay timestamp for Bluetooth LE scan. If set to 0, you will be notified of - * scan results immediately. If > 0, scan results are queued up and delivered after the - * requested delay or 5000 milliseconds (whichever is higher). Note scan results may be - * delivered sooner if the internal buffers fill up. - * - * @param reportDelayMillis how frequently scan results should be delivered in - * milliseconds - * @throws IllegalArgumentException if {@code reportDelayMillis} < 0 - */ - public Builder setReportDelay(long reportDelayMillis) { - if (reportDelayMillis < 0) { - throw new IllegalArgumentException("reportDelay must be > 0"); - } - mReportDelayMillis = reportDelayMillis; - return this; - } - - /** - * Set the number of matches for Bluetooth LE scan filters hardware match - * - * @param numOfMatches The num of matches can be one of - * {@link ScanSettings#MATCH_NUM_ONE_ADVERTISEMENT} - * or {@link ScanSettings#MATCH_NUM_FEW_ADVERTISEMENT} or {@link - * ScanSettings#MATCH_NUM_MAX_ADVERTISEMENT} - * @throws IllegalArgumentException If the {@code matchMode} is invalid. - */ - public Builder setNumOfMatches(int numOfMatches) { - if (numOfMatches < MATCH_NUM_ONE_ADVERTISEMENT - || numOfMatches > MATCH_NUM_MAX_ADVERTISEMENT) { - throw new IllegalArgumentException("invalid numOfMatches " + numOfMatches); - } - mNumOfMatchesPerFilter = numOfMatches; - return this; - } - - /** - * Set match mode for Bluetooth LE scan filters hardware match - * - * @param matchMode The match mode can be one of {@link ScanSettings#MATCH_MODE_AGGRESSIVE} - * or {@link ScanSettings#MATCH_MODE_STICKY} - * @throws IllegalArgumentException If the {@code matchMode} is invalid. - */ - public Builder setMatchMode(int matchMode) { - if (matchMode < MATCH_MODE_AGGRESSIVE - || matchMode > MATCH_MODE_STICKY) { - throw new IllegalArgumentException("invalid matchMode " + matchMode); - } - mMatchMode = matchMode; - return this; - } - - /** - * Set whether only legacy advertisments should be returned in scan results. - * Legacy advertisements include advertisements as specified by the - * Bluetooth core specification 4.2 and below. This is true by default - * for compatibility with older apps. - * - * @param legacy true if only legacy advertisements will be returned - */ - public Builder setLegacy(boolean legacy) { - mLegacy = legacy; - return this; - } - - /** - * Set the Physical Layer to use during this scan. - * This is used only if {@link ScanSettings.Builder#setLegacy} - * is set to false. - * {@link android.bluetooth.BluetoothAdapter#isLeCodedPhySupported} - * may be used to check whether LE Coded phy is supported by calling - * {@link android.bluetooth.BluetoothAdapter#isLeCodedPhySupported}. - * Selecting an unsupported phy will result in failure to start scan. - * - * @param phy Can be one of {@link BluetoothDevice#PHY_LE_1M}, {@link - * BluetoothDevice#PHY_LE_CODED} or {@link ScanSettings#PHY_LE_ALL_SUPPORTED} - */ - public Builder setPhy(int phy) { - mPhy = phy; - return this; - } - - /** - * Build {@link ScanSettings}. - */ - public ScanSettings build() { - return new ScanSettings(mScanMode, mCallbackType, mScanResultType, - mReportDelayMillis, mMatchMode, - mNumOfMatchesPerFilter, mLegacy, mPhy); - } - } -} diff --git a/core/java/android/bluetooth/le/TransportBlock.java b/core/java/android/bluetooth/le/TransportBlock.java deleted file mode 100644 index 18bad9c3c259..000000000000 --- a/core/java/android/bluetooth/le/TransportBlock.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright (C) 2014 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.bluetooth.le; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.os.Parcel; -import android.os.Parcelable; -import android.util.Log; - -import java.nio.BufferOverflowException; -import java.nio.ByteBuffer; -import java.util.Arrays; - -/** - * Wrapper for Transport Discovery Data Transport Blocks. - * This class represents a Transport Block from a Transport Discovery Data. - * - * @see TransportDiscoveryData - * @see AdvertiseData - */ -public final class TransportBlock implements Parcelable { - private static final String TAG = "TransportBlock"; - private final int mOrgId; - private final int mTdsFlags; - private final int mTransportDataLength; - private final byte[] mTransportData; - - /** - * Creates an instance of TransportBlock from raw data. - * - * @param orgId the Organization ID - * @param tdsFlags the TDS flags - * @param transportDataLength the total length of the Transport Data - * @param transportData the Transport Data - */ - public TransportBlock(int orgId, int tdsFlags, int transportDataLength, - @Nullable byte[] transportData) { - mOrgId = orgId; - mTdsFlags = tdsFlags; - mTransportDataLength = transportDataLength; - mTransportData = transportData; - } - - private TransportBlock(Parcel in) { - mOrgId = in.readInt(); - mTdsFlags = in.readInt(); - mTransportDataLength = in.readInt(); - if (mTransportDataLength > 0) { - mTransportData = new byte[mTransportDataLength]; - in.readByteArray(mTransportData); - } else { - mTransportData = null; - } - } - - @Override - public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeInt(mOrgId); - dest.writeInt(mTdsFlags); - dest.writeInt(mTransportDataLength); - if (mTransportData != null) { - dest.writeByteArray(mTransportData); - } - } - - /** - * @hide - */ - @Override - public int describeContents() { - return 0; - } - - /** - * @hide - */ - @Override - public boolean equals(@Nullable Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - TransportBlock other = (TransportBlock) obj; - return Arrays.equals(toByteArray(), other.toByteArray()); - } - - public static final @NonNull Creator<TransportBlock> CREATOR = new Creator<TransportBlock>() { - @Override - public TransportBlock createFromParcel(Parcel in) { - return new TransportBlock(in); - } - - @Override - public TransportBlock[] newArray(int size) { - return new TransportBlock[size]; - } - }; - - /** - * Gets the Organization ID of the Transport Block which corresponds to one of the - * the Bluetooth SIG Assigned Numbers. - */ - public int getOrgId() { - return mOrgId; - } - - /** - * Gets the TDS flags of the Transport Block which represents the role of the device and - * information about its state and supported features. - */ - public int getTdsFlags() { - return mTdsFlags; - } - - /** - * Gets the total number of octets in the Transport Data field in this Transport Block. - */ - public int getTransportDataLength() { - return mTransportDataLength; - } - - /** - * Gets the Transport Data of the Transport Block which contains organization-specific data. - */ - @Nullable - public byte[] getTransportData() { - return mTransportData; - } - - /** - * Converts this TransportBlock to byte array - * - * @return byte array representation of this Transport Block or null if the conversion failed - */ - @Nullable - public byte[] toByteArray() { - try { - ByteBuffer buffer = ByteBuffer.allocate(totalBytes()); - buffer.put((byte) mOrgId); - buffer.put((byte) mTdsFlags); - buffer.put((byte) mTransportDataLength); - if (mTransportData != null) { - buffer.put(mTransportData); - } - return buffer.array(); - } catch (BufferOverflowException e) { - Log.e(TAG, "Error converting to byte array: " + e.toString()); - return null; - } - } - - /** - * @return total byte count of this TransportBlock - */ - public int totalBytes() { - // 3 uint8 + byte[] length - int size = 3 + mTransportDataLength; - return size; - } -} diff --git a/core/java/android/bluetooth/le/TransportDiscoveryData.java b/core/java/android/bluetooth/le/TransportDiscoveryData.java deleted file mode 100644 index 2b52f19798ad..000000000000 --- a/core/java/android/bluetooth/le/TransportDiscoveryData.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright (C) 2014 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.bluetooth.le; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.os.Parcel; -import android.os.Parcelable; -import android.util.Log; - -import java.nio.BufferOverflowException; -import java.nio.BufferUnderflowException; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -/** - * Wrapper for Transport Discovery Data AD Type. - * This class contains the Transport Discovery Data AD Type Code as well as - * a list of potential Transport Blocks. - * - * @see AdvertiseData - */ -public final class TransportDiscoveryData implements Parcelable { - private static final String TAG = "TransportDiscoveryData"; - private final int mTransportDataType; - private final List<TransportBlock> mTransportBlocks; - - /** - * Creates a TransportDiscoveryData instance. - * - * @param transportDataType the Transport Discovery Data AD Type - * @param transportBlocks the list of Transport Blocks - */ - public TransportDiscoveryData(int transportDataType, - @NonNull List<TransportBlock> transportBlocks) { - mTransportDataType = transportDataType; - mTransportBlocks = transportBlocks; - } - - /** - * Creates a TransportDiscoveryData instance from byte arrays. - * - * Uses the transport discovery data bytes and parses them into an usable class. - * - * @param transportDiscoveryData the raw discovery data - */ - public TransportDiscoveryData(@NonNull byte[] transportDiscoveryData) { - ByteBuffer byteBuffer = ByteBuffer.wrap(transportDiscoveryData); - mTransportBlocks = new ArrayList(); - if (byteBuffer.remaining() > 0) { - mTransportDataType = byteBuffer.get(); - } else { - mTransportDataType = -1; - } - try { - while (byteBuffer.remaining() > 0) { - int orgId = byteBuffer.get(); - int tdsFlags = byteBuffer.get(); - int transportDataLength = byteBuffer.get(); - byte[] transportData = new byte[transportDataLength]; - byteBuffer.get(transportData, 0, transportDataLength); - mTransportBlocks.add(new TransportBlock(orgId, tdsFlags, - transportDataLength, transportData)); - } - } catch (BufferUnderflowException e) { - Log.e(TAG, "Error while parsing data: " + e.toString()); - } - } - - private TransportDiscoveryData(Parcel in) { - mTransportDataType = in.readInt(); - mTransportBlocks = in.createTypedArrayList(TransportBlock.CREATOR); - } - - /** - * @hide - */ - @Override - public int describeContents() { - return 0; - } - - /** - * @hide - */ - @Override - public boolean equals(@Nullable Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - TransportDiscoveryData other = (TransportDiscoveryData) obj; - return Arrays.equals(toByteArray(), other.toByteArray()); - } - - @Override - public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeInt(mTransportDataType); - dest.writeTypedList(mTransportBlocks); - } - - public static final @NonNull Creator<TransportDiscoveryData> CREATOR = - new Creator<TransportDiscoveryData>() { - @Override - public TransportDiscoveryData createFromParcel(Parcel in) { - return new TransportDiscoveryData(in); - } - - @Override - public TransportDiscoveryData[] newArray(int size) { - return new TransportDiscoveryData[size]; - } - }; - - /** - * Gets the transport data type. - */ - public int getTransportDataType() { - return mTransportDataType; - } - - /** - * @return the list of {@link TransportBlock} in this TransportDiscoveryData - * or an empty list if there are no Transport Blocks - */ - @NonNull - public List<TransportBlock> getTransportBlocks() { - if (mTransportBlocks == null) { - return Collections.emptyList(); - } - return mTransportBlocks; - } - - /** - * Converts this TransportDiscoveryData to byte array - * - * @return byte array representation of this Transport Discovery Data or null if the - * conversion failed - */ - @Nullable - public byte[] toByteArray() { - try { - ByteBuffer buffer = ByteBuffer.allocate(totalBytes()); - buffer.put((byte) mTransportDataType); - for (TransportBlock transportBlock : getTransportBlocks()) { - buffer.put(transportBlock.toByteArray()); - } - return buffer.array(); - } catch (BufferOverflowException e) { - Log.e(TAG, "Error converting to byte array: " + e.toString()); - return null; - } - } - - /** - * @return total byte count of this TransportDataDiscovery - */ - public int totalBytes() { - int size = 1; // Counting Transport Data Type here. - for (TransportBlock transportBlock : getTransportBlocks()) { - size += transportBlock.totalBytes(); - } - return size; - } -} diff --git a/core/java/android/bluetooth/le/TruncatedFilter.java b/core/java/android/bluetooth/le/TruncatedFilter.java deleted file mode 100644 index 25925888a0d2..000000000000 --- a/core/java/android/bluetooth/le/TruncatedFilter.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2014 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.bluetooth.le; - -import android.annotation.SuppressLint; -import android.annotation.SystemApi; - -import java.util.List; - -/** - * A special scan filter that lets the client decide how the scan record should be stored. - * - * @deprecated this is not used anywhere - * - * @hide - */ -@Deprecated -@SystemApi -@SuppressLint("AndroidFrameworkBluetoothPermission") -public final class TruncatedFilter { - private final ScanFilter mFilter; - private final List<ResultStorageDescriptor> mStorageDescriptors; - - /** - * Constructor for {@link TruncatedFilter}. - * - * @param filter Scan filter of the truncated filter. - * @param storageDescriptors Describes how the scan should be stored. - */ - public TruncatedFilter(ScanFilter filter, List<ResultStorageDescriptor> storageDescriptors) { - mFilter = filter; - mStorageDescriptors = storageDescriptors; - } - - /** - * Returns the scan filter. - */ - public ScanFilter getFilter() { - return mFilter; - } - - /** - * Returns a list of descriptor for scan result storage. - */ - public List<ResultStorageDescriptor> getStorageDescriptors() { - return mStorageDescriptors; - } - - -} diff --git a/core/java/android/bluetooth/package.html b/core/java/android/bluetooth/package.html deleted file mode 100644 index d9ca4f13101f..000000000000 --- a/core/java/android/bluetooth/package.html +++ /dev/null @@ -1,38 +0,0 @@ -<HTML> -<BODY> -<p>Provides classes that manage Bluetooth functionality, such as scanning for -devices, connecting with devices, and managing data transfer between devices. -The Bluetooth API supports both "Classic Bluetooth" and Bluetooth Low Energy.</p> - -<p>For more information about Classic Bluetooth, see the -<a href="{@docRoot}guide/topics/connectivity/bluetooth.html">Bluetooth</a> guide. -For more information about Bluetooth Low Energy, see the -<a href="{@docRoot}guide/topics/connectivity/bluetooth-le.html"> -Bluetooth Low Energy</a> (BLE) guide.</p> -{@more} - -<p>The Bluetooth APIs let applications:</p> -<ul> - <li>Scan for other Bluetooth devices (including BLE devices).</li> - <li>Query the local Bluetooth adapter for paired Bluetooth devices.</li> - <li>Establish RFCOMM channels/sockets.</li> - <li>Connect to specified sockets on other devices.</li> - <li>Transfer data to and from other devices.</li> - <li>Communicate with BLE devices, such as proximity sensors, heart rate - monitors, fitness devices, and so on.</li> - <li>Act as a GATT client or a GATT server (BLE).</li> -</ul> - -<p> -To perform Bluetooth communication using these APIs, an application must -declare the {@link android.Manifest.permission#BLUETOOTH} permission. Some -additional functionality, such as requesting device discovery, -also requires the {@link android.Manifest.permission#BLUETOOTH_ADMIN} -permission. -</p> - -<p class="note"><strong>Note:</strong> -Not all Android-powered devices provide Bluetooth functionality.</p> - -</BODY> -</HTML> 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/CompanionDeviceService.java b/core/java/android/companion/CompanionDeviceService.java index 12ced96a0ffb..610b7ee5befa 100644 --- a/core/java/android/companion/CompanionDeviceService.java +++ b/core/java/android/companion/CompanionDeviceService.java @@ -31,29 +31,71 @@ import android.util.Log; import java.util.Objects; /** - * Service to be implemented by apps that manage a companion device. + * A service that receives calls from the system when the associated companion device appears + * nearby or is connected, as well as when the device is no longer "present" or connected. + * See {@link #onDeviceAppeared(AssociationInfo)}/{@link #onDeviceDisappeared(AssociationInfo)}. * - * System will keep this service bound whenever an associated device is nearby for Bluetooth - * devices or companion app manages the connectivity and reports disappeared, ensuring app stays - * alive + * <p> + * Additionally, the service will receive a call from the system, if and when the system needs to + * transfer data to the companion device. + * See {@link #dispatchMessage(int, int, byte[])}). * - * An app must be {@link CompanionDeviceManager#associate associated} with at leas one device, - * before it can take advantage of this service. + * <p> + * Companion applications must create a service that {@code extends} + * {@link CompanionDeviceService}, and declare it in their AndroidManifest.xml with the + * "android.permission.BIND_COMPANION_DEVICE_SERVICE" permission + * (see {@link android.Manifest.permission#BIND_COMPANION_DEVICE_SERVICE}), + * as well as add an intent filter for the "android.companion.CompanionDeviceService" action + * (see {@link #SERVICE_INTERFACE}). * - * You must declare this service in your manifest with an - * intent-filter action of {@link #SERVICE_INTERFACE} and - * permission of {@link android.Manifest.permission#BIND_COMPANION_DEVICE_SERVICE} + * <p> + * Following is an example of such declaration: + * <pre>{@code + * <service + * android:name=".CompanionService" + * android:label="@string/service_name" + * android:exported="true" + * android:permission="android.permission.BIND_COMPANION_DEVICE_SERVICE"> + * <intent-filter> + * <action android:name="android.companion.CompanionDeviceService" /> + * </intent-filter> + * </service> + * }</pre> * - * <p>If you want to declare more than one of these services, you must declare the meta-data in the - * service of your manifest with the corresponding name and value to true to indicate the - * primary service. - * Only the primary one will get the callback from - * {@link #onDeviceAppeared(AssociationInfo associationInfo)}.</p> + * <p> + * If the companion application has requested observing device presence (see + * {@link CompanionDeviceManager#startObservingDevicePresence(String)}) the system will + * <a href="https://developer.android.com/guide/components/bound-services"> bind the service</a> + * when it detects the device nearby (for BLE devices) or when the device is connected + * (for Bluetooth devices). * - * Example: + * <p> + * The system binding {@link CompanionDeviceService} elevates the priority of the process that + * the service is running in, and thus may prevent + * <a href="https://developer.android.com/topic/performance/memory-management#low-memory_killer"> + * the Low-memory killer</a> from killing the process at expense of other processes with lower + * priority. + * + * <p> + * It is possible for an application to declare multiple {@link CompanionDeviceService}-s. + * In such case, the system will bind all declared services, but will deliver + * {@link #onDeviceAppeared(AssociationInfo)}, {@link #onDeviceDisappeared(AssociationInfo)} and + * {@link #dispatchMessage(int, int, byte[])} only to one "primary" services. + * Applications that declare multiple {@link CompanionDeviceService}-s should indicate the "primary" + * service using "android.companion.primary" tag. + * <pre>{@code * <meta-data - * android:name="primary" - * android:value="true" /> + * android:name="android.companion.primary" + * android:value="true" /> + * }</pre> + * + * <p> + * If the application declares multiple {@link CompanionDeviceService}-s, but does not indicate + * the "primary" one, the system will pick one of the declared services to use as "primary". + * + * <p> + * If the application declares multiple "primary" {@link CompanionDeviceService}-s, the system + * will pick single one of them to use as "primary". */ public abstract class CompanionDeviceService extends Service { 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/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java index 8ab668873f33..c72346837e29 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; @@ -43,10 +42,6 @@ import android.os.RemoteException; import android.os.ResultReceiver; 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 +56,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,12 +80,12 @@ 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); @@ -179,7 +157,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 +167,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 +180,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 +190,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 +229,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 +255,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 +281,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 +308,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() { 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 2df6f9ae2bd6..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; @@ -3601,10 +3602,18 @@ public abstract class Context { * Binds to a service in the given {@code user} in the same manner as * {@link #bindService(Intent, ServiceConnection, int)}. * - * <p>If the given {@code user} is in the same profile group and the target package is the - * same as the caller, {@code android.Manifest.permission.INTERACT_ACROSS_PROFILES} is - * sufficient. Otherwise, requires {@code android.Manifest.permission.INTERACT_ACROSS_USERS} - * for interacting with other users. + * <p>Requires that one of the following conditions are met: + * <ul> + * <li>caller has {@code android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}</li> + * <li>caller has {@code android.Manifest.permission.INTERACT_ACROSS_USERS} and is the same + * package as the {@code service} (determined by its component's package) and the Android + * version is at least {@link android.os.Build.VERSION_CODES#S}</li> + * <li>caller has {@code android.Manifest.permission.INTERACT_ACROSS_USERS} and is in same + * profile group as the given {@code user}</li> + * <li>caller has {@code android.Manifest.permission.INTERACT_ACROSS_PROFILES} and is in same + * profile group as the given {@code user} and is the same package as the {@code service} + * </li> + * </ul> * * @param service Identifies the service to connect to. The Intent must * specify an explicit component name. @@ -3626,8 +3635,9 @@ public abstract class Context { @SuppressWarnings("unused") @RequiresPermission(anyOf = { android.Manifest.permission.INTERACT_ACROSS_USERS, + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, android.Manifest.permission.INTERACT_ACROSS_PROFILES - }) + }, conditional = true) public boolean bindServiceAsUser( @NonNull @RequiresPermission Intent service, @NonNull ServiceConnection conn, int flags, @NonNull UserHandle user) { @@ -3640,7 +3650,11 @@ public abstract class Context { * * @hide */ - @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) + @RequiresPermission(anyOf = { + android.Manifest.permission.INTERACT_ACROSS_USERS, + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, + android.Manifest.permission.INTERACT_ACROSS_PROFILES + }, conditional = true) @UnsupportedAppUsage(trackingBug = 136728678) public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags, Handler handler, UserHandle user) { @@ -3816,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, @@ -5343,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 @@ -5882,17 +5896,6 @@ public abstract class Context { /** * Use with {@link #getSystemService(String)} to retrieve a - * {@link android.app.communal.CommunalManager} for interacting with the global system state. - * - * @see #getSystemService(String) - * @see android.app.communal.CommunalManager - * @hide - */ - @SystemApi - public static final String COMMUNAL_SERVICE = "communal"; - - /** - * Use with {@link #getSystemService(String)} to retrieve a * {@link android.app.LocaleManager}. * * @see #getSystemService(String) @@ -5929,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. * @@ -6415,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)); } /** @@ -6443,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/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 76b4e5c4dba0..9e9dd1edd577 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -1543,6 +1543,11 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { @Nullable private ArrayMap<String, String> mAppClassNamesByProcess; + /** + * Resource file providing the application's locales configuration. + */ + private int localeConfigRes; + public void dump(Printer pw, String prefix) { dump(pw, prefix, DUMP_FLAG_ALL); } @@ -1660,6 +1665,10 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { pw.println(prefix + "requestRawExternalStorageAccess=" + requestRawExternalStorageAccess); } + if (localeConfigRes != 0) { + pw.println(prefix + "localeConfigRes=0x" + + Integer.toHexString(localeConfigRes)); + } } pw.println(prefix + "createTimestamp=" + createTimestamp); super.dumpBack(pw, prefix); @@ -1891,6 +1900,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { memtagMode = orig.memtagMode; nativeHeapZeroInitialized = orig.nativeHeapZeroInitialized; requestRawExternalStorageAccess = orig.requestRawExternalStorageAccess; + localeConfigRes = orig.localeConfigRes; createTimestamp = System.currentTimeMillis(); } @@ -1993,6 +2003,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { dest.writeString(mAppClassNamesByProcess.valueAt(i)); } } + dest.writeInt(localeConfigRes); } public static final @android.annotation.NonNull Parcelable.Creator<ApplicationInfo> CREATOR @@ -2088,6 +2099,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { mAppClassNamesByProcess.put(source.readString(), source.readString()); } } + localeConfigRes = source.readInt(); } /** @@ -2631,4 +2643,16 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { } return className; } + + /** @hide */ public void setLocaleConfigRes(int value) { localeConfigRes = value; } + + /** + * Return the resource id pointing to the resource file that provides the application's locales + * configuration. + * + * @hide + */ + public int getLocaleConfigRes() { + return localeConfigRes; + } } 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 d817f1e3baf4..1021d3e8e3fb 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -50,6 +50,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.IntentSender; import android.content.pm.dex.ArtManager; +import android.content.pm.pkg.FrameworkPackageUserState; import android.content.pm.verify.domain.DomainVerificationManager; import android.content.res.Configuration; import android.content.res.Resources; @@ -88,6 +89,7 @@ import com.android.internal.util.DataClass; import dalvik.system.VMRuntime; +import java.io.File; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.security.cert.Certificate; @@ -4075,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; @@ -7891,8 +7901,7 @@ public abstract class PackageManager { @Deprecated @Nullable public PackageInfo getPackageArchiveInfo(@NonNull String archiveFilePath, int flags) { - throw new UnsupportedOperationException( - "getPackageArchiveInfo() not implemented in subclass"); + return getPackageArchiveInfo(archiveFilePath, PackageInfoFlags.of(flags)); } /** @@ -7901,8 +7910,29 @@ public abstract class PackageManager { @Nullable public PackageInfo getPackageArchiveInfo(@NonNull String archiveFilePath, @NonNull PackageInfoFlags flags) { - throw new UnsupportedOperationException( - "getPackageArchiveInfo() not implemented in subclass"); + long flagsBits = flags.getValue(); + final PackageParser parser = new PackageParser(); + parser.setCallback(new PackageParser.CallbackImpl(this)); + final File apkFile = new File(archiveFilePath); + try { + if ((flagsBits & (MATCH_DIRECT_BOOT_UNAWARE | MATCH_DIRECT_BOOT_AWARE)) != 0) { + // Caller expressed an explicit opinion about what encryption + // aware/unaware components they want to see, so fall through and + // give them what they want + } else { + // Caller expressed no opinion, so match everything + flagsBits |= MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE; + } + + PackageParser.Package pkg = parser.parsePackage(apkFile, 0, false); + if ((flagsBits & GET_SIGNATURES) != 0) { + PackageParser.collectCertificates(pkg, false /* skipVerify */); + } + return PackageParser.generatePackageInfo(pkg, null, (int) flagsBits, 0, 0, null, + FrameworkPackageUserState.DEFAULT); + } catch (PackageParser.PackageParserException e) { + return null; + } } /** diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index e2c91a4b1bea..98cc8f6b0670 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007 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. @@ -37,6 +37,8 @@ import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTEN import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION; +import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS; +import static android.content.pm.PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS; import static android.os.Build.VERSION_CODES.O; import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED; @@ -55,11 +57,9 @@ import android.content.ComponentName; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.overlay.OverlayPaths; -import android.content.pm.parsing.ParsingPackageUtils; -import android.content.pm.pkg.FrameworkPackageUserState; -import android.content.pm.pkg.PackageUserStateUtils; import android.content.pm.parsing.result.ParseResult; import android.content.pm.parsing.result.ParseTypeImpl; +import android.content.pm.pkg.FrameworkPackageUserState; import android.content.res.ApkAssets; import android.content.res.AssetManager; import android.content.res.Configuration; @@ -68,6 +68,7 @@ import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.os.Build; import android.os.Bundle; +import android.os.Debug; import android.os.FileUtils; import android.os.Parcel; import android.os.Parcelable; @@ -83,6 +84,7 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.AttributeSet; import android.util.Base64; +import android.util.DebugUtils; import android.util.DisplayMetrics; import android.util.IntArray; import android.util.Log; @@ -128,6 +130,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.UUID; @@ -148,7 +151,7 @@ import java.util.UUID; * </ul> * * @deprecated This class is mostly unused and no new changes should be added to it. Use - * {@link android.content.pm.parsing.ParsingPackageUtils} and related parsing v2 infrastructure in + * ParsingPackageUtils and related parsing v2 infrastructure in * the core/services parsing subpackages. Or for a quick parse of a provided APK, use * {@link PackageManager#getPackageArchiveInfo(String, int)}. * @@ -655,7 +658,7 @@ public class PackageParser { // If available for the target user, or trying to match uninstalled packages and it's // a system app. - return PackageUserStateUtils.isAvailable(state, flags) + return isAvailable(state, flags) || (appInfo != null && appInfo.isSystemApp() && ((flags & PackageManager.MATCH_KNOWN_PACKAGES) != 0 || (flags & PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS) != 0)); @@ -765,7 +768,7 @@ public class PackageParser { final ActivityInfo[] res = new ActivityInfo[N]; for (int i = 0; i < N; i++) { final Activity a = p.activities.get(i); - if (PackageUserStateUtils.isMatch(state, a.info, flags)) { + if (isMatch(state, a.info, flags)) { if (PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME.equals(a.className)) { continue; } @@ -782,7 +785,7 @@ public class PackageParser { final ActivityInfo[] res = new ActivityInfo[N]; for (int i = 0; i < N; i++) { final Activity a = p.receivers.get(i); - if (PackageUserStateUtils.isMatch(state, a.info, flags)) { + if (isMatch(state, a.info, flags)) { res[num++] = generateActivityInfo(a, flags, state, userId); } } @@ -796,7 +799,7 @@ public class PackageParser { final ServiceInfo[] res = new ServiceInfo[N]; for (int i = 0; i < N; i++) { final Service s = p.services.get(i); - if (PackageUserStateUtils.isMatch(state, s.info, flags)) { + if (isMatch(state, s.info, flags)) { res[num++] = generateServiceInfo(s, flags, state, userId); } } @@ -810,7 +813,7 @@ public class PackageParser { final ProviderInfo[] res = new ProviderInfo[N]; for (int i = 0; i < N; i++) { final Provider pr = p.providers.get(i); - if (PackageUserStateUtils.isMatch(state, pr.info, flags)) { + if (isMatch(state, pr.info, flags)) { res[num++] = generateProviderInfo(pr, flags, state, userId); } } @@ -7297,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(); } @@ -7305,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); @@ -7327,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; } @@ -7364,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; } @@ -7385,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(); @@ -7397,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; } @@ -7428,7 +7431,7 @@ public class PackageParser { mCompileSdkVersionCodename = dest.readString(); mUpgradeKeySets = (ArraySet<String>) dest.readArraySet(boot); - mKeySetMapping = ParsingPackageUtils.readKeySetMapping(dest); + mKeySetMapping = readKeySetMapping(dest); cpuAbiOverride = dest.readString(); use32bitAbi = (dest.readInt() == 1); @@ -7554,7 +7557,7 @@ public class PackageParser { dest.writeInt(mCompileSdkVersion); dest.writeString(mCompileSdkVersionCodename); dest.writeArraySet(mUpgradeKeySets); - ParsingPackageUtils.writeKeySetMapping(dest, mKeySetMapping); + writeKeySetMapping(dest, mKeySetMapping); dest.writeString(cpuAbiOverride); dest.writeInt(use32bitAbi ? 1 : 0); dest.writeByteArray(restrictUpdateHash); @@ -7806,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>() { @@ -7867,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>() { @@ -7977,7 +7980,7 @@ public class PackageParser { if (ai.category == ApplicationInfo.CATEGORY_UNDEFINED) { ai.category = FallbackCategoryProvider.getFallbackCategory(ai.packageName); } - ai.seInfoUser = SELinuxUtil.getSeinfoUser(state); + ai.seInfoUser = getSeinfoUser(state); final OverlayPaths overlayPaths = state.getAllOverlayPaths(); if (overlayPaths != null) { ai.resourceDirs = overlayPaths.getResourceDirs().toArray(new String[0]); @@ -8160,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(); @@ -8254,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; @@ -8344,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) { @@ -8436,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(); @@ -9074,4 +9077,194 @@ public class PackageParser { return mCachedSplitApks[0][0]; } } + + + + public static boolean isMatch(@NonNull FrameworkPackageUserState state, + ComponentInfo componentInfo, long flags) { + return isMatch(state, componentInfo.applicationInfo.isSystemApp(), + componentInfo.applicationInfo.enabled, componentInfo.enabled, + componentInfo.directBootAware, componentInfo.name, flags); + } + + public static boolean isMatch(@NonNull FrameworkPackageUserState state, boolean isSystem, + boolean isPackageEnabled, ComponentInfo component, long flags) { + return isMatch(state, isSystem, isPackageEnabled, component.isEnabled(), + component.directBootAware, component.name, flags); + } + + /** + * Test if the given component is considered installed, enabled and a match for the given + * flags. + * + * <p> + * Expects at least one of {@link PackageManager#MATCH_DIRECT_BOOT_AWARE} and {@link + * PackageManager#MATCH_DIRECT_BOOT_UNAWARE} are specified in {@code flags}. + * </p> + */ + public static boolean isMatch(@NonNull FrameworkPackageUserState state, boolean isSystem, + boolean isPackageEnabled, boolean isComponentEnabled, + boolean isComponentDirectBootAware, String componentName, long flags) { + final boolean matchUninstalled = (flags & PackageManager.MATCH_KNOWN_PACKAGES) != 0; + if (!isAvailable(state, flags) && !(isSystem && matchUninstalled)) { + return reportIfDebug(false, flags); + } + + if (!isEnabled(state, isPackageEnabled, isComponentEnabled, componentName, flags)) { + return reportIfDebug(false, flags); + } + + if ((flags & PackageManager.MATCH_SYSTEM_ONLY) != 0) { + if (!isSystem) { + return reportIfDebug(false, flags); + } + } + + final boolean matchesUnaware = ((flags & PackageManager.MATCH_DIRECT_BOOT_UNAWARE) != 0) + && !isComponentDirectBootAware; + final boolean matchesAware = ((flags & PackageManager.MATCH_DIRECT_BOOT_AWARE) != 0) + && isComponentDirectBootAware; + return reportIfDebug(matchesUnaware || matchesAware, flags); + } + + public static boolean isAvailable(@NonNull FrameworkPackageUserState state, long flags) { + // True if it is installed for this user and it is not hidden. If it is hidden, + // still return true if the caller requested MATCH_UNINSTALLED_PACKAGES + final boolean matchAnyUser = (flags & PackageManager.MATCH_ANY_USER) != 0; + final boolean matchUninstalled = (flags & PackageManager.MATCH_UNINSTALLED_PACKAGES) != 0; + return matchAnyUser + || (state.isInstalled() + && (!state.isHidden() || matchUninstalled)); + } + + public static boolean reportIfDebug(boolean result, long flags) { + if (DEBUG_PARSER && !result) { + Slog.i(TAG, "No match!; flags: " + + DebugUtils.flagsToString(PackageManager.class, "MATCH_", flags) + " " + + Debug.getCaller()); + } + return result; + } + + public static boolean isEnabled(@NonNull FrameworkPackageUserState state, ComponentInfo componentInfo, + long flags) { + return isEnabled(state, componentInfo.applicationInfo.enabled, componentInfo.enabled, + componentInfo.name, flags); + } + + public static boolean isEnabled(@NonNull FrameworkPackageUserState state, boolean isPackageEnabled, + ComponentInfo parsedComponent, long flags) { + return isEnabled(state, isPackageEnabled, parsedComponent.isEnabled(), + parsedComponent.name, flags); + } + + /** + * Test if the given component is considered enabled. + */ + public static boolean isEnabled(@NonNull FrameworkPackageUserState state, + boolean isPackageEnabled, boolean isComponentEnabled, String componentName, + long flags) { + if ((flags & MATCH_DISABLED_COMPONENTS) != 0) { + return true; + } + + // First check if the overall package is disabled; if the package is + // enabled then fall through to check specific component + switch (state.getEnabledState()) { + case PackageManager.COMPONENT_ENABLED_STATE_DISABLED: + case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER: + return false; + case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED: + if ((flags & MATCH_DISABLED_UNTIL_USED_COMPONENTS) == 0) { + return false; + } + // fallthrough + case PackageManager.COMPONENT_ENABLED_STATE_DEFAULT: + if (!isPackageEnabled) { + return false; + } + // fallthrough + case PackageManager.COMPONENT_ENABLED_STATE_ENABLED: + break; + } + + // Check if component has explicit state before falling through to + // the manifest default + if (state.isComponentEnabled(componentName)) { + return true; + } else if (state.isComponentDisabled(componentName)) { + return false; + } + + return isComponentEnabled; + } + + /** + * Writes the keyset mapping to the provided package. {@code null} mappings are permitted. + */ + public static void writeKeySetMapping(@NonNull Parcel dest, + @NonNull Map<String, ArraySet<PublicKey>> keySetMapping) { + if (keySetMapping == null) { + dest.writeInt(-1); + return; + } + + final int N = keySetMapping.size(); + dest.writeInt(N); + + for (String key : keySetMapping.keySet()) { + dest.writeString(key); + ArraySet<PublicKey> keys = keySetMapping.get(key); + if (keys == null) { + dest.writeInt(-1); + continue; + } + + final int M = keys.size(); + dest.writeInt(M); + for (int j = 0; j < M; j++) { + dest.writeSerializable(keys.valueAt(j)); + } + } + } + + /** + * Reads a keyset mapping from the given parcel at the given data position. May return + * {@code null} if the serialized mapping was {@code null}. + */ + @NonNull + public static ArrayMap<String, ArraySet<PublicKey>> readKeySetMapping(@NonNull Parcel in) { + final int N = in.readInt(); + if (N == -1) { + return null; + } + + ArrayMap<String, ArraySet<PublicKey>> keySetMapping = new ArrayMap<>(); + for (int i = 0; i < N; ++i) { + String key = in.readString(); + final int M = in.readInt(); + if (M == -1) { + keySetMapping.put(key, null); + continue; + } + + ArraySet<PublicKey> keys = new ArraySet<>(M); + for (int j = 0; j < M; ++j) { + PublicKey pk = + in.readSerializable(PublicKey.class.getClassLoader(), PublicKey.class); + keys.add(pk); + } + + keySetMapping.put(key, keys); + } + + return keySetMapping; + } + + public static String getSeinfoUser(FrameworkPackageUserState userState) { + if (userState.isInstantApp()) { + return ":ephemeralapp:complete"; + } + return ":complete"; + } } 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/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java index 1639ee92b882..d5498a0dc8cd 100644 --- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java +++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java @@ -18,13 +18,6 @@ package android.content.pm.parsing; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; -import static android.content.pm.parsing.ParsingPackageUtils.PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY; -import static android.content.pm.parsing.ParsingPackageUtils.checkRequiredSystemProperties; -import static android.content.pm.parsing.ParsingPackageUtils.parsePublicKey; -import static android.content.pm.parsing.ParsingPackageUtils.validateName; -import static android.content.pm.parsing.ParsingUtils.ANDROID_RES_NAMESPACE; -import static android.content.pm.parsing.ParsingUtils.DEFAULT_MIN_SDK_VERSION; -import static android.content.pm.parsing.ParsingUtils.DEFAULT_TARGET_SDK_VERSION; import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; import android.annotation.NonNull; @@ -37,6 +30,7 @@ import android.content.pm.parsing.result.ParseInput; import android.content.pm.parsing.result.ParseResult; import android.content.res.ApkAssets; import android.content.res.XmlResourceParser; +import android.os.Build; import android.os.Trace; import android.text.TextUtils; import android.util.ArrayMap; @@ -66,7 +60,7 @@ import java.util.Set; /** @hide */ public class ApkLiteParseUtils { - private static final String TAG = ParsingUtils.TAG; + private static final String TAG = "ApkLiteParseUtils"; private static final int PARSE_DEFAULT_INSTALL_LOCATION = PackageInfo.INSTALL_LOCATION_UNSPECIFIED; @@ -75,6 +69,26 @@ public class ApkLiteParseUtils { public static final String APK_FILE_EXTENSION = ".apk"; + + // Constants copied from services.jar side since they're not accessible + private static final String ANDROID_RES_NAMESPACE = + "http://schemas.android.com/apk/res/android"; + private static final int DEFAULT_MIN_SDK_VERSION = 1; + private static final int DEFAULT_TARGET_SDK_VERSION = 0; + public static final String ANDROID_MANIFEST_FILENAME = "AndroidManifest.xml"; + private static final int PARSE_IS_SYSTEM_DIR = 1 << 4; + private static final int PARSE_COLLECT_CERTIFICATES = 1 << 5; + private static final String TAG_APPLICATION = "application"; + private static final String TAG_PACKAGE_VERIFIER = "package-verifier"; + private static final String TAG_PROFILEABLE = "profileable"; + private static final String TAG_RECEIVER = "receiver"; + private static final String TAG_OVERLAY = "overlay"; + private static final String TAG_USES_SDK = "uses-sdk"; + private static final String TAG_USES_SPLIT = "uses-split"; + private static final String TAG_MANIFEST = "manifest"; + private static final int SDK_VERSION = Build.VERSION.SDK_INT; + private static final String[] SDK_CODENAMES = Build.VERSION.ACTIVE_CODENAMES; + /** * Parse only lightweight details about the package at the given location. * Automatically detects if the package is a monolithic style (single APK @@ -312,15 +326,16 @@ public class ApkLiteParseUtils { "Failed to parse " + apkPath, e); } - parser = apkAssets.openXml(ParsingPackageUtils.ANDROID_MANIFEST_FILENAME); + parser = apkAssets.openXml(ANDROID_MANIFEST_FILENAME); final SigningDetails signingDetails; - if ((flags & ParsingPackageUtils.PARSE_COLLECT_CERTIFICATES) != 0) { - final boolean skipVerify = (flags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) != 0; + if ((flags & PARSE_COLLECT_CERTIFICATES) != 0) { + final boolean skipVerify = (flags & PARSE_IS_SYSTEM_DIR) != 0; Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates"); try { final ParseResult<SigningDetails> result = - ParsingPackageUtils.getSigningDetails(input, apkFile.getAbsolutePath(), + FrameworkParsingPackageUtils.getSigningDetails(input, + apkFile.getAbsolutePath(), skipVerify, /* isStaticSharedLibrary */ false, SigningDetails.UNKNOWN, DEFAULT_TARGET_SDK_VERSION); if (result.isError()) { @@ -417,12 +432,12 @@ public class ApkLiteParseUtils { continue; } - if (ParsingPackageUtils.TAG_PACKAGE_VERIFIER.equals(parser.getName())) { + if (TAG_PACKAGE_VERIFIER.equals(parser.getName())) { final VerifierInfo verifier = parseVerifier(parser); if (verifier != null) { verifiers.add(verifier); } - } else if (ParsingPackageUtils.TAG_APPLICATION.equals(parser.getName())) { + } else if (TAG_APPLICATION.equals(parser.getName())) { debuggable = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE, "debuggable", false); multiArch = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE, "multiArch", @@ -453,15 +468,15 @@ public class ApkLiteParseUtils { continue; } - if (ParsingPackageUtils.TAG_PROFILEABLE.equals(parser.getName())) { + if (TAG_PROFILEABLE.equals(parser.getName())) { profilableByShell = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE, "shell", profilableByShell); - } else if (ParsingPackageUtils.TAG_RECEIVER.equals(parser.getName())) { + } else if (TAG_RECEIVER.equals(parser.getName())) { hasDeviceAdminReceiver |= isDeviceAdminReceiver( parser, hasBindDeviceAdminPermission); } } - } else if (ParsingPackageUtils.TAG_OVERLAY.equals(parser.getName())) { + } else if (TAG_OVERLAY.equals(parser.getName())) { requiredSystemPropertyName = parser.getAttributeValue(ANDROID_RES_NAMESPACE, "requiredSystemPropertyName"); requiredSystemPropertyValue = parser.getAttributeValue(ANDROID_RES_NAMESPACE, @@ -470,7 +485,7 @@ public class ApkLiteParseUtils { overlayIsStatic = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE, "isStatic", false); overlayPriority = parser.getAttributeIntValue(ANDROID_RES_NAMESPACE, "priority", 0); - } else if (ParsingPackageUtils.TAG_USES_SPLIT.equals(parser.getName())) { + } else if (TAG_USES_SPLIT.equals(parser.getName())) { if (usesSplitName != null) { Slog.w(TAG, "Only one <uses-split> permitted. Ignoring others."); continue; @@ -481,8 +496,8 @@ public class ApkLiteParseUtils { return input.error(PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, "<uses-split> tag requires 'android:name' attribute"); } - } else if (ParsingPackageUtils.TAG_USES_SDK.equals(parser.getName())) { - // Mirrors ParsingPackageUtils#parseUsesSdk until lite and full parsing is combined + } else if (TAG_USES_SDK.equals(parser.getName())) { + // Mirrors FrameworkParsingPackageUtils#parseUsesSdk until lite and full parsing is combined String minSdkVersionString = parser.getAttributeValue(ANDROID_RES_NAMESPACE, "minSdkVersion"); String targetSdkVersionString = parser.getAttributeValue(ANDROID_RES_NAMESPACE, @@ -515,16 +530,15 @@ public class ApkLiteParseUtils { targetCode = minCode; } - ParseResult<Integer> targetResult = ParsingPackageUtils.computeTargetSdkVersion( - targetVer, targetCode, ParsingPackageUtils.SDK_CODENAMES, input); + ParseResult<Integer> targetResult = FrameworkParsingPackageUtils.computeTargetSdkVersion( + targetVer, targetCode, SDK_CODENAMES, input); if (targetResult.isError()) { return input.error(targetResult); } targetSdkVersion = targetResult.getResult(); - ParseResult<Integer> minResult = ParsingPackageUtils.computeMinSdkVersion( - minVer, minCode, ParsingPackageUtils.SDK_VERSION, - ParsingPackageUtils.SDK_CODENAMES, input); + ParseResult<Integer> minResult = FrameworkParsingPackageUtils.computeMinSdkVersion( + minVer, minCode, SDK_VERSION, SDK_CODENAMES, input); if (minResult.isError()) { return input.error(minResult); } @@ -533,9 +547,9 @@ public class ApkLiteParseUtils { } // Check to see if overlay should be excluded based on system property condition - if ((flags & PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY) == 0 - && !checkRequiredSystemProperties( - requiredSystemPropertyName, requiredSystemPropertyValue)) { + if ((flags & FrameworkParsingPackageUtils.PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY) + == 0 && !FrameworkParsingPackageUtils.checkRequiredSystemProperties( + requiredSystemPropertyName, requiredSystemPropertyValue)) { String message = "Skipping target and overlay pair " + targetPackage + " and " + codePath + ": overlay ignored due to required system property: " + requiredSystemPropertyName + " with value: " + requiredSystemPropertyValue; @@ -600,14 +614,15 @@ public class ApkLiteParseUtils { return input.error(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, "No start tag found"); } - if (!parser.getName().equals(ParsingPackageUtils.TAG_MANIFEST)) { + if (!parser.getName().equals(TAG_MANIFEST)) { return input.error(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, "No <manifest> tag"); } final String packageName = parser.getAttributeValue(null, "package"); if (!"android".equals(packageName)) { - final ParseResult<?> nameResult = validateName(input, packageName, true, true); + final ParseResult<?> nameResult = FrameworkParsingPackageUtils.validateName(input, + packageName, true, true); if (nameResult.isError()) { return input.error(INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME, "Invalid manifest package: " + nameResult.getErrorMessage()); @@ -619,7 +634,8 @@ public class ApkLiteParseUtils { if (splitName.length() == 0) { splitName = null; } else { - final ParseResult<?> nameResult = validateName(input, splitName, false, false); + final ParseResult<?> nameResult = FrameworkParsingPackageUtils.validateName(input, + splitName, false, false); if (nameResult.isError()) { return input.error(INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME, "Invalid manifest split: " + nameResult.getErrorMessage()); @@ -666,7 +682,7 @@ public class ApkLiteParseUtils { final String type = value.trim(); // Using requireFilename as true because it limits length of the name to the // {@link #MAX_FILE_NAME_SIZE}. - final ParseResult<?> nameResult = validateName(input, type, + final ParseResult<?> nameResult = FrameworkParsingPackageUtils.validateName(input, type, false /* requireSeparator */, true /* requireFilename */); if (nameResult.isError()) { return input.error(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, @@ -688,7 +704,7 @@ public class ApkLiteParseUtils { return null; } - final PublicKey publicKey = parsePublicKey(encodedPublicKey); + final PublicKey publicKey = FrameworkParsingPackageUtils.parsePublicKey(encodedPublicKey); if (publicKey == null) { Slog.i(TAG, "Unable to parse verifier public key for " + packageName); return null; diff --git a/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java b/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java new file mode 100644 index 000000000000..8b86a16075e9 --- /dev/null +++ b/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java @@ -0,0 +1,397 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm.parsing; + +import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES; + +import android.annotation.CheckResult; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.pm.PackageManager; +import android.content.pm.Signature; +import android.content.pm.SigningDetails; +import android.content.pm.parsing.result.ParseInput; +import android.content.pm.parsing.result.ParseResult; +import android.os.Build; +import android.os.FileUtils; +import android.os.SystemProperties; +import android.text.TextUtils; +import android.util.Base64; +import android.util.Slog; +import android.util.apk.ApkSignatureVerifier; + +import com.android.internal.util.ArrayUtils; + +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.spec.EncodedKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; +import java.util.Arrays; + +/** @hide */ +public class FrameworkParsingPackageUtils { + + private static final String TAG = "FrameworkParsingPackageUtils"; + + /** + * For those names would be used as a part of the file name. Limits size to 223 and reserves 32 + * for the OS. + */ + private static final int MAX_FILE_NAME_SIZE = 223; + + public static final int PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY = 1 << 7; + + /** + * Check if the given name is valid. + * + * @param name The name to check. + * @param requireSeparator {@code true} if the name requires containing a separator at least. + * @param requireFilename {@code true} to apply file name validation to the given name. It also + * limits length of the name to the {@link #MAX_FILE_NAME_SIZE}. + * @return Success if it's valid. + */ + public static String validateName(String name, boolean requireSeparator, + boolean requireFilename) { + final int N = name.length(); + boolean hasSep = false; + boolean front = true; + for (int i = 0; i < N; i++) { + final char c = name.charAt(i); + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { + front = false; + continue; + } + if (!front) { + if ((c >= '0' && c <= '9') || c == '_') { + continue; + } + } + if (c == '.') { + hasSep = true; + front = true; + continue; + } + return "bad character '" + c + "'"; + } + if (requireFilename) { + if (!FileUtils.isValidExtFilename(name)) { + return "Invalid filename"; + } else if (N > MAX_FILE_NAME_SIZE) { + return "the length of the name is greater than " + MAX_FILE_NAME_SIZE; + } + } + return hasSep || !requireSeparator ? null : "must have at least one '.' separator"; + } + + /** + * @see #validateName(String, boolean, boolean) + */ + public static ParseResult validateName(ParseInput input, String name, boolean requireSeparator, + boolean requireFilename) { + final String errorMessage = validateName(name, requireSeparator, requireFilename); + if (errorMessage != null) { + return input.error(errorMessage); + } + return input.success(null); + } + + /** + * @return {@link PublicKey} of a given encoded public key. + */ + public static PublicKey parsePublicKey(final String encodedPublicKey) { + if (encodedPublicKey == null) { + Slog.w(TAG, "Could not parse null public key"); + return null; + } + + try { + return parsePublicKey(Base64.decode(encodedPublicKey, Base64.DEFAULT)); + } catch (IllegalArgumentException e) { + Slog.w(TAG, "Could not parse verifier public key; invalid Base64"); + return null; + } + } + + /** + * @return {@link PublicKey} of the given byte array of a public key. + */ + public static PublicKey parsePublicKey(final byte[] publicKey) { + if (publicKey == null) { + Slog.w(TAG, "Could not parse null public key"); + return null; + } + + final EncodedKeySpec keySpec; + try { + keySpec = new X509EncodedKeySpec(publicKey); + } catch (IllegalArgumentException e) { + Slog.w(TAG, "Could not parse verifier public key; invalid Base64"); + return null; + } + + /* First try the key as an RSA key. */ + try { + final KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + return keyFactory.generatePublic(keySpec); + } catch (NoSuchAlgorithmException e) { + Slog.wtf(TAG, "Could not parse public key: RSA KeyFactory not included in build"); + } catch (InvalidKeySpecException e) { + // Not a RSA public key. + } + + /* Now try it as a ECDSA key. */ + try { + final KeyFactory keyFactory = KeyFactory.getInstance("EC"); + return keyFactory.generatePublic(keySpec); + } catch (NoSuchAlgorithmException e) { + Slog.wtf(TAG, "Could not parse public key: EC KeyFactory not included in build"); + } catch (InvalidKeySpecException e) { + // Not a ECDSA public key. + } + + /* Now try it as a DSA key. */ + try { + final KeyFactory keyFactory = KeyFactory.getInstance("DSA"); + return keyFactory.generatePublic(keySpec); + } catch (NoSuchAlgorithmException e) { + Slog.wtf(TAG, "Could not parse public key: DSA KeyFactory not included in build"); + } catch (InvalidKeySpecException e) { + // Not a DSA public key. + } + + /* Not a supported key type */ + return null; + } + + /** + * Returns {@code true} if both the property name and value are empty or if the given system + * property is set to the specified value. Properties can be one or more, and if properties are + * more than one, they must be separated by comma, and count of names and values must be equal, + * and also every given system property must be set to the corresponding value. + * In all other cases, returns {@code false} + */ + public static boolean checkRequiredSystemProperties(@Nullable String rawPropNames, + @Nullable String rawPropValues) { + if (TextUtils.isEmpty(rawPropNames) || TextUtils.isEmpty(rawPropValues)) { + if (!TextUtils.isEmpty(rawPropNames) || !TextUtils.isEmpty(rawPropValues)) { + // malformed condition - incomplete + Slog.w(TAG, "Disabling overlay - incomplete property :'" + rawPropNames + + "=" + rawPropValues + "' - require both requiredSystemPropertyName" + + " AND requiredSystemPropertyValue to be specified."); + return false; + } + // no valid condition set - so no exclusion criteria, overlay will be included. + return true; + } + + final String[] propNames = rawPropNames.split(","); + final String[] propValues = rawPropValues.split(","); + + if (propNames.length != propValues.length) { + Slog.w(TAG, "Disabling overlay - property :'" + rawPropNames + + "=" + rawPropValues + "' - require both requiredSystemPropertyName" + + " AND requiredSystemPropertyValue lists to have the same size."); + return false; + } + for (int i = 0; i < propNames.length; i++) { + // Check property value: make sure it is both set and equal to expected value + final String currValue = SystemProperties.get(propNames[i]); + if (!TextUtils.equals(currValue, propValues[i])) { + return false; + } + } + return true; + } + + @CheckResult + public static ParseResult<SigningDetails> getSigningDetails(ParseInput input, + String baseCodePath, boolean skipVerify, boolean isStaticSharedLibrary, + @NonNull SigningDetails existingSigningDetails, int targetSdk) { + int minSignatureScheme = ApkSignatureVerifier.getMinimumSignatureSchemeVersionForTargetSdk( + targetSdk); + if (isStaticSharedLibrary) { + // must use v2 signing scheme + minSignatureScheme = SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2; + } + final ParseResult<SigningDetails> verified; + if (skipVerify) { + // systemDir APKs are already trusted, save time by not verifying; since the + // signature is not verified and some system apps can have their V2+ signatures + // stripped allow pulling the certs from the jar signature. + verified = ApkSignatureVerifier.unsafeGetCertsWithoutVerification(input, baseCodePath, + SigningDetails.SignatureSchemeVersion.JAR); + } else { + verified = ApkSignatureVerifier.verify(input, baseCodePath, minSignatureScheme); + } + + if (verified.isError()) { + return input.error(verified); + } + + // Verify that entries are signed consistently with the first pkg + // we encountered. Note that for splits, certificates may have + // already been populated during an earlier parse of a base APK. + if (existingSigningDetails == SigningDetails.UNKNOWN) { + return verified; + } else { + if (!Signature.areExactMatch(existingSigningDetails.getSignatures(), + verified.getResult().getSignatures())) { + return input.error(INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES, + baseCodePath + " has mismatched certificates"); + } + + return input.success(existingSigningDetails); + } + } + + /** + * Computes the minSdkVersion to use at runtime. If the package is not compatible with this + * platform, populates {@code outError[0]} with an error message. + * <p> + * If {@code minCode} is not specified, e.g. the value is {@code null}, then behavior varies + * based on the {@code platformSdkVersion}: + * <ul> + * <li>If the platform SDK version is greater than or equal to the + * {@code minVers}, returns the {@code mniVers} unmodified. + * <li>Otherwise, returns -1 to indicate that the package is not + * compatible with this platform. + * </ul> + * <p> + * Otherwise, the behavior varies based on whether the current platform + * is a pre-release version, e.g. the {@code platformSdkCodenames} array + * has length > 0: + * <ul> + * <li>If this is a pre-release platform and the value specified by + * {@code targetCode} is contained within the array of allowed pre-release + * codenames, this method will return {@link Build.VERSION_CODES#CUR_DEVELOPMENT}. + * <li>If this is a released platform, this method will return -1 to + * indicate that the package is not compatible with this platform. + * </ul> + * + * @param minVers minSdkVersion number, if specified in the application manifest, + * or 1 otherwise + * @param minCode minSdkVersion code, if specified in the application manifest, or + * {@code null} otherwise + * @param platformSdkVersion platform SDK version number, typically Build.VERSION.SDK_INT + * @param platformSdkCodenames array of allowed prerelease SDK codenames for this platform + * @return the minSdkVersion to use at runtime if successful + */ + public static ParseResult<Integer> computeMinSdkVersion(@IntRange(from = 1) int minVers, + @Nullable String minCode, @IntRange(from = 1) int platformSdkVersion, + @NonNull String[] platformSdkCodenames, @NonNull ParseInput input) { + // If it's a release SDK, make sure we meet the minimum SDK requirement. + if (minCode == null) { + if (minVers <= platformSdkVersion) { + return input.success(minVers); + } + + // We don't meet the minimum SDK requirement. + return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK, + "Requires newer sdk version #" + minVers + + " (current version is #" + platformSdkVersion + ")"); + } + + // If it's a pre-release SDK and the codename matches this platform, we + // definitely meet the minimum SDK requirement. + if (matchTargetCode(platformSdkCodenames, minCode)) { + return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT); + } + + // Otherwise, we're looking at an incompatible pre-release SDK. + if (platformSdkCodenames.length > 0) { + return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK, + "Requires development platform " + minCode + + " (current platform is any of " + + Arrays.toString(platformSdkCodenames) + ")"); + } else { + return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK, + "Requires development platform " + minCode + + " but this is a release platform."); + } + } + + /** + * Computes the targetSdkVersion to use at runtime. If the package is not compatible with this + * platform, populates {@code outError[0]} with an error message. + * <p> + * If {@code targetCode} is not specified, e.g. the value is {@code null}, then the {@code + * targetVers} will be returned unmodified. + * <p> + * Otherwise, the behavior varies based on whether the current platform is a pre-release + * version, e.g. the {@code platformSdkCodenames} array has length > 0: + * <ul> + * <li>If this is a pre-release platform and the value specified by + * {@code targetCode} is contained within the array of allowed pre-release + * codenames, this method will return {@link Build.VERSION_CODES#CUR_DEVELOPMENT}. + * <li>If this is a released platform, this method will return -1 to + * indicate that the package is not compatible with this platform. + * </ul> + * + * @param targetVers targetSdkVersion number, if specified in the application + * manifest, or 0 otherwise + * @param targetCode targetSdkVersion code, if specified in the application manifest, + * or {@code null} otherwise + * @param platformSdkCodenames array of allowed pre-release SDK codenames for this platform + * @return the targetSdkVersion to use at runtime if successful + */ + public static ParseResult<Integer> computeTargetSdkVersion(@IntRange(from = 0) int targetVers, + @Nullable String targetCode, @NonNull String[] platformSdkCodenames, + @NonNull ParseInput input) { + // If it's a release SDK, return the version number unmodified. + if (targetCode == null) { + return input.success(targetVers); + } + + // If it's a pre-release SDK and the codename matches this platform, it + // definitely targets this SDK. + if (matchTargetCode(platformSdkCodenames, targetCode)) { + return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT); + } + + // Otherwise, we're looking at an incompatible pre-release SDK. + if (platformSdkCodenames.length > 0) { + return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK, + "Requires development platform " + targetCode + + " (current platform is any of " + + Arrays.toString(platformSdkCodenames) + ")"); + } else { + return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK, + "Requires development platform " + targetCode + + " but this is a release platform."); + } + } + + /** + * Matches a given {@code targetCode} against a set of release codeNames. Target codes can + * either be of the form {@code [codename]}" (e.g {@code "Q"}) or of the form {@code + * [codename].[fingerprint]} (e.g {@code "Q.cafebc561"}). + */ + private static boolean matchTargetCode(@NonNull String[] codeNames, + @NonNull String targetCode) { + final String targetCodeName; + final int targetCodeIdx = targetCode.indexOf('.'); + if (targetCodeIdx == -1) { + targetCodeName = targetCode; + } else { + targetCodeName = targetCode.substring(0, targetCodeIdx); + } + return ArrayUtils.contains(codeNames, targetCodeName); + } +} diff --git a/core/java/android/content/pm/parsing/result/ParseTypeImpl.java b/core/java/android/content/pm/parsing/result/ParseTypeImpl.java index 1a3fc850243e..c32370441e97 100644 --- a/core/java/android/content/pm/parsing/result/ParseTypeImpl.java +++ b/core/java/android/content/pm/parsing/result/ParseTypeImpl.java @@ -16,14 +16,11 @@ package android.content.pm.parsing.result; -import static android.content.pm.parsing.ParsingUtils.NOT_SET; - import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; -import android.content.pm.parsing.ParsingUtils; import android.os.ServiceManager; import android.util.ArrayMap; import android.util.Log; @@ -35,7 +32,7 @@ import com.android.internal.util.CollectionUtils; /** @hide */ public class ParseTypeImpl implements ParseInput, ParseResult<Object> { - private static final String TAG = ParsingUtils.TAG; + private static final String TAG = "ParseTypeImpl"; public static final boolean DEBUG_FILL_STACK_TRACE = false; @@ -64,7 +61,7 @@ public class ParseTypeImpl implements ParseInput, ParseResult<Object> { private ArrayMap<Long, String> mDeferredErrors = null; private String mPackageName; - private int mTargetSdkVersion = NOT_SET; + private int mTargetSdkVersion = -1; /** * Specifically for {@link PackageManager#getPackageArchiveInfo(String, int)} where @@ -121,7 +118,7 @@ public class ParseTypeImpl implements ParseInput, ParseResult<Object> { // how many APKs they're going through. mDeferredErrors.erase(); } - mTargetSdkVersion = NOT_SET; + mTargetSdkVersion = -1; return this; } @@ -141,7 +138,7 @@ public class ParseTypeImpl implements ParseInput, ParseResult<Object> { if (DEBUG_THROW_ALL_ERRORS) { return error(parseError); } - if (mTargetSdkVersion != NOT_SET) { + if (mTargetSdkVersion != -1) { if (mDeferredErrors != null && mDeferredErrors.containsKey(deferredError)) { // If the map already contains the key, that means it's already been checked and // found to be disabled. Otherwise it would've failed when mTargetSdkVersion was diff --git a/core/java/android/content/pm/pkg/FrameworkPackageUserState.java b/core/java/android/content/pm/pkg/FrameworkPackageUserState.java index 0daf6cf4319a..bac29b49217c 100644 --- a/core/java/android/content/pm/pkg/FrameworkPackageUserState.java +++ b/core/java/android/content/pm/pkg/FrameworkPackageUserState.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 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. @@ -31,8 +31,9 @@ import java.util.Set; * See the services variant for method documentation. * * @hide - * @deprecated Unless you know exactly what you're doing, you probably want the services variant. + * @deprecated Unused by framework. */ +@Deprecated public interface FrameworkPackageUserState { FrameworkPackageUserState DEFAULT = new FrameworkPackageUserStateDefault(); diff --git a/core/java/android/content/pm/pkg/FrameworkPackageUserStateDefault.java b/core/java/android/content/pm/pkg/FrameworkPackageUserStateDefault.java index 27255da1e64a..359062064ebf 100644 --- a/core/java/android/content/pm/pkg/FrameworkPackageUserStateDefault.java +++ b/core/java/android/content/pm/pkg/FrameworkPackageUserStateDefault.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 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. @@ -25,7 +25,11 @@ import java.util.Collections; import java.util.Map; import java.util.Set; -/** @hide */ +/** + * @hide + * @deprecated Unused by framework. + */ +@Deprecated class FrameworkPackageUserStateDefault implements FrameworkPackageUserState { @Override diff --git a/core/java/android/content/pm/split/OWNERS b/core/java/android/content/pm/split/OWNERS deleted file mode 100644 index 3d126d297e60..000000000000 --- a/core/java/android/content/pm/split/OWNERS +++ /dev/null @@ -1,5 +0,0 @@ -# Bug component: 36137 - -toddke@android.com -toddke@google.com -patb@google.com 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/ISerialManager.aidl b/core/java/android/hardware/ISerialManager.aidl index 74d30f7afefe..65a0fa4f893e 100644 --- a/core/java/android/hardware/ISerialManager.aidl +++ b/core/java/android/hardware/ISerialManager.aidl @@ -22,8 +22,10 @@ import android.os.ParcelFileDescriptor; interface ISerialManager { /* Returns a list of all available serial ports */ + @EnforcePermission("SERIAL_PORT") String[] getSerialPorts(); /* Returns a file descriptor for the serial port. */ + @EnforcePermission("SERIAL_PORT") ParcelFileDescriptor openSerialPort(String name); } diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java index 0e42b02d5956..37cfb4935f0d 100644 --- a/core/java/android/hardware/Sensor.java +++ b/core/java/android/hardware/Sensor.java @@ -712,6 +712,20 @@ public final class Sensor { public static final String STRING_TYPE_HINGE_ANGLE = "android.sensor.hinge_angle"; /** + * A constant describing a head tracker sensor. + * + * See {@link android.hardware.SensorEvent#values SensorEvent.values} for more details. + */ + public static final int TYPE_HEAD_TRACKER = 37; + + /** + * A constant string describing a head tracker sensor. + * + * See {@link android.hardware.SensorEvent#values SensorEvent.values} for more details. + */ + public static final String STRING_TYPE_HEAD_TRACKER = "android.sensor.head_tracker"; + + /** * A constant describing all sensor types. */ @@ -831,6 +845,7 @@ public final class Sensor { 1, // SENSOR_TYPE_LOW_LATENCY_OFFBODY_DETECT 6, // SENSOR_TYPE_ACCELEROMETER_UNCALIBRATED 1, // SENSOR_TYPE_HINGE_ANGLE + 6, // SENSOR_TYPE_HEAD_TRACKER (discontinuity count is excluded) }; /** @@ -1283,6 +1298,9 @@ public final class Sensor { case TYPE_HINGE_ANGLE: mStringType = STRING_TYPE_HINGE_ANGLE; return true; + case TYPE_HEAD_TRACKER: + mStringType = STRING_TYPE_HEAD_TRACKER; + return true; default: return false; } diff --git a/core/java/android/hardware/SensorEvent.java b/core/java/android/hardware/SensorEvent.java index 232f23429720..c77c8cc635e6 100644 --- a/core/java/android/hardware/SensorEvent.java +++ b/core/java/android/hardware/SensorEvent.java @@ -641,6 +641,41 @@ public class SensorEvent { * <li> values[0]: Measured hinge angle between 0 and 360 degrees inclusive</li> * </ul> * + * <h4>{@link android.hardware.Sensor#TYPE_HEAD_TRACKER Sensor.TYPE_HEAD_TRACKER}:</h4> + * + * A sensor of this type measures the orientation of a user's head relative to an arbitrary + * reference frame, as well as the rate of rotation. + * + * Events produced by this sensor follow a special head-centric coordinate frame, where: + * <ul> + * <li> The X axis crosses through the user's ears, with the positive X direction extending + * out of the user's right ear</li> + * <li> The Y axis crosses from the back of the user's head through their nose, with the + * positive direction extending out of the nose, and the X/Y plane being nominally + * parallel to the ground when the user is upright and looking straight ahead</li> + * <li> The Z axis crosses from the neck through the top of the user's head, with the + * positive direction extending out from the top of the head</li> + * </ul> + * + * Data is provided in Euler vector representation, which is a vector whose direction indicates + * the axis of rotation and magnitude indicates the angle to rotate around that axis, in + * radians. + * + * The first three elements provide the transform from the (arbitrary, possibly slowly drifting) + * reference frame to the head frame. The magnitude of this vector is in range [0, π] + * radians, while the value of individual axes is in range [-π, π]. The next three + * elements provide the estimated rotational velocity of the user's head relative to itself, in + * radians per second. + * + * <ul> + * <li> values[0] : X component of Euler vector representing rotation</li> + * <li> values[1] : Y component of Euler vector representing rotation</li> + * <li> values[2] : Z component of Euler vector representing rotation</li> + * <li> values[3] : X component of Euler vector representing angular velocity</li> + * <li> values[4] : Y component of Euler vector representing angular velocity</li> + * <li> values[5] : Z component of Euler vector representing angular velocity</li> + * </ul> + * * @see GeomagneticField */ public final float[] values; diff --git a/core/java/android/hardware/SensorEventCallback.java b/core/java/android/hardware/SensorEventCallback.java index bac212ab5b20..7b0092da1196 100644 --- a/core/java/android/hardware/SensorEventCallback.java +++ b/core/java/android/hardware/SensorEventCallback.java @@ -16,6 +16,8 @@ package android.hardware; +import android.annotation.NonNull; + /** * Used for receiving sensor additional information frames. */ @@ -52,4 +54,21 @@ public abstract class SensorEventCallback implements SensorEventListener2 { * reported from sensor hardware. */ public void onSensorAdditionalInfo(SensorAdditionalInfo info) {} + + /** + * Called when the next {@link android.hardware.SensorEvent SensorEvent} to be delivered via the + * {@link #onSensorChanged(SensorEvent) onSensorChanged} method represents the first event after + * a discontinuity. + * + * The exact meaning of discontinuity depends on the sensor type. For {@link + * android.hardware.Sensor#TYPE_HEAD_TRACKER Sensor.TYPE_HEAD_TRACKER}, this means that the + * reference frame has suddenly and significantly changed. + * + * Note that this concept is either not relevant to or not supported by most sensor types, + * {@link android.hardware.Sensor#TYPE_HEAD_TRACKER Sensor.TYPE_HEAD_TRACKER} being the notable + * exception. + * + * @param sensor The {@link android.hardware.Sensor Sensor} which experienced the discontinuity. + */ + public void onSensorDiscontinuity(@NonNull Sensor sensor) {} } diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java index 3a8513b9323f..32a5ee77508e 100644 --- a/core/java/android/hardware/SystemSensorManager.java +++ b/core/java/android/hardware/SystemSensorManager.java @@ -676,6 +676,7 @@ public class SystemSensorManager extends SensorManager { private long mNativeSensorEventQueue; private final SparseBooleanArray mActiveSensors = new SparseBooleanArray(); protected final SparseIntArray mSensorAccuracies = new SparseIntArray(); + protected final SparseIntArray mSensorDiscontinuityCounts = new SparseIntArray(); private final CloseGuard mCloseGuard = CloseGuard.get(); protected final SystemSensorManager mManager; @@ -875,10 +876,21 @@ public class SystemSensorManager extends SensorManager { // call onAccuracyChanged() only if the value changes final int accuracy = mSensorAccuracies.get(handle); - if ((t.accuracy >= 0) && (accuracy != t.accuracy)) { + if (t.accuracy >= 0 && accuracy != t.accuracy) { mSensorAccuracies.put(handle, t.accuracy); mListener.onAccuracyChanged(t.sensor, t.accuracy); } + + // call onSensorDiscontinuity() if the discontinuity counter changed + if (t.sensor.getType() == Sensor.TYPE_HEAD_TRACKER + && mListener instanceof SensorEventCallback) { + final int lastCount = mSensorDiscontinuityCounts.get(handle); + final int curCount = Float.floatToIntBits(values[6]); + if (lastCount >= 0 && lastCount != curCount) { + ((SensorEventCallback) mListener).onSensorDiscontinuity(t.sensor); + } + } + mListener.onSensorChanged(t); } 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/hdmi/DeviceFeatures.java b/core/java/android/hardware/hdmi/DeviceFeatures.java new file mode 100644 index 000000000000..dc530ffbc68b --- /dev/null +++ b/core/java/android/hardware/hdmi/DeviceFeatures.java @@ -0,0 +1,395 @@ +/* + * 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.hdmi; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; + +/** + * Immutable class that stores support status for features in the [Device Features] operand. + * Each feature may be supported, be not supported, or have an unknown support status. + * + * @hide + */ +public class DeviceFeatures { + + @IntDef({ + FEATURE_NOT_SUPPORTED, + FEATURE_SUPPORTED, + FEATURE_SUPPORT_UNKNOWN + }) + public @interface FeatureSupportStatus {}; + + public static final int FEATURE_NOT_SUPPORTED = 0; + public static final int FEATURE_SUPPORTED = 1; + public static final int FEATURE_SUPPORT_UNKNOWN = 2; + + /** + * Instance representing no knowledge of any feature's support. + */ + @NonNull + public static final DeviceFeatures ALL_FEATURES_SUPPORT_UNKNOWN = + new Builder(FEATURE_SUPPORT_UNKNOWN).build(); + + /** + * Instance representing no support for any feature. + */ + @NonNull + public static final DeviceFeatures NO_FEATURES_SUPPORTED = + new Builder(FEATURE_NOT_SUPPORTED).build(); + + @FeatureSupportStatus private final int mRecordTvScreenSupport; + @FeatureSupportStatus private final int mSetOsdStringSupport; + @FeatureSupportStatus private final int mDeckControlSupport; + @FeatureSupportStatus private final int mSetAudioRateSupport; + @FeatureSupportStatus private final int mArcTxSupport; + @FeatureSupportStatus private final int mArcRxSupport; + @FeatureSupportStatus private final int mSetAudioVolumeLevelSupport; + + private DeviceFeatures(@NonNull Builder builder) { + this.mRecordTvScreenSupport = builder.mRecordTvScreenSupport; + this.mSetOsdStringSupport = builder.mOsdStringSupport; + this.mDeckControlSupport = builder.mDeckControlSupport; + this.mSetAudioRateSupport = builder.mSetAudioRateSupport; + this.mArcTxSupport = builder.mArcTxSupport; + this.mArcRxSupport = builder.mArcRxSupport; + this.mSetAudioVolumeLevelSupport = builder.mSetAudioVolumeLevelSupport; + } + + /** + * Converts an instance to a builder. + */ + public Builder toBuilder() { + return new Builder(this); + } + + /** + * Constructs an instance from a [Device Features] operand. + * + * Bit 7 of [Device Features] is currently ignored. It indicates whether the operand spans more + * than one byte, but only the first byte contains information as of CEC 2.0. + * + * @param deviceFeaturesOperand The [Device Features] operand to parse. + * @return Instance representing the [Device Features] operand. + */ + @NonNull + public static DeviceFeatures fromOperand(@NonNull byte[] deviceFeaturesOperand) { + Builder builder = new Builder(FEATURE_SUPPORT_UNKNOWN); + + // Read feature support status from the bits of [Device Features] + if (deviceFeaturesOperand.length >= 1) { + byte b = deviceFeaturesOperand[0]; + builder + .setRecordTvScreenSupport(bitToFeatureSupportStatus(((b >> 6) & 1) == 1)) + .setSetOsdStringSupport(bitToFeatureSupportStatus(((b >> 5) & 1) == 1)) + .setDeckControlSupport(bitToFeatureSupportStatus(((b >> 4) & 1) == 1)) + .setSetAudioRateSupport(bitToFeatureSupportStatus(((b >> 3) & 1) == 1)) + .setArcTxSupport(bitToFeatureSupportStatus(((b >> 2) & 1) == 1)) + .setArcRxSupport(bitToFeatureSupportStatus(((b >> 1) & 1) == 1)) + .setSetAudioVolumeLevelSupport(bitToFeatureSupportStatus((b & 1) == 1)); + } + return builder.build(); + } + + /** + * Returns the input that is not {@link #FEATURE_SUPPORT_UNKNOWN}. If neither is equal to + * {@link #FEATURE_SUPPORT_UNKNOWN}, returns the second input. + */ + private static @FeatureSupportStatus int updateFeatureSupportStatus( + @FeatureSupportStatus int oldStatus, @FeatureSupportStatus int newStatus) { + if (newStatus == FEATURE_SUPPORT_UNKNOWN) { + return oldStatus; + } else { + return newStatus; + } + } + + /** + * Returns the [Device Features] operand corresponding to this instance. + * {@link #FEATURE_SUPPORT_UNKNOWN} maps to 0, indicating no support. + * + * As of CEC 2.0, the returned byte array will always be of length 1. + */ + @NonNull + public byte[] toOperand() { + byte result = 0; + + if (mRecordTvScreenSupport == FEATURE_SUPPORTED) result |= (byte) (1 << 6); + if (mSetOsdStringSupport == FEATURE_SUPPORTED) result = (byte) (1 << 5); + if (mDeckControlSupport == FEATURE_SUPPORTED) result = (byte) (1 << 4); + if (mSetAudioRateSupport == FEATURE_SUPPORTED) result = (byte) (1 << 3); + if (mArcTxSupport == FEATURE_SUPPORTED) result = (byte) (1 << 2); + if (mArcRxSupport == FEATURE_SUPPORTED) result = (byte) (1 << 1); + if (mSetAudioVolumeLevelSupport == FEATURE_SUPPORTED) result = (byte) 1; + + return new byte[]{ result }; + } + + @FeatureSupportStatus + private static int bitToFeatureSupportStatus(boolean bit) { + return bit ? FEATURE_SUPPORTED : FEATURE_NOT_SUPPORTED; + } + + /** + * Returns whether the device is a TV that supports <Record TV Screen>. + */ + @FeatureSupportStatus + public int getRecordTvScreenSupport() { + return mRecordTvScreenSupport; + } + + /** + * Returns whether the device is a TV that supports <Set OSD String>. + */ + @FeatureSupportStatus + public int getSetOsdStringSupport() { + return mSetOsdStringSupport; + } + + /** + * Returns whether the device supports being controlled by Deck Control. + */ + @FeatureSupportStatus + public int getDeckControlSupport() { + return mDeckControlSupport; + } + + /** + * Returns whether the device is a Source that supports <Set Audio Rate>. + */ + @FeatureSupportStatus + public int getSetAudioRateSupport() { + return mSetAudioRateSupport; + } + + /** + * Returns whether the device is a Sink that supports ARC Tx. + */ + @FeatureSupportStatus + public int getArcTxSupport() { + return mArcTxSupport; + } + + /** + * Returns whether the device is a Source that supports ARC Rx. + */ + @FeatureSupportStatus + public int getArcRxSupport() { + return mArcRxSupport; + } + + /** + * Returns whether the device supports <Set Audio Volume Level>. + */ + @FeatureSupportStatus + public int getSetAudioVolumeLevelSupport() { + return mSetAudioVolumeLevelSupport; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof DeviceFeatures)) { + return false; + } + + DeviceFeatures other = (DeviceFeatures) obj; + return mRecordTvScreenSupport == other.mRecordTvScreenSupport + && mSetOsdStringSupport == other.mSetOsdStringSupport + && mDeckControlSupport == other.mDeckControlSupport + && mSetAudioRateSupport == other.mSetAudioRateSupport + && mArcTxSupport == other.mArcTxSupport + && mArcRxSupport == other.mArcRxSupport + && mSetAudioVolumeLevelSupport == other.mSetAudioVolumeLevelSupport; + } + + @Override + public int hashCode() { + return java.util.Objects.hash( + mRecordTvScreenSupport, + mSetOsdStringSupport, + mDeckControlSupport, + mSetAudioRateSupport, + mArcTxSupport, + mArcRxSupport, + mSetAudioVolumeLevelSupport + ); + } + + @NonNull + @Override + public String toString() { + StringBuilder s = new StringBuilder(); + s.append("Device features: "); + s.append("record_tv_screen: ") + .append(featureSupportStatusToString(mRecordTvScreenSupport)).append(" "); + s.append("set_osd_string: ") + .append(featureSupportStatusToString(mSetOsdStringSupport)).append(" "); + s.append("deck_control: ") + .append(featureSupportStatusToString(mDeckControlSupport)).append(" "); + s.append("set_audio_rate: ") + .append(featureSupportStatusToString(mSetAudioRateSupport)).append(" "); + s.append("arc_tx: ") + .append(featureSupportStatusToString(mArcTxSupport)).append(" "); + s.append("arc_rx: ") + .append(featureSupportStatusToString(mArcRxSupport)).append(" "); + s.append("set_audio_volume_level: ") + .append(featureSupportStatusToString(mSetAudioVolumeLevelSupport)).append(" "); + return s.toString(); + } + + @NonNull + private static String featureSupportStatusToString(@FeatureSupportStatus int status) { + switch (status) { + case FEATURE_SUPPORTED: + return "Y"; + case FEATURE_NOT_SUPPORTED: + return "N"; + case FEATURE_SUPPORT_UNKNOWN: + default: + return "?"; + } + } + + /** + * Builder for {@link DeviceFeatures} instances. + */ + public static final class Builder { + @FeatureSupportStatus private int mRecordTvScreenSupport; + @FeatureSupportStatus private int mOsdStringSupport; + @FeatureSupportStatus private int mDeckControlSupport; + @FeatureSupportStatus private int mSetAudioRateSupport; + @FeatureSupportStatus private int mArcTxSupport; + @FeatureSupportStatus private int mArcRxSupport; + @FeatureSupportStatus private int mSetAudioVolumeLevelSupport; + + private Builder(@FeatureSupportStatus int defaultFeatureSupportStatus) { + mRecordTvScreenSupport = defaultFeatureSupportStatus; + mOsdStringSupport = defaultFeatureSupportStatus; + mDeckControlSupport = defaultFeatureSupportStatus; + mSetAudioRateSupport = defaultFeatureSupportStatus; + mArcTxSupport = defaultFeatureSupportStatus; + mArcRxSupport = defaultFeatureSupportStatus; + mSetAudioVolumeLevelSupport = defaultFeatureSupportStatus; + } + + private Builder(DeviceFeatures info) { + mRecordTvScreenSupport = info.getRecordTvScreenSupport(); + mOsdStringSupport = info.getSetOsdStringSupport(); + mDeckControlSupport = info.getDeckControlSupport(); + mSetAudioRateSupport = info.getSetAudioRateSupport(); + mArcTxSupport = info.getArcTxSupport(); + mArcRxSupport = info.getArcRxSupport(); + mSetAudioVolumeLevelSupport = info.getSetAudioVolumeLevelSupport(); + } + + /** + * Creates a new {@link DeviceFeatures} object. + */ + public DeviceFeatures build() { + return new DeviceFeatures(this); + } + + /** + * Sets the value for {@link #getRecordTvScreenSupport()}. + */ + @NonNull + public Builder setRecordTvScreenSupport(@FeatureSupportStatus int recordTvScreenSupport) { + mRecordTvScreenSupport = recordTvScreenSupport; + return this; + } + + /** + * Sets the value for {@link #getSetOsdStringSupport()}. + */ + @NonNull + public Builder setSetOsdStringSupport(@FeatureSupportStatus int setOsdStringSupport) { + mOsdStringSupport = setOsdStringSupport; + return this; + } + + /** + * Sets the value for {@link #getDeckControlSupport()}. + */ + @NonNull + public Builder setDeckControlSupport(@FeatureSupportStatus int deckControlSupport) { + mDeckControlSupport = deckControlSupport; + return this; + } + + /** + * Sets the value for {@link #getSetAudioRateSupport()}. + */ + @NonNull + public Builder setSetAudioRateSupport(@FeatureSupportStatus int setAudioRateSupport) { + mSetAudioRateSupport = setAudioRateSupport; + return this; + } + + /** + * Sets the value for {@link #getArcTxSupport()}. + */ + @NonNull + public Builder setArcTxSupport(@FeatureSupportStatus int arcTxSupport) { + mArcTxSupport = arcTxSupport; + return this; + } + + /** + * Sets the value for {@link #getArcRxSupport()}. + */ + @NonNull + public Builder setArcRxSupport(@FeatureSupportStatus int arcRxSupport) { + mArcRxSupport = arcRxSupport; + return this; + } + + /** + * Sets the value for {@link #getSetAudioRateSupport()}. + */ + @NonNull + public Builder setSetAudioVolumeLevelSupport( + @FeatureSupportStatus int setAudioVolumeLevelSupport) { + mSetAudioVolumeLevelSupport = setAudioVolumeLevelSupport; + return this; + } + + /** + * Updates all fields with those in a 'new' instance of {@link DeviceFeatures}. + * All fields are replaced with those in the new instance, except when the field is + * {@link #FEATURE_SUPPORT_UNKNOWN} in the new instance. + */ + @NonNull + public Builder update(DeviceFeatures newDeviceFeatures) { + mRecordTvScreenSupport = updateFeatureSupportStatus(mRecordTvScreenSupport, + newDeviceFeatures.getRecordTvScreenSupport()); + mOsdStringSupport = updateFeatureSupportStatus(mOsdStringSupport, + newDeviceFeatures.getSetOsdStringSupport()); + mDeckControlSupport = updateFeatureSupportStatus(mDeckControlSupport, + newDeviceFeatures.getDeckControlSupport()); + mSetAudioRateSupport = updateFeatureSupportStatus(mSetAudioRateSupport, + newDeviceFeatures.getSetAudioRateSupport()); + mArcTxSupport = updateFeatureSupportStatus(mArcTxSupport, + newDeviceFeatures.getArcTxSupport()); + mArcRxSupport = updateFeatureSupportStatus(mArcRxSupport, + newDeviceFeatures.getArcRxSupport()); + mSetAudioVolumeLevelSupport = updateFeatureSupportStatus(mSetAudioVolumeLevelSupport, + newDeviceFeatures.getSetAudioVolumeLevelSupport()); + return this; + } + } +} diff --git a/core/java/android/hardware/hdmi/HdmiDeviceInfo.java b/core/java/android/hardware/hdmi/HdmiDeviceInfo.java index c0b177d0efde..e3ea4e25ddd1 100644 --- a/core/java/android/hardware/hdmi/HdmiDeviceInfo.java +++ b/core/java/android/hardware/hdmi/HdmiDeviceInfo.java @@ -68,6 +68,9 @@ public class HdmiDeviceInfo implements Parcelable { */ public static final int ADDR_INTERNAL = 0; + /** Invalid or uninitialized logical address */ + public static final int ADDR_INVALID = -1; + /** * Physical address used to indicate the source comes from internal device. The physical address * of TV(0) is used. @@ -83,7 +86,15 @@ public class HdmiDeviceInfo implements Parcelable { /** Invalid device ID */ public static final int ID_INVALID = 0xFFFF; - /** Device info used to indicate an inactivated device. */ + /** Unknown vendor ID */ + public static final int VENDOR_ID_UNKNOWN = 0xFFFFFF; + + /** + * Instance that represents an inactive device. + * Can be passed to an input change listener to indicate that the active source + * yielded its status, allowing the listener to take an appropriate action such as + * switching to another input. + */ public static final HdmiDeviceInfo INACTIVE_DEVICE = new HdmiDeviceInfo(); private static final int HDMI_DEVICE_TYPE_CEC = 0; @@ -109,10 +120,11 @@ public class HdmiDeviceInfo implements Parcelable { private final int mLogicalAddress; private final int mDeviceType; @HdmiCecVersion - private final int mHdmiCecVersion; + private final int mCecVersion; private final int mVendorId; private final String mDisplayName; private final int mDevicePowerStatus; + private final DeviceFeatures mDeviceFeatures; // MHL only parameters. private final int mDeviceId; @@ -137,14 +149,22 @@ public class HdmiDeviceInfo implements Parcelable { int powerStatus = source.readInt(); String displayName = source.readString(); int cecVersion = source.readInt(); - return new HdmiDeviceInfo(logicalAddress, physicalAddress, portId, - deviceType, vendorId, displayName, powerStatus, cecVersion); + return cecDeviceBuilder() + .setLogicalAddress(logicalAddress) + .setPhysicalAddress(physicalAddress) + .setPortId(portId) + .setDeviceType(deviceType) + .setVendorId(vendorId) + .setDisplayName(displayName) + .setDevicePowerStatus(powerStatus) + .setCecVersion(cecVersion) + .build(); case HDMI_DEVICE_TYPE_MHL: int deviceId = source.readInt(); int adopterId = source.readInt(); - return new HdmiDeviceInfo(physicalAddress, portId, adopterId, deviceId); + return mhlDevice(physicalAddress, portId, adopterId, deviceId); case HDMI_DEVICE_TYPE_HARDWARE: - return new HdmiDeviceInfo(physicalAddress, portId); + return hardwarePort(physicalAddress, portId); case HDMI_DEVICE_TYPE_INACTIVE: return HdmiDeviceInfo.INACTIVE_DEVICE; default: @@ -159,97 +179,82 @@ public class HdmiDeviceInfo implements Parcelable { }; /** - * Constructor. Used to initialize the instance for CEC device. + * Constructor. Initializes the instance representing an inactive device. + * Can be passed to an input change listener to indicate that the active source + * yielded its status, allowing the listener to take an appropriate action such as + * switching to another input. * - * @param logicalAddress logical address of HDMI-CEC device - * @param physicalAddress physical address of HDMI-CEC device - * @param portId HDMI port ID (1 for HDMI1) - * @param deviceType type of device - * @param vendorId vendor id of device. Used for vendor specific command. - * @param displayName name of device - * @param powerStatus device power status - * @hide + * @deprecated Use {@link #INACTIVE_DEVICE} instead. */ - public HdmiDeviceInfo(int logicalAddress, int physicalAddress, int portId, int deviceType, - int vendorId, String displayName, int powerStatus, int hdmiCecVersion) { - mHdmiDeviceType = HDMI_DEVICE_TYPE_CEC; - mPhysicalAddress = physicalAddress; - mPortId = portId; - - mId = idForCecDevice(logicalAddress); - mLogicalAddress = logicalAddress; - mDeviceType = deviceType; - mHdmiCecVersion = hdmiCecVersion; - mVendorId = vendorId; - mDevicePowerStatus = powerStatus; - mDisplayName = displayName; + @Deprecated + public HdmiDeviceInfo() { + mHdmiDeviceType = HDMI_DEVICE_TYPE_INACTIVE; + mPhysicalAddress = PATH_INVALID; + mId = ID_INVALID; + + mLogicalAddress = ADDR_INVALID; + mDeviceType = DEVICE_INACTIVE; + mCecVersion = HdmiControlManager.HDMI_CEC_VERSION_1_4_B; + mPortId = PORT_INVALID; + mDevicePowerStatus = HdmiControlManager.POWER_STATUS_UNKNOWN; + mDisplayName = "Inactive"; + mVendorId = 0; + mDeviceFeatures = DeviceFeatures.ALL_FEATURES_SUPPORT_UNKNOWN; mDeviceId = -1; mAdopterId = -1; } /** - * Constructor. Used to initialize the instance for CEC device. + * Converts an instance to a builder. * - * @param logicalAddress logical address of HDMI-CEC device - * @param physicalAddress physical address of HDMI-CEC device - * @param portId HDMI port ID (1 for HDMI1) - * @param deviceType type of device - * @param vendorId vendor id of device. Used for vendor specific command. - * @param displayName name of device - * @param powerStatus device power status * @hide */ - public HdmiDeviceInfo(int logicalAddress, int physicalAddress, int portId, int deviceType, - int vendorId, String displayName, int powerStatus) { - this(logicalAddress, physicalAddress, portId, deviceType, - vendorId, displayName, powerStatus, HdmiControlManager.HDMI_CEC_VERSION_1_4_B); + public Builder toBuilder() { + return new Builder(this); } - /** - * Constructor. Used to initialize the instance for CEC device. - * - * @param logicalAddress logical address of HDMI-CEC device - * @param physicalAddress physical address of HDMI-CEC device - * @param portId HDMI port ID (1 for HDMI1) - * @param deviceType type of device - * @param vendorId vendor id of device. Used for vendor specific command. - * @param displayName name of device - * @hide - */ - public HdmiDeviceInfo(int logicalAddress, int physicalAddress, int portId, int deviceType, - int vendorId, String displayName) { - this(logicalAddress, physicalAddress, portId, deviceType, - vendorId, displayName, HdmiControlManager.POWER_STATUS_UNKNOWN, - HdmiControlManager.HDMI_CEC_VERSION_1_4_B); + private HdmiDeviceInfo(Builder builder) { + this.mHdmiDeviceType = builder.mHdmiDeviceType; + this.mPhysicalAddress = builder.mPhysicalAddress; + this.mPortId = builder.mPortId; + this.mLogicalAddress = builder.mLogicalAddress; + this.mDeviceType = builder.mDeviceType; + this.mCecVersion = builder.mCecVersion; + this.mVendorId = builder.mVendorId; + this.mDisplayName = builder.mDisplayName; + this.mDevicePowerStatus = builder.mDevicePowerStatus; + this.mDeviceFeatures = builder.mDeviceFeatures; + this.mDeviceId = builder.mDeviceId; + this.mAdopterId = builder.mAdopterId; + + switch (mHdmiDeviceType) { + case HDMI_DEVICE_TYPE_MHL: + this.mId = idForMhlDevice(mPortId); + break; + case HDMI_DEVICE_TYPE_HARDWARE: + this.mId = idForHardware(mPortId); + break; + case HDMI_DEVICE_TYPE_CEC: + this.mId = idForCecDevice(mLogicalAddress); + break; + case HDMI_DEVICE_TYPE_INACTIVE: + default: + this.mId = ID_INVALID; + } } /** - * Constructor. Used to initialize the instance for device representing hardware port. + * Creates a Builder for an {@link HdmiDeviceInfo} representing a CEC device. * - * @param physicalAddress physical address of the port - * @param portId HDMI port ID (1 for HDMI1) * @hide */ - public HdmiDeviceInfo(int physicalAddress, int portId) { - mHdmiDeviceType = HDMI_DEVICE_TYPE_HARDWARE; - mPhysicalAddress = physicalAddress; - mPortId = portId; - - mId = idForHardware(portId); - mLogicalAddress = -1; - mDeviceType = DEVICE_RESERVED; - mHdmiCecVersion = HdmiControlManager.HDMI_CEC_VERSION_1_4_B; - mVendorId = 0; - mDevicePowerStatus = HdmiControlManager.POWER_STATUS_UNKNOWN; - mDisplayName = "HDMI" + portId; - - mDeviceId = -1; - mAdopterId = -1; + public static Builder cecDeviceBuilder() { + return new Builder(HDMI_DEVICE_TYPE_CEC); } /** - * Constructor. Used to initialize the instance for MHL device. + * Creates an {@link HdmiDeviceInfo} representing an MHL device. * * @param physicalAddress physical address of HDMI device * @param portId portId HDMI port ID (1 for HDMI1) @@ -257,44 +262,32 @@ public class HdmiDeviceInfo implements Parcelable { * @param deviceId device id of MHL * @hide */ - public HdmiDeviceInfo(int physicalAddress, int portId, int adopterId, int deviceId) { - mHdmiDeviceType = HDMI_DEVICE_TYPE_MHL; - mPhysicalAddress = physicalAddress; - mPortId = portId; - - mId = idForMhlDevice(portId); - mLogicalAddress = -1; - mDeviceType = DEVICE_RESERVED; - mHdmiCecVersion = HdmiControlManager.HDMI_CEC_VERSION_1_4_B; - mVendorId = 0; - mDevicePowerStatus = HdmiControlManager.POWER_STATUS_UNKNOWN; - mDisplayName = "Mobile"; - - mDeviceId = adopterId; - mAdopterId = deviceId; + public static HdmiDeviceInfo mhlDevice( + int physicalAddress, int portId, int adopterId, int deviceId) { + return new Builder(HDMI_DEVICE_TYPE_MHL) + .setPhysicalAddress(physicalAddress) + .setPortId(portId) + .setVendorId(0) + .setDisplayName("Mobile") + .setDeviceId(adopterId) + .setAdopterId(deviceId) + .build(); } /** - * Constructor. Used to initialize the instance representing an inactivated device. - * Can be passed input change listener to indicate the active source yielded - * its status, hence the listener should take an appropriate action such as - * switching to other input. + * Creates an {@link HdmiDeviceInfo} representing a hardware port. + * + * @param physicalAddress physical address of the port + * @param portId HDMI port ID (1 for HDMI1) + * @hide */ - public HdmiDeviceInfo() { - mHdmiDeviceType = HDMI_DEVICE_TYPE_INACTIVE; - mPhysicalAddress = PATH_INVALID; - mId = ID_INVALID; - - mLogicalAddress = -1; - mDeviceType = DEVICE_INACTIVE; - mHdmiCecVersion = HdmiControlManager.HDMI_CEC_VERSION_1_4_B; - mPortId = PORT_INVALID; - mDevicePowerStatus = HdmiControlManager.POWER_STATUS_UNKNOWN; - mDisplayName = "Inactive"; - mVendorId = 0; - - mDeviceId = -1; - mAdopterId = -1; + public static HdmiDeviceInfo hardwarePort(int physicalAddress, int portId) { + return new Builder(HDMI_DEVICE_TYPE_HARDWARE) + .setPhysicalAddress(physicalAddress) + .setPortId(portId) + .setVendorId(0) + .setDisplayName("HDMI" + portId) + .build(); } /** @@ -305,6 +298,15 @@ public class HdmiDeviceInfo implements Parcelable { } /** + * Returns the CEC features that this device supports. + * + * @hide + */ + public DeviceFeatures getDeviceFeatures() { + return mDeviceFeatures; + } + + /** * Returns the id to be used for CEC device. * * @param address logical address of CEC device @@ -372,7 +374,7 @@ public class HdmiDeviceInfo implements Parcelable { */ @HdmiCecVersion public int getCecVersion() { - return mHdmiCecVersion; + return mCecVersion; } /** @@ -485,7 +487,7 @@ public class HdmiDeviceInfo implements Parcelable { dest.writeInt(mVendorId); dest.writeInt(mDevicePowerStatus); dest.writeString(mDisplayName); - dest.writeInt(mHdmiCecVersion); + dest.writeInt(mCecVersion); break; case HDMI_DEVICE_TYPE_MHL: dest.writeInt(mDeviceId); @@ -508,7 +510,7 @@ public class HdmiDeviceInfo implements Parcelable { s.append("logical_address: ").append(String.format("0x%02X", mLogicalAddress)); s.append(" "); s.append("device_type: ").append(mDeviceType).append(" "); - s.append("cec_version: ").append(mHdmiCecVersion).append(" "); + s.append("cec_version: ").append(mCecVersion).append(" "); s.append("vendor_id: ").append(mVendorId).append(" "); s.append("display_name: ").append(mDisplayName).append(" "); s.append("power_status: ").append(mDevicePowerStatus).append(" "); @@ -531,6 +533,11 @@ public class HdmiDeviceInfo implements Parcelable { s.append("physical_address: ").append(String.format("0x%04X", mPhysicalAddress)); s.append(" "); s.append("port_id: ").append(mPortId); + + if (mHdmiDeviceType == HDMI_DEVICE_TYPE_CEC) { + s.append("\n ").append(mDeviceFeatures.toString()); + } + return s.toString(); } @@ -546,7 +553,7 @@ public class HdmiDeviceInfo implements Parcelable { && mPortId == other.mPortId && mLogicalAddress == other.mLogicalAddress && mDeviceType == other.mDeviceType - && mHdmiCecVersion == other.mHdmiCecVersion + && mCecVersion == other.mCecVersion && mVendorId == other.mVendorId && mDevicePowerStatus == other.mDevicePowerStatus && mDisplayName.equals(other.mDisplayName) @@ -562,11 +569,180 @@ public class HdmiDeviceInfo implements Parcelable { mPortId, mLogicalAddress, mDeviceType, - mHdmiCecVersion, + mCecVersion, mVendorId, mDevicePowerStatus, mDisplayName, mDeviceId, mAdopterId); } + + /** + * Builder for {@link HdmiDeviceInfo} instances. + * + * @hide + */ + public static final class Builder { + // Required parameters + private final int mHdmiDeviceType; + + // Common parameters + private int mPhysicalAddress = PATH_INVALID; + private int mPortId = PORT_INVALID; + + // CEC parameters + private int mLogicalAddress = ADDR_INVALID; + private int mDeviceType = DEVICE_RESERVED; + @HdmiCecVersion + private int mCecVersion = HdmiControlManager.HDMI_CEC_VERSION_1_4_B; + private int mVendorId = VENDOR_ID_UNKNOWN; + private String mDisplayName = ""; + private int mDevicePowerStatus = HdmiControlManager.POWER_STATUS_UNKNOWN; + private DeviceFeatures mDeviceFeatures; + + // MHL parameters + private int mDeviceId = -1; + private int mAdopterId = -1; + + private Builder(int hdmiDeviceType) { + mHdmiDeviceType = hdmiDeviceType; + if (hdmiDeviceType == HDMI_DEVICE_TYPE_CEC) { + mDeviceFeatures = DeviceFeatures.ALL_FEATURES_SUPPORT_UNKNOWN; + } else { + mDeviceFeatures = DeviceFeatures.NO_FEATURES_SUPPORTED; + } + } + + private Builder(@NonNull HdmiDeviceInfo hdmiDeviceInfo) { + mHdmiDeviceType = hdmiDeviceInfo.mHdmiDeviceType; + mPhysicalAddress = hdmiDeviceInfo.mPhysicalAddress; + mPortId = hdmiDeviceInfo.mPortId; + mLogicalAddress = hdmiDeviceInfo.mLogicalAddress; + mDeviceType = hdmiDeviceInfo.mDeviceType; + mCecVersion = hdmiDeviceInfo.mCecVersion; + mVendorId = hdmiDeviceInfo.mVendorId; + mDisplayName = hdmiDeviceInfo.mDisplayName; + mDevicePowerStatus = hdmiDeviceInfo.mDevicePowerStatus; + mDeviceId = hdmiDeviceInfo.mDeviceId; + mAdopterId = hdmiDeviceInfo.mAdopterId; + mDeviceFeatures = hdmiDeviceInfo.mDeviceFeatures; + } + + /** + * Create a new {@link HdmiDeviceInfo} object. + */ + @NonNull + public HdmiDeviceInfo build() { + return new HdmiDeviceInfo(this); + } + + /** + * Sets the value for {@link #getPhysicalAddress()}. + */ + @NonNull + public Builder setPhysicalAddress(int physicalAddress) { + mPhysicalAddress = physicalAddress; + return this; + } + + /** + * Sets the value for {@link #getPortId()}. + */ + @NonNull + public Builder setPortId(int portId) { + mPortId = portId; + return this; + } + + /** + * Sets the value for {@link #getLogicalAddress()}. + */ + @NonNull + public Builder setLogicalAddress(int logicalAddress) { + mLogicalAddress = logicalAddress; + return this; + } + + /** + * Sets the value for {@link #getDeviceType()}. + */ + @NonNull + public Builder setDeviceType(int deviceType) { + mDeviceType = deviceType; + return this; + } + + /** + * Sets the value for {@link #getCecVersion()}. + */ + @NonNull + public Builder setCecVersion(int hdmiCecVersion) { + mCecVersion = hdmiCecVersion; + return this; + } + + /** + * Sets the value for {@link #getVendorId()}. + */ + @NonNull + public Builder setVendorId(int vendorId) { + mVendorId = vendorId; + return this; + } + + /** + * Sets the value for {@link #getDisplayName()}. + */ + @NonNull + public Builder setDisplayName(@NonNull String displayName) { + mDisplayName = displayName; + return this; + } + + /** + * Sets the value for {@link #getDevicePowerStatus()}. + */ + @NonNull + public Builder setDevicePowerStatus(int devicePowerStatus) { + mDevicePowerStatus = devicePowerStatus; + return this; + } + + /** + * Sets the value for {@link #getDeviceFeatures()}. + */ + @NonNull + public Builder setDeviceFeatures(DeviceFeatures deviceFeatures) { + this.mDeviceFeatures = deviceFeatures; + return this; + } + + /** + * Sets the value for {@link #getDeviceId()}. + */ + @NonNull + public Builder setDeviceId(int deviceId) { + mDeviceId = deviceId; + return this; + } + + /** + * Sets the value for {@link #getAdopterId()}. + */ + @NonNull + public Builder setAdopterId(int adopterId) { + mAdopterId = adopterId; + return this; + } + + /** + * Updates the value for {@link #getDeviceFeatures()} with a new set of device features. + * New information overrides the old, except when feature support was unknown. + */ + @NonNull + public Builder updateDeviceFeatures(DeviceFeatures deviceFeatures) { + mDeviceFeatures = mDeviceFeatures.toBuilder().update(deviceFeatures).build(); + return this; + } + } } 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..1d0837e3f118 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,7 @@ 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); /* Gets the USB Hal Version. */ int getUsbHalVersion(); @@ -156,9 +157,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/services/core/java/com/android/server/wm/BackGestureController.java b/core/java/android/hardware/usb/IUsbOperationInternal.aidl index f8f625486e56..3f3bbf63ed8b 100644 --- a/services/core/java/com/android/server/wm/BackGestureController.java +++ b/core/java/android/hardware/usb/IUsbOperationInternal.aidl @@ -14,24 +14,11 @@ * limitations under the License. */ -package com.android.server.wm; - -import android.os.SystemProperties; +package android.hardware.usb; /** - * Controller to handle actions related to the back gesture on the server side. + * @hide */ -public class BackGestureController { - - private static final String BACK_PREDICTABILITY_PROP = "persist.debug.back_predictability"; - - public static boolean isEnabled() { - return SystemProperties.getInt(BACK_PREDICTABILITY_PROP, 0) > 0; - } - - /** - * Start a remote animation the back gesture. - */ - public void startBackPreview() { - } +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..eb3e84d27c7c 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,73 @@ 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(); + } + } + + /** * 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..e908c245fc8e 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; @@ -34,15 +38,23 @@ import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SINK; import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SOURCE; 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 +63,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 +77,83 @@ 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{} + /** @hide */ public UsbPort(@NonNull UsbManager usbManager, @NonNull String id, int supportedModes, int supportedContaminantProtectionModes, @@ -157,7 +247,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 +267,88 @@ 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; + } + } + + /** + * 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) { diff --git a/core/java/android/hardware/usb/UsbPortStatus.java b/core/java/android/hardware/usb/UsbPortStatus.java index bb7aff651b3d..934c5067c11f 100644 --- a/core/java/android/hardware/usb/UsbPortStatus.java +++ b/core/java/android/hardware/usb/UsbPortStatus.java @@ -19,7 +19,6 @@ package android.hardware.usb; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SystemApi; -import android.hardware.usb.V1_0.Constants; import android.os.Parcel; import android.os.Parcelable; @@ -36,27 +35,30 @@ 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 mUsbDataEnabled; + private final boolean mPowerTransferLimited; /** * 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 +71,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 +94,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 +122,76 @@ 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; @IntDef(prefix = { "CONTAMINANT_DETECTION_" }, value = { CONTAMINANT_DETECTION_NOT_SUPPORTED, @@ -234,6 +225,21 @@ public final class UsbPortStatus implements Parcelable { /** @hide */ public UsbPortStatus(int currentMode, int currentPowerRole, int currentDataRole, int supportedRoleCombinations, int contaminantProtectionStatus, + int contaminantDetectionStatus, boolean usbDataEnabled, + boolean powerTransferLimited) { + mCurrentMode = currentMode; + mCurrentPowerRole = currentPowerRole; + mCurrentDataRole = currentDataRole; + mSupportedRoleCombinations = supportedRoleCombinations; + mContaminantProtectionStatus = contaminantProtectionStatus; + mContaminantDetectionStatus = contaminantDetectionStatus; + mUsbDataEnabled = usbDataEnabled; + mPowerTransferLimited = powerTransferLimited; + } + + /** @hide */ + public UsbPortStatus(int currentMode, int currentPowerRole, int currentDataRole, + int supportedRoleCombinations, int contaminantProtectionStatus, int contaminantDetectionStatus) { mCurrentMode = currentMode; mCurrentPowerRole = currentPowerRole; @@ -241,6 +247,8 @@ public final class UsbPortStatus implements Parcelable { mSupportedRoleCombinations = supportedRoleCombinations; mContaminantProtectionStatus = contaminantProtectionStatus; mContaminantDetectionStatus = contaminantDetectionStatus; + mUsbDataEnabled = true; + mPowerTransferLimited = false; } /** @@ -323,6 +331,25 @@ public final class UsbPortStatus implements Parcelable { return mContaminantProtectionStatus; } + /** + * Returns UsbData status. + * + * @hide + */ + public boolean getUsbDataStatus() { + return mUsbDataEnabled; + } + + /** + * Returns whether power transfer is limited. + * + * @return true when power transfer is limited. + * false otherwise. + */ + public boolean isPowerTransferLimited() { + return mPowerTransferLimited; + } + @NonNull @Override public String toString() { @@ -336,6 +363,10 @@ public final class UsbPortStatus implements Parcelable { + getContaminantDetectionStatus() + ", contaminantProtectionStatus=" + getContaminantProtectionStatus() + + ", usbDataEnabled=" + + getUsbDataStatus() + + ", isPowerTransferLimited=" + + isPowerTransferLimited() + "}"; } @@ -352,6 +383,8 @@ public final class UsbPortStatus implements Parcelable { dest.writeInt(mSupportedRoleCombinations); dest.writeInt(mContaminantProtectionStatus); dest.writeInt(mContaminantDetectionStatus); + dest.writeBoolean(mUsbDataEnabled); + dest.writeBoolean(mPowerTransferLimited); } public static final @NonNull Parcelable.Creator<UsbPortStatus> CREATOR = @@ -364,9 +397,11 @@ public final class UsbPortStatus implements Parcelable { int supportedRoleCombinations = in.readInt(); int contaminantProtectionStatus = in.readInt(); int contaminantDetectionStatus = in.readInt(); + boolean usbDataEnabled = in.readBoolean(); + boolean powerTransferLimited = in.readBoolean(); return new UsbPortStatus(currentMode, currentPowerRole, currentDataRole, supportedRoleCombinations, contaminantProtectionStatus, - contaminantDetectionStatus); + contaminantDetectionStatus, usbDataEnabled, powerTransferLimited); } @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/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/PrivateDnsConnectivityChecker.java b/core/java/android/net/PrivateDnsConnectivityChecker.java index cfd458c65362..ac97b3616c3b 100644 --- a/core/java/android/net/PrivateDnsConnectivityChecker.java +++ b/core/java/android/net/PrivateDnsConnectivityChecker.java @@ -44,7 +44,7 @@ public class PrivateDnsConnectivityChecker { */ public static boolean canConnectToPrivateDnsServer(@NonNull String hostname) { final SocketFactory factory = SSLSocketFactory.getDefault(); - TrafficStats.setThreadStatsTag(TrafficStats.TAG_SYSTEM_APP); + TrafficStats.setThreadStatsTagApp(); try (SSLSocket socket = (SSLSocket) factory.createSocket()) { socket.setSoTimeout(CONNECTION_TIMEOUT_MS); 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/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 c6071959f438..47a272c2337f 100644 --- a/core/java/android/os/BatteryConsumer.java +++ b/core/java/android/os/BatteryConsumer.java @@ -191,6 +191,8 @@ public abstract class BatteryConsumer { private static final int[] SUPPORTED_POWER_COMPONENTS_PER_PROCESS_STATE = { 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 520730fa7352..2d338179186e 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -461,6 +461,12 @@ public abstract class BatteryStats implements Parcelable { public abstract long getCountLocked(int which); /** + * Returns the count accumulated by this Counter for the specified process state. + * If the counter does not support per-procstate tracking, returns 0. + */ + public abstract long getCountForProcessState(@BatteryConsumer.ProcessState int procState); + + /** * Temporary for debugging. */ public abstract void logState(Printer pw, String prefix); @@ -1031,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. @@ -1096,6 +1112,17 @@ public abstract class BatteryStats implements Parcelable { public abstract long getWifiMeasuredBatteryConsumptionUC(); /** + * Returns the battery consumption (in microcoulombs) of the uid's wifi usage when in the + * specified process state. + * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable. + * + * {@hide} + */ + public abstract long getWifiMeasuredBatteryConsumptionUC( + @BatteryConsumer.ProcessState int processState); + + + /** * Returns the battery consumption (in microcoulombs) used by this uid for each * {@link android.hardware.power.stats.EnergyConsumer.ordinal} of (custom) energy consumer * type {@link android.hardware.power.stats.EnergyConsumerType#OTHER}). @@ -3379,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/Binder.java b/core/java/android/os/Binder.java index 429450ce8e5e..80cf2f87f1d6 100644 --- a/core/java/android/os/Binder.java +++ b/core/java/android/os/Binder.java @@ -311,6 +311,7 @@ public class Binder implements IBinder { private final long mObject; private IInterface mOwner; + @Nullable private String mDescriptor; private volatile String[] mTransactionTraceNames = null; private volatile String mSimpleDescriptor = null; @@ -930,8 +931,8 @@ public class Binder implements IBinder { transactionNames[i] = buf.toString(); buf.setLength(0); } - mTransactionTraceNames = transactionNames; mSimpleDescriptor = descriptor; + mTransactionTraceNames = transactionNames; } final int index = transactionCode - FIRST_CALL_TRANSACTION; if (index < 0 || index >= mTransactionTraceNames.length) { @@ -940,13 +941,19 @@ public class Binder implements IBinder { return mTransactionTraceNames[index]; } - private String getSimpleDescriptor() { - final int dot = mDescriptor.lastIndexOf("."); + private @NonNull String getSimpleDescriptor() { + String descriptor = mDescriptor; + if (descriptor == null) { + // Just "Binder" to avoid null checks in transaction name tracing. + return "Binder"; + } + + final int dot = descriptor.lastIndexOf("."); if (dot > 0) { // Strip the package name - return mDescriptor.substring(dot + 1); + return descriptor.substring(dot + 1); } - return mDescriptor; + return descriptor; } /** 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/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/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/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/VibratorInfo.java b/core/java/android/os/VibratorInfo.java index 189e454f1488..5271c4df11ef 100644 --- a/core/java/android/os/VibratorInfo.java +++ b/core/java/android/os/VibratorInfo.java @@ -69,7 +69,7 @@ public class VibratorInfo implements Parcelable { mPwlePrimitiveDurationMax = in.readInt(); mPwleSizeMax = in.readInt(); mQFactor = in.readFloat(); - mFrequencyMapping = in.readParcelable(VibratorInfo.class.getClassLoader()); + mFrequencyMapping = in.readParcelable(VibratorInfo.class.getClassLoader(), android.os.VibratorInfo.FrequencyMapping.class); } /** 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/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/permission/IPermissionController.aidl b/core/java/android/permission/IPermissionController.aidl index 5814bac06f59..0894e372efc2 100644 --- a/core/java/android/permission/IPermissionController.aidl +++ b/core/java/android/permission/IPermissionController.aidl @@ -56,6 +56,6 @@ oneway interface IPermissionController { in AndroidFuture<String> callback); void getUnusedAppCount( in AndroidFuture callback); - void selfRevokePermissions(in String packageName, in List<String> permissions, + 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..a0115a94e7fe 100644 --- a/core/java/android/permission/PermissionControllerManager.java +++ b/core/java/android/permission/PermissionControllerManager.java @@ -835,17 +835,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..b1e3cfc161de 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"); } @@ -669,13 +669,13 @@ public abstract class PermissionControllerService extends Service { } @Override - public void selfRevokePermissions(@NonNull String packageName, + 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 72e28630da40..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 */ @@ -10525,7 +10560,7 @@ public final class Settings { DEVICE_STATE_ROTATION_LOCK_UNLOCKED, }) @Retention(RetentionPolicy.SOURCE) - @interface DeviceStateRotationLockSetting { + public @interface DeviceStateRotationLockSetting { } /** @@ -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/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/GameService.java b/core/java/android/service/games/GameService.java index 105c2aa53374..870a7e3f2646 100644 --- a/core/java/android/service/games/GameService.java +++ b/core/java/android/service/games/GameService.java @@ -16,6 +16,8 @@ package android.service.games; +import android.annotation.IntRange; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SdkConstant; import android.annotation.SystemApi; @@ -38,6 +40,12 @@ import java.util.Objects; * when a game session should begin. It is always kept running by the system. * Because of this it should be kept as lightweight as possible. * + * <p> Instead of requiring permissions for sensitive actions (e.g., starting a new game session), + * this class is provided with an {@link IGameServiceController} instance which exposes the + * sensitive functionality. This controller is provided by the system server when calling the + * {@link IGameService#connected(IGameServiceController)} method exposed by this class. The system + * server does so only when creating the bound game service. + * * <p>Heavyweight operations (such as showing UI) should be implemented in the * associated {@link GameSessionService} when a game session is taking place. Its * implementation should run in a separate process from the {@link GameService}. @@ -79,12 +87,13 @@ public class GameService extends Service { */ public static final String SERVICE_META_DATA = "android.game_service"; + private IGameServiceController mGameServiceController; private IGameManagerService mGameManagerService; private final IGameService mInterface = new IGameService.Stub() { @Override - public void connected() { + public void connected(IGameServiceController gameServiceController) { Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage( - GameService::doOnConnected, GameService.this)); + GameService::doOnConnected, GameService.this, gameServiceController)); } @Override @@ -92,6 +101,12 @@ public class GameService extends Service { Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage( GameService::onDisconnected, GameService.this)); } + + @Override + public void gameStarted(GameStartedEvent gameStartedEvent) { + Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage( + GameService::onGameStarted, GameService.this, gameStartedEvent)); + } }; private final IBinder.DeathRecipient mGameManagerServiceDeathRecipient = () -> { @@ -111,7 +126,7 @@ public class GameService extends Service { return null; } - private void doOnConnected() { + private void doOnConnected(@NonNull IGameServiceController gameServiceController) { mGameManagerService = IGameManagerService.Stub.asInterface( ServiceManager.getService(Context.GAME_SERVICE)); @@ -122,6 +137,7 @@ public class GameService extends Service { Log.w(TAG, "Unable to link to death with system service"); } + mGameServiceController = gameServiceController; onConnected(); } @@ -138,4 +154,34 @@ public class GameService extends Service { * The service should clean up any resources that it holds at this point. */ public void onDisconnected() {} + + /** + * Called when a game task is started. It is the responsibility of the service to determine what + * action to take (e.g., request that a game session be created). + * + * @param gameStartedEvent Contains information about the game being started. + */ + public void onGameStarted(@NonNull GameStartedEvent gameStartedEvent) {} + + /** + * Call to create a new game session be created for a game. This method may be called + * by a game service following {@link #onGameStarted}, using the task ID provided by the + * provided {@link GameStartedEvent} (using {@link GameStartedEvent#getTaskId}). + * + * If a game session already exists for the game task, this call will be ignored and the + * existing session will continue. + * + * @param taskId The taskId of the game. + */ + public final void createGameSession(@IntRange(from = 0) int taskId) { + if (mGameServiceController == null) { + throw new IllegalStateException("Can not call before connected()"); + } + + try { + mGameServiceController.createGameSession(taskId); + } catch (RemoteException e) { + Log.e(TAG, "Request for game session failed", e); + } + } } 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/GameStartedEvent.aidl b/core/java/android/service/games/GameStartedEvent.aidl new file mode 100644 index 000000000000..8a5a4a16abab --- /dev/null +++ b/core/java/android/service/games/GameStartedEvent.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 GameStartedEvent;
\ No newline at end of file diff --git a/core/java/android/service/games/GameStartedEvent.java b/core/java/android/service/games/GameStartedEvent.java new file mode 100644 index 000000000000..bf292606fbf6 --- /dev/null +++ b/core/java/android/service/games/GameStartedEvent.java @@ -0,0 +1,119 @@ +/* + * 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.IntRange; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * Event object provided when a game task is started. + * + * This is provided to the Game Service via + * {@link GameService#onGameStarted(GameStartedEvent)}. It includes the game's taskId + * (see {@link #getTaskId}) that the game's package name (see {@link #getPackageName}). + * + * @hide + */ +@SystemApi +public final class GameStartedEvent implements Parcelable { + + @NonNull + public static final Parcelable.Creator<GameStartedEvent> CREATOR = + new Parcelable.Creator<GameStartedEvent>() { + @Override + public GameStartedEvent createFromParcel(Parcel source) { + return new GameStartedEvent( + source.readInt(), + source.readString()); + } + + @Override + public GameStartedEvent[] newArray(int size) { + return new GameStartedEvent[0]; + } + }; + + private final int mTaskId; + private final String mPackageName; + + public GameStartedEvent(@IntRange(from = 0) int taskId, @NonNull String packageName) { + this.mTaskId = taskId; + this.mPackageName = packageName; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mTaskId); + dest.writeString(mPackageName); + } + + /** + * Unique identifier for the task associated with the game. + */ + @IntRange(from = 0) + public int getTaskId() { + return mTaskId; + } + + /** + * The package name for the game. + */ + @NonNull + public String getPackageName() { + return mPackageName; + } + + @Override + public String toString() { + return "GameStartedEvent{" + + "mTaskId=" + + mTaskId + + ", mPackageName='" + + mPackageName + + "\'}"; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (!(o instanceof GameStartedEvent)) { + return false; + } + + GameStartedEvent that = (GameStartedEvent) o; + return mTaskId == that.mTaskId + && Objects.equals(mPackageName, that.mPackageName); + } + + @Override + public int hashCode() { + return Objects.hash(mTaskId, mPackageName); + } +} diff --git a/core/java/android/service/games/IGameService.aidl b/core/java/android/service/games/IGameService.aidl index 8a0d6365977b..38c8416117e0 100644 --- a/core/java/android/service/games/IGameService.aidl +++ b/core/java/android/service/games/IGameService.aidl @@ -16,10 +16,14 @@ package android.service.games; +import android.service.games.GameStartedEvent; +import android.service.games.IGameServiceController; + /** * @hide */ oneway interface IGameService { - void connected(); + void connected(in IGameServiceController gameServiceController); void disconnected(); + void gameStarted(in GameStartedEvent gameStartedEvent); } diff --git a/core/java/android/service/games/IGameServiceController.aidl b/core/java/android/service/games/IGameServiceController.aidl new file mode 100644 index 000000000000..886f519b6605 --- /dev/null +++ b/core/java/android/service/games/IGameServiceController.aidl @@ -0,0 +1,24 @@ +/* + * 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 + */ +oneway interface IGameServiceController { + void createGameSession(int taskId); +}
\ No newline at end of file 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/games/OWNERS b/core/java/android/service/games/OWNERS new file mode 100644 index 000000000000..81d94e090d7f --- /dev/null +++ b/core/java/android/service/games/OWNERS @@ -0,0 +1 @@ +include /GAME_MANAGER_OWNERS 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/IRecognitionService.aidl b/core/java/android/speech/IRecognitionService.aidl index cc349c8d030d..ad3ad7ad1e05 100644 --- a/core/java/android/speech/IRecognitionService.aidl +++ b/core/java/android/speech/IRecognitionService.aidl @@ -20,6 +20,7 @@ import android.os.Bundle; import android.content.AttributionSource; import android.content.Intent; import android.speech.IRecognitionListener; +import android.speech.IRecognitionSupportCallback; /** * A Service interface to speech recognition. Call startListening when @@ -60,4 +61,18 @@ oneway interface IRecognitionService { * @param listener to receive callbacks, note that this must be non-null */ void cancel(in IRecognitionListener listener, boolean isShutdown); + + /** + * Checks whether this RecognitionService could {@link #startListening} successfully on the + * given recognizerIntent. For more information see {@link #startListening} and + * {@link RecognizerIntent}. + */ + void checkRecognitionSupport(in Intent recognizerIntent, in IRecognitionSupportCallback listener); + + /** + * Requests RecognitionService to download the support for the given recognizerIntent. For more + * information see {@link #checkRecognitionSupport}, {@link #startListening} and + * {@link RecognizerIntent}. + */ + void triggerModelDownload(in Intent recognizerIntent); } diff --git a/core/java/android/speech/IRecognitionSupportCallback.aidl b/core/java/android/speech/IRecognitionSupportCallback.aidl new file mode 100644 index 000000000000..f5a54739af25 --- /dev/null +++ b/core/java/android/speech/IRecognitionSupportCallback.aidl @@ -0,0 +1,37 @@ +/* + * 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.speech; + +import android.os.Bundle; +import android.speech.RecognitionSupport; + +/** + * Callback for speech recognition support checks, used with RecognitionService. + * This provides the {@link RecognitionSupport} for a given recognition request, callers can use + * it to check whether RecognitionService can fulfill a given recognition request. + * {@hide} + */ +oneway interface IRecognitionSupportCallback { + void onSupportResult(in RecognitionSupport recognitionSupport); + + /** + * A network or recognition error occurred. + * + * @param error code is defined in {@link SpeechRecognizer} + */ + void onError(in int error); +} diff --git a/core/java/android/speech/RecognitionService.java b/core/java/android/speech/RecognitionService.java index 5e647a4531bc..5dbbc045077e 100644 --- a/core/java/android/speech/RecognitionService.java +++ b/core/java/android/speech/RecognitionService.java @@ -37,6 +37,7 @@ import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.util.Log; +import android.util.Pair; import com.android.internal.util.function.pooled.PooledLambda; @@ -90,6 +91,10 @@ public abstract class RecognitionService extends Service { private static final int MSG_RESET = 4; + private static final int MSG_CHECK_RECOGNITION_SUPPORT = 5; + + private static final int MSG_TRIGGER_MODEL_DOWNLOAD = 6; + private final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { @@ -107,6 +112,15 @@ public abstract class RecognitionService extends Service { case MSG_RESET: dispatchClearCallback(); break; + case MSG_CHECK_RECOGNITION_SUPPORT: + Pair<Intent, IRecognitionSupportCallback> intentAndListener = + (Pair<Intent, IRecognitionSupportCallback>) msg.obj; + dispatchCheckRecognitionSupport( + intentAndListener.first, intentAndListener.second); + break; + case MSG_TRIGGER_MODEL_DOWNLOAD: + dispatchTriggerModelDownload((Intent) msg.obj); + break; } } }; @@ -179,6 +193,15 @@ public abstract class RecognitionService extends Service { mStartedDataDelivery = false; } + private void dispatchCheckRecognitionSupport( + Intent intent, IRecognitionSupportCallback callback) { + RecognitionService.this.onCheckRecognitionSupport(intent, new SupportCallback(callback)); + } + + private void dispatchTriggerModelDownload(Intent intent) { + RecognitionService.this.triggerModelDownload(intent); + } + private class StartListeningArgs { public final Intent mIntent; @@ -238,6 +261,34 @@ public abstract class RecognitionService extends Service { */ protected abstract void onStopListening(Callback listener); + /** + * Queries the service on whether it would support a {@link #onStartListening(Intent, Callback)} + * for the same {@code recognizerIntent}. + * + * <p>The service will notify the caller about the level of support or error via + * {@link SupportCallback}. + * + * <p>If the service does not offer the support check it will notify the caller with + * {@link SpeechRecognizer#ERROR_CANNOT_CHECK_SUPPORT}. + */ + public void onCheckRecognitionSupport( + @NonNull Intent recognizerIntent, + @NonNull SupportCallback supportCallback) { + if (DBG) { + Log.i(TAG, String.format("#onSupports [%s]", recognizerIntent)); + } + supportCallback.onError(SpeechRecognizer.ERROR_CANNOT_CHECK_SUPPORT); + } + + /** + * Requests the download of the recognizer support for {@code recognizerIntent}. + */ + public void triggerModelDownload(@NonNull Intent recognizerIntent) { + if (DBG) { + Log.i(TAG, String.format("#downloadModel [%s]", recognizerIntent)); + } + } + @Override @SuppressLint("MissingNullability") public Context createContext(@NonNull ContextParams contextParams) { @@ -410,7 +461,45 @@ public abstract class RecognitionService extends Service { } } - /** Binder of the recognition service */ + /** + * This class receives callbacks from the speech recognition service and forwards them to the + * user. An instance of this class is passed to the + * {@link RecognitionService#onCheckRecognitionSupport(Intent, SupportCallback)} method. Recognizers may call + * these methods on any thread. + */ + public static class SupportCallback { + + private final IRecognitionSupportCallback mCallback; + + private SupportCallback(IRecognitionSupportCallback callback) { + this.mCallback = callback; + } + + /** The service should call this method to notify the caller about the level of support. */ + public void onSupportResult(@NonNull RecognitionSupport recognitionSupport) { + try { + mCallback.onSupportResult(recognitionSupport); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * The service should call this method when an error occurred and can't satisfy the support + * request. + * + * @param errorCode code is defined in {@link SpeechRecognizer} + */ + public void onError(@SpeechRecognizer.RecognitionError int errorCode) { + try { + mCallback.onError(errorCode); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + +/** Binder of the recognition service */ private static final class RecognitionServiceBinder extends IRecognitionService.Stub { private final WeakReference<RecognitionService> mServiceRef; @@ -452,6 +541,27 @@ public abstract class RecognitionService extends Service { } } + @Override + public void checkRecognitionSupport( + Intent recognizerIntent, IRecognitionSupportCallback callback) { + final RecognitionService service = mServiceRef.get(); + if (service != null) { + service.mHandler.sendMessage( + Message.obtain(service.mHandler, MSG_CHECK_RECOGNITION_SUPPORT, + Pair.create(recognizerIntent, callback))); + } + } + + @Override + public void triggerModelDownload(Intent recognizerIntent) { + final RecognitionService service = mServiceRef.get(); + if (service != null) { + service.mHandler.sendMessage( + Message.obtain( + service.mHandler, MSG_TRIGGER_MODEL_DOWNLOAD, recognizerIntent)); + } + } + public void clearReference() { mServiceRef.clear(); } diff --git a/core/java/android/speech/RecognitionSupport.aidl b/core/java/android/speech/RecognitionSupport.aidl new file mode 100644 index 000000000000..20e52a8f04f5 --- /dev/null +++ b/core/java/android/speech/RecognitionSupport.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.speech; + +parcelable RecognitionSupport; diff --git a/core/java/android/speech/RecognitionSupport.java b/core/java/android/speech/RecognitionSupport.java new file mode 100644 index 000000000000..3a86d0b30639 --- /dev/null +++ b/core/java/android/speech/RecognitionSupport.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.speech; + +import android.annotation.NonNull; +import android.os.Parcelable; + +import com.android.internal.util.DataClass; + +import java.util.List; + +/** Encodes the level of support for a given speech recognition request */ +@DataClass( + genConstructor = false, + genBuilder = true, + genEqualsHashCode = true, + genHiddenConstDefs = true, + genParcelable = true, + genToString = true +) +public final class RecognitionSupport implements Parcelable { + + /** Support for this request is ready for use on this device for the returned languages. */ + @NonNull + private List<String> mInstalledLanguages = null; + + /** Support for this request is scheduled for download for the returned languages. */ + @NonNull private List<String> mPendingLanguages = null; + + /** These languages are supported but need to be downloaded before use. */ + @NonNull + private List<String> mSupportedLanguages = null; + + + + // 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/speech/RecognitionSupport.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + @DataClass.Generated.Member + /* package-private */ RecognitionSupport( + @NonNull List<String> installedLanguages, + @NonNull List<String> pendingLanguages, + @NonNull List<String> supportedLanguages) { + this.mInstalledLanguages = installedLanguages; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mInstalledLanguages); + this.mPendingLanguages = pendingLanguages; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mPendingLanguages); + this.mSupportedLanguages = supportedLanguages; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mSupportedLanguages); + + // onConstructed(); // You can define this method to get a callback + } + + /** + * Support for this request is ready for use on this device for the returned languages. + */ + @DataClass.Generated.Member + public @NonNull List<String> getInstalledLanguages() { + return mInstalledLanguages; + } + + /** + * Support for this request is scheduled for download for the returned languages. + */ + @DataClass.Generated.Member + public @NonNull List<String> getPendingLanguages() { + return mPendingLanguages; + } + + /** + * These languages are supported but need to be downloaded before use. + */ + @DataClass.Generated.Member + public @NonNull List<String> getSupportedLanguages() { + return mSupportedLanguages; + } + + @Override + @DataClass.Generated.Member + public String toString() { + // You can override field toString logic by defining methods like: + // String fieldNameToString() { ... } + + return "RecognitionSupport { " + + "installedLanguages = " + mInstalledLanguages + ", " + + "pendingLanguages = " + mPendingLanguages + ", " + + "supportedLanguages = " + mSupportedLanguages + + " }"; + } + + @Override + @DataClass.Generated.Member + public boolean equals(@android.annotation.Nullable Object o) { + // You can override field equality logic by defining either of the methods like: + // boolean fieldNameEquals(RecognitionSupport other) { ... } + // boolean fieldNameEquals(FieldType otherValue) { ... } + + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + @SuppressWarnings("unchecked") + RecognitionSupport that = (RecognitionSupport) o; + //noinspection PointlessBooleanExpression + return true + && java.util.Objects.equals(mInstalledLanguages, that.mInstalledLanguages) + && java.util.Objects.equals(mPendingLanguages, that.mPendingLanguages) + && java.util.Objects.equals(mSupportedLanguages, that.mSupportedLanguages); + } + + @Override + @DataClass.Generated.Member + public int hashCode() { + // You can override field hashCode logic by defining methods like: + // int fieldNameHashCode() { ... } + + int _hash = 1; + _hash = 31 * _hash + java.util.Objects.hashCode(mInstalledLanguages); + _hash = 31 * _hash + java.util.Objects.hashCode(mPendingLanguages); + _hash = 31 * _hash + java.util.Objects.hashCode(mSupportedLanguages); + return _hash; + } + + @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.writeStringList(mInstalledLanguages); + dest.writeStringList(mPendingLanguages); + dest.writeStringList(mSupportedLanguages); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + /* package-private */ RecognitionSupport(@NonNull android.os.Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + List<String> installedLanguages = new java.util.ArrayList<>(); + in.readStringList(installedLanguages); + List<String> pendingLanguages = new java.util.ArrayList<>(); + in.readStringList(pendingLanguages); + List<String> supportedLanguages = new java.util.ArrayList<>(); + in.readStringList(supportedLanguages); + + this.mInstalledLanguages = installedLanguages; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mInstalledLanguages); + this.mPendingLanguages = pendingLanguages; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mPendingLanguages); + this.mSupportedLanguages = supportedLanguages; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mSupportedLanguages); + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator<RecognitionSupport> CREATOR + = new Parcelable.Creator<RecognitionSupport>() { + @Override + public RecognitionSupport[] newArray(int size) { + return new RecognitionSupport[size]; + } + + @Override + public RecognitionSupport createFromParcel(@NonNull android.os.Parcel in) { + return new RecognitionSupport(in); + } + }; + + /** + * A builder for {@link RecognitionSupport} + */ + @SuppressWarnings("WeakerAccess") + @DataClass.Generated.Member + public static final class Builder { + + private @NonNull List<String> mInstalledLanguages; + private @NonNull List<String> mPendingLanguages; + private @NonNull List<String> mSupportedLanguages; + + private long mBuilderFieldsSet = 0L; + + public Builder() { + } + + /** + * Support for this request is ready for use on this device for the returned languages. + */ + @DataClass.Generated.Member + public @NonNull Builder setInstalledLanguages(@NonNull List<String> value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x1; + mInstalledLanguages = value; + return this; + } + + /** @see #setInstalledLanguages */ + @DataClass.Generated.Member + public @NonNull Builder addInstalledLanguages(@NonNull String value) { + // You can refine this method's name by providing item's singular name, e.g.: + // @DataClass.PluralOf("item")) mItems = ... + + if (mInstalledLanguages == null) setInstalledLanguages(new java.util.ArrayList<>()); + mInstalledLanguages.add(value); + return this; + } + + /** + * Support for this request is scheduled for download for the returned languages. + */ + @DataClass.Generated.Member + public @NonNull Builder setPendingLanguages(@NonNull List<String> value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x2; + mPendingLanguages = value; + return this; + } + + /** @see #setPendingLanguages */ + @DataClass.Generated.Member + public @NonNull Builder addPendingLanguages(@NonNull String value) { + // You can refine this method's name by providing item's singular name, e.g.: + // @DataClass.PluralOf("item")) mItems = ... + + if (mPendingLanguages == null) setPendingLanguages(new java.util.ArrayList<>()); + mPendingLanguages.add(value); + return this; + } + + /** + * These languages are supported but need to be downloaded before use. + */ + @DataClass.Generated.Member + public @NonNull Builder setSupportedLanguages(@NonNull List<String> value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x4; + mSupportedLanguages = value; + return this; + } + + /** @see #setSupportedLanguages */ + @DataClass.Generated.Member + public @NonNull Builder addSupportedLanguages(@NonNull String value) { + // You can refine this method's name by providing item's singular name, e.g.: + // @DataClass.PluralOf("item")) mItems = ... + + if (mSupportedLanguages == null) setSupportedLanguages(new java.util.ArrayList<>()); + mSupportedLanguages.add(value); + return this; + } + + /** Builds the instance. This builder should not be touched after calling this! */ + public @NonNull RecognitionSupport build() { + checkNotUsed(); + mBuilderFieldsSet |= 0x8; // Mark builder used + + if ((mBuilderFieldsSet & 0x1) == 0) { + mInstalledLanguages = null; + } + if ((mBuilderFieldsSet & 0x2) == 0) { + mPendingLanguages = null; + } + if ((mBuilderFieldsSet & 0x4) == 0) { + mSupportedLanguages = null; + } + RecognitionSupport o = new RecognitionSupport( + mInstalledLanguages, + mPendingLanguages, + mSupportedLanguages); + return o; + } + + private void checkNotUsed() { + if ((mBuilderFieldsSet & 0x8) != 0) { + throw new IllegalStateException( + "This Builder should not be reused. Use a new Builder instance instead"); + } + } + } + + @DataClass.Generated( + time = 1639158640137L, + codegenVersion = "1.0.23", + sourceFile = "frameworks/base/core/java/android/speech/RecognitionSupport.java", + inputSignatures = "private @android.annotation.NonNull java.util.List<java.lang.String> mInstalledLanguages\nprivate @android.annotation.NonNull java.util.List<java.lang.String> mPendingLanguages\nprivate @android.annotation.NonNull java.util.List<java.lang.String> mSupportedLanguages\nclass RecognitionSupport extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/core/java/android/speech/RecognitionSupportCallback.java b/core/java/android/speech/RecognitionSupportCallback.java new file mode 100644 index 000000000000..9278e719ac81 --- /dev/null +++ b/core/java/android/speech/RecognitionSupportCallback.java @@ -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 android.speech; + +import android.annotation.NonNull; + +/** + * Used for receiving notifications from the SpeechRecognizer about the device support status for + * the given recognition request. + */ +public interface RecognitionSupportCallback { + + /** Notifies the caller about the support for the given request. */ + void onSupportResult(@NonNull RecognitionSupport recognitionSupport); + + /** Notifies the caller about an error during the recognition support request */ + void onError(@SpeechRecognizer.RecognitionError int error); +} diff --git a/core/java/android/speech/SpeechRecognizer.java b/core/java/android/speech/SpeechRecognizer.java index 3cdd8b8d8436..71c1e882a1f6 100644 --- a/core/java/android/speech/SpeechRecognizer.java +++ b/core/java/android/speech/SpeechRecognizer.java @@ -38,6 +38,7 @@ import android.os.ServiceManager; import android.provider.Settings; import android.text.TextUtils; import android.util.Log; +import android.util.Pair; import android.util.Slog; import com.android.internal.R; @@ -46,6 +47,7 @@ import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; +import java.util.Objects; import java.util.Queue; import java.util.concurrent.LinkedBlockingQueue; @@ -112,7 +114,8 @@ public class SpeechRecognizer { ERROR_TOO_MANY_REQUESTS, ERROR_SERVER_DISCONNECTED, ERROR_LANGUAGE_NOT_SUPPORTED, - ERROR_LANGUAGE_UNAVAILABLE + ERROR_LANGUAGE_UNAVAILABLE, + ERROR_CANNOT_CHECK_SUPPORT, }) public @interface RecognitionError {} @@ -155,19 +158,24 @@ public class SpeechRecognizer { /** Requested language is supported, but not available currently (e.g. not downloaded yet). */ public static final int ERROR_LANGUAGE_UNAVAILABLE = 13; + /** The service does not allow to check for support. */ + public static final int ERROR_CANNOT_CHECK_SUPPORT = 14; + /** action codes */ private static final int MSG_START = 1; private static final int MSG_STOP = 2; private static final int MSG_CANCEL = 3; private static final int MSG_CHANGE_LISTENER = 4; private static final int MSG_SET_TEMPORARY_ON_DEVICE_COMPONENT = 5; + private static final int MSG_CHECK_RECOGNITION_SUPPORT = 6; + private static final int MSG_TRIGGER_MODEL_DOWNLOAD = 7; /** The actual RecognitionService endpoint */ private IRecognitionService mService; /** Context with which the manager was created */ private final Context mContext; - + /** Component to direct service intent to */ private final ComponentName mServiceComponent; @@ -197,6 +205,15 @@ public class SpeechRecognizer { case MSG_SET_TEMPORARY_ON_DEVICE_COMPONENT: handleSetTemporaryComponent((ComponentName) msg.obj); break; + case MSG_CHECK_RECOGNITION_SUPPORT: + Pair<Intent, RecognitionSupportCallback> intentAndListener = + (Pair<Intent, RecognitionSupportCallback>) msg.obj; + handleCheckRecognitionSupport( + intentAndListener.first, intentAndListener.second); + break; + case MSG_TRIGGER_MODEL_DOWNLOAD: + handleTriggerModelDownload((Intent) msg.obj); + break; } } }; @@ -208,7 +225,7 @@ public class SpeechRecognizer { private final Queue<Message> mPendingTasks = new LinkedBlockingQueue<>(); /** The Listener that will receive all the callbacks */ - private final InternalListener mListener = new InternalListener(); + private final InternalRecognitionListener mListener = new InternalRecognitionListener(); private final IBinder mClientToken = new Binder(); @@ -465,6 +482,38 @@ public class SpeechRecognizer { } /** + * Checks whether {@code recognizerIntent} is supported by + * {@link SpeechRecognizer#startListening(Intent)}. + * + * @param recognizerIntent contains parameters for the recognition to be performed. The intent + * may also contain optional extras. See {@link RecognizerIntent} for the list of + * supported extras, any unlisted extra might be ignored. + * @param supportListener the listener on which to receive the support query results. + */ + public void checkRecognitionSupport( + @NonNull Intent recognizerIntent, + @NonNull RecognitionSupportCallback supportListener) { + Objects.requireNonNull(recognizerIntent, "intent must not be null"); + Objects.requireNonNull(supportListener, "listener must not be null"); + + putMessage(Message.obtain(mHandler, MSG_CHECK_RECOGNITION_SUPPORT, + Pair.create(recognizerIntent, supportListener))); + } + + /** + * Attempts to download the support for the given {@code recognizerIntent}. This might trigger + * user interaction to approve the download. Callers can verify the status of the request via + * {@link #checkRecognitionSupport(Intent, RecognitionSupportCallback)}. + * + * @param recognizerIntent contains parameters for the recognition to be performed. The intent + * may also contain optional extras, see {@link RecognizerIntent}. + */ + public void triggerModelDownload(@NonNull Intent recognizerIntent) { + Objects.requireNonNull(recognizerIntent, "intent must not be null"); + putMessage(Message.obtain(mHandler, MSG_TRIGGER_MODEL_DOWNLOAD)); + } + + /** * Sets a temporary component to power on-device speech recognizer. * * <p>This is only expected to be called in tests, system would reject calls from client apps. @@ -503,7 +552,7 @@ public class SpeechRecognizer { } try { mService.startListening(recognizerIntent, mListener, mContext.getAttributionSource()); - if (DBG) Log.d(TAG, "service start listening command succeded"); + if (DBG) Log.d(TAG, "service start listening command succeeded"); } catch (final RemoteException e) { Log.e(TAG, "startListening() failed", e); mListener.onError(ERROR_CLIENT); @@ -517,7 +566,7 @@ public class SpeechRecognizer { } try { mService.stopListening(mListener); - if (DBG) Log.d(TAG, "service stop listening command succeded"); + if (DBG) Log.d(TAG, "service stop listening command succeeded"); } catch (final RemoteException e) { Log.e(TAG, "stopListening() failed", e); mListener.onError(ERROR_CLIENT); @@ -531,7 +580,7 @@ public class SpeechRecognizer { } try { mService.cancel(mListener, /*isShutdown*/ false); - if (DBG) Log.d(TAG, "service cancel command succeded"); + if (DBG) Log.d(TAG, "service cancel command succeeded"); } catch (final RemoteException e) { Log.e(TAG, "cancel() failed", e); mListener.onError(ERROR_CLIENT); @@ -554,6 +603,35 @@ public class SpeechRecognizer { } } + private void handleCheckRecognitionSupport( + Intent recognizerIntent, RecognitionSupportCallback recognitionSupportCallback) { + if (!maybeInitializeManagerService()) { + return; + } + try { + mService.checkRecognitionSupport( + recognizerIntent, + new InternalSupportCallback(recognitionSupportCallback)); + if (DBG) Log.d(TAG, "service support command succeeded"); + } catch (final RemoteException e) { + Log.e(TAG, "checkRecognitionSupport() failed", e); + mListener.onError(ERROR_CLIENT); + } + } + + private void handleTriggerModelDownload(Intent recognizerIntent) { + if (!maybeInitializeManagerService()) { + return; + } + try { + mService.triggerModelDownload(recognizerIntent); + if (DBG) Log.d(TAG, "service download support command succeeded"); + } catch (final RemoteException e) { + Log.e(TAG, "downloadModel() failed", e); + mListener.onError(ERROR_CLIENT); + } + } + private boolean checkOpenConnection() { if (mService != null) { return true; @@ -626,7 +704,7 @@ public class SpeechRecognizer { } } - private boolean maybeInitializeManagerService() { + private synchronized boolean maybeInitializeManagerService() { if (mManagerService != null) { return true; } @@ -678,7 +756,7 @@ public class SpeechRecognizer { * Internal wrapper of IRecognitionListener which will propagate the results to * RecognitionListener */ - private static class InternalListener extends IRecognitionListener.Stub { + private static class InternalRecognitionListener extends IRecognitionListener.Stub { private RecognitionListener mInternalListener; private static final int MSG_BEGINNING_OF_SPEECH = 1; @@ -766,4 +844,42 @@ public class SpeechRecognizer { .sendToTarget(); } } + + private static class InternalSupportCallback extends IRecognitionSupportCallback.Stub { + private final RecognitionSupportCallback mCallback; + + private static final int MSG_SUPPORT_RESULT = 1; + private static final int MSG_ERROR = 2; + + private final Handler mInternalHandler = new Handler(Looper.getMainLooper()) { + @Override + public void handleMessage(Message msg) { + if (mCallback == null) { + return; + } + switch (msg.what) { + case MSG_SUPPORT_RESULT: + mCallback.onSupportResult((RecognitionSupport) msg.obj); + break; + case MSG_ERROR: + mCallback.onError((Integer) msg.obj); + break; + } + } + }; + + private InternalSupportCallback(RecognitionSupportCallback callback) { + this.mCallback = callback; + } + + @Override + public void onSupportResult(RecognitionSupport recognitionSupport) throws RemoteException { + Message.obtain(mInternalHandler, MSG_SUPPORT_RESULT, recognitionSupport).sendToTarget(); + } + + @Override + public void onError(int errorCode) throws RemoteException { + Message.obtain(mInternalHandler, MSG_ERROR, errorCode).sendToTarget(); + } + } } 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/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java index 6a3c6182b96d..748d55123b9a 100644 --- a/core/java/android/text/MeasuredParagraph.java +++ b/core/java/android/text/MeasuredParagraph.java @@ -22,6 +22,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.Paint; import android.graphics.Rect; +import android.graphics.text.LineBreakConfig; import android.graphics.text.MeasuredText; import android.text.AutoGrowArray.ByteArray; import android.text.AutoGrowArray.FloatArray; @@ -124,7 +125,7 @@ public class MeasuredParagraph { // The native MeasuredParagraph. private @Nullable MeasuredText mMeasuredText; - // Following two objects are for avoiding object allocation. + // Following three objects are for avoiding object allocation. private @NonNull TextPaint mCachedPaint = new TextPaint(); private @Nullable Paint.FontMetricsInt mCachedFm; @@ -350,7 +351,8 @@ public class MeasuredParagraph { if (mt.mSpanned == null) { // No style change by MetricsAffectingSpan. Just measure all text. mt.applyMetricsAffectingSpan( - paint, null /* spans */, start, end, null /* native builder ptr */); + paint, null /* lineBreakConfig */, null /* spans */, start, end, + null /* native builder ptr */); } else { // There may be a MetricsAffectingSpan. Split into span transitions and apply styles. int spanEnd; @@ -360,7 +362,8 @@ public class MeasuredParagraph { MetricAffectingSpan.class); spans = TextUtils.removeEmptySpans(spans, mt.mSpanned, MetricAffectingSpan.class); mt.applyMetricsAffectingSpan( - paint, spans, spanStart, spanEnd, null /* native builder ptr */); + paint, null /* line break config */, spans, spanStart, spanEnd, + null /* native builder ptr */); } } return mt; @@ -373,6 +376,7 @@ public class MeasuredParagraph { * result to recycle and returns recycle. * * @param paint the paint to be used for rendering the text. + * @param lineBreakConfig the line break configuration for text wrapping. * @param text the character sequence to be measured * @param start the inclusive start offset of the target region in the text * @param end the exclusive end offset of the target region in the text @@ -386,6 +390,7 @@ public class MeasuredParagraph { */ public static @NonNull MeasuredParagraph buildForStaticLayout( @NonNull TextPaint paint, + @Nullable LineBreakConfig lineBreakConfig, @NonNull CharSequence text, @IntRange(from = 0) int start, @IntRange(from = 0) int end, @@ -411,7 +416,8 @@ public class MeasuredParagraph { } else { if (mt.mSpanned == null) { // No style change by MetricsAffectingSpan. Just measure all text. - mt.applyMetricsAffectingSpan(paint, null /* spans */, start, end, builder); + mt.applyMetricsAffectingSpan(paint, lineBreakConfig, null /* spans */, start, end, + builder); mt.mSpanEndCache.append(end); } else { // There may be a MetricsAffectingSpan. Split into span transitions and apply @@ -424,7 +430,9 @@ public class MeasuredParagraph { MetricAffectingSpan.class); spans = TextUtils.removeEmptySpans(spans, mt.mSpanned, MetricAffectingSpan.class); - mt.applyMetricsAffectingSpan(paint, spans, spanStart, spanEnd, builder); + // TODO: Update line break config with spans. + mt.applyMetricsAffectingSpan(paint, lineBreakConfig, spans, spanStart, spanEnd, + builder); mt.mSpanEndCache.append(spanEnd); } } @@ -500,12 +508,13 @@ public class MeasuredParagraph { private void applyReplacementRun(@NonNull ReplacementSpan replacement, @IntRange(from = 0) int start, // inclusive, in copied buffer @IntRange(from = 0) int end, // exclusive, in copied buffer + @NonNull TextPaint paint, @Nullable MeasuredText.Builder builder) { // Use original text. Shouldn't matter. // TODO: passing uninitizlied FontMetrics to developers. Do we need to keep this for // backward compatibility? or Should we initialize them for getFontMetricsInt? final float width = replacement.getSize( - mCachedPaint, mSpanned, start + mTextStart, end + mTextStart, mCachedFm); + paint, mSpanned, start + mTextStart, end + mTextStart, mCachedFm); if (builder == null) { // Assigns all width to the first character. This is the same behavior as minikin. mWidths.set(start, width); @@ -514,22 +523,24 @@ public class MeasuredParagraph { } mWholeWidth += width; } else { - builder.appendReplacementRun(mCachedPaint, end - start, width); + builder.appendReplacementRun(paint, end - start, width); } } private void applyStyleRun(@IntRange(from = 0) int start, // inclusive, in copied buffer @IntRange(from = 0) int end, // exclusive, in copied buffer + @NonNull TextPaint paint, + @Nullable LineBreakConfig config, @Nullable MeasuredText.Builder builder) { if (mLtrWithoutBidi) { // If the whole text is LTR direction, just apply whole region. if (builder == null) { - mWholeWidth += mCachedPaint.getTextRunAdvances( + mWholeWidth += paint.getTextRunAdvances( mCopiedBuffer, start, end - start, start, end - start, false /* isRtl */, mWidths.getRawArray(), start); } else { - builder.appendStyleRun(mCachedPaint, end - start, false /* isRtl */); + builder.appendStyleRun(paint, config, end - start, false /* isRtl */); } } else { // If there is multiple bidi levels, split into individual bidi level and apply style. @@ -541,11 +552,11 @@ public class MeasuredParagraph { final boolean isRtl = (level & 0x1) != 0; if (builder == null) { final int levelLength = levelEnd - levelStart; - mWholeWidth += mCachedPaint.getTextRunAdvances( + mWholeWidth += paint.getTextRunAdvances( mCopiedBuffer, levelStart, levelLength, levelStart, levelLength, isRtl, mWidths.getRawArray(), levelStart); } else { - builder.appendStyleRun(mCachedPaint, levelEnd - levelStart, isRtl); + builder.appendStyleRun(paint, config, levelEnd - levelStart, isRtl); } if (levelEnd == end) { break; @@ -559,6 +570,7 @@ public class MeasuredParagraph { private void applyMetricsAffectingSpan( @NonNull TextPaint paint, + @Nullable LineBreakConfig lineBreakConfig, @Nullable MetricAffectingSpan[] spans, @IntRange(from = 0) int start, // inclusive, in original text buffer @IntRange(from = 0) int end, // exclusive, in original text buffer @@ -595,9 +607,11 @@ public class MeasuredParagraph { } if (replacement != null) { - applyReplacementRun(replacement, startInCopiedBuffer, endInCopiedBuffer, builder); + applyReplacementRun(replacement, startInCopiedBuffer, endInCopiedBuffer, mCachedPaint, + builder); } else { - applyStyleRun(startInCopiedBuffer, endInCopiedBuffer, builder); + applyStyleRun(startInCopiedBuffer, endInCopiedBuffer, mCachedPaint, + lineBreakConfig, builder); } if (needFontMetrics) { diff --git a/core/java/android/text/PrecomputedText.java b/core/java/android/text/PrecomputedText.java index 152570ffd1c7..ce63376a6d63 100644 --- a/core/java/android/text/PrecomputedText.java +++ b/core/java/android/text/PrecomputedText.java @@ -22,6 +22,7 @@ import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.Rect; +import android.graphics.text.LineBreakConfig; import android.graphics.text.MeasuredText; import android.text.style.MetricAffectingSpan; @@ -96,6 +97,9 @@ public class PrecomputedText implements Spannable { // The hyphenation frequency for this measured text. private final @Layout.HyphenationFrequency int mHyphenationFrequency; + // The line break configuration for calculating text wrapping. + private final @Nullable LineBreakConfig mLineBreakConfig; + /** * A builder for creating {@link Params}. */ @@ -113,6 +117,9 @@ public class PrecomputedText implements Spannable { private @Layout.HyphenationFrequency int mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NORMAL; + // The line break configuration for calculating text wrapping. + private @Nullable LineBreakConfig mLineBreakConfig; + /** * Builder constructor. * @@ -130,6 +137,7 @@ public class PrecomputedText implements Spannable { mTextDir = params.mTextDir; mBreakStrategy = params.mBreakStrategy; mHyphenationFrequency = params.mHyphenationFrequency; + mLineBreakConfig = params.mLineBreakConfig; } /** @@ -177,24 +185,41 @@ public class PrecomputedText implements Spannable { } /** + * Set the line break config for the text wrapping. + * + * @param lineBreakConfig the newly line break configuration. + * @return this builder, useful for chaining. + * @see StaticLayout.Builder#setLineBreakConfig + */ + public @NonNull Builder setLineBreakConfig(@NonNull LineBreakConfig lineBreakConfig) { + mLineBreakConfig = lineBreakConfig; + return this; + } + + /** * Build the {@link Params}. * * @return the layout parameter */ public @NonNull Params build() { - return new Params(mPaint, mTextDir, mBreakStrategy, mHyphenationFrequency); + return new Params(mPaint, mLineBreakConfig, mTextDir, mBreakStrategy, + mHyphenationFrequency); } } // This is public hidden for internal use. // For the external developers, use Builder instead. /** @hide */ - public Params(@NonNull TextPaint paint, @NonNull TextDirectionHeuristic textDir, - @Layout.BreakStrategy int strategy, @Layout.HyphenationFrequency int frequency) { + public Params(@NonNull TextPaint paint, + @Nullable LineBreakConfig lineBreakConfig, + @NonNull TextDirectionHeuristic textDir, + @Layout.BreakStrategy int strategy, + @Layout.HyphenationFrequency int frequency) { mPaint = paint; mTextDir = textDir; mBreakStrategy = strategy; mHyphenationFrequency = frequency; + mLineBreakConfig = lineBreakConfig; } /** @@ -233,6 +258,15 @@ public class PrecomputedText implements Spannable { return mHyphenationFrequency; } + /** + * Return the line break configuration for this text. + * + * @return the current line break configuration, null if no line break configuration is set. + */ + public @Nullable LineBreakConfig getLineBreakConfig() { + return mLineBreakConfig; + } + /** @hide */ @IntDef(value = { UNUSABLE, NEED_RECOMPUTE, USABLE }) @Retention(RetentionPolicy.SOURCE) @@ -262,8 +296,9 @@ public class PrecomputedText implements Spannable { /** @hide */ public @CheckResultUsableResult int checkResultUsable(@NonNull TextPaint paint, @NonNull TextDirectionHeuristic textDir, @Layout.BreakStrategy int strategy, - @Layout.HyphenationFrequency int frequency) { + @Layout.HyphenationFrequency int frequency, @Nullable LineBreakConfig lbConfig) { if (mBreakStrategy == strategy && mHyphenationFrequency == frequency + && isLineBreakEquals(mLineBreakConfig, lbConfig) && mPaint.equalsForTextMeasurement(paint)) { return mTextDir == textDir ? USABLE : NEED_RECOMPUTE; } else { @@ -272,6 +307,29 @@ public class PrecomputedText implements Spannable { } /** + * Check the two LineBreakConfig instances are equal. + * This method assumes they are equal if one parameter is null and the other parameter has + * a LineBreakStyle value of LineBreakConfig.LINE_BREAK_STYLE_NONE. + * + * @param o1 the first LineBreakConfig instance. + * @param o2 the second LineBreakConfig instance. + * @return true if the two LineBreakConfig instances are equal. + */ + private boolean isLineBreakEquals(LineBreakConfig o1, LineBreakConfig o2) { + if (Objects.equals(o1, o2)) { + return true; + } + if (o1 == null && (o2 != null + && o2.getLineBreakStyle() == LineBreakConfig.LINE_BREAK_STYLE_NONE)) { + return true; + } else if (o2 == null && (o1 != null + && o1.getLineBreakStyle() == LineBreakConfig.LINE_BREAK_STYLE_NONE)) { + return true; + } + return false; + } + + /** * Check if the same text layout. * * @return true if this and the given param result in the same text layout @@ -286,21 +344,25 @@ public class PrecomputedText implements Spannable { } Params param = (Params) o; return checkResultUsable(param.mPaint, param.mTextDir, param.mBreakStrategy, - param.mHyphenationFrequency) == Params.USABLE; + param.mHyphenationFrequency, param.mLineBreakConfig) == Params.USABLE; } @Override public int hashCode() { // TODO: implement MinikinPaint::hashCode and use it to keep consistency with equals. + int lineBreakStyle = (mLineBreakConfig != null) + ? mLineBreakConfig.getLineBreakStyle() : LineBreakConfig.LINE_BREAK_STYLE_NONE; return Objects.hash(mPaint.getTextSize(), mPaint.getTextScaleX(), mPaint.getTextSkewX(), mPaint.getLetterSpacing(), mPaint.getWordSpacing(), mPaint.getFlags(), mPaint.getTextLocales(), mPaint.getTypeface(), mPaint.getFontVariationSettings(), mPaint.isElegantTextHeight(), mTextDir, - mBreakStrategy, mHyphenationFrequency); + mBreakStrategy, mHyphenationFrequency, lineBreakStyle); } @Override public String toString() { + int lineBreakStyle = (mLineBreakConfig != null) + ? mLineBreakConfig.getLineBreakStyle() : LineBreakConfig.LINE_BREAK_STYLE_NONE; return "{" + "textSize=" + mPaint.getTextSize() + ", textScaleX=" + mPaint.getTextScaleX() @@ -313,6 +375,7 @@ public class PrecomputedText implements Spannable { + ", textDir=" + mTextDir + ", breakStrategy=" + mBreakStrategy + ", hyphenationFrequency=" + mHyphenationFrequency + + ", lineBreakStyle=" + lineBreakStyle + "}"; } }; @@ -369,7 +432,8 @@ public class PrecomputedText implements Spannable { final PrecomputedText.Params hintParams = hintPct.getParams(); final @Params.CheckResultUsableResult int checkResult = hintParams.checkResultUsable(params.mPaint, params.mTextDir, - params.mBreakStrategy, params.mHyphenationFrequency); + params.mBreakStrategy, params.mHyphenationFrequency, + params.mLineBreakConfig); switch (checkResult) { case Params.USABLE: return hintPct; @@ -418,9 +482,9 @@ public class PrecomputedText implements Spannable { final int paraStart = pct.getParagraphStart(i); final int paraEnd = pct.getParagraphEnd(i); result.add(new ParagraphInfo(paraEnd, MeasuredParagraph.buildForStaticLayout( - params.getTextPaint(), pct, paraStart, paraEnd, params.getTextDirection(), - hyphenationMode, computeLayout, pct.getMeasuredParagraph(i), - null /* no recycle */))); + params.getTextPaint(), params.getLineBreakConfig(), pct, paraStart, paraEnd, + params.getTextDirection(), hyphenationMode, computeLayout, + pct.getMeasuredParagraph(i), null /* no recycle */))); } return result.toArray(new ParagraphInfo[result.size()]); } @@ -456,8 +520,9 @@ public class PrecomputedText implements Spannable { } result.add(new ParagraphInfo(paraEnd, MeasuredParagraph.buildForStaticLayout( - params.getTextPaint(), text, paraStart, paraEnd, params.getTextDirection(), - hyphenationMode, computeLayout, null /* no hint */, null /* no recycle */))); + params.getTextPaint(), params.getLineBreakConfig(), text, paraStart, paraEnd, + params.getTextDirection(), hyphenationMode, computeLayout, null /* no hint */, + null /* no recycle */))); } return result.toArray(new ParagraphInfo[result.size()]); } @@ -544,11 +609,11 @@ public class PrecomputedText implements Spannable { public @Params.CheckResultUsableResult int checkResultUsable(@IntRange(from = 0) int start, @IntRange(from = 0) int end, @NonNull TextDirectionHeuristic textDir, @NonNull TextPaint paint, @Layout.BreakStrategy int strategy, - @Layout.HyphenationFrequency int frequency) { + @Layout.HyphenationFrequency int frequency, @NonNull LineBreakConfig lbConfig) { if (mStart != start || mEnd != end) { return Params.UNUSABLE; } else { - return mParams.checkResultUsable(paint, textDir, strategy, frequency); + return mParams.checkResultUsable(paint, textDir, strategy, frequency, lbConfig); } } diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index b1bc7667da16..b10fc37bff2f 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -22,6 +22,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.graphics.Paint; +import android.graphics.text.LineBreakConfig; import android.graphics.text.LineBreaker; import android.os.Build; import android.text.style.LeadingMarginSpan; @@ -403,6 +404,21 @@ public class StaticLayout extends Layout { } /** + * Set the line break configuration. The line break will be passed to native used for + * calculating the text wrapping. The default value of the line break style is + * {@link LineBreakConfig#LINE_BREAK_STYLE_NONE} + * + * @param lineBreakConfig the line break configuration for text wrapping. + * @return this builder, useful for chaining. + * @see android.widget.TextView#setLineBreakConfig + */ + @NonNull + public Builder setLineBreakConfig(@NonNull LineBreakConfig lineBreakConfig) { + mLineBreakConfig = lineBreakConfig; + return this; + } + + /** * Build the {@link StaticLayout} after options have been set. * * <p>Note: the builder object must not be reused in any way after calling this @@ -438,6 +454,7 @@ public class StaticLayout extends Layout { @Nullable private int[] mRightIndents; private int mJustificationMode; private boolean mAddLastLineLineSpacing; + private LineBreakConfig mLineBreakConfig; private final Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt(); @@ -670,7 +687,7 @@ public class StaticLayout extends Layout { PrecomputedText precomputed = (PrecomputedText) source; final @PrecomputedText.Params.CheckResultUsableResult int checkResult = precomputed.checkResultUsable(bufStart, bufEnd, textDir, paint, - b.mBreakStrategy, b.mHyphenationFrequency); + b.mBreakStrategy, b.mHyphenationFrequency, b.mLineBreakConfig); switch (checkResult) { case PrecomputedText.Params.UNUSABLE: break; @@ -680,6 +697,7 @@ public class StaticLayout extends Layout { .setBreakStrategy(b.mBreakStrategy) .setHyphenationFrequency(b.mHyphenationFrequency) .setTextDirection(textDir) + .setLineBreakConfig(b.mLineBreakConfig) .build(); precomputed = PrecomputedText.create(precomputed, newParams); paragraphInfo = precomputed.getParagraphInfo(); @@ -692,8 +710,8 @@ public class StaticLayout extends Layout { } if (paragraphInfo == null) { - final PrecomputedText.Params param = new PrecomputedText.Params(paint, textDir, - b.mBreakStrategy, b.mHyphenationFrequency); + final PrecomputedText.Params param = new PrecomputedText.Params(paint, + b.mLineBreakConfig, textDir, b.mBreakStrategy, b.mHyphenationFrequency); paragraphInfo = PrecomputedText.createMeasuredParagraphs(source, param, bufStart, bufEnd, false /* computeLayout */); } 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/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/util/apk/ApkSignatureVerifier.java b/core/java/android/util/apk/ApkSignatureVerifier.java index 41b749eb5f40..bff5426f9c64 100644 --- a/core/java/android/util/apk/ApkSignatureVerifier.java +++ b/core/java/android/util/apk/ApkSignatureVerifier.java @@ -27,7 +27,7 @@ import static android.util.apk.ApkSignatureSchemeV4Verifier.APK_SIGNATURE_SCHEME import android.content.pm.Signature; import android.content.pm.SigningDetails; import android.content.pm.SigningDetails.SignatureSchemeVersion; -import android.content.pm.parsing.ParsingPackageUtils; +import android.content.pm.parsing.ApkLiteParseUtils; import android.content.pm.parsing.result.ParseInput; import android.content.pm.parsing.result.ParseResult; import android.os.Build; @@ -380,7 +380,7 @@ public class ApkSignatureVerifier { // Gather certs from AndroidManifest.xml, which every APK must have, as an optimization // to not need to verify the whole APK when verifyFUll == false. final ZipEntry manifestEntry = jarFile.findEntry( - ParsingPackageUtils.ANDROID_MANIFEST_FILENAME); + ApkLiteParseUtils.ANDROID_MANIFEST_FILENAME); if (manifestEntry == null) { return input.error(INSTALL_PARSE_FAILED_BAD_MANIFEST, "Package " + apkPath + " has no manifest"); @@ -394,7 +394,7 @@ public class ApkSignatureVerifier { if (ArrayUtils.isEmpty(lastCerts)) { return input.error(INSTALL_PARSE_FAILED_NO_CERTIFICATES, "Package " + apkPath + " has no certificates at entry " - + ParsingPackageUtils.ANDROID_MANIFEST_FILENAME); + + ApkLiteParseUtils.ANDROID_MANIFEST_FILENAME); } lastSigs = convertToSignatures(lastCerts); @@ -407,7 +407,7 @@ public class ApkSignatureVerifier { final String entryName = entry.getName(); if (entryName.startsWith("META-INF/")) continue; - if (entryName.equals(ParsingPackageUtils.ANDROID_MANIFEST_FILENAME)) continue; + if (entryName.equals(ApkLiteParseUtils.ANDROID_MANIFEST_FILENAME)) continue; toVerify.add(entry); } diff --git a/core/java/android/view/BatchedInputEventReceiver.java b/core/java/android/view/BatchedInputEventReceiver.java index 1ed12f74ba2c..e679f2998ca1 100644 --- a/core/java/android/view/BatchedInputEventReceiver.java +++ b/core/java/android/view/BatchedInputEventReceiver.java @@ -78,7 +78,7 @@ public class BatchedInputEventReceiver extends InputEventReceiver { } } - void doConsumeBatchedInput(long frameTimeNanos) { + protected void doConsumeBatchedInput(long frameTimeNanos) { if (mBatchedInputScheduled) { mBatchedInputScheduled = false; if (consumeBatchedInputEvents(frameTimeNanos) && frameTimeNanos != -1) { @@ -114,4 +114,38 @@ public class BatchedInputEventReceiver extends InputEventReceiver { } } private final BatchedInputRunnable mBatchedInputRunnable = new BatchedInputRunnable(); + + /** + * A {@link BatchedInputEventReceiver} that reports events to an {@link InputEventListener}. + * @hide + */ + public static class SimpleBatchedInputEventReceiver extends BatchedInputEventReceiver { + + /** @hide */ + public interface InputEventListener { + /** + * Process the input event. + * @return handled + */ + boolean onInputEvent(InputEvent event); + } + + protected InputEventListener mListener; + + public SimpleBatchedInputEventReceiver(InputChannel inputChannel, Looper looper, + Choreographer choreographer, InputEventListener listener) { + super(inputChannel, looper, choreographer); + mListener = listener; + } + + @Override + public void onInputEvent(InputEvent event) { + boolean handled = false; + try { + handled = mListener.onInputEvent(event); + } finally { + finishInputEvent(event, handled); + } + } + } } diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java index be172f748b55..b8eb6027b09c 100644 --- a/core/java/android/view/Choreographer.java +++ b/core/java/android/view/Choreographer.java @@ -19,6 +19,9 @@ package android.view; import static android.view.DisplayEventReceiver.VSYNC_SOURCE_APP; import static android.view.DisplayEventReceiver.VSYNC_SOURCE_SURFACE_FLINGER; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.graphics.FrameInfo; @@ -151,10 +154,15 @@ public final class Choreographer { private static final int MSG_DO_SCHEDULE_VSYNC = 1; private static final int MSG_DO_SCHEDULE_CALLBACK = 2; - // All frame callbacks posted by applications have this token. + // All frame callbacks posted by applications have this token or EXTENDED_FRAME_CALLBACK_TOKEN. private static final Object FRAME_CALLBACK_TOKEN = new Object() { public String toString() { return "FRAME_CALLBACK_TOKEN"; } }; + private static final Object EXTENDED_FRAME_CALLBACK_TOKEN = new Object() { + public String toString() { + return "EXTENDED_FRAME_CALLBACK_TOKEN"; + } + }; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private final Object mLock = new Object(); @@ -484,6 +492,24 @@ public final class Choreographer { } /** + * Posts an extended frame callback to run on the next frame. + * <p> + * The callback runs once then is automatically removed. + * </p> + * + * @param callback The extended frame callback to run during the next frame. + * + * @see #removeExtendedFrameCallback + */ + public void postExtendedFrameCallback(@NonNull ExtendedFrameCallback callback) { + if (callback == null) { + throw new IllegalArgumentException("callback must not be null"); + } + + postCallbackDelayedInternal(CALLBACK_ANIMATION, callback, EXTENDED_FRAME_CALLBACK_TOKEN, 0); + } + + /** * Removes callbacks that have the specified action and token. * * @param callbackType The callback type. @@ -573,6 +599,21 @@ public final class Choreographer { } /** + * Removes a previously posted extended frame callback. + * + * @param callback The extended frame callback to remove. + * + * @see #postExtendedFrameCallback + */ + public void removeExtendedFrameCallback(@Nullable ExtendedFrameCallback callback) { + if (callback == null) { + throw new IllegalArgumentException("callback must not be null"); + } + + removeCallbacksInternal(CALLBACK_ANIMATION, callback, EXTENDED_FRAME_CALLBACK_TOKEN); + } + + /** * Gets the time when the current frame started. * <p> * This method provides the time in milliseconds when the frame started being rendered. @@ -673,7 +714,7 @@ public final class Choreographer { * @hide */ public long getVsyncId() { - return mLastVsyncEventData.id; + return mLastVsyncEventData.preferredFrameTimeline().vsyncId; } /** @@ -684,7 +725,7 @@ public final class Choreographer { * @hide */ public long getFrameDeadline() { - return mLastVsyncEventData.frameDeadline; + return mLastVsyncEventData.preferredFrameTimeline().deadline; } void setFPSDivisor(int divisor) { @@ -705,8 +746,9 @@ public final class Choreographer { try { if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, - "Choreographer#doFrame " + vsyncEventData.id); + "Choreographer#doFrame " + vsyncEventData.preferredFrameTimeline().vsyncId); } + FrameData frameData = new FrameData(frameTimeNanos, vsyncEventData); synchronized (mLock) { if (!mFrameScheduled) { traceMessage("Frame not scheduled"); @@ -737,6 +779,7 @@ public final class Choreographer { + "time to " + (lastFrameOffset * 0.000001f) + " ms in the past."); } frameTimeNanos = startNanos - lastFrameOffset; + frameData.setFrameTimeNanos(frameTimeNanos); } if (frameTimeNanos < mLastFrameTimeNanos) { @@ -758,8 +801,10 @@ public final class Choreographer { } } - mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos, vsyncEventData.id, - vsyncEventData.frameDeadline, startNanos, vsyncEventData.frameInterval); + mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos, + vsyncEventData.preferredFrameTimeline().vsyncId, + vsyncEventData.preferredFrameTimeline().deadline, startNanos, + vsyncEventData.frameInterval); mFrameScheduled = false; mLastFrameTimeNanos = frameTimeNanos; mLastFrameIntervalNanos = frameIntervalNanos; @@ -769,17 +814,17 @@ public final class Choreographer { AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS); mFrameInfo.markInputHandlingStart(); - doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos, frameIntervalNanos); + doCallbacks(Choreographer.CALLBACK_INPUT, frameData, frameIntervalNanos); mFrameInfo.markAnimationsStart(); - doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos, frameIntervalNanos); - doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos, + doCallbacks(Choreographer.CALLBACK_ANIMATION, frameData, frameIntervalNanos); + doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameData, frameIntervalNanos); mFrameInfo.markPerformTraversalsStart(); - doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos, frameIntervalNanos); + doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameData, frameIntervalNanos); - doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos, frameIntervalNanos); + doCallbacks(Choreographer.CALLBACK_COMMIT, frameData, frameIntervalNanos); } finally { AnimationUtils.unlockAnimationClock(); Trace.traceEnd(Trace.TRACE_TAG_VIEW); @@ -793,8 +838,9 @@ public final class Choreographer { } } - void doCallbacks(int callbackType, long frameTimeNanos, long frameIntervalNanos) { + void doCallbacks(int callbackType, FrameData frameData, long frameIntervalNanos) { CallbackRecord callbacks; + long frameTimeNanos = frameData.mFrameTimeNanos; synchronized (mLock) { // We use "now" to determine when callbacks become due because it's possible // for earlier processing phases in a frame to post callbacks that should run @@ -831,6 +877,7 @@ public final class Choreographer { } frameTimeNanos = now - lastFrameOffset; mLastFrameTimeNanos = frameTimeNanos; + frameData.setFrameTimeNanos(frameTimeNanos); } } } @@ -842,7 +889,7 @@ public final class Choreographer { + ", action=" + c.action + ", token=" + c.token + ", latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime)); } - c.run(frameTimeNanos); + c.run(frameData); } } finally { synchronized (mLock) { @@ -942,6 +989,130 @@ public final class Choreographer { public void doFrame(long frameTimeNanos); } + /** Holds data that describes one possible VSync frame event to render at. */ + public static class FrameTimeline { + static final FrameTimeline INVALID_FRAME_TIMELINE = new FrameTimeline( + FrameInfo.INVALID_VSYNC_ID, Long.MAX_VALUE, Long.MAX_VALUE); + + FrameTimeline(long vsyncId, long expectedPresentTimeNanos, long deadlineNanos) { + this.mVsyncId = vsyncId; + this.mExpectedPresentTimeNanos = expectedPresentTimeNanos; + this.mDeadlineNanos = deadlineNanos; + } + + private long mVsyncId; + private long mExpectedPresentTimeNanos; + private long mDeadlineNanos; + + /** + * The id that corresponds to this frame timeline, used to correlate a frame + * produced by HWUI with the timeline data stored in Surface Flinger. + */ + public long getVsyncId() { + return mVsyncId; + } + + /** Sets the vsync ID. */ + void resetVsyncId() { + mVsyncId = FrameInfo.INVALID_VSYNC_ID; + } + + /** + * The time in {@link System#nanoTime()} timebase which this frame is expected to be + * presented. + */ + public long getExpectedPresentTimeNanos() { + return mExpectedPresentTimeNanos; + } + + /** + * The time in {@link System#nanoTime()} timebase which this frame needs to be ready by. + */ + public long getDeadlineNanos() { + return mDeadlineNanos; + } + } + + /** + * The payload for {@link ExtendedFrameCallback} which includes frame information such as when + * the frame started being rendered, and multiple possible frame timelines and their + * information including deadline and expected present time. + */ + public static class FrameData { + static final FrameTimeline[] INVALID_FRAME_TIMELINES = new FrameTimeline[0]; + FrameData() { + this.mFrameTimelines = INVALID_FRAME_TIMELINES; + this.mPreferredFrameTimeline = FrameTimeline.INVALID_FRAME_TIMELINE; + } + + FrameData(long frameTimeNanos, DisplayEventReceiver.VsyncEventData vsyncEventData) { + FrameTimeline[] frameTimelines = + new FrameTimeline[vsyncEventData.frameTimelines.length]; + for (int i = 0; i < vsyncEventData.frameTimelines.length; i++) { + DisplayEventReceiver.VsyncEventData.FrameTimeline frameTimeline = + vsyncEventData.frameTimelines[i]; + frameTimelines[i] = new FrameTimeline(frameTimeline.vsyncId, + frameTimeline.expectedPresentTime, frameTimeline.deadline); + } + this.mFrameTimeNanos = frameTimeNanos; + this.mFrameTimelines = frameTimelines; + this.mPreferredFrameTimeline = + frameTimelines[vsyncEventData.preferredFrameTimelineIndex]; + } + + private long mFrameTimeNanos; + private final FrameTimeline[] mFrameTimelines; + private final FrameTimeline mPreferredFrameTimeline; + + void setFrameTimeNanos(long frameTimeNanos) { + mFrameTimeNanos = frameTimeNanos; + for (FrameTimeline ft : mFrameTimelines) { + // The ID is no longer valid because the frame time that was registered with the ID + // no longer matches. + // TODO(b/205721584): Ask SF for valid vsync information. + ft.resetVsyncId(); + } + } + + /** The time in nanoseconds when the frame started being rendered. */ + public long getFrameTimeNanos() { + return mFrameTimeNanos; + } + + /** The possible frame timelines, sorted chronologically. */ + @NonNull + @SuppressLint("ArrayReturn") // For API consistency and speed. + public FrameTimeline[] getFrameTimelines() { + return mFrameTimelines; + } + + /** The platform-preferred frame timeline. */ + @NonNull + public FrameTimeline getPreferredFrameTimeline() { + return mPreferredFrameTimeline; + } + } + + /** + * Implement this interface to receive a callback to start the next frame. The callback is + * invoked on the {@link Looper} thread to which the {@link Choreographer} is attached. The + * callback payload contains information about multiple possible frames, allowing choice of + * the appropriate frame based on latency requirements. + * + * @see FrameCallback + */ + public interface ExtendedFrameCallback { + /** + * Called when a new display frame is being rendered. + * + * @param data The payload which includes frame information. Divide nanosecond values by + * {@code 1000000} to convert it to the {@link SystemClock#uptimeMillis()} + * time base. + * @see FrameCallback#doFrame + **/ + void onVsync(@NonNull FrameData data); + } + private final class FrameHandler extends Handler { public FrameHandler(Looper looper) { super(looper); @@ -983,7 +1154,8 @@ public final class Choreographer { try { if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, - "Choreographer#onVsync " + vsyncEventData.id); + "Choreographer#onVsync " + + vsyncEventData.preferredFrameTimeline().vsyncId); } // Post the vsync event to the Handler. // The idea is to prevent incoming vsync events from completely starving @@ -1026,7 +1198,9 @@ public final class Choreographer { private static final class CallbackRecord { public CallbackRecord next; public long dueTime; - public Object action; // Runnable or FrameCallback + /** Runnable or FrameCallback or ExtendedFrameCallback object. */ + public Object action; + /** Denotes the action type. */ public Object token; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) @@ -1037,6 +1211,14 @@ public final class Choreographer { ((Runnable)action).run(); } } + + void run(FrameData frameData) { + if (token == EXTENDED_FRAME_CALLBACK_TOKEN) { + ((ExtendedFrameCallback) action).onVsync(frameData); + } else { + run(frameData.getFrameTimeNanos()); + } + } } private final class CallbackQueue { 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/DisplayCutout.java b/core/java/android/view/DisplayCutout.java index ae323226d9cc..9889eaaf12e3 100644 --- a/core/java/android/view/DisplayCutout.java +++ b/core/java/android/view/DisplayCutout.java @@ -1249,4 +1249,119 @@ public final class DisplayCutout { return String.valueOf(mInner); } } + + /** + * A Builder class to construct a DisplayCutout instance. + * + * <p>Note that this is only for tests purpose. For production code, developers should always + * use a {@link DisplayCutout} obtained from the system.</p> + */ + public static final class Builder { + private Insets mSafeInsets = Insets.NONE; + private Insets mWaterfallInsets = Insets.NONE; + private Path mCutoutPath; + private final Rect mBoundingRectLeft = new Rect(); + private final Rect mBoundingRectTop = new Rect(); + private final Rect mBoundingRectRight = new Rect(); + private final Rect mBoundingRectBottom = new Rect(); + + /** + * Begin building a DisplayCutout. + */ + public Builder() { + } + + /** + * Construct a new {@link DisplayCutout} with the set parameters. + */ + @NonNull + public DisplayCutout build() { + final CutoutPathParserInfo info; + if (mCutoutPath != null) { + // Create a fake CutoutPathParserInfo and set it to sCachedCutoutPathParserInfo so + // that when getCutoutPath() is called, it will return the cached Path. + info = new CutoutPathParserInfo(0, 0, 0, "test", 0, 1f); + synchronized (CACHE_LOCK) { + DisplayCutout.sCachedCutoutPathParserInfo = info; + DisplayCutout.sCachedCutoutPath = mCutoutPath; + } + } else { + info = null; + } + return new DisplayCutout(mSafeInsets.toRect(), mWaterfallInsets, mBoundingRectLeft, + mBoundingRectTop, mBoundingRectRight, mBoundingRectBottom, info, false); + } + + /** + * Set the safe insets. If not set, the default value is {@link Insets#NONE}. + */ + @SuppressWarnings("MissingGetterMatchingBuilder") + @NonNull + public Builder setSafeInsets(@NonNull Insets safeInsets) { + mSafeInsets = safeInsets; + return this; + } + + /** + * Set the waterfall insets of the DisplayCutout. If not set, the default value is + * {@link Insets#NONE} + */ + @NonNull + public Builder setWaterfallInsets(@NonNull Insets waterfallInsets) { + mWaterfallInsets = waterfallInsets; + return this; + } + + /** + * Set a bounding rectangle for a non-functional area on the display which is located on + * the left of the screen. If not set, the default value is an empty rectangle. + */ + @NonNull + public Builder setBoundingRectLeft(@NonNull Rect boundingRectLeft) { + mBoundingRectLeft.set(boundingRectLeft); + return this; + } + + /** + * Set a bounding rectangle for a non-functional area on the display which is located on + * the top of the screen. If not set, the default value is an empty rectangle. + */ + @NonNull + public Builder setBoundingRectTop(@NonNull Rect boundingRectTop) { + mBoundingRectTop.set(boundingRectTop); + return this; + } + + /** + * Set a bounding rectangle for a non-functional area on the display which is located on + * the right of the screen. If not set, the default value is an empty rectangle. + */ + @NonNull + public Builder setBoundingRectRight(@NonNull Rect boundingRectRight) { + mBoundingRectRight.set(boundingRectRight); + return this; + } + + /** + * Set a bounding rectangle for a non-functional area on the display which is located on + * the bottom of the screen. If not set, the default value is an empty rectangle. + */ + @NonNull + public Builder setBoundingRectBottom(@NonNull Rect boundingRectBottom) { + mBoundingRectBottom.set(boundingRectBottom); + return this; + } + + /** + * Set the cutout {@link Path}. + * + * Note that not support creating/testing multiple display cutouts with setCutoutPath() in + * parallel. + */ + @NonNull + public Builder setCutoutPath(@NonNull Path cutoutPath) { + mCutoutPath = cutoutPath; + return this; + } + } } diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java index 5c086328bda7..774bab41fb9a 100644 --- a/core/java/android/view/DisplayEventReceiver.java +++ b/core/java/android/view/DisplayEventReceiver.java @@ -138,13 +138,28 @@ public abstract class DisplayEventReceiver { } static final class VsyncEventData { - // The frame timeline vsync id, used to correlate a frame - // produced by HWUI with the timeline data stored in Surface Flinger. - public final long id; - // The frame deadline timestamp in {@link System#nanoTime()} timebase that it is - // allotted for the frame to be completed. - public final long frameDeadline; + static final FrameTimeline[] INVALID_FRAME_TIMELINES = + {new FrameTimeline(FrameInfo.INVALID_VSYNC_ID, Long.MAX_VALUE, Long.MAX_VALUE)}; + + public static class FrameTimeline { + FrameTimeline(long vsyncId, long expectedPresentTime, long deadline) { + this.vsyncId = vsyncId; + this.expectedPresentTime = expectedPresentTime; + this.deadline = deadline; + } + + // The frame timeline vsync id, used to correlate a frame + // produced by HWUI with the timeline data stored in Surface Flinger. + public final long vsyncId; + + // The frame timestamp for when the frame is expected to be presented. + public final long expectedPresentTime; + + // The frame deadline timestamp in {@link System#nanoTime()} timebase that it is + // allotted for the frame to be completed. + public final long deadline; + } /** * The current interval between frames in ns. This will be used to align @@ -153,16 +168,27 @@ public abstract class DisplayEventReceiver { */ public final long frameInterval; - VsyncEventData(long id, long frameDeadline, long frameInterval) { - this.id = id; - this.frameDeadline = frameDeadline; + public final FrameTimeline[] frameTimelines; + + public final int preferredFrameTimelineIndex; + + // Called from native code. + @SuppressWarnings("unused") + VsyncEventData(FrameTimeline[] frameTimelines, int preferredFrameTimelineIndex, + long frameInterval) { + this.frameTimelines = frameTimelines; + this.preferredFrameTimelineIndex = preferredFrameTimelineIndex; this.frameInterval = frameInterval; } VsyncEventData() { - this.id = FrameInfo.INVALID_VSYNC_ID; - this.frameDeadline = Long.MAX_VALUE; this.frameInterval = -1; + this.frameTimelines = INVALID_FRAME_TIMELINES; + this.preferredFrameTimelineIndex = 0; + } + + public FrameTimeline preferredFrameTimeline() { + return frameTimelines[preferredFrameTimelineIndex]; } } @@ -256,9 +282,8 @@ public abstract class DisplayEventReceiver { // Called from native code. @SuppressWarnings("unused") private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame, - long frameTimelineVsyncId, long frameDeadline, long frameInterval) { - onVsync(timestampNanos, physicalDisplayId, frame, - new VsyncEventData(frameTimelineVsyncId, frameDeadline, frameInterval)); + VsyncEventData vsyncEventData) { + onVsync(timestampNanos, physicalDisplayId, frame, vsyncEventData); } // Called from native code. 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..4d24965a419e 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -66,6 +66,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. @@ -922,4 +923,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..0f59fced4075 100644 --- a/core/java/android/view/IWindowSession.aidl +++ b/core/java/android/view/IWindowSession.aidl @@ -290,6 +290,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. */ 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 9ce59bb8823a..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; } } @@ -8663,12 +8669,31 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (mAttachInfo == null) { return; } + RectF position = mAttachInfo.mTmpTransformRect; + getBoundsToScreenInternal(position, clipToParent); + outRect.set(Math.round(position.left), Math.round(position.top), + Math.round(position.right), Math.round(position.bottom)); + } + /** + * Gets the location of this view in screen coordinates. + * + * @param outRect The output location + * @param clipToParent Whether to clip child bounds to the parent ones. + * @hide + */ + public void getBoundsOnScreen(RectF outRect, boolean clipToParent) { + if (mAttachInfo == null) { + return; + } RectF position = mAttachInfo.mTmpTransformRect; + getBoundsToScreenInternal(position, clipToParent); + outRect.set(position.left, position.top, position.right, position.bottom); + } + + private void getBoundsToScreenInternal(RectF position, boolean clipToParent) { position.set(0, 0, mRight - mLeft, mBottom - mTop); mapRectFromViewToScreenCoords(position, clipToParent); - outRect.set(Math.round(position.left), Math.round(position.top), - Math.round(position.right), Math.round(position.bottom)); } /** @@ -11646,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); } } @@ -11708,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 @@ -15101,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); } } } @@ -31257,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..d247ce8d00db 100644 --- a/core/java/android/view/WindowlessWindowManager.java +++ b/core/java/android/view/WindowlessWindowManager.java @@ -459,6 +459,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) { 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/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java index 7a33507e9d78..e54ed18c51a6 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -1766,6 +1766,74 @@ public final class AccessibilityManager { } } + /** + * Sets the system audio caption enabled state. + * + * @param isEnabled The system audio captioning enabled state. + * @param userId The user Id. + * @hide + */ + @RequiresPermission(Manifest.permission.SET_SYSTEM_AUDIO_CAPTION) + public void setSystemAudioCaptioningRequested(boolean isEnabled, int userId) { + final IAccessibilityManager service; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return; + } + } + try { + service.setSystemAudioCaptioningRequested(isEnabled, userId); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * Gets the system audio caption UI enabled state. + * + * @param userId The user Id. + * @return the system audio caption UI enabled state. + * @hide + */ + public boolean isSystemAudioCaptioningUiRequested(int userId) { + final IAccessibilityManager service; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return false; + } + } + try { + return service.isSystemAudioCaptioningUiRequested(userId); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * Sets the system audio caption UI enabled state. + * + * @param isEnabled The system audio captioning UI enabled state. + * @param userId The user Id. + * @hide + */ + @RequiresPermission(Manifest.permission.SET_SYSTEM_AUDIO_CAPTION) + public void setSystemAudioCaptioningUiRequested(boolean isEnabled, int userId) { + final IAccessibilityManager service; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return; + } + } + try { + service.setSystemAudioCaptioningUiRequested(isEnabled, userId); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + private IAccessibilityManager getServiceLocked() { if (mService == null) { tryConnectToServiceLocked(null); 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 3f6a87196d70..05c74f2d0111 100644 --- a/core/java/android/view/accessibility/CaptioningManager.java +++ b/core/java/android/view/accessibility/CaptioningManager.java @@ -16,8 +16,11 @@ package android.view.accessibility; +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.compat.annotation.UnsupportedAppUsage; import android.content.ContentResolver; @@ -44,6 +47,7 @@ import java.util.Locale; public class CaptioningManager { /** Default captioning enabled value. */ private static final int DEFAULT_ENABLED = 0; + private static final boolean SYSTEM_AUDIO_CAPTIONING_DEFAULT_ENABLED = false; /** Default style preset as an index into {@link CaptionStyle#PRESETS}. */ private static final int DEFAULT_PRESET = 0; @@ -55,6 +59,8 @@ public class CaptioningManager { private final ContentResolver mContentResolver; private final ContentObserver mContentObserver; private final Resources mResources; + private final Context mContext; + private final AccessibilityManager mAccessibilityManager; /** * Creates a new captioning manager for the specified context. @@ -62,7 +68,9 @@ public class CaptioningManager { * @hide */ public CaptioningManager(Context context) { + mContext = context; mContentResolver = context.getContentResolver(); + mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class); final Handler handler = new Handler(context.getMainLooper()); mContentObserver = new MyContentObserver(handler); @@ -142,6 +150,60 @@ public class CaptioningManager { } /** + * @return the system audio caption enabled state. + */ + public final boolean isSystemAudioCaptioningRequested() { + return Secure.getIntForUser(mContentResolver, Secure.ODI_CAPTIONS_ENABLED, + SYSTEM_AUDIO_CAPTIONING_DEFAULT_ENABLED ? 1 : 0, mContext.getUserId()) == 1; + } + + /** + * Sets the system audio caption enabled state. + * + * @param isEnabled The system audio captioning enabled state. + * + * @throws SecurityException if the caller does not have permission + * {@link Manifest.permission#SET_SYSTEM_AUDIO_CAPTION} + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.SET_SYSTEM_AUDIO_CAPTION) + public final void setSystemAudioCaptioningRequested(boolean isEnabled) { + if (mAccessibilityManager != null) { + mAccessibilityManager.setSystemAudioCaptioningRequested(isEnabled, + mContext.getUserId()); + } + } + + /** + * @return the system audio caption UI enabled state. + */ + public final boolean isSystemAudioCaptioningUiRequested() { + return mAccessibilityManager != null + && mAccessibilityManager.isSystemAudioCaptioningUiRequested(mContext.getUserId()); + } + + /** + * Sets the system audio caption UI enabled state. + * + * @param isEnabled The system audio captioning UI enabled state. + * + * @throws SecurityException if the caller does not have permission + * {@link Manifest.permission#SET_SYSTEM_AUDIO_CAPTION} + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.SET_SYSTEM_AUDIO_CAPTION) + public final void setSystemAudioCaptioningUiRequested(boolean isEnabled) { + if (mAccessibilityManager != null) { + mAccessibilityManager.setSystemAudioCaptioningUiRequested(isEnabled, + mContext.getUserId()); + } + } + + /** * Adds a listener for changes in the user's preferred captioning enabled * state and visual properties. * @@ -160,6 +222,8 @@ public class CaptioningManager { registerObserver(Secure.ACCESSIBILITY_CAPTIONING_FONT_SCALE); registerObserver(Secure.ACCESSIBILITY_CAPTIONING_LOCALE); registerObserver(Secure.ACCESSIBILITY_CAPTIONING_PRESET); + registerObserver(Secure.ODI_CAPTIONS_ENABLED); + registerObserver(Secure.ODI_CAPTIONS_VOLUME_UI_ENABLED); } mListeners.add(listener); @@ -229,6 +293,24 @@ public class CaptioningManager { } } + private void notifySystemAudioCaptionChanged() { + final boolean enabled = isSystemAudioCaptioningRequested(); + synchronized (mListeners) { + for (CaptioningChangeListener listener : mListeners) { + listener.onSystemAudioCaptioningChanged(enabled); + } + } + } + + private void notifySystemAudioCaptionUiChanged() { + final boolean enabled = isSystemAudioCaptioningUiRequested(); + synchronized (mListeners) { + for (CaptioningChangeListener listener : mListeners) { + listener.onSystemAudioCaptioningUiChanged(enabled); + } + } + } + private class MyContentObserver extends ContentObserver { private final Handler mHandler; @@ -248,6 +330,10 @@ public class CaptioningManager { notifyLocaleChanged(); } else if (Secure.ACCESSIBILITY_CAPTIONING_FONT_SCALE.equals(name)) { notifyFontScaleChanged(); + } else if (Secure.ODI_CAPTIONS_ENABLED.equals(name)) { + notifySystemAudioCaptionChanged(); + } else if (Secure.ODI_CAPTIONS_VOLUME_UI_ENABLED.equals(name)) { + notifySystemAudioCaptionUiChanged(); } else { // We only need a single callback when multiple style properties // change in rapid succession. @@ -565,5 +651,51 @@ public class CaptioningManager { * @see CaptioningManager#getFontScale() */ public void onFontScaleChanged(float fontScale) {} + + + /** + * Called when the system audio caption enabled state changes. + * + * @param enabled the system audio caption enabled state + */ + public void onSystemAudioCaptioningChanged(boolean enabled) {} + + /** + * Called when the system audio caption UI enabled state changes. + * + * @param enabled the system audio caption UI enabled state + */ + public void onSystemAudioCaptioningUiChanged(boolean enabled) {} + } + + /** + * Interface for accessing the system audio captioning related secure setting keys. + * + * @hide + */ + public interface SystemAudioCaptioningAccessing { + /** + * Sets the system audio caption enabled state. + * + * @param isEnabled The system audio captioning enabled state. + * @param userId The user Id. + */ + void setSystemAudioCaptioningRequested(boolean isEnabled, int userId); + + /** + * Gets the system audio caption UI enabled state. + * + * @param userId The user Id. + * @return the system audio caption UI enabled state. + */ + boolean isSystemAudioCaptioningUiRequested(int userId); + + /** + * Sets the system audio caption UI enabled state. + * + * @param isEnabled The system audio captioning UI enabled state. + * @param userId The user Id. + */ + void setSystemAudioCaptioningUiRequested(boolean isEnabled, int userId); } } diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl index 4e8d2da70159..645ddf5542f7 100644 --- a/core/java/android/view/accessibility/IAccessibilityManager.aidl +++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl @@ -100,4 +100,14 @@ interface IAccessibilityManager { int getFocusColor(); boolean isAudioDescriptionByDefaultEnabled(); + + // Requires Manifest.permission.SET_SYSTEM_AUDIO_CAPTION + // System process only + void setSystemAudioCaptioningRequested(boolean isEnabled, int userId); + + boolean isSystemAudioCaptioningUiRequested(int userId); + + // Requires Manifest.permission.SET_SYSTEM_AUDIO_CAPTION + // System process only + void setSystemAudioCaptioningUiRequested(boolean isEnabled, int userId); } diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java index b1d618eff40a..ab749ee284a8 100644 --- a/core/java/android/view/animation/Animation.java +++ b/core/java/android/view/animation/Animation.java @@ -209,6 +209,13 @@ public abstract class Animation implements Cloneable { private boolean mShowWallpaper; private boolean mHasRoundedCorners; + /** + * Whether to show a background behind the windows during the animation. + * @see #getShowBackground() + * @see #setShowBackground(boolean) + */ + private boolean mShowBackground; + private boolean mMore = true; private boolean mOneMoreTime = true; @@ -266,6 +273,8 @@ public abstract class Animation implements Cloneable { a.getBoolean(com.android.internal.R.styleable.Animation_showWallpaper, false)); setHasRoundedCorners( a.getBoolean(com.android.internal.R.styleable.Animation_hasRoundedCorners, false)); + setShowBackground( + a.getBoolean(com.android.internal.R.styleable.Animation_showBackground, false)); final int resID = a.getResourceId(com.android.internal.R.styleable.Animation_interpolator, 0); @@ -698,6 +707,24 @@ public abstract class Animation implements Cloneable { } /** + * If showBackground is {@code true} and this animation is applied on a window, then the windows + * in the animation will animate with the background associated with this window behind them. + * + * The background comes from the {@link android.R.styleable#Theme_colorBackground} that is + * applied to this window through its theme. + * + * If multiple animating windows have showBackground set to {@code true} during an animation, + * the top most window with showBackground set to {@code true} and a valid background color + * takes precedence. + * + * @param showBackground Whether to show a background behind the windows during the animation. + * @attr ref android.R.styleable#Animation_showBackground + */ + public void setShowBackground(boolean showBackground) { + mShowBackground = showBackground; + } + + /** * Gets the acceleration curve type for this animation. * * @return the {@link Interpolator} associated to this animation @@ -838,6 +865,24 @@ public abstract class Animation implements Cloneable { } /** + * If showBackground is {@code true} and this animation is applied on a window, then the windows + * in the animation will animate with the background associated with this window behind them. + * + * The background comes from the {@link android.R.styleable#Theme_colorBackground} that is + * applied to this window through its theme. + * + * If multiple animating windows have showBackground set to {@code true} during an animation, + * the top most window with showBackground set to {@code true} and a valid background color + * takes precedence. + * + * @return if the background of this window should be shown behind the animating windows. + * @attr ref android.R.styleable#Animation_showBackground + */ + public boolean getShowBackground() { + return mShowBackground; + } + + /** * <p>Indicates whether or not this animation will affect the transformation * matrix. For instance, a fade animation will not affect the matrix whereas * a scale animation will.</p> 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 fbc947071c99..8600f55eee70 100644 --- a/core/java/android/view/inputmethod/CursorAnchorInfo.java +++ b/core/java/android/view/inputmethod/CursorAnchorInfo.java @@ -105,6 +105,13 @@ public final class CursorAnchorInfo implements Parcelable { private final SparseRectFArray mCharacterBoundsArray; /** + * Container of rectangular position of Editor in the local coordinates that will be transformed + * with the transformation matrix when rendered on the screen. + * @see {@link EditorBoundsInfo}. + */ + private final EditorBoundsInfo mEditorBoundsInfo; + + /** * Transformation matrix that is applied to any positional information of this class to * transform local coordinates into screen coordinates. */ @@ -140,7 +147,8 @@ 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(); } @@ -163,6 +171,7 @@ public final class CursorAnchorInfo implements Parcelable { dest.writeFloat(mInsertionMarkerBaseline); dest.writeFloat(mInsertionMarkerBottom); dest.writeParcelable(mCharacterBoundsArray, flags); + dest.writeTypedObject(mEditorBoundsInfo, flags); dest.writeFloatArray(mMatrixValues); } @@ -216,6 +225,10 @@ public final class CursorAnchorInfo implements Parcelable { return false; } + if (!Objects.equals(mEditorBoundsInfo, that.mEditorBoundsInfo)) { + return false; + } + // Following fields are (partially) covered by hashCode(). if (mComposingTextStart != that.mComposingTextStart @@ -248,6 +261,7 @@ public final class CursorAnchorInfo implements Parcelable { + " mInsertionMarkerBaseline=" + mInsertionMarkerBaseline + " mInsertionMarkerBottom=" + mInsertionMarkerBottom + " mCharacterBoundsArray=" + Objects.toString(mCharacterBoundsArray) + + " mEditorBoundsInfo=" + mEditorBoundsInfo + " mMatrix=" + Arrays.toString(mMatrixValues) + "}"; } @@ -266,6 +280,7 @@ public final class CursorAnchorInfo implements Parcelable { private float mInsertionMarkerBottom = Float.NaN; private int mInsertionMarkerFlags = 0; private SparseRectFArrayBuilder mCharacterBoundsArrayBuilder = null; + private EditorBoundsInfo mEditorBoundsInfo = null; private float[] mMatrixValues = null; private boolean mMatrixInitialized = false; @@ -356,6 +371,17 @@ public final class CursorAnchorInfo implements Parcelable { } /** + * Sets the current editor related bounds. + * + * @param bounds {@link EditorBoundsInfo} in local coordinates. + */ + @NonNull + public Builder setEditorBoundsInfo(@Nullable EditorBoundsInfo bounds) { + mEditorBoundsInfo = bounds; + return this; + } + + /** * Sets the matrix that transforms local coordinates into screen coordinates. * @param matrix transformation matrix from local coordinates into screen coordinates. null * is interpreted as an identity matrix. @@ -410,6 +436,7 @@ public final class CursorAnchorInfo implements Parcelable { if (mCharacterBoundsArrayBuilder != null) { mCharacterBoundsArrayBuilder.reset(); } + mEditorBoundsInfo = null; } } @@ -425,6 +452,7 @@ public final class CursorAnchorInfo implements Parcelable { mInsertionMarkerBottom = builder.mInsertionMarkerBottom; mCharacterBoundsArray = builder.mCharacterBoundsArrayBuilder != null ? builder.mCharacterBoundsArrayBuilder.build() : null; + mEditorBoundsInfo = builder.mEditorBoundsInfo; mMatrixValues = new float[9]; if (builder.mMatrixInitialized) { System.arraycopy(builder.mMatrixValues, 0, mMatrixValues, 0, 9); @@ -547,6 +575,14 @@ public final class CursorAnchorInfo implements Parcelable { } /** + * Returns {@link EditorBoundsInfo} editor related bounds. + */ + @Nullable + public EditorBoundsInfo getEditorBoundsInfo() { + return mEditorBoundsInfo; + } + + /** * Returns a new instance of {@link android.graphics.Matrix} that indicates the transformation * matrix that is to be applied other positional data in this class. * @return a new instance (copy) of the transformation matrix. diff --git a/core/java/android/view/inputmethod/EditorBoundsInfo.java b/core/java/android/view/inputmethod/EditorBoundsInfo.java new file mode 100644 index 000000000000..081dc819f10b --- /dev/null +++ b/core/java/android/view/inputmethod/EditorBoundsInfo.java @@ -0,0 +1,177 @@ +/* + * 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.inputmethod; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.RectF; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * Container of rectangular position related info for the Editor. + */ +public final class EditorBoundsInfo implements Parcelable { + + /** + * Container of rectangular position of Editor in the current window. + */ + private final RectF mEditorBounds; + + /** + * Container of rectangular position of Editor with additional padding around for initiating + * Stylus Handwriting in the current window. + */ + private final RectF mHandwritingBounds; + + private final int mHashCode; + + private EditorBoundsInfo(@NonNull Parcel source) { + mHashCode = source.readInt(); + mEditorBounds = source.readTypedObject(RectF.CREATOR); + mHandwritingBounds = source.readTypedObject(RectF.CREATOR); + } + + /** + * Returns the bounds of the Editor in local coordinates. + * + * Screen coordinates can be obtained by transforming with the + * {@link CursorAnchorInfo#getMatrix} of the containing {@code CursorAnchorInfo}. + */ + @Nullable + public RectF getEditorBounds() { + return mEditorBounds; + } + + /** + * Returns the bounds of the area that should be considered for initiating stylus handwriting + * in local coordinates. + * + * Screen coordinates can be obtained by transforming with the + * {@link CursorAnchorInfo#getMatrix} of the containing {@code CursorAnchorInfo}. + */ + @Nullable + public RectF getHandwritingBounds() { + return mHandwritingBounds; + } + + @Override + public int hashCode() { + return mHashCode; + } + + @Override + public String toString() { + return "EditorBoundsInfo{mEditorBounds=" + mEditorBounds + + " mHandwritingBounds=" + mHandwritingBounds + + "}"; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (obj == null) { + return false; + } + EditorBoundsInfo bounds; + if (obj instanceof EditorBoundsInfo) { + bounds = (EditorBoundsInfo) obj; + } else { + return false; + } + return Objects.equals(bounds.mEditorBounds, mEditorBounds) + && Objects.equals(bounds.mHandwritingBounds, mHandwritingBounds); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mHashCode); + dest.writeTypedObject(mEditorBounds, flags); + dest.writeTypedObject(mHandwritingBounds, flags); + } + + /** + * Used to make this class parcelable. + */ + public static final @android.annotation.NonNull Parcelable.Creator<EditorBoundsInfo> CREATOR + = new Parcelable.Creator<EditorBoundsInfo>() { + @Override + public EditorBoundsInfo createFromParcel(@NonNull Parcel source) { + return new EditorBoundsInfo(source); + } + + @Override + public EditorBoundsInfo[] newArray(int size) { + return new EditorBoundsInfo[size]; + } + }; + + /** + * Builder for {@link CursorAnchorInfo}. + */ + public static final class Builder { + private RectF mEditorBounds = null; + private RectF mHandwritingBounds = null; + + /** + * Sets the bounding box of the current editor. + * + * @param bounds {@link RectF} in local coordinates. + */ + @NonNull + public EditorBoundsInfo.Builder setEditorBounds(@Nullable RectF bounds) { + mEditorBounds = bounds; + return this; + } + + /** + * Sets the current editor's bounds with padding for handwriting. + * + * @param bounds {@link RectF} in local coordinates. + */ + @NonNull + public EditorBoundsInfo.Builder setHandwritingBounds(@Nullable RectF bounds) { + mHandwritingBounds = bounds; + return this; + } + + /** + * Returns {@link EditorBoundsInfo} using parameters in this + * {@link EditorBoundsInfo.Builder}. + */ + @NonNull + public EditorBoundsInfo build() { + return new EditorBoundsInfo(this); + } + } + + private EditorBoundsInfo(final EditorBoundsInfo.Builder builder) { + mEditorBounds = builder.mEditorBounds; + mHandwritingBounds = builder.mHandwritingBounds; + + int hash = Objects.hashCode(mEditorBounds); + hash *= 31; + hash += Objects.hashCode(mHandwritingBounds); + mHashCode = hash; + } +} 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/AbsListView.java b/core/java/android/widget/AbsListView.java index 414a7f1f0e1c..1d8e9a36fb3f 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -2656,6 +2656,27 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } } + /** + * Returns whether the selected child view (from the adapter's getView) is enabled. + * + * @return true if enabled + */ + public boolean isSelectedChildViewEnabled() { + return mIsChildViewEnabled; + } + + /** + * Set whether the selected child view (from the adapter's getView) is enabled. + * + * When refreshDrawableState is called, AbsListView will control the "enabled" state + * of the selector based on this. + * + * @param selectedChildViewEnabled true if enabled + */ + public void setSelectedChildViewEnabled(boolean selectedChildViewEnabled) { + mIsChildViewEnabled = selectedChildViewEnabled; + } + @Override protected void dispatchDraw(Canvas canvas) { int saveCount = 0; diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 6284bc2f3513..ac28c31ecf49 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -115,6 +115,7 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.LinearInterpolator; import android.view.inputmethod.CorrectionInfo; import android.view.inputmethod.CursorAnchorInfo; +import android.view.inputmethod.EditorBoundsInfo; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; @@ -4570,6 +4571,12 @@ public class Editor { mTextView.getLocationOnScreen(mTmpIntOffset); mViewToScreenMatrix.postTranslate(mTmpIntOffset[0], mTmpIntOffset[1]); builder.setMatrix(mViewToScreenMatrix); + final RectF bounds = new RectF(); + mTextView.getBoundsOnScreen(bounds, false /* clipToParent */); + EditorBoundsInfo.Builder boundsBuilder = new EditorBoundsInfo.Builder(); + //TODO(b/210039666): add Handwriting bounds once they're available. + builder.setEditorBoundsInfo( + boundsBuilder.setEditorBounds(bounds).build()); final float viewportToContentHorizontalOffset = mTextView.viewportToContentHorizontalOffset(); 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 1a808b2e7c24..0fe06befa789 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -79,6 +79,7 @@ import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.graphics.fonts.FontStyle; import android.graphics.fonts.FontVariationAxis; +import android.graphics.text.LineBreakConfig; import android.icu.text.DecimalFormatSymbols; import android.os.AsyncTask; import android.os.Build; @@ -348,6 +349,7 @@ import java.util.function.Supplier; * @attr ref android.R.styleable#TextView_fontVariationSettings * @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_autoSizeTextType * @attr ref android.R.styleable#TextView_autoSizeMinTextSize * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize @@ -775,6 +777,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private Layout mLayout; private boolean mLocalesChanged = false; private int mTextSizeUnit = -1; + private LineBreakConfig mLineBreakConfig = new LineBreakConfig(); // This is used to reflect the current user preference for changing font weight and making text // more bold. @@ -1442,6 +1445,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mHyphenationFrequency = a.getInt(attr, Layout.HYPHENATION_FREQUENCY_NONE); break; + case com.android.internal.R.styleable.TextView_lineBreakStyle: + mLineBreakConfig.setLineBreakStyle( + a.getInt(attr, LineBreakConfig.LINE_BREAK_STYLE_NONE)); + break; + case com.android.internal.R.styleable.TextView_autoSizeTextType: mAutoSizeTextType = a.getInt(attr, AUTO_SIZE_TEXT_TYPE_NONE); break; @@ -4788,13 +4796,50 @@ 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): + * {@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. + * 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) { + if (mLineBreakConfig.equals(lineBreakConfig)) { + return; + } + mLineBreakConfig.set(lineBreakConfig); + if (mLayout != null) { + nullLayouts(); + requestLayout(); + invalidate(); + } + } + + /** + * Get the current line break configuration for text wrapping. + * + * @return the current line break configuration to be used for text wrapping. + */ + public @NonNull LineBreakConfig getLineBreakConfig() { + LineBreakConfig lbConfig = new LineBreakConfig(); + lbConfig.set(mLineBreakConfig); + return lbConfig; + } + + /** * Gets the parameters for text layout precomputation, for use with {@link PrecomputedText}. * * @return a current {@link PrecomputedText.Params} * @see PrecomputedText */ public @NonNull PrecomputedText.Params getTextMetricsParams() { - return new PrecomputedText.Params(new TextPaint(mTextPaint), getTextDirectionHeuristic(), + return new PrecomputedText.Params(new TextPaint(mTextPaint), mLineBreakConfig, + getTextDirectionHeuristic(), mBreakStrategy, mHyphenationFrequency); } @@ -4810,6 +4855,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mTextDir = params.getTextDirection(); mBreakStrategy = params.getBreakStrategy(); mHyphenationFrequency = params.getHyphenationFrequency(); + mLineBreakConfig.set(params.getLineBreakConfig()); if (mLayout != null) { nullLayouts(); requestLayout(); @@ -6348,7 +6394,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } final @PrecomputedText.Params.CheckResultUsableResult int checkResult = precomputed.getParams().checkResultUsable(getPaint(), mTextDir, mBreakStrategy, - mHyphenationFrequency); + mHyphenationFrequency, mLineBreakConfig); switch (checkResult) { case PrecomputedText.Params.UNUSABLE: throw new IllegalArgumentException( @@ -9244,7 +9290,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener .setBreakStrategy(mBreakStrategy) .setHyphenationFrequency(mHyphenationFrequency) .setJustificationMode(mJustificationMode) - .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); + .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE) + .setLineBreakConfig(mLineBreakConfig); if (shouldEllipsize) { builder.setEllipsize(mEllipsize) .setEllipsizedWidth(ellipsisWidth); @@ -9358,7 +9405,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener .setBreakStrategy(mBreakStrategy) .setHyphenationFrequency(mHyphenationFrequency) .setJustificationMode(mJustificationMode) - .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); + .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE) + .setLineBreakConfig(mLineBreakConfig); if (shouldEllipsize) { builder.setEllipsize(effectiveEllipsize) .setEllipsizedWidth(ellipsisWidth); @@ -9725,7 +9773,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener .setHyphenationFrequency(getHyphenationFrequency()) .setJustificationMode(getJustificationMode()) .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE) - .setTextDirection(getTextDirectionHeuristic()); + .setTextDirection(getTextDirectionHeuristic()) + .setLineBreakConfig(mLineBreakConfig); final StaticLayout layout = layoutBuilder.build(); 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/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/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index 915c8fb9a6dd..fd1e84822193 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -36,6 +36,7 @@ import static android.view.WindowManager.TransitionFlags; import static android.view.WindowManager.TransitionType; import static android.view.WindowManager.transitTypeToString; +import android.annotation.ColorInt; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -366,6 +367,7 @@ public final class TransitionInfo implements Parcelable { private int mStartRotation = ROTATION_UNDEFINED; private int mEndRotation = ROTATION_UNDEFINED; private int mRotationAnimation = ROTATION_ANIMATION_UNSPECIFIED; + private @ColorInt int mBackgroundColor; public Change(@Nullable WindowContainerToken container, @NonNull SurfaceControl leash) { mContainer = container; @@ -387,6 +389,7 @@ public final class TransitionInfo implements Parcelable { mStartRotation = in.readInt(); mEndRotation = in.readInt(); mRotationAnimation = in.readInt(); + mBackgroundColor = in.readInt(); } /** Sets the parent of this change's container. The parent must be a participant or null. */ @@ -446,6 +449,11 @@ public final class TransitionInfo implements Parcelable { mRotationAnimation = anim; } + /** Sets the background color of this change's container. */ + public void setBackgroundColor(@ColorInt int backgroundColor) { + mBackgroundColor = backgroundColor; + } + /** @return the container that is changing. May be null if non-remotable (eg. activity) */ @Nullable public WindowContainerToken getContainer() { @@ -526,6 +534,12 @@ public final class TransitionInfo implements Parcelable { return mRotationAnimation; } + /** @return get the background color of this change's container. */ + @ColorInt + public int getBackgroundColor() { + return mBackgroundColor; + } + /** @hide */ @Override public void writeToParcel(@NonNull Parcel dest, int flags) { @@ -542,6 +556,7 @@ public final class TransitionInfo implements Parcelable { dest.writeInt(mStartRotation); dest.writeInt(mEndRotation); dest.writeInt(mRotationAnimation); + dest.writeInt(mBackgroundColor); } @NonNull diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java new file mode 100644 index 000000000000..53734eb88720 --- /dev/null +++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java @@ -0,0 +1,75 @@ +/* + * 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.view.IWindow; +import android.view.IWindowSession; +import android.view.OnBackInvokedCallback; +import android.view.OnBackInvokedDispatcher; + +/** + * 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; + + /** + * 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; + // TODO(b/209867448): Send the top callback to WM (if one exists). + } + + /** Detaches the dispatcher instance from its window. */ + public void detachFromWindow() { + mWindow = null; + mWindowSession = null; + } + + @Override + public void registerOnBackInvokedCallback( + @NonNull OnBackInvokedCallback callback, @Priority int priority) { + // TODO(b/209867448): To be implemented. + } + + @Override + public void unregisterOnBackInvokedCallback( + @NonNull OnBackInvokedCallback callback) { + // TODO(b/209867448): To be implemented. + } + + /** Clears all registered callbacks on the instance. */ + public void clear() { + // TODO(b/209867448): To be implemented. + } +} 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/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/LocalePickerWithRegion.java b/core/java/com/android/internal/app/LocalePickerWithRegion.java index d0719eeca04e..b4ae56f23443 100644 --- a/core/java/com/android/internal/app/LocalePickerWithRegion.java +++ b/core/java/com/android/internal/app/LocalePickerWithRegion.java @@ -159,6 +159,14 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O } @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + // In order to make the list view work with CollapsingToolbarLayout, + // we have to enable the nested scrolling feature of the list view. + getListView().setNestedScrollingEnabled(true); + } + + @Override public boolean onOptionsItemSelected(MenuItem menuItem) { int id = menuItem.getItemId(); switch (id) { 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/content/om/OverlayConfig.java b/core/java/com/android/internal/content/om/OverlayConfig.java index 29b31d37e862..b786526ce676 100644 --- a/core/java/com/android/internal/content/om/OverlayConfig.java +++ b/core/java/com/android/internal/content/om/OverlayConfig.java @@ -19,7 +19,6 @@ package com.android.internal.content.om; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.pm.PackagePartitions; -import android.content.pm.parsing.ParsingPackageRead; import android.os.Build; import android.os.Trace; import android.util.ArrayMap; @@ -82,7 +81,22 @@ public class OverlayConfig { public interface PackageProvider { /** Performs the given action for each package. */ - void forEachPackage(TriConsumer<ParsingPackageRead, Boolean, File> p); + void forEachPackage(TriConsumer<Package, Boolean, File> p); + + interface Package { + + String getBaseApkPath(); + + int getOverlayPriority(); + + String getOverlayTarget(); + + String getPackageName(); + + int getTargetSdkVersion(); + + boolean isOverlayIsStatic(); + } } private static final Comparator<ParsedConfiguration> sStaticOverlayComparator = (c1, c2) -> { @@ -304,7 +318,7 @@ public class OverlayConfig { private static Map<String, ParsedOverlayInfo> getOverlayPackageInfos( @NonNull PackageProvider packageManager) { final HashMap<String, ParsedOverlayInfo> overlays = new HashMap<>(); - packageManager.forEachPackage((ParsingPackageRead p, Boolean isSystem, + packageManager.forEachPackage((PackageProvider.Package p, Boolean isSystem, @Nullable File preInstalledApexPath) -> { if (p.getOverlayTarget() != null && isSystem) { overlays.put(p.getPackageName(), new ParsedOverlayInfo(p.getPackageName(), diff --git a/core/java/com/android/internal/content/om/OverlayScanner.java b/core/java/com/android/internal/content/om/OverlayScanner.java index e4e0228a0429..0fafd1063ed1 100644 --- a/core/java/com/android/internal/content/om/OverlayScanner.java +++ b/core/java/com/android/internal/content/om/OverlayScanner.java @@ -16,15 +16,13 @@ package com.android.internal.content.om; -import static android.content.pm.parsing.ParsingPackageUtils.PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY; -import static android.content.pm.parsing.ParsingPackageUtils.checkRequiredSystemProperties; - import static com.android.internal.content.om.OverlayConfig.TAG; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.pm.parsing.ApkLite; import android.content.pm.parsing.ApkLiteParseUtils; +import android.content.pm.parsing.FrameworkParsingPackageUtils; import android.content.pm.parsing.result.ParseResult; import android.content.pm.parsing.result.ParseTypeImpl; import android.text.TextUtils; @@ -183,7 +181,8 @@ public class OverlayScanner { List<Pair<String, File>> outExcludedOverlayPackages) { final ParseTypeImpl input = ParseTypeImpl.forParsingWithoutPlatformCompat(); final ParseResult<ApkLite> ret = ApkLiteParseUtils.parseApkLite(input.reset(), - overlayApk, PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY); + overlayApk, + FrameworkParsingPackageUtils.PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY); if (ret.isError()) { Log.w(TAG, "Got exception loading overlay.", ret.getException()); return null; @@ -196,7 +195,8 @@ public class OverlayScanner { final String propName = apkLite.getRequiredSystemPropertyName(); final String propValue = apkLite.getRequiredSystemPropertyValue(); if ((!TextUtils.isEmpty(propName) || !TextUtils.isEmpty(propValue)) - && !checkRequiredSystemProperties(propName, propValue)) { + && !FrameworkParsingPackageUtils.checkRequiredSystemProperties(propName, + propValue)) { // The overlay package should be excluded. Adds it into the outExcludedOverlayPackages // for overlay configuration parser to ignore it. outExcludedOverlayPackages.add(Pair.create(apkLite.getPackageName(), overlayApk)); 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/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 9429c79697bf..400cbd28ff5d 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; @@ -164,7 +162,7 @@ public class BatteryStatsImpl extends BatteryStats { private static final int MAGIC = 0xBA757475; // 'BATSTATS' // Current on-disk Parcel version - static final int VERSION = 205; + static final int VERSION = 206; // The maximum number of names wakelocks we will keep track of // per uid; once the limit is reached, we batch the remaining wakelocks @@ -214,6 +212,26 @@ public class BatteryStatsImpl extends BatteryStats { private static final double MILLISECONDS_IN_HOUR = 3600 * 1000; private static final long MILLISECONDS_IN_YEAR = 365 * 24 * 3600 * 1000L; + private static final LongCounter ZERO_LONG_COUNTER = new LongCounter() { + @Override + public long getCountLocked(int which) { + return 0; + } + + @Override + public long getCountForProcessState(int procState) { + return 0; + } + + @Override + public void logState(Printer pw, String prefix) { + pw.println(prefix + "mCount=0"); + } + }; + + private static final LongCounter[] ZERO_LONG_COUNTER_ARRAY = + new LongCounter[]{ZERO_LONG_COUNTER}; + private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader(); private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats(); @@ -244,6 +262,8 @@ public class BatteryStatsImpl extends BatteryStats { private static final int[] SUPPORTED_PER_PROCESS_STATE_STANDARD_ENERGY_BUCKETS = { 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 @@ -1177,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<>(); @@ -1742,7 +1804,7 @@ public class BatteryStatsImpl extends BatteryStats { } } - private static class TimeMultiStateCounter implements TimeBaseObs { + private static class TimeMultiStateCounter extends LongCounter implements TimeBaseObs { private final TimeBase mTimeBase; private final LongMultiStateCounter mCounter; @@ -1794,7 +1856,7 @@ public class BatteryStatsImpl extends BatteryStats { /** * Returns accumulated count for the specified state. */ - public long getCountLocked(int procState) { + public long getCountForProcessState(@BatteryConsumer.ProcessState int procState) { return mCounter.getCount(procState); } @@ -1802,6 +1864,12 @@ public class BatteryStatsImpl extends BatteryStats { return mCounter.getTotalCount(); } + @Override + public long getCountLocked(int statsType) { + return getTotalCountLocked(); + } + + @Override public void logState(Printer pw, String prefix) { pw.println(prefix + "mCounter=" + mCounter); } @@ -1947,6 +2015,14 @@ public class BatteryStatsImpl extends BatteryStats { } @Override + public long getCountForProcessState(int procState) { + if (procState == BatteryConsumer.PROCESS_STATE_ANY) { + return getCountLocked(STATS_SINCE_CHARGED); + } + return 0; + } + + @Override public void logState(Printer pw, String prefix) { pw.println(prefix + "mCount=" + mCount); } @@ -3226,57 +3302,50 @@ public class BatteryStatsImpl extends BatteryStats { public static class ControllerActivityCounterImpl extends ControllerActivityCounter implements Parcelable { - private final LongSamplingCounter mIdleTimeMillis; + private final Clock mClock; + private final TimeBase mTimeBase; + private int mNumTxStates; + private int mProcessState; + private TimeMultiStateCounter mIdleTimeMillis; private final LongSamplingCounter mScanTimeMillis; private final LongSamplingCounter mSleepTimeMillis; - private final LongSamplingCounter mRxTimeMillis; - private final LongSamplingCounter[] mTxTimeMillis; + private TimeMultiStateCounter mRxTimeMillis; + private TimeMultiStateCounter[] mTxTimeMillis; private final LongSamplingCounter mPowerDrainMaMs; private final LongSamplingCounter mMonitoredRailChargeConsumedMaMs; - public ControllerActivityCounterImpl(TimeBase timeBase, int numTxStates) { - mIdleTimeMillis = new LongSamplingCounter(timeBase); + public ControllerActivityCounterImpl(Clock clock, TimeBase timeBase, int numTxStates) { + mClock = clock; + mTimeBase = timeBase; + mNumTxStates = numTxStates; mScanTimeMillis = new LongSamplingCounter(timeBase); mSleepTimeMillis = new LongSamplingCounter(timeBase); - mRxTimeMillis = new LongSamplingCounter(timeBase); - mTxTimeMillis = new LongSamplingCounter[numTxStates]; - for (int i = 0; i < numTxStates; i++) { - mTxTimeMillis[i] = new LongSamplingCounter(timeBase); - } mPowerDrainMaMs = new LongSamplingCounter(timeBase); mMonitoredRailChargeConsumedMaMs = new LongSamplingCounter(timeBase); } - public ControllerActivityCounterImpl(TimeBase timeBase, int numTxStates, Parcel in) { - mIdleTimeMillis = new LongSamplingCounter(timeBase, in); + public ControllerActivityCounterImpl(Clock clock, TimeBase timeBase, int numTxStates, + Parcel in) { + mClock = clock; + mTimeBase = timeBase; + mNumTxStates = numTxStates; + mIdleTimeMillis = readTimeMultiStateCounter(in, timeBase); mScanTimeMillis = new LongSamplingCounter(timeBase, in); mSleepTimeMillis = new LongSamplingCounter(timeBase, in); - mRxTimeMillis = new LongSamplingCounter(timeBase, in); - final int recordedTxStates = in.readInt(); - if (recordedTxStates != numTxStates) { - throw new ParcelFormatException("inconsistent tx state lengths"); - } + mRxTimeMillis = readTimeMultiStateCounter(in, timeBase); + mTxTimeMillis = readTimeMultiStateCounters(in, timeBase, numTxStates); - mTxTimeMillis = new LongSamplingCounter[numTxStates]; - for (int i = 0; i < numTxStates; i++) { - mTxTimeMillis[i] = new LongSamplingCounter(timeBase, in); - } mPowerDrainMaMs = new LongSamplingCounter(timeBase, in); mMonitoredRailChargeConsumedMaMs = new LongSamplingCounter(timeBase, in); } public void readSummaryFromParcel(Parcel in) { - mIdleTimeMillis.readSummaryFromParcelLocked(in); + mIdleTimeMillis = readTimeMultiStateCounter(in, mTimeBase); mScanTimeMillis.readSummaryFromParcelLocked(in); mSleepTimeMillis.readSummaryFromParcelLocked(in); - mRxTimeMillis.readSummaryFromParcelLocked(in); - final int recordedTxStates = in.readInt(); - if (recordedTxStates != mTxTimeMillis.length) { - throw new ParcelFormatException("inconsistent tx state lengths"); - } - for (LongSamplingCounter counter : mTxTimeMillis) { - counter.readSummaryFromParcelLocked(in); - } + mRxTimeMillis = readTimeMultiStateCounter(in, mTimeBase); + mTxTimeMillis = readTimeMultiStateCounters(in, mTimeBase, mNumTxStates); + mPowerDrainMaMs.readSummaryFromParcelLocked(in); mMonitoredRailChargeConsumedMaMs.readSummaryFromParcelLocked(in); } @@ -3287,52 +3356,98 @@ public class BatteryStatsImpl extends BatteryStats { } public void writeSummaryToParcel(Parcel dest) { - mIdleTimeMillis.writeSummaryFromParcelLocked(dest); + writeTimeMultiStateCounter(dest, mIdleTimeMillis); mScanTimeMillis.writeSummaryFromParcelLocked(dest); mSleepTimeMillis.writeSummaryFromParcelLocked(dest); - mRxTimeMillis.writeSummaryFromParcelLocked(dest); - dest.writeInt(mTxTimeMillis.length); - for (LongSamplingCounter counter : mTxTimeMillis) { - counter.writeSummaryFromParcelLocked(dest); - } + writeTimeMultiStateCounter(dest, mRxTimeMillis); + writeTimeMultiStateCounters(dest, mTxTimeMillis); mPowerDrainMaMs.writeSummaryFromParcelLocked(dest); mMonitoredRailChargeConsumedMaMs.writeSummaryFromParcelLocked(dest); } @Override public void writeToParcel(Parcel dest, int flags) { - mIdleTimeMillis.writeToParcel(dest); + writeTimeMultiStateCounter(dest, mIdleTimeMillis); mScanTimeMillis.writeToParcel(dest); mSleepTimeMillis.writeToParcel(dest); - mRxTimeMillis.writeToParcel(dest); - dest.writeInt(mTxTimeMillis.length); - for (LongSamplingCounter counter : mTxTimeMillis) { - counter.writeToParcel(dest); - } + writeTimeMultiStateCounter(dest, mRxTimeMillis); + writeTimeMultiStateCounters(dest, mTxTimeMillis); mPowerDrainMaMs.writeToParcel(dest); mMonitoredRailChargeConsumedMaMs.writeToParcel(dest); } + private TimeMultiStateCounter readTimeMultiStateCounter(Parcel in, TimeBase timeBase) { + if (in.readBoolean()) { + final TimeMultiStateCounter counter = + new TimeMultiStateCounter(timeBase, in, mClock.elapsedRealtime()); + if (counter.getStateCount() == BatteryConsumer.PROCESS_STATE_COUNT) { + return counter; + } + } + return null; + } + + private void writeTimeMultiStateCounter(Parcel dest, TimeMultiStateCounter counter) { + if (counter != null) { + dest.writeBoolean(true); + counter.writeToParcel(dest); + } else { + dest.writeBoolean(false); + } + } + + private TimeMultiStateCounter[] readTimeMultiStateCounters(Parcel in, TimeBase timeBase, + int expectedNumCounters) { + if (in.readBoolean()) { + final int numCounters = in.readInt(); + boolean valid = (numCounters == expectedNumCounters); + // Need to read counters out of the Parcel, even if all or some of them are + // invalid. + TimeMultiStateCounter[] counters = new TimeMultiStateCounter[numCounters]; + for (int i = 0; i < numCounters; i++) { + final TimeMultiStateCounter counter = + new TimeMultiStateCounter(timeBase, in, mClock.elapsedRealtime()); + if (counter.getStateCount() == BatteryConsumer.PROCESS_STATE_COUNT) { + counters[i] = counter; + } else { + valid = false; + } + } + if (valid) { + return counters; + } + } + return null; + } + + private void writeTimeMultiStateCounters(Parcel dest, TimeMultiStateCounter[] counters) { + if (counters != null) { + dest.writeBoolean(true); + dest.writeInt(counters.length); + for (TimeMultiStateCounter counter : counters) { + counter.writeToParcel(dest); + } + } else { + dest.writeBoolean(false); + } + } + public void reset(boolean detachIfReset, long elapsedRealtimeUs) { - mIdleTimeMillis.reset(detachIfReset, elapsedRealtimeUs); + resetIfNotNull(mIdleTimeMillis, detachIfReset, elapsedRealtimeUs); mScanTimeMillis.reset(detachIfReset, elapsedRealtimeUs); mSleepTimeMillis.reset(detachIfReset, elapsedRealtimeUs); - mRxTimeMillis.reset(detachIfReset, elapsedRealtimeUs); - for (LongSamplingCounter counter : mTxTimeMillis) { - counter.reset(detachIfReset, elapsedRealtimeUs); - } + resetIfNotNull(mRxTimeMillis, detachIfReset, elapsedRealtimeUs); + resetIfNotNull(mTxTimeMillis, detachIfReset, elapsedRealtimeUs); mPowerDrainMaMs.reset(detachIfReset, elapsedRealtimeUs); mMonitoredRailChargeConsumedMaMs.reset(detachIfReset, elapsedRealtimeUs); } public void detach() { - mIdleTimeMillis.detach(); + detachIfNotNull(mIdleTimeMillis); mScanTimeMillis.detach(); mSleepTimeMillis.detach(); - mRxTimeMillis.detach(); - for (LongSamplingCounter counter : mTxTimeMillis) { - counter.detach(); - } + detachIfNotNull(mRxTimeMillis); + detachIfNotNull(mTxTimeMillis); mPowerDrainMaMs.detach(); mMonitoredRailChargeConsumedMaMs.detach(); } @@ -3342,7 +3457,17 @@ public class BatteryStatsImpl extends BatteryStats { * milliseconds. */ @Override - public LongSamplingCounter getIdleTimeCounter() { + public LongCounter getIdleTimeCounter() { + if (mIdleTimeMillis == null) { + return ZERO_LONG_COUNTER; + } + return mIdleTimeMillis; + } + + private TimeMultiStateCounter getOrCreateIdleTimeCounter() { + if (mIdleTimeMillis == null) { + mIdleTimeMillis = createTimeMultiStateCounter(); + } return mIdleTimeMillis; } @@ -3369,7 +3494,17 @@ public class BatteryStatsImpl extends BatteryStats { * milliseconds. */ @Override - public LongSamplingCounter getRxTimeCounter() { + public LongCounter getRxTimeCounter() { + if (mRxTimeMillis == null) { + return ZERO_LONG_COUNTER; + } + return mRxTimeMillis; + } + + private TimeMultiStateCounter getOrCreateRxTimeCounter() { + if (mRxTimeMillis == null) { + mRxTimeMillis = createTimeMultiStateCounter(); + } return mRxTimeMillis; } @@ -3378,10 +3513,33 @@ public class BatteryStatsImpl extends BatteryStats { * milliseconds. */ @Override - public LongSamplingCounter[] getTxTimeCounters() { + public LongCounter[] getTxTimeCounters() { + if (mTxTimeMillis == null) { + return ZERO_LONG_COUNTER_ARRAY; + } return mTxTimeMillis; } + private TimeMultiStateCounter[] getOrCreateTxTimeCounters() { + if (mTxTimeMillis == null) { + mTxTimeMillis = new TimeMultiStateCounter[mNumTxStates]; + for (int i = 0; i < mNumTxStates; i++) { + mTxTimeMillis[i] = createTimeMultiStateCounter(); + } + } + return mTxTimeMillis; + } + + private TimeMultiStateCounter createTimeMultiStateCounter() { + final long timestampMs = mClock.elapsedRealtime(); + TimeMultiStateCounter counter = new TimeMultiStateCounter(mTimeBase, + BatteryConsumer.PROCESS_STATE_COUNT, timestampMs); + counter.setState(mapUidProcessStateToBatteryConsumerProcessState(mProcessState), + timestampMs); + counter.update(0, timestampMs); + return counter; + } + /** * @return a LongSamplingCounter, measuring power use in milli-ampere milliseconds (mAmS). */ @@ -3398,6 +3556,21 @@ public class BatteryStatsImpl extends BatteryStats { public LongSamplingCounter getMonitoredRailChargeConsumedMaMs() { return mMonitoredRailChargeConsumedMaMs; } + + private void setState(int processState, long elapsedTimeMs) { + mProcessState = processState; + if (mIdleTimeMillis != null) { + mIdleTimeMillis.setState(processState, elapsedTimeMs); + } + if (mRxTimeMillis != null) { + mRxTimeMillis.setState(processState, elapsedTimeMs); + } + if (mTxTimeMillis != null) { + for (int i = 0; i < mTxTimeMillis.length; i++) { + mTxTimeMillis[i].setState(processState, elapsedTimeMs); + } + } + } } /** Get Resource Power Manager stats. Create a new one if it doesn't already exist. */ @@ -8234,6 +8407,16 @@ public class BatteryStatsImpl extends BatteryStats { mapUidProcessStateToBatteryConsumerProcessState(procState); getCpuActiveTimeCounter().setState(batteryConsumerProcessState, elapsedTimeMs); getMobileRadioActiveTimeCounter().setState(batteryConsumerProcessState, elapsedTimeMs); + final ControllerActivityCounterImpl wifiControllerActivity = + getWifiControllerActivity(); + 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) { @@ -8271,7 +8454,7 @@ public class BatteryStatsImpl extends BatteryStats { long activeTime = 0; for (int procState = 0; procState < BatteryConsumer.PROCESS_STATE_COUNT; procState++) { - activeTime += mCpuActiveTimeMs.getCountLocked(procState); + activeTime += mCpuActiveTimeMs.getCountForProcessState(procState); } return activeTime; } @@ -8283,7 +8466,7 @@ public class BatteryStatsImpl extends BatteryStats { return 0; } - return mCpuActiveTimeMs.getCountLocked(procState); + return mCpuActiveTimeMs.getCountForProcessState(procState); } @Override @@ -8576,12 +8759,12 @@ public class BatteryStatsImpl extends BatteryStats { } @Override - public ControllerActivityCounter getWifiControllerActivity() { + public ControllerActivityCounterImpl getWifiControllerActivity() { return mWifiControllerActivity; } @Override - public ControllerActivityCounter getBluetoothControllerActivity() { + public ControllerActivityCounterImpl getBluetoothControllerActivity() { return mBluetoothControllerActivity; } @@ -8592,24 +8775,24 @@ public class BatteryStatsImpl extends BatteryStats { public ControllerActivityCounterImpl getOrCreateWifiControllerActivityLocked() { if (mWifiControllerActivity == null) { - mWifiControllerActivity = new ControllerActivityCounterImpl(mBsi.mOnBatteryTimeBase, - NUM_BT_TX_LEVELS); + mWifiControllerActivity = new ControllerActivityCounterImpl(mBsi.mClock, + mBsi.mOnBatteryTimeBase, NUM_WIFI_TX_LEVELS); } return mWifiControllerActivity; } public ControllerActivityCounterImpl getOrCreateBluetoothControllerActivityLocked() { if (mBluetoothControllerActivity == null) { - mBluetoothControllerActivity = new ControllerActivityCounterImpl(mBsi.mOnBatteryTimeBase, - NUM_BT_TX_LEVELS); + mBluetoothControllerActivity = new ControllerActivityCounterImpl(mBsi.mClock, + mBsi.mOnBatteryTimeBase, NUM_BT_TX_LEVELS); } return mBluetoothControllerActivity; } public ControllerActivityCounterImpl getOrCreateModemControllerActivityLocked() { if (mModemControllerActivity == null) { - mModemControllerActivity = new ControllerActivityCounterImpl(mBsi.mOnBatteryTimeBase, - ModemActivityInfo.getNumTxPowerLevels()); + mModemControllerActivity = new ControllerActivityCounterImpl(mBsi.mClock, + mBsi.mOnBatteryTimeBase, ModemActivityInfo.getNumTxPowerLevels()); } return mModemControllerActivity; } @@ -8702,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); } @@ -8745,6 +8936,13 @@ public class BatteryStatsImpl extends BatteryStats { return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_WIFI); } + @GuardedBy("mBsi") + @Override + public long getWifiMeasuredBatteryConsumptionUC(int processState) { + return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_WIFI, + processState); + } + /** * Gets the minimum of the uid's foreground activity time and its PROCESS_STATE_TOP time * since last marked. Also sets the mark time for both these timers. @@ -9341,7 +9539,7 @@ public class BatteryStatsImpl extends BatteryStats { if (processState == BatteryConsumer.PROCESS_STATE_ANY) { return mMobileRadioActiveTime.getTotalCountLocked(); } else { - return mMobileRadioActiveTime.getCountLocked(processState); + return mMobileRadioActiveTime.getCountForProcessState(processState); } } @@ -10291,22 +10489,22 @@ public class BatteryStatsImpl extends BatteryStats { } if (in.readInt() != 0) { - mWifiControllerActivity = new ControllerActivityCounterImpl(mBsi.mOnBatteryTimeBase, - NUM_WIFI_TX_LEVELS, in); + mWifiControllerActivity = new ControllerActivityCounterImpl(mBsi.mClock, + mBsi.mOnBatteryTimeBase, NUM_WIFI_TX_LEVELS, in); } else { mWifiControllerActivity = null; } if (in.readInt() != 0) { - mBluetoothControllerActivity = new ControllerActivityCounterImpl(mBsi.mOnBatteryTimeBase, - NUM_BT_TX_LEVELS, in); + mBluetoothControllerActivity = new ControllerActivityCounterImpl(mBsi.mClock, + mBsi.mOnBatteryTimeBase, NUM_BT_TX_LEVELS, in); } else { mBluetoothControllerActivity = null; } if (in.readInt() != 0) { - mModemControllerActivity = new ControllerActivityCounterImpl(mBsi.mOnBatteryTimeBase, - ModemActivityInfo.getNumTxPowerLevels(), in); + mModemControllerActivity = new ControllerActivityCounterImpl(mBsi.mClock, + mBsi.mOnBatteryTimeBase, ModemActivityInfo.getNumTxPowerLevels(), in); } else { mModemControllerActivity = null; } @@ -11273,6 +11471,20 @@ public class BatteryStatsImpl extends BatteryStats { getMobileRadioActiveTimeCounter() .setState(batteryConsumerProcessState, elapsedRealtimeMs); + + final ControllerActivityCounterImpl wifiControllerActivity = + getWifiControllerActivity(); + if (wifiControllerActivity != null) { + wifiControllerActivity.setState(batteryConsumerProcessState, elapsedRealtimeMs); + } + + final ControllerActivityCounterImpl bluetoothControllerActivity = + getBluetoothControllerActivity(); + if (bluetoothControllerActivity != null) { + bluetoothControllerActivity.setState(batteryConsumerProcessState, + elapsedRealtimeMs); + } + final MeasuredEnergyStats energyStats = getOrCreateMeasuredEnergyStatsIfSupportedLocked(); if (energyStats != null) { @@ -11681,10 +11893,11 @@ public class BatteryStatsImpl extends BatteryStats { mNetworkByteActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase); mNetworkPacketActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase); } - mWifiActivity = new ControllerActivityCounterImpl(mOnBatteryTimeBase, NUM_WIFI_TX_LEVELS); - mBluetoothActivity = new ControllerActivityCounterImpl(mOnBatteryTimeBase, + mWifiActivity = new ControllerActivityCounterImpl(mClock, mOnBatteryTimeBase, + NUM_WIFI_TX_LEVELS); + mBluetoothActivity = new ControllerActivityCounterImpl(mClock, mOnBatteryTimeBase, NUM_BT_TX_LEVELS); - mModemActivity = new ControllerActivityCounterImpl(mOnBatteryTimeBase, + mModemActivity = new ControllerActivityCounterImpl(mClock, mOnBatteryTimeBase, ModemActivityInfo.getNumTxPowerLevels()); mMobileRadioActiveTimer = new StopwatchTimer(mClock, null, -400, null, mOnBatteryTimeBase); mMobileRadioActivePerAppTimer = new StopwatchTimer(mClock, null, -401, null, @@ -12517,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") @@ -12536,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(); } /** @@ -12562,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 @@ -12594,6 +12801,8 @@ 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) { @@ -12613,7 +12822,8 @@ public class BatteryStatsImpl extends BatteryStats { continue; } - final Uid u = getUidStatsLocked(mapUid(entry.uid), elapsedRealtimeMs, uptimeMs); + final int uid = mapUid(entry.uid); + final Uid u = getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs); if (entry.rxBytes != 0) { u.noteNetworkActivityLocked(NETWORK_WIFI_RX_DATA, entry.rxBytes, entry.rxPackets); @@ -12626,8 +12836,7 @@ public class BatteryStatsImpl extends BatteryStats { mNetworkPacketActivityCounters[NETWORK_WIFI_RX_DATA].addCountLocked( entry.rxPackets); - // TODO(b/182845426): What if u was a mapped isolated uid? Shouldn't we sum? - rxPackets.put(u.getUid(), entry.rxPackets); + rxPackets.incrementValue(uid, entry.rxPackets); // Sum the total number of packets so that the Rx Power can // be evenly distributed amongst the apps. @@ -12646,8 +12855,7 @@ public class BatteryStatsImpl extends BatteryStats { mNetworkPacketActivityCounters[NETWORK_WIFI_TX_DATA].addCountLocked( entry.txPackets); - // TODO(b/182845426): What if u was a mapped isolated uid? Shouldn't we sum? - txPackets.put(u.getUid(), entry.txPackets); + txPackets.incrementValue(uid, entry.txPackets); // Sum the total number of packets so that the Tx Power can // be evenly distributed amongst the apps. @@ -12677,13 +12885,12 @@ public class BatteryStatsImpl extends BatteryStats { } } - uidEstimatedConsumptionMah.add(u.getUid(), + uidEstimatedConsumptionMah.incrementValue(u.getUid(), mWifiPowerCalculator.calcPowerWithoutControllerDataMah( entry.rxPackets, entry.txPackets, uidRunningMs, uidScanMs, uidBatchScanMs)); } } - mNetworkStatsPool.release(delta); delta = null; } @@ -12772,11 +12979,9 @@ public class BatteryStatsImpl extends BatteryStats { + scanTxTimeSinceMarkMs + " ms)"); } - ControllerActivityCounterImpl activityCounter = - uid.getOrCreateWifiControllerActivityLocked(); - activityCounter.getRxTimeCounter().addCountLocked(scanRxTimeSinceMarkMs); - activityCounter.getTxTimeCounters()[0].addCountLocked( - scanTxTimeSinceMarkMs); + rxTimesMs.incrementValue(uid.getUid(), scanRxTimeSinceMarkMs); + txTimesMs.incrementValue(uid.getUid(), scanTxTimeSinceMarkMs); + leftOverRxTimeMs -= scanRxTimeSinceMarkMs; leftOverTxTimeMs -= scanTxTimeSinceMarkMs; } @@ -12796,14 +13001,14 @@ public class BatteryStatsImpl extends BatteryStats { Slog.d(TAG, " IdleTime for UID " + uid.getUid() + ": " + myIdleTimeMs + " ms"); } - uid.getOrCreateWifiControllerActivityLocked().getIdleTimeCounter() - .addCountLocked(myIdleTimeMs); + uid.getOrCreateWifiControllerActivityLocked().getOrCreateIdleTimeCounter() + .increment(myIdleTimeMs, elapsedRealtimeMs); } if (uidEstimatedConsumptionMah != null) { double uidEstMah = mWifiPowerCalculator.calcPowerFromControllerDataMah( scanRxTimeSinceMarkMs, scanTxTimeSinceMarkMs, myIdleTimeMs); - uidEstimatedConsumptionMah.add(uid.getUid(), uidEstMah); + uidEstimatedConsumptionMah.incrementValue(uid.getUid(), uidEstMah); } } @@ -12815,36 +13020,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().getTxTimeCounters()[0] - .addCountLocked(myTxTimeMs); + 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().getRxTimeCounter() - .addCountLocked(myRxTimeMs); + + 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)); } @@ -12852,16 +13072,15 @@ 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.getRxTimeCounter().addCountLocked( - info.getControllerRxDurationMillis()); - mWifiActivity.getTxTimeCounters()[0].addCountLocked( - info.getControllerTxDurationMillis()); + mWifiActivity.getOrCreateRxTimeCounter().increment( + info.getControllerRxDurationMillis(), elapsedRealtimeMs); + mWifiActivity.getOrCreateTxTimeCounters()[0].increment( + info.getControllerTxDurationMillis(), elapsedRealtimeMs); mWifiActivity.getScanTimeCounter().addCountLocked( info.getControllerScanDurationMillis()); - mWifiActivity.getIdleTimeCounter().addCountLocked( - info.getControllerIdleDurationMillis()); + mWifiActivity.getOrCreateIdleTimeCounter().increment( + info.getControllerIdleDurationMillis(), elapsedRealtimeMs); // POWER_WIFI_CONTROLLER_OPERATING_VOLTAGE is measured in mV, so convert to V. final double opVolt = mPowerProfile.getAveragePower( @@ -12931,21 +13150,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; } @@ -12961,14 +13174,16 @@ public class BatteryStatsImpl extends BatteryStats { if (deltaInfo != null) { mHasModemReporting = true; - mModemActivity.getIdleTimeCounter().addCountLocked( - deltaInfo.getIdleTimeMillis()); + mModemActivity.getOrCreateIdleTimeCounter() + .increment(deltaInfo.getIdleTimeMillis(), elapsedRealtimeMs); mModemActivity.getSleepTimeCounter().addCountLocked( deltaInfo.getSleepTimeMillis()); - mModemActivity.getRxTimeCounter().addCountLocked(deltaInfo.getReceiveTimeMillis()); + mModemActivity.getOrCreateRxTimeCounter() + .increment(deltaInfo.getReceiveTimeMillis(), elapsedRealtimeMs); for (int lvl = 0; lvl < ModemActivityInfo.getNumTxPowerLevels(); lvl++) { - mModemActivity.getTxTimeCounters()[lvl] - .addCountLocked(deltaInfo.getTransmitDurationMillisAtPowerLevel(lvl)); + mModemActivity.getOrCreateTxTimeCounters()[lvl] + .increment(deltaInfo.getTransmitDurationMillisAtPowerLevel(lvl), + elapsedRealtimeMs); } // POWER_MODEM_CONTROLLER_OPERATING_VOLTAGE is measured in mV, so convert to V. @@ -13070,7 +13285,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)); } @@ -13086,7 +13301,8 @@ public class BatteryStatsImpl extends BatteryStats { if (totalRxPackets > 0 && entry.rxPackets > 0) { final long rxMs = (entry.rxPackets * deltaInfo.getReceiveTimeMillis()) / totalRxPackets; - activityCounter.getRxTimeCounter().addCountLocked(rxMs); + activityCounter.getOrCreateRxTimeCounter() + .increment(rxMs, elapsedRealtimeMs); } if (totalTxPackets > 0 && entry.txPackets > 0) { @@ -13095,7 +13311,8 @@ public class BatteryStatsImpl extends BatteryStats { long txMs = entry.txPackets * deltaInfo.getTransmitDurationMillisAtPowerLevel(lvl); txMs /= totalTxPackets; - activityCounter.getTxTimeCounters()[lvl].addCountLocked(txMs); + activityCounter.getOrCreateTxTimeCounters()[lvl] + .increment(txMs, elapsedRealtimeMs); } } } @@ -13147,7 +13364,6 @@ public class BatteryStatsImpl extends BatteryStats { totalEstimatedConsumptionMah, elapsedRealtimeMs); } - mNetworkStatsPool.release(delta); delta = null; } } @@ -13193,8 +13409,8 @@ public class BatteryStatsImpl extends BatteryStats { energy = info.getControllerEnergyUsed(); if (!info.getUidTraffic().isEmpty()) { for (UidTraffic traffic : info.getUidTraffic()) { - uidRxBytes.put(traffic.getUid(), traffic.getRxBytes()); - uidTxBytes.put(traffic.getUid(), traffic.getTxBytes()); + uidRxBytes.incrementValue(traffic.getUid(), traffic.getRxBytes()); + uidTxBytes.incrementValue(traffic.getUid(), traffic.getTxBytes()); } } } @@ -13286,6 +13502,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) { @@ -13315,13 +13534,11 @@ public class BatteryStatsImpl extends BatteryStats { scanTimeTxSinceMarkMs = (txTimeMs * scanTimeTxSinceMarkMs) / totalScanTimeMs; } - final ControllerActivityCounterImpl counter = - u.getOrCreateBluetoothControllerActivityLocked(); - counter.getRxTimeCounter().addCountLocked(scanTimeRxSinceMarkMs); - counter.getTxTimeCounters()[0].addCountLocked(scanTimeTxSinceMarkMs); + 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)); } @@ -13382,35 +13599,52 @@ 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.getRxTimeCounter().addCountLocked(timeRxMs); - - 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.getTxTimeCounters()[0].addCountLocked(timeTxMs); + 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)); } } } - mBluetoothActivity.getRxTimeCounter().addCountLocked(rxTimeMs); - mBluetoothActivity.getTxTimeCounters()[0].addCountLocked(txTimeMs); - mBluetoothActivity.getIdleTimeCounter().addCountLocked(idleTimeMs); + mBluetoothActivity.getOrCreateRxTimeCounter().increment(rxTimeMs, elapsedRealtimeMs); + mBluetoothActivity.getOrCreateTxTimeCounters()[0].increment(txTimeMs, elapsedRealtimeMs); + mBluetoothActivity.getOrCreateIdleTimeCounter().increment(idleTimeMs, elapsedRealtimeMs); // POWER_BLUETOOTH_CONTROLLER_OPERATING_VOLTAGE is measured in mV, so convert to V. final double opVolt = mPowerProfile.getAveragePower( @@ -16024,6 +16258,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") @@ -16288,6 +16534,7 @@ public class BatteryStatsImpl extends BatteryStats { @GuardedBy("this") public void readSummaryFromParcel(Parcel in) throws ParcelFormatException { final int version = in.readInt(); + if (version != VERSION) { Slog.w("BatteryStats", "readFromParcel: version got " + version + ", expected " + VERSION + "; erasing old stats"); @@ -17447,15 +17694,15 @@ public class BatteryStatsImpl extends BatteryStats { } mWifiActiveTimer = new StopwatchTimer(mClock, null, -900, null, mOnBatteryTimeBase, in); - mWifiActivity = new ControllerActivityCounterImpl(mOnBatteryTimeBase, + mWifiActivity = new ControllerActivityCounterImpl(mClock, mOnBatteryTimeBase, NUM_WIFI_TX_LEVELS, in); for (int i=0; i<mGpsSignalQualityTimer.length; i++) { mGpsSignalQualityTimer[i] = new StopwatchTimer(mClock, null, -1000 - i, null, mOnBatteryTimeBase, in); } - mBluetoothActivity = new ControllerActivityCounterImpl(mOnBatteryTimeBase, + mBluetoothActivity = new ControllerActivityCounterImpl(mClock, mOnBatteryTimeBase, NUM_BT_TX_LEVELS, in); - mModemActivity = new ControllerActivityCounterImpl(mOnBatteryTimeBase, + mModemActivity = new ControllerActivityCounterImpl(mClock, mOnBatteryTimeBase, ModemActivityInfo.getNumTxPowerLevels(), in); mHasWifiReporting = in.readInt() != 0; mHasBluetoothReporting = in.readInt() != 0; 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/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..7f8acccd72bb 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,35 @@ 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 int SUBSYSTEM_SHIFT = 32; + private static final long SUBSYSTEM_MASK = 0xF << SUBSYSTEM_SHIFT; + /** + * Power constant not associated with a subsystem. + */ + public static final long SUBSYSTEM_NONE = 0 << SUBSYSTEM_SHIFT; + /** + * Modem power constant. + */ + public static final long SUBSYSTEM_MODEM = 1 << SUBSYSTEM_SHIFT; + + @LongDef(prefix = { "SUBSYSTEM_" }, value = { + SUBSYSTEM_NONE, + SUBSYSTEM_MODEM, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Subsystem {} + + private static final long SUBSYSTEM_FIELDS_MASK = 0xFFFFFFFF; + + /** * A map from Power Use Item to its power consumption. */ static final HashMap<String, Double> sPowerItemMap = new HashMap<>(); @@ -268,12 +304,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 +329,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 +401,8 @@ public class PowerProfile { array.add(value); } } + } else if (element.equals(TAG_MODEM)) { + sModemPowerProfile.parseFromXml(parser); } } if (parsingArray) { @@ -515,6 +578,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_SLEEP, 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 +656,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 +917,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/os/WifiPowerCalculator.java b/core/java/com/android/internal/os/WifiPowerCalculator.java index 3915b0e01e7f..2a71ac6f441b 100644 --- a/core/java/com/android/internal/os/WifiPowerCalculator.java +++ b/core/java/com/android/internal/os/WifiPowerCalculator.java @@ -25,6 +25,7 @@ import android.os.UserHandle; import android.util.Log; import android.util.SparseArray; +import java.util.Arrays; import java.util.List; /** @@ -34,6 +35,9 @@ import java.util.List; public class WifiPowerCalculator extends PowerCalculator { private static final boolean DEBUG = BatteryStatsHelper.DEBUG; private static final String TAG = "WifiPowerCalculator"; + + private static final BatteryConsumer.Key[] UNINITIALIZED_KEYS = new BatteryConsumer.Key[0]; + private final UsageBasedPowerEstimator mIdlePowerEstimator; private final UsageBasedPowerEstimator mTxPowerEstimator; private final UsageBasedPowerEstimator mRxPowerEstimator; @@ -51,6 +55,9 @@ public class WifiPowerCalculator extends PowerCalculator { public long wifiTxPackets; public long wifiRxBytes; public long wifiTxBytes; + + public BatteryConsumer.Key[] keys; + public double[] powerPerKeyMah; } public WifiPowerCalculator(PowerProfile profile) { @@ -77,7 +84,7 @@ public class WifiPowerCalculator extends PowerCalculator { @Override public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats, long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) { - + BatteryConsumer.Key[] keys = UNINITIALIZED_KEYS; long totalAppDurationMs = 0; double totalAppPowerMah = 0; final PowerDurationAndTraffic powerDurationAndTraffic = new PowerDurationAndTraffic(); @@ -85,9 +92,20 @@ public class WifiPowerCalculator extends PowerCalculator { builder.getUidBatteryConsumerBuilders(); for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) { final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i); + if (keys == UNINITIALIZED_KEYS) { + if (query.isProcessStateDataNeeded()) { + keys = app.getKeys(BatteryConsumer.POWER_COMPONENT_WIFI); + powerDurationAndTraffic.keys = keys; + powerDurationAndTraffic.powerPerKeyMah = new double[keys.length]; + } else { + keys = null; + } + } + final long consumptionUC = app.getBatteryStatsUid().getWifiMeasuredBatteryConsumptionUC(); final int powerModel = getPowerModel(consumptionUC, query); + calculateApp(powerDurationAndTraffic, app.getBatteryStatsUid(), powerModel, rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED, batteryStats.hasWifiActivityReporting(), consumptionUC); @@ -99,6 +117,20 @@ public class WifiPowerCalculator extends PowerCalculator { powerDurationAndTraffic.durationMs); app.setConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI, powerDurationAndTraffic.powerMah, powerModel); + + if (query.isProcessStateDataNeeded() && keys != null) { + for (int j = 0; j < keys.length; j++) { + BatteryConsumer.Key key = keys[j]; + final int processState = key.processState; + if (processState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) { + // Already populated with the total across all process states + continue; + } + + app.setConsumedPower(key, powerDurationAndTraffic.powerPerKeyMah[j], + powerModel); + } + } } final long consumptionUC = batteryStats.getWifiMeasuredBatteryConsumptionUC(); @@ -193,21 +225,23 @@ public class WifiPowerCalculator extends PowerCalculator { BatteryStats.NETWORK_WIFI_TX_DATA, statsType); - if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) { - powerDurationAndTraffic.powerMah = uCtoMah(consumptionUC); - } - if (hasWifiActivityReporting && mHasWifiPowerController) { final BatteryStats.ControllerActivityCounter counter = u.getWifiControllerActivity(); if (counter != null) { - final long idleTime = counter.getIdleTimeCounter().getCountLocked(statsType); - final long txTime = counter.getTxTimeCounters()[0].getCountLocked(statsType); - final long rxTime = counter.getRxTimeCounter().getCountLocked(statsType); + final BatteryStats.LongCounter rxTimeCounter = counter.getRxTimeCounter(); + final BatteryStats.LongCounter txTimeCounter = counter.getTxTimeCounters()[0]; + final BatteryStats.LongCounter idleTimeCounter = counter.getIdleTimeCounter(); + + final long rxTime = rxTimeCounter.getCountLocked(statsType); + final long txTime = txTimeCounter.getCountLocked(statsType); + final long idleTime = idleTimeCounter.getCountLocked(statsType); powerDurationAndTraffic.durationMs = idleTime + rxTime + txTime; if (powerModel == BatteryConsumer.POWER_MODEL_POWER_PROFILE) { powerDurationAndTraffic.powerMah = calcPowerFromControllerDataMah(rxTime, txTime, idleTime); + } else { + powerDurationAndTraffic.powerMah = uCtoMah(consumptionUC); } if (DEBUG && powerDurationAndTraffic.powerMah != 0) { @@ -215,9 +249,32 @@ public class WifiPowerCalculator extends PowerCalculator { + "ms tx=" + txTime + "ms power=" + formatCharge( powerDurationAndTraffic.powerMah)); } + + if (powerDurationAndTraffic.keys != null) { + for (int i = 0; i < powerDurationAndTraffic.keys.length; i++) { + final int processState = powerDurationAndTraffic.keys[i].processState; + if (processState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) { + continue; + } + + if (powerModel == BatteryConsumer.POWER_MODEL_POWER_PROFILE) { + powerDurationAndTraffic.powerPerKeyMah[i] = + calcPowerFromControllerDataMah( + rxTimeCounter.getCountForProcessState(processState), + txTimeCounter.getCountForProcessState(processState), + idleTimeCounter.getCountForProcessState(processState)); + } else { + powerDurationAndTraffic.powerPerKeyMah[i] = + uCtoMah(u.getWifiMeasuredBatteryConsumptionUC(processState)); + } + } + } } else { powerDurationAndTraffic.durationMs = 0; powerDurationAndTraffic.powerMah = 0; + if (powerDurationAndTraffic.powerPerKeyMah != null) { + Arrays.fill(powerDurationAndTraffic.powerPerKeyMah, 0); + } } } else { final long wifiRunningTime = u.getWifiRunningTime(rawRealtimeUs, statsType) / 1000; @@ -233,6 +290,14 @@ public class WifiPowerCalculator extends PowerCalculator { powerDurationAndTraffic.wifiRxPackets, powerDurationAndTraffic.wifiTxPackets, wifiRunningTime, wifiScanTimeMs, batchTimeMs); + } else { + powerDurationAndTraffic.powerMah = uCtoMah(consumptionUC); + } + + if (powerDurationAndTraffic.powerPerKeyMah != null) { + // Per-process state attribution is not supported in the absence of WiFi + // activity reporting + Arrays.fill(powerDurationAndTraffic.powerPerKeyMah, 0); } if (DEBUG && powerDurationAndTraffic.powerMah != 0) { 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..456ff4ba8a26 --- /dev/null +++ b/core/java/com/android/internal/power/ModemPowerProfile.java @@ -0,0 +1,442 @@ +/* + * 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.SparseDoubleArray; + +import com.android.internal.telephony.util.ArrayUtils; +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_SHIFT = 28; + private static final int MODEM_DRAIN_TYPE_MASK = 0xF << MODEM_DRAIN_TYPE_SHIFT; + + private static final int MODEM_TX_LEVEL_SHIFT = 24; + private static final int MODEM_TX_LEVEL_MASK = 0xF << MODEM_TX_LEVEL_SHIFT; + + private static final int MODEM_RAT_TYPE_SHIFT = 20; + private static final int MODEM_RAT_TYPE_MASK = 0xF << MODEM_RAT_TYPE_SHIFT; + + private static final int MODEM_NR_FREQUENCY_RANGE_SHIFT = 16; + private static final int MODEM_NR_FREQUENCY_RANGE_MASK = 0xF << MODEM_NR_FREQUENCY_RANGE_SHIFT; + + /** + * Corresponds to the overall modem battery drain while asleep. + */ + public static final int MODEM_DRAIN_TYPE_SLEEP = 0 << MODEM_DRAIN_TYPE_SHIFT; + + /** + * Corresponds to the overall modem battery drain while idle. + */ + public static final int MODEM_DRAIN_TYPE_IDLE = 1 << MODEM_DRAIN_TYPE_SHIFT; + + /** + * 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 = 2 << MODEM_DRAIN_TYPE_SHIFT; + + /** + * 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 = 3 << MODEM_DRAIN_TYPE_SHIFT; + + @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 String[] MODEM_DRAIN_TYPE_NAMES = + new String[]{"SLEEP", "IDLE", "RX", "TX"}; + + /** + * Corresponds to {@link ModemActivityInfo#TX_POWER_LEVEL_0}. + */ + public static final int MODEM_TX_LEVEL_0 = 0 << MODEM_TX_LEVEL_SHIFT; + + /** + * Corresponds to {@link ModemActivityInfo#TX_POWER_LEVEL_1}. + */ + public static final int MODEM_TX_LEVEL_1 = 1 << MODEM_TX_LEVEL_SHIFT; + + /** + * Corresponds to {@link ModemActivityInfo#TX_POWER_LEVEL_2}. + */ + public static final int MODEM_TX_LEVEL_2 = 2 << MODEM_TX_LEVEL_SHIFT; + + /** + * Corresponds to {@link ModemActivityInfo#TX_POWER_LEVEL_3}. + */ + public static final int MODEM_TX_LEVEL_3 = 3 << MODEM_TX_LEVEL_SHIFT; + + /** + * Corresponds to {@link ModemActivityInfo#TX_POWER_LEVEL_4}. + */ + public static final int MODEM_TX_LEVEL_4 = 4 << MODEM_TX_LEVEL_SHIFT; + + 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 { + } + + /** + * 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 = 0 << MODEM_RAT_TYPE_SHIFT; + + /** + * Corresponds to active modem usage on 4G {@link TelephonyManager#NETWORK_TYPE_LTE} RAT. + */ + public static final int MODEM_RAT_TYPE_LTE = 1 << MODEM_RAT_TYPE_SHIFT; + + /** + * Corresponds to active modem usage on 5G {@link TelephonyManager#NETWORK_TYPE_NR} RAT. + */ + public static final int MODEM_RAT_TYPE_NR = 2 << MODEM_RAT_TYPE_SHIFT; + + @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 String[] MODEM_RAT_TYPE_NAMES = new String[]{"DEFAULT", "LTE", "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 = 0 << MODEM_NR_FREQUENCY_RANGE_SHIFT; + + /** + * Corresponds to active NR modem usage on {@link ServiceState#FREQUENCY_RANGE_LOW}. + */ + public static final int MODEM_NR_FREQUENCY_RANGE_LOW = 1 << MODEM_NR_FREQUENCY_RANGE_SHIFT; + + /** + * Corresponds to active NR modem usage on {@link ServiceState#FREQUENCY_RANGE_MID}. + */ + public static final int MODEM_NR_FREQUENCY_RANGE_MID = 2 << MODEM_NR_FREQUENCY_RANGE_SHIFT; + + /** + * Corresponds to active NR modem usage on {@link ServiceState#FREQUENCY_RANGE_HIGH}. + */ + public static final int MODEM_NR_FREQUENCY_RANGE_HIGH = 3 << MODEM_NR_FREQUENCY_RANGE_SHIFT; + + /** + * Corresponds to active NR modem usage on {@link ServiceState#FREQUENCY_RANGE_MMWAVE}. + */ + public static final int MODEM_NR_FREQUENCY_RANGE_MMWAVE = 4 << MODEM_NR_FREQUENCY_RANGE_SHIFT; + + @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 String[] MODEM_NR_FREQUENCY_RANGE_NAMES = + new String[]{"DEFAULT", "LOW", "MID", "HIGH", "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_SHIFT, + MODEM_RAT_TYPE_NAMES); + if (ratType == MODEM_RAT_TYPE_NR) { + nrfType = getTypeFromAttribute(parser, ATTR_NR_FREQUENCY, + MODEM_NR_FREQUENCY_RANGE_SHIFT, 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 = level << MODEM_TX_LEVEL_SHIFT; + Slog.d("MWACHENS", + "parsing tx at level:" + level + ", aka 0x" + Integer.toHexString( + modemTxLevel)); + 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, int shift, + String[] names) { + final String value = XmlUtils.readStringAttribute(parser, attr); + final int index = ArrayUtils.indexOf(names, value); + if (value == null) { + // Attribute was not specified, just use the default. + return 0; + } + if (index < 0) { + throw new IllegalArgumentException( + "Unexpected " + attr + " value : " + value + ". Acceptable values are " + + Arrays.toString(names)); + } + return index << shift; + } + + /** + * 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 >> MODEM_DRAIN_TYPE_SHIFT); + sb.append(","); + + if (drainType == MODEM_DRAIN_TYPE_TX) { + final int txLevel = (key & MODEM_TX_LEVEL_MASK) >> MODEM_TX_LEVEL_SHIFT; + sb.append("level:"); + sb.append(txLevel); + sb.append(","); + } + + final int ratType = key & MODEM_RAT_TYPE_MASK; + appendFieldToString(sb, "RAT", MODEM_RAT_TYPE_NAMES, ratType >> MODEM_RAT_TYPE_SHIFT); + + 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 >> MODEM_NR_FREQUENCY_RANGE_SHIFT); + } + return sb.toString(); + } + + private static void appendFieldToString(StringBuilder sb, String fieldName, String[] names, + int index) { + sb.append(fieldName); + sb.append(":"); + if (index < 0 || index >= names.length) { + sb.append("UNKNOWN("); + sb.append(index); + sb.append(")"); + } else { + sb.append(names[index]); + } + } + + /** + * 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..b06a7f4e56e4 100644 --- a/core/java/com/android/internal/usb/DumpUtils.java +++ b/core/java/com/android/internal/usb/DumpUtils.java @@ -244,7 +244,10 @@ public class DumpUtils { writeContaminantPresenceStatus(dump, "contaminant_presence_status", UsbPortStatusProto.CONTAMINANT_PRESENCE_STATUS, status.getContaminantDetectionStatus()); - + dump.write("usb_data_enabled", UsbPortStatusProto.USB_DATA_ENABLED, + status.getUsbDataStatus()); + dump.write("is_power_transfer_limited", UsbPortStatusProto.IS_POWER_TRANSFER_LIMITED, + status.isPowerTransferLimited()); 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..2b25b8d99243 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", 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/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_view_DisplayEventReceiver.cpp b/core/jni/android_view_DisplayEventReceiver.cpp index ce772cf9faff..d91d526e3d4c 100644 --- a/core/jni/android_view_DisplayEventReceiver.cpp +++ b/core/jni/android_view_DisplayEventReceiver.cpp @@ -48,6 +48,16 @@ static struct { jmethodID init; } frameRateOverrideClassInfo; + struct { + jclass clazz; + jmethodID init; + } frameTimelineClassInfo; + + struct { + jclass clazz; + jmethodID init; + } vsyncEventDataClassInfo; + } gDisplayEventReceiverClassInfo; @@ -105,9 +115,38 @@ void NativeDisplayEventReceiver::dispatchVsync(nsecs_t timestamp, PhysicalDispla ScopedLocalRef<jobject> receiverObj(env, jniGetReferent(env, mReceiverWeakGlobal)); if (receiverObj.get()) { ALOGV("receiver %p ~ Invoking vsync handler.", this); + + ScopedLocalRef<jobjectArray> + frameTimelineObjs(env, + env->NewObjectArray(vsyncEventData.frameTimelines.size(), + gDisplayEventReceiverClassInfo + .frameTimelineClassInfo.clazz, + /*initial element*/ NULL)); + for (int i = 0; i < vsyncEventData.frameTimelines.size(); i++) { + VsyncEventData::FrameTimeline frameTimeline = vsyncEventData.frameTimelines[i]; + ScopedLocalRef<jobject> + frameTimelineObj(env, + env->NewObject(gDisplayEventReceiverClassInfo + .frameTimelineClassInfo.clazz, + gDisplayEventReceiverClassInfo + .frameTimelineClassInfo.init, + frameTimeline.id, + frameTimeline.expectedPresentTime, + frameTimeline.deadlineTimestamp)); + env->SetObjectArrayElement(frameTimelineObjs.get(), i, frameTimelineObj.get()); + } + ScopedLocalRef<jobject> + vsyncEventDataJava(env, + env->NewObject(gDisplayEventReceiverClassInfo + .vsyncEventDataClassInfo.clazz, + gDisplayEventReceiverClassInfo + .vsyncEventDataClassInfo.init, + frameTimelineObjs.get(), + vsyncEventData.preferredFrameTimelineIndex, + vsyncEventData.frameInterval)); + env->CallVoidMethod(receiverObj.get(), gDisplayEventReceiverClassInfo.dispatchVsync, - timestamp, displayId.value, count, vsyncEventData.id, - vsyncEventData.deadlineTimestamp, vsyncEventData.frameInterval); + timestamp, displayId.value, count, vsyncEventDataJava.get()); ALOGV("receiver %p ~ Returned from vsync handler.", this); } @@ -239,7 +278,7 @@ int register_android_view_DisplayEventReceiver(JNIEnv* env) { gDisplayEventReceiverClassInfo.dispatchVsync = GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz, "dispatchVsync", - "(JJIJJJ)V"); + "(JJILandroid/view/DisplayEventReceiver$VsyncEventData;)V"); gDisplayEventReceiverClassInfo.dispatchHotplug = GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz, "dispatchHotplug", "(JJZ)V"); gDisplayEventReceiverClassInfo.dispatchModeChanged = @@ -258,6 +297,24 @@ int register_android_view_DisplayEventReceiver(JNIEnv* env) { GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.frameRateOverrideClassInfo.clazz, "<init>", "(IF)V"); + jclass frameTimelineClazz = + FindClassOrDie(env, "android/view/DisplayEventReceiver$VsyncEventData$FrameTimeline"); + gDisplayEventReceiverClassInfo.frameTimelineClassInfo.clazz = + MakeGlobalRefOrDie(env, frameTimelineClazz); + gDisplayEventReceiverClassInfo.frameTimelineClassInfo.init = + GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.frameTimelineClassInfo.clazz, + "<init>", "(JJJ)V"); + + jclass vsyncEventDataClazz = + FindClassOrDie(env, "android/view/DisplayEventReceiver$VsyncEventData"); + gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.clazz = + MakeGlobalRefOrDie(env, vsyncEventDataClazz); + gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.init = + GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.clazz, + "<init>", + "([Landroid/view/" + "DisplayEventReceiver$VsyncEventData$FrameTimeline;IJ)V"); + return res; } 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..b3f54f9afce2 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,8 @@ message UsbPortStatusProto { optional DataRole data_role = 4; repeated UsbPortStatusRoleCombinationProto role_combinations = 5; optional android.service.ContaminantPresenceStatus contaminant_presence_status = 6; + optional bool usb_data_enabled = 7; + optional bool is_power_transfer_limited = 8; } message UsbPortStatusRoleCombinationProto { diff --git a/core/res/Android.bp b/core/res/Android.bp index 60630626fb68..c42517d8a873 100644 --- a/core/res/Android.bp +++ b/core/res/Android.bp @@ -37,7 +37,6 @@ license { visibility: [":__subpackages__"], license_kinds: [ "SPDX-license-identifier-Apache-2.0", - "SPDX-license-identifier-GPL", ], license_text: [ "NOTICE", diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index d6bbe71586fa..a4d406929b79 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -716,6 +716,7 @@ <!-- Added in T --> <protected-broadcast android:name="android.intent.action.REFRESH_SAFETY_SOURCES" /> + <protected-broadcast android:name="android.app.action.DEVICE_POLICY_RESOURCE_UPDATED" /> <!-- ====================================================================== --> <!-- RUNTIME PERMISSIONS --> @@ -3830,6 +3831,13 @@ <permission android:name="android.permission.GET_TOP_ACTIVITY_INFO" android:protectionLevel="signature|recents" /> + <!-- @SystemApi Allows an application to set the system audio caption and its UI + enabled state. + <p>Not for use by third-party applications. + @hide --> + <permission android:name="android.permission.SET_SYSTEM_AUDIO_CAPTION" + android:protectionLevel="signature|role" /> + <!-- Allows an application to retrieve the current state of keys and switches. <p>Not for use by third-party applications. @@ -4737,6 +4745,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 @@ -5114,6 +5128,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" @@ -5602,13 +5621,6 @@ <permission android:name="android.permission.VIEW_INSTANT_APPS" android:protectionLevel="signature|preinstalled" /> - <!-- Allows an application to interact with the currently active - {@link com.android.server.communal.CommunalManagerService}. - @hide - @TestApi --> - <permission android:name="android.permission.WRITE_COMMUNAL_STATE" - android:protectionLevel="signature" /> - <!-- Allows the holder to manage whether the system can bind to services provided by instant apps. This permission is intended to protect test/development fucntionality and should be used only in such cases. @@ -6031,10 +6043,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. @@ -6093,11 +6110,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" @@ -6111,7 +6138,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 --> @@ -6139,6 +6166,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/default_dream_preview.xml b/core/res/res/drawable/default_dream_preview.xml new file mode 100644 index 000000000000..bf4a04b30f19 --- /dev/null +++ b/core/res/res/drawable/default_dream_preview.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. + --> + +<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> + <solid android:color="@android:color/white"/> +</shape>
\ No newline at end of file diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml index 74ac6806a9de..3bc02830b0b9 100644 --- a/core/res/res/values-af/strings.xml +++ b/core/res/res/values-af/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Laat die program toe om Moenie Steur Nie-opstelling te lees en skryf."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"begin kyk van toestemminggebruik"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Laat die houer toe om die toestemminggebruik vir \'n program te begin. Behoort nooit vir normale programme nodig te wees nie."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"begin Bekyk Toestemmingbesluite"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Laat die houer toe om skerm te begin om toestemmingbesluite na te gaan. Behoort nooit vir normale programme nodig te wees nie."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"begin Bekyk Programkenmerke"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Laat die houer toe om die kenmerke-inligting vir \'n program te begin bekyk."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"kry toegang tot sensordata teen \'n hoë monsternemingkoers"</string> diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml index 3f5b5e033275..fb52e6ad965e 100644 --- a/core/res/res/values-am/strings.xml +++ b/core/res/res/values-am/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"መተግበሪያው የአትረብሽ ውቅረትን እንዲያነብብ እና እንዲጸፍ ይፈቅዳል።"</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"የእይታ ፈቃድ መጠቀምን መጀመር"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"ያዢው ለአንድ መተግበሪያ የፈቃድ አጠቃቀሙን እንዲያስጀምር ያስችለዋል። ለመደበኛ መተግበሪያዎች በጭራሽ ሊያስፈልግ አይገባም።"</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"የእይታ ፈቃድ ውሳኔዎችን ይጀምሩ"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"ያዢው የፈቃድ ውሳኔዎችን ለመገምገም ማያ ገጽ እንዲጀምሩ ያስችላቸዋል። ለመደበኛ መተግበሪያዎች በጭራሽ ሊያስፈልግ አይገባም።"</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"የመተግበሪያ ባህሪያትን ማየት መጀመር"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"ያዢው የአንድ መተግበሪያ የባህሪያት መረጃን ማየት እንዲጀምር ያስችለዋል።"</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"የዳሳሽ ውሂቡን በከፍተኛ የናሙና ብዛት ላይ ይድረሱበት"</string> diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml index 9dc59c0c8f9a..12d1b83ea1df 100644 --- a/core/res/res/values-ar/strings.xml +++ b/core/res/res/values-ar/strings.xml @@ -745,10 +745,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"للسماح للتطبيق بقراءة إعداد \"عدم الإزعاج\" وكتابتها."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"بدء استخدام إذن العرض"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"للسماح للمالك ببدء استخدام الإذن لأحد التطبيقات. ولن تكون هناك حاجة إليه مطلقًا مع التطبيقات العادية."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"بدء اتخاذ القرارات المتعلقة بالإذن بعرض البيانات"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"يمكنك السماح للمالك ببدء الشاشة لمراجعة القرارات المتعلقة بالأذونات. لا حاجة لذلك مع التطبيقات العادية."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"بدء عرض ميزات التطبيق"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"للسماح للمالك ببدء عرض معلومات عن ميزات التطبيق."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"الوصول إلى بيانات جهاز الاستشعار بمعدّل مرتفع للبيانات في الملف الصوتي"</string> diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml index bd2b97dde961..f57e42bf7664 100644 --- a/core/res/res/values-as/strings.xml +++ b/core/res/res/values-as/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"অসুবিধা নিদিবৰ কনফিগাৰেশ্বনক পঢ়িবলৈ আৰু সালসলনি কৰিবলৈ এপটোক অনুমতি দিয়ে।"</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"চোৱাৰ অনুমতিৰ ব্যৱহাৰ আৰম্ভ কৰক"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"ধাৰকক কোনো এপৰ বাবে অনুমতিৰ ব্যৱহাৰ আৰম্ভ কৰিবলৈ দিয়ে। সাধাৰণ এপ্সমূহৰ বাবে কেতিয়াও প্ৰয়োজন হ’ব নালাগে।"</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"অনুমতিৰ সিদ্ধান্তসমূহ চোৱা আৰম্ভ কৰক"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"ধাৰকক অনুমতিৰ সিদ্ধান্তসমূহ পৰ্যালোচনা কৰিবলৈ স্ক্ৰীন আৰম্ভ কৰিবলৈ দিয়ে। সাধাৰণ এপ্সমূহৰ বাবে কেতিয়াও প্ৰয়োজন হ’ব নালাগে।"</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"এপৰ সুবিধাসমূহৰ সম্পর্কীয় তথ্য চোৱাটো আৰম্ভ কৰক"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"ধাৰকক কোনো এপৰ সুবিধাসমূহৰ সম্পর্কীয় তথ্য চোৱাটো আৰম্ভ কৰিবলৈ দিয়ে।"</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"এটা উচ্চ ছেম্পলিঙৰ হাৰত ছেন্সৰৰ ডেটা এক্সেছ কৰে"</string> diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml index 3b0c6c863840..76384f9ce6b7 100644 --- a/core/res/res/values-az/strings.xml +++ b/core/res/res/values-az/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Tətbiqə \"Narahat Etməyin\" konfiqurasiyasını oxumağa və yazmağa icazə verin."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"Baxış icazəsinin istifadəsinə başlayın"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Sahibinə tətbiqin icazədən istifadəsinə başlamağa imkan verir. Adi tətbiqlər üçün heç vaxt tələb edilmir."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"icazə qərarlarına baxışı başladın"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Sahibin icazə qərarlarını nəzərdən keçirmək üçün ekranı başlatmasına icazə verir. Normal tətbiqlər tərəfindən heç vaxt tələb edilmir."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"tətbiqin funksiyalarını görməyə başlamaq"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"İstifadəçinin tətbiqin funksiyaları barədə məlumatları görməyə başlamasına icazə verir."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"sensor datasına yüksək ölçmə sürəti ilə giriş etmək"</string> diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml index 96f8d7d48913..15e3aa09cec5 100644 --- a/core/res/res/values-b+sr+Latn/strings.xml +++ b/core/res/res/values-b+sr+Latn/strings.xml @@ -736,10 +736,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Dozvoljava aplikaciji da čita i upisuje konfiguraciju podešavanja Ne uznemiravaj."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"početak korišćenja dozvole za pregled"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Dozvoljava vlasniku da započne korišćenje dozvole za aplikaciju. Nikada ne bi trebalo da bude potrebna za uobičajene aplikacije."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"pokretanje pregleda odluka o dozvolama"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Dozvoljava vlasniku da pokrene ekran za proveru odluka o dozvolama. Nikada ne bi trebalo da bude potrebno za obične aplikacije."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"pokretanje prikaza funkcija aplikacije"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Dozvoljava nosiocu dozvole da započne pregledanje informacija o funkcijama aplikacije."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"pristup podacima senzora pri velikoj brzini uzorkovanja"</string> @@ -1552,7 +1550,7 @@ <string name="vpn_lockdown_disconnected" msgid="5573611651300764955">"Veza sa uvek uključenim VPN-om je prekinuta"</string> <string name="vpn_lockdown_error" msgid="4453048646854247947">"Povezivanje na stalno uključeni VPN nije uspelo"</string> <string name="vpn_lockdown_config" msgid="8331697329868252169">"Promenite podešavanja VPN-a"</string> - <string name="upload_file" msgid="8651942222301634271">"Odaberi datoteku"</string> + <string name="upload_file" msgid="8651942222301634271">"Odaberi fajl"</string> <string name="no_file_chosen" msgid="4146295695162318057">"Nije izabrana nijedna datoteka"</string> <string name="reset" msgid="3865826612628171429">"Resetuj"</string> <string name="submit" msgid="862795280643405865">"Pošalji"</string> diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml index b44449901571..e182c286ce40 100644 --- a/core/res/res/values-be/strings.xml +++ b/core/res/res/values-be/strings.xml @@ -739,10 +739,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Дазваляе праграме чытаць і выконваць запіс у канфігурацыю рэжыму «Не турбаваць»."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"запусціць выкарыстанне дазволаў на прагляд"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Дазваляе трымальніку запусціць выкарыстанне дазволаў праграмай. Не патрэбна для звычайных праграм."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"запускаць прагляд рашэнняў наконт дазволаў"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Дазваляе ўладальніку запускаць экран, на якім можна праглядаць рашэнні наконт дазволаў. Ніколі не павінна патрабавацца для звычайных праграм."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"запусціць прагляд функцый праграмы"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Дазваляе трымальніку запусціць прагляд інфармацыі пра функцыі для праграмы."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"атрымліваць даныя датчыка з высокай частатой дыскрэтызацыі"</string> diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml index c3d8410f80e8..41aa9f7ed95e 100644 --- a/core/res/res/values-bg/strings.xml +++ b/core/res/res/values-bg/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Предоставя на приложението достъп за четене и запис до конфигурацията на „Не безпокойте“."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"стартиране на прегледа на използваните разрешения"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Разрешава на притежателя да стартира прегледа на използваните разрешения за дадено приложение. Нормалните приложения би трябвало никога да не се нуждаят от това."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"стартиране на прегледа на решенията за разрешенията"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Дава възможност на притежателя да стартира екрана с цел преглед на решенията за разрешенията. Нормалните приложения би трябвало никога да не се нуждаят от това."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"стартиране на прегледа на функциите на приложението"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Разрешава на притежателя да стартира прегледа на информацията за функциите на дадено приложение."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"осъществяване на достъп до данните от сензорите при висока скорост на семплиране"</string> diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml index 6bcf9aa8a127..7dac240dd079 100644 --- a/core/res/res/values-bn/strings.xml +++ b/core/res/res/values-bn/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"অ্যাপটিকে \'বিরক্ত করবে না\' কনফিগারেশন পড়া এবং লেখার অনুমতি দেয়।"</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"দেখার অনুমতি কাজে লাগানো শুরু করুন"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"কোনও অ্যাপের কোনও নির্দিষ্ট অনুমতির ব্যবহার শুরু করার ক্ষেত্রে হোল্ডারকে সাহায্য করে। সাধারণ অ্যাপের জন্য এটির পরিবর্তন হওয়ার কথা নয়।"</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"অনুমতি সংক্রান্ত সিদ্ধান্ত দেখা শুরু করুন"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"অনুমতি সংক্রান্ত সিদ্ধান্ত পর্যালোচনা করার জন্য, হোল্ডারকে স্ক্রিন চালু করতে দেয়। সাধারণ অ্যাপের জন্য কখনই প্রয়োজন হওয়া উচিত নয়।"</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"অ্যাপের ফিচার দেখা শুরু করুন"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"কোনও অ্যাপের ফিচার সম্পর্কিত তথ্য দেখা শুরু করতে অনুমতি দেয়।"</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"হাই স্যাম্পলিং রেটে সেন্সর ডেটা অ্যাক্সেস করুন"</string> diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml index c1823c9e9d5b..28ca86d2e534 100644 --- a/core/res/res/values-bs/strings.xml +++ b/core/res/res/values-bs/strings.xml @@ -736,10 +736,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Omogućava aplikaciji da čita i upisuje konfiguraciju načina rada Ne ometaj."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"pokrenuti korištenje odobrenja za pregled"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Dozvoljava vlasniku da pokrene korištenje odobrenja za aplikaciju. Ne bi trebalo biti potrebno za obične aplikacije."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"prikažite odluke o odobrenjima"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Dozvoljava vlasniku da pokrene ekran radi pregleda odluka o odobrenju. Obično nije potrebno za obične aplikacije."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"pokretanje pregleda funkcija aplikacije"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Dozvoljava vlasniku da pokrene pregled informacija o funkcijama za aplikaciju."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"pristup podacima senzora velikom brzinom uzorkovanja"</string> diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml index 325b6207e65f..be1b43c69670 100644 --- a/core/res/res/values-ca/strings.xml +++ b/core/res/res/values-ca/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Permet que l\'aplicació llegeixi la configuració No molestis i hi escrigui."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"comença a utilitzar el permís de visualització"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Permet que un propietari comenci a utilitzar el permís amb una aplicació. No s\'hauria de necessitar mai per a les aplicacions normals."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"inicia la visualització de les decisions sobre permisos"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Permet que l\'aplicació en qüestió iniciï la pantalla per revisar les decisions sobre permisos. No s\'hauria de necessitar mai per a les aplicacions normals."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"iniciar la visualització de les funcions d\'una aplicació"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Permet que el propietari vegi la informació de les funcions d\'una aplicació."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"accedir a les dades del sensor a una freqüència de mostratge alta"</string> diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml index 68128a490fe1..d038dfcac045 100644 --- a/core/res/res/values-cs/strings.xml +++ b/core/res/res/values-cs/strings.xml @@ -739,10 +739,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Umožňuje aplikaci číst a zapisovat konfiguraci režimu Nerušit."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"zahájení zobrazení využití oprávnění"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Umožňuje přístup zahájit využití oprávnění jiné aplikace. Běžné aplikace by toto oprávnění neměly nikdy požadovat."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"zobrazit rozhodnutí o oprávnění"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Umožňuje držiteli aktivovat obrazovku a zkontrolovat rozhodnutí o oprávnění. Běžné aplikace by toto oprávnění neměly nikdy potřebovat."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"zobrazení informací o funkcích aplikace"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Umožňuje držiteli zobrazit informace o funkcích aplikace."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"přístup k datům ze senzorů s vyšší vzorkovací frekvencí"</string> diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml index 9134c570fd1a..afe1175aca56 100644 --- a/core/res/res/values-da/strings.xml +++ b/core/res/res/values-da/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Giver appen tilladelse til at læse og redigere konfigurationen af Forstyr ikke."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"start brugen at tilladelsesvisning"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Tillader, at brugeren kan bruge en tilladelse for en app. Dette bør aldrig være nødvendigt for almindelige apps."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"starte visningen af beslutninger om tilladelser"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Giver modtageren mulighed for at åbne skærmen til gennemgang af beslutninger om tilladelser. Dette bør aldrig være nødvendigt for standardapps."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"se appfunktioner"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Giver den app, som har tilladelsen, mulighed for at se oplysninger om en apps funktioner."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"få adgang til sensordata ved høj samplingfrekvens"</string> diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml index ed2a92affb88..ecaa63c1a337 100644 --- a/core/res/res/values-de/strings.xml +++ b/core/res/res/values-de/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Ermöglicht der App Lese- und Schreibzugriff auf die „Bitte nicht stören“-Konfiguration"</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"Mit der Verwendung der Anzeigeberechtigung beginnen"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Ermöglicht dem Inhaber, die Berechtigungsnutzung für eine App zu beginnen. Sollte für normale Apps nie benötigt werden."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"Entscheidungen zu Leseberechtigung starten"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Ermöglicht dem Inhaber, das Display zu starten, um Berechtigungsentscheidungen anzusehen. Sollte für normale Apps nie benötigt werden."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"App-Funktionen ansehen"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Ermöglicht der App, die Informationen über die Funktionen einer anderen App anzusehen."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"Sensordaten mit hoher Frequenz auslesen"</string> diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml index ef2364008782..44fe4261526b 100644 --- a/core/res/res/values-el/strings.xml +++ b/core/res/res/values-el/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Επιτρέπει στην εφαρμογή την εγγραφή και τη σύνταξη διαμόρφωσης για τη λειτουργία \"Μην ενοχλείτε\"."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"έναρξη χρήσης άδειας προβολής"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Επιτρέπει στον κάτοχο να ξεκινήσει τη χρήση της άδειας για μια εφαρμογή. Δεν απαιτείται ποτέ για κανονικές εφαρμογές."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"έναρξη προβολής αποφάσεων για άδειες"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Επιτρέπει στον κάτοχο να ξεκινήσει την προβολή για τον έλεγχο των αποφάσεων για τις άδειες. Δεν χρειάζεται ποτέ για κανονικές εφαρμογές."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"έναρξη προβολής λειτουργιών εφαρμογής"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Επιτρέπει στον κάτοχο να ξεκινήσει την προβολή των πληροφοριών για τις λειτουργίες μιας εφαρμογής."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"πρόσβαση σε δεδομένα αισθητήρα με υψηλό ρυθμό δειγματοληψίας"</string> diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml index a5cb42199580..fad71eb7d67c 100644 --- a/core/res/res/values-es-rUS/strings.xml +++ b/core/res/res/values-es-rUS/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Permite que la aplicación lea y modifique la configuración de la función No interrumpir."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"iniciar uso de permiso de vista"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Permite que el propietario inicie el uso de permisos para una app. No debería requerirse para apps normales."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"iniciar vista de las decisiones sobre permisos"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Permite que el propietario vea la pantalla a fin de revisar las decisiones que tomó sobre los permisos. Las apps normales no deberían necesitar este permiso."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"iniciar vista de funciones de la app"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Permite que el propietario vea la información de las funciones de una app."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"Acceder a los datos de sensores a una tasa de muestreo alta"</string> @@ -779,7 +777,7 @@ <string name="policydesc_disableKeyguardFeatures" msgid="6641673177041195957">"Evita el uso de algunas funciones de bloqueo de pantalla."</string> <string-array name="phoneTypes"> <item msgid="8996339953292723951">"Casa"</item> - <item msgid="7740243458912727194">"Móvil"</item> + <item msgid="7740243458912727194">"Celular"</item> <item msgid="8526146065496663766">"Trabajo"</item> <item msgid="8150904584178569699">"Fax laboral"</item> <item msgid="4537253139152229577">"Fax residencial"</item> @@ -822,7 +820,7 @@ </string-array> <string name="phoneTypeCustom" msgid="5120365721260686814">"Personalizado"</string> <string name="phoneTypeHome" msgid="3880132427643623588">"Casa"</string> - <string name="phoneTypeMobile" msgid="1178852541462086735">"Móvil"</string> + <string name="phoneTypeMobile" msgid="1178852541462086735">"Celular"</string> <string name="phoneTypeWork" msgid="6604967163358864607">"Trabajo"</string> <string name="phoneTypeFaxWork" msgid="6757519896109439123">"Fax laboral"</string> <string name="phoneTypeFaxHome" msgid="6678559953115904345">"Fax personal"</string> diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml index 21910b9cb188..669e914ef8fb 100644 --- a/core/res/res/values-es/strings.xml +++ b/core/res/res/values-es/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Permite que la aplicación lea y modifique la configuración de No molestar."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"iniciar uso de permiso de visualización"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Permite que el titular inicie el uso de permisos de una aplicación. Las aplicaciones normales no deberían necesitar nunca este permiso."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"iniciar la revisión de decisiones sobre los permisos"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Permite que el titular inicie la revisión de las decisiones sobre los permisos. Las aplicaciones normales no deberían necesitar nunca este permiso."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"ver funciones de una aplicación"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Permite que el titular vea la información de las funciones de una aplicación."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"acceder a datos de sensores a una frecuencia de muestreo alta"</string> diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml index 118d605bd6ec..e4d33a7a1e6e 100644 --- a/core/res/res/values-et/strings.xml +++ b/core/res/res/values-et/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Võimaldab rakendusel lugeda ja kirjutada funktsiooni Mitte segada seadistusi."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"vaatamisloa kasutamise alustamine"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Võimaldab omanikul rakenduse puhul alustada loa kasutamist. Tavarakenduste puhul ei peaks seda kunagi vaja minema."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"Alustada lubade otsuste vaatamist."</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Võimaldab omanikul ekraani käivitada, et lubade otsused üle vaadata. Tavaliste rakenduste puhul ei tohiks seda kunagi vaja minna."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"rakenduse funktsioonide vaatamise alustamine"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Võimaldab omanikul alustada rakenduse funktsioonide teabe vaatamist."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"juurdepääs anduri andmetele kõrgel diskreetimissagedusel"</string> diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml index 2c3bdfb2df38..a3766b767f7d 100644 --- a/core/res/res/values-eu/strings.xml +++ b/core/res/res/values-eu/strings.xml @@ -562,7 +562,7 @@ <string name="permlab_useBiometric" msgid="6314741124749633786">"erabili hardware biometrikoa"</string> <string name="permdesc_useBiometric" msgid="7502858732677143410">"Autentifikatzeko hardware biometrikoa erabiltzeko baimena ematen die aplikazioei."</string> <string name="permlab_manageFingerprint" msgid="7432667156322821178">"kudeatu hatz-marken hardwarea"</string> - <string name="permdesc_manageFingerprint" msgid="2025616816437339865">"Erreferentzia-gako digitalen txantiloiak gehitzeko eta ezabatzeko metodoei dei egitea baimentzen die aplikazioei."</string> + <string name="permdesc_manageFingerprint" msgid="2025616816437339865">"Aztarna digitalaren txantiloiak gehitzeko eta ezabatzeko metodoei dei egitea baimentzen die aplikazioei."</string> <string name="permlab_useFingerprint" msgid="1001421069766751922">"erabili hatz-marken hardwarea"</string> <string name="permdesc_useFingerprint" msgid="412463055059323742">"Autentifikatzeko hatz-marken hardwarea erabiltzeko baimena ematen die aplikazioei."</string> <string name="permlab_audioWrite" msgid="8501705294265669405">"musika-bilduma aldatu"</string> @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Ez molestatzeko moduaren konfigurazioa irakurtzeko eta bertan idazteko baimena ematen die aplikazioei."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"hasi ikusteko baimena erabiltzen"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Aplikazioaren baimena erabiltzen hasteko baimena ematen die titularrei. Aplikazio normalek ez lukete beharko."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"hasi baimenen inguruko erabakiak ikusten"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Baimenen inguruko erabakiak berrikusteko pantaila ikusten hasteko baimena ematen die titularrei. Aplikazio normalek ez lukete beharko."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"hasi aplikazioaren eginbideak ikusten"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Aplikazio baten eginbideei buruzko informazioa ikusten hasteko baimena ematen die titularrei."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"atzitu sentsoreen datuen laginak abiadura handian"</string> @@ -1635,8 +1633,8 @@ <string name="expires_on" msgid="1623640879705103121">"Iraungitze-data:"</string> <string name="serial_number" msgid="3479576915806623429">"Serie-zenbakia:"</string> <string name="fingerprints" msgid="148690767172613723">"Erreferentzia-fitxategiak:"</string> - <string name="sha256_fingerprint" msgid="7103976380961964600">"SHA-256 erreferentzia-gako digitala:"</string> - <string name="sha1_fingerprint" msgid="2339915142825390774">"SHA-1 erreferentzia-gako digitala:"</string> + <string name="sha256_fingerprint" msgid="7103976380961964600">"SHA-256 aztarna digitala:"</string> + <string name="sha1_fingerprint" msgid="2339915142825390774">"SHA-1 aztarna digitala:"</string> <string name="activity_chooser_view_see_all" msgid="3917045206812726099">"Ikusi guztiak"</string> <string name="activity_chooser_view_dialog_title_default" msgid="8880731437191978314">"Aukeratu jarduera"</string> <string name="share_action_provider_share_with" msgid="1904096863622941880">"Partekatu hauekin:"</string> diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml index 050580c062fd..c9dedf919195 100644 --- a/core/res/res/values-fa/strings.xml +++ b/core/res/res/values-fa/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"به برنامه امکان میدهد پیکربندی «مزاحم نشوید» را بخواند و بنویسد."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"شروع مشاهده استفاده از مجوز"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"به دارنده اجازه شروع استفاده از مجوز را برای برنامه میدهد. هرگز برای برنامههای معمول نیاز نیست."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"شروع مشاهده تصمیمهای مربوط به اجازهها"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"به دارنده امکان میدهد صفحه را برای مرور تصمیمهای مربوط به اجازهها شروع کند. هرگز نباید برای برنامههای عادی لازم باشد."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"مشاهده ویژگیهای برنامه"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"به دارنده اجازه میدهد اطلاعات مربوط به ویژگیهای برنامه را مشاهده کند."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"دسترسی به دادههای حسگر با نرخ نمونهبرداری بالا"</string> diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml index d511e2898779..b7234beabb6c 100644 --- a/core/res/res/values-fi/strings.xml +++ b/core/res/res/values-fi/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Sallii sovelluksen lukea ja muokata Älä häiritse -tilan asetuksia."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"aloita katseluoikeuksien käyttö"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Antaa luvanhaltijan käynnistää sovelluksen käyttöoikeuksien käytön. Ei tavallisten sovelluksien käyttöön."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"aloita lupapäätösten tarkistaminen"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Antaa luvanhaltijan käynnistää näytön lupapäätösten tarkistamiseksi. Ei tavallisten sovellusten käyttöön."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"aloittaa sovellusominaisuuksien katselun"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Antaa luvanhaltijan aloittaa sovelluksen ominaisuustietojen katselun."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"saada pääsyn anturidataan suuremmalla näytteenottotaajuudella"</string> @@ -1647,7 +1645,7 @@ <string name="activity_resolver_use_once" msgid="948462794469672658">"Vain kerran"</string> <string name="activity_resolver_work_profiles_support" msgid="4071345609235361269">"%1$s ei tue työprofiilia"</string> <string name="default_audio_route_name" product="tablet" msgid="367936735632195517">"Tabletti"</string> - <string name="default_audio_route_name" product="tv" msgid="4908971385068087367">"Televisio"</string> + <string name="default_audio_route_name" product="tv" msgid="4908971385068087367">"TV"</string> <string name="default_audio_route_name" product="default" msgid="9213546147739983977">"Puhelin"</string> <string name="default_audio_route_name_dock_speakers" msgid="1551166029093995289">"Telineen kaiuttimet"</string> <string name="default_audio_route_name_hdmi" msgid="5474470558160717850">"HDMI"</string> diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml index 2723a6d79731..6a08237d41ef 100644 --- a/core/res/res/values-fr-rCA/strings.xml +++ b/core/res/res/values-fr-rCA/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Permet à l\'application de consulter et de modifier la configuration du mode Ne pas déranger."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"démarrer l\'affichage de l\'usage des autorisations"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Permet au détenteur de démarrer l\'usage des autorisations pour une application. Cette fonctionnalité ne devrait pas être nécessaire pour les applications standards."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"démarrer les décisions d\'autorisation de lecture"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Permet au détenteur de démarrer l\'écran pour revoir les décisions d\'autorisation. Ne devrait pas être requis pour les applications standards."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"démarrer l\'affichage des fonctionnalités de l\'application"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Permet au détenteur de commencer à afficher les renseignements sur les fonctionnalités d\'une application."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"accéder aux données des capteurs à un taux d’échantillonnage élevé"</string> diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml index 365b68fcf554..c606ed3048e6 100644 --- a/core/res/res/values-fr/strings.xml +++ b/core/res/res/values-fr/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Permet à l\'application de consulter et de modifier la configuration du mode Ne pas déranger."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"activer l\'utilisation de l\'autorisation d\'affichage"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Permet à l\'application autorisée d\'activer l\'utilisation de l\'autorisation pour une application. Cette fonctionnalité ne devrait pas être nécessaire pour les applications standards."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"activer l\'affichage des décisions liées aux autorisations"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Permet à l\'appli autorisée de lancer un écran pour examiner les décisions liées aux autorisations. Ne devrait jamais être nécessaire pour les applis standards."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"commencer à voir les fonctionnalités d\'une appli"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Permet à l\'appli autorisée de commencer à voir les infos sur les fonctionnalités d\'une appli."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"accéder aux données des capteurs à un taux d\'échantillonnage élevé"</string> diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml index 9c0d33a6a908..24287d92c5c2 100644 --- a/core/res/res/values-gl/strings.xml +++ b/core/res/res/values-gl/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Permite á aplicación ler e escribir a configuración do modo Non molestar."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"iniciar uso de permiso de vista"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Permite ao propietario iniciar o uso de permisos dunha aplicación. As aplicacións normais non deberían precisalo nunca."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"iniciar vista das decisións sobre os permisos"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Permite que a persoa propietaria acceda a unha pantalla onde poderá examinar as decisións relativas aos permisos. Esta opción nunca debería ser necesaria para as aplicacións normais."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"comezar a ver as funcións da aplicación"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Permite que o propietario comece a ver a información das funcións dunha aplicación."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"acceder aos datos dos sensores usando unha taxa de mostraxe alta"</string> diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml index 2996cc3e8771..5ca2c39e2f99 100644 --- a/core/res/res/values-gu/strings.xml +++ b/core/res/res/values-gu/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"એપ્લિકેશનને ખલેલ પાડશો નહીં ગોઠવણી વાંચવા અને લખવાની મંજૂરી આપે છે."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"પરવાનગી વપરાશ જુઓને શરૂ કરો"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"કોઈ ઍપ માટે પરવાનગી વપરાશ શરૂ કરવાની ધારકને મંજૂરી આપે છે. સામાન્ય ઍપ માટે ક્યારેય જરૂર પડી ન શકે."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"પરવાનગી સંબંધિત નિર્ણયો જોવાનું શરૂ કરો"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"ધારકને પરવાનગી સંબંધિત નિર્ણયોનો રિવ્યૂ કરવા માટે સ્ક્રીન શરૂ કરવાની મંજૂરી આપે છે. સામાન્ય ઍપ માટે ક્યારેય જરૂરી હોવું જોઈએ નહીં."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"ઍપની સુવિધાઓ જોવા માટેની પરવાનગી ચાલુ કરો"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"ધારકને ઍપ માટેની સુવિધાઓની માહિતી જોવાનું શરૂ કરવાની મંજૂરી આપે છે."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"ઉચ્ચ સેમ્પ્લિંગ રેટ પર સેન્સરનો ડેટા ઍક્સેસ કરો"</string> diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml index 5c6eaeb26581..b846ff8024c3 100644 --- a/core/res/res/values-hi/strings.xml +++ b/core/res/res/values-hi/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"ऐप को परेशान न करें कॉन्फ़िगरेशन पढ़ने और लिखने देती है."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"देखने की अनुमतियां चालू करें"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"इस्तेमाल करने वाले को किसी ऐप्लिकेशन के लिए अनुमतियों का इस्तेमाल शुरू करने देता है. सामान्य ऐप्लिकेशन के लिए इसकी ज़रूरत कभी नहीं पड़ती."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"अनुमतियों को देखना चालू करना"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"ऐप्लिकेशन को स्क्रीन शुरू करने की अनुमति मिलती है, ताकि अनुमतियों की समीक्षा की जा सके. सामान्य ऐप्लिकेशन के लिए, इसकी ज़रूरत कभी नहीं पड़ती."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"ऐप्लिकेशन की सुविधाओं को देखना शुरू करें"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"ऐप्लिकेशन को, किसी ऐप्लिकेशन की सुविधाओं की जानकारी देखने की अनुमति देता है."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"सेंसर डेटा को, नमूने लेने की तेज़ दर पर ऐक्सेस करें"</string> diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml index 15f59dc83281..e88b331c7ff3 100644 --- a/core/res/res/values-hr/strings.xml +++ b/core/res/res/values-hr/strings.xml @@ -736,10 +736,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Omogućuje aplikaciji čitanje i pisanje konfiguracije opcije Ne uznemiravaj."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"pokrenuti upotrebu dopuštenja za pregled"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Dopušta nositelju pokretanje upotrebe dopuštenja za aplikaciju. Ne bi smjelo biti potrebno za uobičajene aplikacije."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"pokrenuti odluke o dopuštenju za pregled"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Dopušta nositelju pokretanje zaslona za pregled odluka o dopuštenjima. Ne bi trebalo biti potrebno za uobičajene aplikacije."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"pokretanje prikaza značajki aplikacije"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Dopušta nositelju pokretanje prikaza informacija o značajkama aplikacije."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"pristup podacima senzora pri višoj brzini uzorkovanja"</string> diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml index b7e35cf2481d..46fcc2a40677 100644 --- a/core/res/res/values-hu/strings.xml +++ b/core/res/res/values-hu/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Az alkalmazás olvashatja és szerkesztheti a „Ne zavarjanak” funkció beállításait."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"engedélyhasználat megtekintésének elindítása"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Lehetővé teszi a felhasználó számára, hogy elindítsa az alkalmazás engedélyhasználatát. A normál alkalmazásoknak erre soha nincs szükségük."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"engedélyezési döntések megtekintésének elindítása"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Lehetővé teszi az alkalmazás számára, hogy elindítsa a képernyőt az engedélyezési döntések felülvizsgálata érdekében. A normál alkalmazások esetében erre nincs szükség."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"alkalmazásfunkciók megtekintésének megkezdése"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Engedélyezi az alkalmazás számára, hogy megkezdje az alkalmazások funkcióira vonatkozó adatok megtekintését."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"hozzáférés a szenzoradatokhoz nagy mintavételezési gyakorisággal"</string> diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml index 13d87803475f..b60f08b82374 100644 --- a/core/res/res/values-hy/strings.xml +++ b/core/res/res/values-hy/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Թույլ է տալիս հավելվածին փոփոխել «Չանհանգստացնել» գործառույթի կազմաձևումը:"</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"թույլտվությունների մասին տվյալների հասանելիություն"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Հավելվածին հասանելի կդառնան թույլտվությունների մասին տվյալները։ Այս թույլտվությունն անհրաժեշտ չէ սովորական հավելվածներին։"</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"սկսել թույլտվությունների հետ գործողությունների դիտումը"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Թույլ է տալիս դիտել թույլտվությունների հետ գործողությունները։ Սովորական հավելվածների համար երբևէ չպետք է անհրաժեշտ լինի։"</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"հավելվածի գործառույթների դիտում"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Թույլ է տալիս դիտել հավելվածի գործառույթների մասին տեղեկությունները։"</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"օգտագործել սենսորների տվյալները բարձր հաճախականության վրա"</string> diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml index 997b25e2d6ba..7925b3f1ce6b 100644 --- a/core/res/res/values-in/strings.xml +++ b/core/res/res/values-in/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Mengizinkan aplikasi membaca dan menulis konfigurasi status Jangan Ganggu."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"mulai melihat penggunaan izin"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Memungkinkan pemegang memulai penggunaan izin untuk aplikasi. Tidak diperlukan untuk aplikasi normal."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"mulai melihat keputusan izin"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Mengizinkan pemegang memulai layar untuk meninjau keputusan izin. Tidak pernah dibutuhkan untuk aplikasi normal."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"mulai lihat fitur aplikasi"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Memungkinkan pemegang mulai melihat info fitur untuk aplikasi."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"mengakses data sensor pada frekuensi pengambilan sampel yang tinggi"</string> diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml index 99e4d1a1daa6..c758de5f406b 100644 --- a/core/res/res/values-is/strings.xml +++ b/core/res/res/values-is/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Leyfir forriti að lesa og skrifa í grunnstillingu „Ónáðið ekki“."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"heimildanotkun upphafsyfirlits"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Leyfir handhafa að byrja heimildanotkun fyrir forrit. Ætti aldrei að þurfa fyrir venjuleg forrit."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"opna ákvarðanir um skoðunarheimildir"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Gerir notandanum kleift að opna skjá til að fara yfir ákvarðanir um heimildir. Þetta ætti aldrei að þurfa fyrir venjuleg forrit."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"byrja að skoða eiginleika forrits"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Leyfir handhafa að skoða upplýsingar um eiginleika tiltekins forrits."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"aðgangur að skynjaragögnum með hárri upptökutíðni"</string> diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml index 4b3f305faf65..c5683faa45b4 100644 --- a/core/res/res/values-it/strings.xml +++ b/core/res/res/values-it/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Consente all\'app di leggere e modificare la configurazione della funzione Non disturbare."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"avvio dell\'uso dell\'autorizzazione di visualizzazione"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Consente al titolare di avviare l\'uso delle autorizzazioni per un\'app. Non dovrebbe essere mai necessaria per le normali applicazioni."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"Inizio della visualizzazione delle decisioni relative all\'autorizzazione"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Consente all\'app che ha questa autorizzazione di avviare lo schermo per esaminare le decisioni relative all\'autorizzazione. Non dovrebbe mai essere necessaria per le normali app."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"Inizio della visualizzazione di funzionalità delle app"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Consente all\'app che ha questa autorizzazione di iniziare a visualizzare le informazioni relative alle funzionalità di un\'app."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"Accesso ai dati dei sensori a una frequenza di campionamento elevata"</string> diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml index bdc87fe8f4b6..9da03ea1538e 100644 --- a/core/res/res/values-iw/strings.xml +++ b/core/res/res/values-iw/strings.xml @@ -739,10 +739,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"מאפשרת לאפליקציה לקרוא ולכתוב את התצורה של התכונה \'נא לא להפריע\'."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"התחלת צפייה בהרשאות השימוש"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"מאפשרת לבעלים להפעיל את השימוש בהרשאות עבור אפליקציה מסוימת. הרשאה זו אף פעם לא נדרשת עבור אפליקציות רגילות."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"ההחלטות לגבי ההרשאות להפעלת התצוגה"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"בעלי ההרשאה יוכלו להפעיל את המסך כדי לעיין בהחלטות לגבי הרשאות. ההרשאה לא נחוצה לאפליקציות רגילות בדרך כלל."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"התחלת צפייה בהרשאות של אפליקציות"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"בעלי ההרשאה יוכלו להתחיל לצפות בפרטי התכונות של אפליקציות."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"גישה לנתוני חיישנים בתדירות דגימה גבוהה"</string> diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml index e6e2ed82afa0..76d248c4467a 100644 --- a/core/res/res/values-ja/strings.xml +++ b/core/res/res/values-ja/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"サイレント モード設定の読み取りと書き込みをアプリに許可します。"</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"表示権限の使用の開始"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"アプリの権限使用の開始を所有者に許可します。通常のアプリでは不要です。"</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"閲覧権限の許可 / 拒否の開始"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"権限の許可 / 拒否を確認するための画面の開始を所有者に許可します。通常のアプリでは不要です。"</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"アプリ機能の表示の開始"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"アプリの機能情報の表示の開始を所有者に許可します。"</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"高サンプリング レートでセンサーデータにアクセスする"</string> diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml index 16f3fe03d8a3..91144c65832f 100644 --- a/core/res/res/values-ka/strings.xml +++ b/core/res/res/values-ka/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"საშუალებას აძლევს აპს, წაიკითხოს და დაწეროს კონფიგურაცია „არ შემაწუხოთ“."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"ნახვის ნებართვის გამოყენების დაწყება"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"მფლობელს საშუალებას აძლევს, დაიწყოს აპის ნებართვის გამოყენება. ჩვეულებრივი აპებისთვის არასოდეს უნდა იყოს საჭირო."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"ნებართვის შესახებ გადაწყვეტილებების ნახვის დაწყება"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"მფლობელს საშუალებას აძლევს, გაუშვას ეკრანი ნებართვის შესახებ გადაწყვეტილებების სანახავად. ჩვეულებრივი აპებისთვის არასოდეს უნდა იყოს საჭირო."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"აპის ფუნქციების ნახვის დაწყება"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"მფლობელს საშუალებას აძლევს, დაიწყოს აპის ფუნქციების ინფორმაციის ნახვა."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"სენსორის მონაცემებზე წვდომა სემპლინგის მაღალი სიხშირით"</string> diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml index 155a77aac3b2..3f75495920b7 100644 --- a/core/res/res/values-kk/strings.xml +++ b/core/res/res/values-kk/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Қолданбаға «Мазаламау» конфигурациясын оқу және жазу мүмкіндігін береді."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"рұқсаттарды пайдалану туралы деректерді көру"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Пайдаланушы қолданбаға берілетін рұқсаттарды басқара алады. Ондай рұқсаттар әдеттегі қолданбаларға керек емес."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"Рұқсаттары бар әрекеттерді көру"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Қолданбаға рұқсаттары бар әрекеттерді көруге мүмкіндік береді. Әдеттегі қолданбаларға қажет емес."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"қолданба функцияларын көре бастау"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Қолданбаға функциялар туралы ақпаратты көре бастауды кідіртуге мүмкіндік береді."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"жоғары дискретизация жиілігіндегі датчик деректерін пайдалану"</string> diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml index 8021330769a3..ecae41a98eca 100644 --- a/core/res/res/values-km/strings.xml +++ b/core/res/res/values-km/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"អនុញ្ញាតឲ្យកម្មវិធីអាន និងសរសេរការកំណត់រចនាសម្ព័ន្ធមុខងារ កុំរំខាន។"</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"ចាប់ផ្ដើមមើលការប្រើប្រាស់ការអនុញ្ញាត"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"អនុញ្ញាតឱ្យម្ចាស់ចាប់ផ្ដើមការប្រើប្រាស់ការអនុញ្ញាតសម្រាប់កម្មវិធី។ មិនគួរចាំបាច់សម្រាប់កម្មវិធីធម្មតាទេ។"</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"ចាប់ផ្ដើមមើលការសម្រេចលើការអនុញ្ញាត"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"អនុញ្ញាតឱ្យកម្មវិធីចាប់ផ្ដើមត្រួតពិនិត្យ ដើម្បីពិនិត្យមើលការសម្រេចលើការអនុញ្ញាត។ មិនចាំបាច់ទេសម្រាប់កម្មវិធីធម្មតា។"</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"ចាប់ផ្ដើមមើលមុខងារកម្មវិធី"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"អនុញ្ញាតឱ្យកម្មវិធីចាប់ផ្ដើមមើលព័ត៌មានមុខងារសម្រាប់កម្មវិធី។"</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"ចូលប្រើទិន្នន័យឧបករណ៍ចាប់សញ្ញានៅអត្រាសំណាកខ្ពស់"</string> diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml index 4c8871ad25cc..a24166cc0faf 100644 --- a/core/res/res/values-kn/strings.xml +++ b/core/res/res/values-kn/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"ಅಡಚಣೆ ಮಾಡಬೇಡಿ ಕಾನ್ಫಿಗರೇಶನ್ ಅನ್ನು ಓದಲು ಮತ್ತು ಬರೆಯಲು ಅಪ್ಲಿಕೇಶನ್ಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"ವೀಕ್ಷಣಾ ಅನುಮತಿಯ ಬಳಕೆಯನ್ನು ಪ್ರಾರಂಭಿಸಿ"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"ಆ್ಯಪ್ಗಾಗಿ ಅನುಮತಿ ಬಳಕೆಯನ್ನು ಪ್ರಾರಂಭಿಸಲು ಹೊಂದಿರುವವರಿಗೆ ಅನುಮತಿಸುತ್ತದೆ. ಸಾಮಾನ್ಯ ಆ್ಯಪ್ಗಳಿಗೆ ಎಂದಿಗೂ ಅಗತ್ಯವಿರುವುದಿಲ್ಲ."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"ಅನುಮತಿಯ ನಿರ್ಧಾರಗಳನ್ನು ವೀಕ್ಷಿಸಲು ಪ್ರಾರಂಭಿಸಿ"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"ಅನುಮತಿಯ ನಿರ್ಧಾರಗಳನ್ನು ಪರಿಶೀಲಿಸುವುದಕ್ಕಾಗಿ ಸ್ಕ್ರೀನ್ ಅನ್ನು ಪ್ರಾರಂಭಿಸಲು ಬಳಕೆದಾರರನ್ನು ಅನುಮತಿಸುತ್ತದೆ. ಸಾಮಾನ್ಯ ಆ್ಯಪ್ಗಳಿಗೆ ಎಂದಿಗೂ ಅಗತ್ಯವಿರುವುದಿಲ್ಲ."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"ಆ್ಯಪ್ ವೈಶಿಷ್ಟ್ಯಗಳನ್ನು ವೀಕ್ಷಿಸಲು ಪ್ರಾರಂಭಿಸಿ"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"ಆ್ಯಪ್ನ ವೈಶಿಷ್ಟ್ಯಗಳ ಮಾಹಿತಿಯನ್ನು ವೀಕ್ಷಿಸಲು ಬಳಕೆದಾರರನ್ನು ಅನುಮತಿಸುತ್ತದೆ."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"ಹೆಚ್ಚಿನ ನಮೂನೆ ದರದಲ್ಲಿ ಸೆನ್ಸಾರ್ ಡೇಟಾ ಪ್ರವೇಶಿಸಿ"</string> diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml index 1f71f0f9f6bc..8920be2481d4 100644 --- a/core/res/res/values-ko/strings.xml +++ b/core/res/res/values-ko/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"앱에서 방해 금지 모드 설정을 읽고 작성하도록 허용합니다."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"권한 사용 보기 시작"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"앱의 권한 사용을 시작하려면 보유자를 허용하세요. 일반 앱에는 필요하지 않습니다."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"권한 결정 보기 시작"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"권한 결정 검토를 위해 기기 보유자가 화면을 시작할 수 있도록 허용합니다. 일반 앱에는 필요하지 않습니다."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"앱 기능 보기"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"권한을 보유한 앱에서 앱의 기능 정보를 보도록 허용합니다."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"더 높은 샘플링 레이트로 센서 데이터 액세스"</string> diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml index ca703f46c7e5..50c939361ec9 100644 --- a/core/res/res/values-ky/strings.xml +++ b/core/res/res/values-ky/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Колдонмого \"Тынчымды алба\" режиминин конфигурациясын окуу жана жазуу мүмкүнчүлүгүн берет."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"уруксаттын колдонулушун көрүп баштоо"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Колдонмонун пайдаланылышына уруксат берүүгө мүмкүнчүлүк берет. Кадимки колдонмолорго эч качан талап кылынбашы керек."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"уруксаттар боюнча кабыл алынган чечимдерди карап чыгуу"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Ээсине уруксаттар боюнча кабыл алынган чечимдерди карап чыгуу үчүн экранды иштетүү мүмкүнчүлүгүн берет. Кадимки колдонмолорго эч качан талап кылынбашы керек."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"колдонмонун функцияларын көрүп баштоо"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Колдонуучуга функциялары тууралуу маалыматты көрүп баштоо мүмкүнчүлүгүн берет."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"үлгүнү жаздыруу ылдамдыгы жогору болгон сенсор дайындарынын үлгүсүнө мүмкүнчүлүк алуу"</string> diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml index 20df320fc39e..398b6b3b3e4f 100644 --- a/core/res/res/values-lo/strings.xml +++ b/core/res/res/values-lo/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"ອະນຸຍາດໃຫ້ແອັບອ່ານ ແລະຂຽນການກນຳດຄ່າ ບໍ່ລົບກວນ."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"ເລີ່ມການໃຊ້ສິດອະນຸຍາດການເບິ່ງ"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"ອະນຸຍາດໃຫ້ຜູ້ຖືເລີ່ມການໃຊ້ສິດອະນຸຍາດສຳລັບແອັບໃດໜຶ່ງໄດ້. ແອັບປົກກະຕິບໍ່ຄວນຕ້ອງໃຊ້."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"ເລີ່ມເບິ່ງການຕັດສິນໃຈການອະນຸຍາດ"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"ອະນຸຍາດໃຫ້ຜູ້ຖືເລີ່ມໜ້າຈໍເພື່ອກວດສອບການຕັດສິນໃຈການອະນຸຍາດ. ແອັບປົກກະຕິບໍ່ຄວນຈຳເປັນຕ້ອງໃຊ້."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"ເລີ່ມເບິ່ງຄຸນສົມບັດແອັບ"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"ອະນຸຍາດໃຫ້ຜູ້ຖືເລີ່ມການເບິ່ງຂໍ້ມູນຄຸນສົມບັດສຳລັບແອັບໃດໜຶ່ງ."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"ເຂົ້າເຖິງຂໍ້ມູນເຊັນເຊີໃນອັດຕາຕົວຢ່າງສູງ"</string> diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml index f307213a424a..de36cb68442b 100644 --- a/core/res/res/values-lt/strings.xml +++ b/core/res/res/values-lt/strings.xml @@ -739,10 +739,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Leidžiama programai skaityti ir rašyti „Do Not Disturb“ konfigūraciją."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"pradėti peržiūrėti leidimo naudojimą"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Leidžia savininkui pradėti naudoti programos leidimą. Įprastoms programoms to neturėtų prireikti."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"pradėti sprendimų dėl leidimų peržiūrą"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Savininkui leidžiama atidaryti ekraną ir peržiūrėti sprendimus dėl leidimų. Neturėtų prireikti naudojant įprastas programas."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"pradėti programos funkcijų peržiūrą"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Savininkui leidžiama pradėti programos funkcijų informacijos peržiūrą."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"pasiekti jutiklių duomenis dideliu skaitmeninimo dažniu"</string> diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml index ee3ba2834782..93414e0a49f2 100644 --- a/core/res/res/values-lv/strings.xml +++ b/core/res/res/values-lv/strings.xml @@ -736,10 +736,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Ļauj lietotnei lasīt un rakstīt režīma “Netraucēt” konfigurāciju."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"Datu skatīšana par izmantojamajām atļaujām"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Ļauj atļaujas īpašniekam sākt lietotnes atļauju izmantošanu. Parastām lietotnēm tas nekad nav nepieciešams."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"Skatīt darbības ar atļaujām"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Lietotne ar šo atļauju var pārskatīt darbības ar atļaujām. Parastām lietotnēm šīs atļauja nekad nav nepieciešama."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"Skatīt lietotnes funkcijas"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Lietotne ar šo atļauju var skatīt informāciju par citas lietotnes funkcijām."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"piekļuve sensoru datiem, izmantojot augstu iztveršanas frekvenci"</string> diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml index 2c460ab3eb80..d4f099f20068 100644 --- a/core/res/res/values-mk/strings.xml +++ b/core/res/res/values-mk/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Дозволува апликацијата да чита и пишува конфигурација Не вознемирувај."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"започнете со користење на дозволата за приказ"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Дозволува сопственикот да почне со користење на дозволата за апликација. Не треба да се користи за стандардни апликации."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"да го стартува приказот за одлуки за дозволи"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Му дозволува на сопственикот да го стартува екранот за прегледување одлуки за дозволи. Не треба да се користи за стандардни апликации."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"да почне со прегледување на функциите на апликацијата"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"му дозволува на сопственикот да почне со прегледување на податоците за функциите за некоја апликација"</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"пристапува до податоците со висока фреквенција на семпл"</string> diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml index a0698a7a6108..1c98f25cd95a 100644 --- a/core/res/res/values-ml/strings.xml +++ b/core/res/res/values-ml/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"\'ശല്യപ്പെടുത്തരുത്\' കോൺഫിഗറേഷൻ വായിക്കുന്നതിനും എഴുതുന്നതിനും ആപ്പിനെ അനുവദിക്കുന്നു."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"അനുമതി ഉപയോഗം കാണാൻ ആരംഭിക്കുക"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"ഒരു ആപ്പിനുള്ള അനുമതി ഉപയോഗം ആരംഭിക്കാൻ ഹോൾഡറിനെ അനുവദിക്കുന്നു. സാധാരണ ആപ്പുകൾക്ക് ഒരിക്കലും ആവശ്യമില്ല."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"അനുമതിയുമായി ബന്ധപ്പെട്ട തീരുമാനങ്ങൾ കാണാൻ ആരംഭിക്കുക"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"അനുമതിയുമായി ബന്ധപ്പെട്ട തീരുമാനങ്ങൾ അവലോകനം ചെയ്യുന്നതിന് സ്ക്രീൻ ആരംഭിക്കാൻ ഹോൾഡറിനെ അനുവദിക്കുന്നു. സാധാരണ ആപ്പുകൾക്ക് ഒരിക്കലും ആവശ്യമില്ല."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"ആപ്പ് ഫീച്ചറുകൾ കാണാൻ ആരംഭിക്കുക"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"ആപ്പിനുള്ള ഫീച്ചറുകളുടെ വിവരങ്ങൾ കാണാൻ ആരംഭിക്കാൻ ഹോൾഡറിനെ അനുവദിക്കുന്നു."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"ഉയർന്ന സാംപ്ലിംഗ് റേറ്റിൽ സെൻസർ ഡാറ്റ ആക്സസ് ചെയ്യുക"</string> diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml index f86c8751bc12..db67e904d29d 100644 --- a/core/res/res/values-mn/strings.xml +++ b/core/res/res/values-mn/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Апп-д Бүү саад бол тохируулгыг уншиж, бичихийг зөвшөөрөх"</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"зөвшөөрлийн ашиглалтыг харж эхлэх"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Эзэмшигчид аппын зөвшөөрлөө ашиглаж эхлэхийг зөвшөөрдөг. Энгийн аппуудад шаардлагагүй."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"зөвшөөрлийн шийдвэрийг хянах дэлгэцийг эхлүүлэх"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Эзэмшигчид зөвшөөрлийн шийдвэрийг хянах дэлгэцийг эхлүүлэх боломжийг олгоно. Энгийн аппуудад хэзээ ч шаардагдахгүй."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"аппын онцлогуудыг үзэж эхлэх"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Аппын онцлогуудын мэдээллийг үзэж эхлэхийг эзэмшигчид зөвшөөрдөг."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"түүврийн өндөр хувиар мэдрэгчийн өгөгдөлд хандах"</string> diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml index a244a2f6c33b..1385dcd51878 100644 --- a/core/res/res/values-mr/strings.xml +++ b/core/res/res/values-mr/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"व्यत्यय आणू नका कॉंफिगरेशन वाचण्यासाठी आणि लिहिण्यासाठी ॲपला अनुमती देते."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"व्ह्यू परवानगी वापर सुरू करा"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"धारकास अॅपसाठी परवानगी वापरणे सुरू करण्याची अनुमती देते. सामान्य अॅप्ससाठी कधीही आवश्यकता नसते."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"परवानगीशी संबंधित निर्णय पाहणे सुरू करा"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"होल्डरला परवानगीशी संबंधित निर्णयांचे पुनरावलोकन करण्यासाठी स्क्रीन सुरू करण्याची अनुमती देते. सामान्य ॲप्ससाठी कधीही आवश्यक नसते."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"ॲप वैशिष्ट्ये पाहणे सुरू करा"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"होल्डरला ॲपसाठी वैशिष्ट्यांची माहिती पाहण्यास सुरू करण्याची अनुमती देते."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"उच्च नमुना दराने सेन्सर डेटा अॅक्सेस करते"</string> diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml index 12cd501431b1..43847feefea9 100644 --- a/core/res/res/values-ms/strings.xml +++ b/core/res/res/values-ms/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Membenarkan apl membaca dan menulis konfigurasi Jangan Ganggu."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"mulakan lihat penggunaan kebenaran"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Membenarkan pemegang memulakan penggunaan kebenaran untuk apl. Tidak sekali-kali diperlukan untuk apl biasa."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"mula melihat keputusan kebenaran"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Membenarkan pemegang memulakan skrin untuk menyemak keputusan kebenaran. Tidak diperlukan untuk apl biasa."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"mula melihat ciri apl"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Membenarkan pemegang mula melihat maklumat ciri untuk apl."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"akses data penderia pada data pensampelan yang tinggi"</string> diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml index 59505a5ca435..f509906480a2 100644 --- a/core/res/res/values-my/strings.xml +++ b/core/res/res/values-my/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"မနှောင့်ယှက်ရန် ချိန်ညှိမှုကို အပ်ဖ်များ ဖတ်ခြင်း ပြင်ခြင်းပြုလုပ်နိုင်ရန် ခွင့်ပြုမည်။"</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"အစမြင်ကွင်း ခွင့်ပြုချက် အသုံးပြုမှု"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"အက်ပ်တစ်ခုအတွက် ခွင့်ပြုချက်စတင်အသုံးပြုမှုကို ကိုင်ဆောင်သူအား ခွင့်ပြုသည်။ ပုံမှန်အက်ပ်များအတွက် ဘယ်သောအခါမျှ မလိုအပ်ပါ။"</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"ခွင့်ပြုသည့် ဆုံးဖြတ်ချက်များကို စတင်ကြည့်ခြင်း"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"ခွင့်ပြုထားသည့်အက်ပ်အား ခွင့်ပြုသည့်ဆုံးဖြတ်ချက်များကို ကြည့်နိုင်ရန်အတွက် စခရင်စတင်ရန် ခွင့်ပြုနိုင်သည်။ သာမန်အက်ပ်များအတွက် မည်သည့်အခါမျှ မလိုအပ်နိုင်ပါ။"</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"အက်ပ်ဝန်ဆောင်မှုများကို စတင်ကြည့်ခြင်း"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"ဝန်ဆောင်မှုအချက်အလက်ကိုများကို ခွင့်ပြုချက်ရထားသည့် အက်ပ်အား စတင်ကြည့်နိုင်ရန် ခွင့်ပြုသည်။"</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"နမူနာနှုန်းမြင့်သော အာရုံခံစနစ်ဒေတာကို သုံးပါ"</string> diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml index f12287f50392..2ddd45da07be 100644 --- a/core/res/res/values-nb/strings.xml +++ b/core/res/res/values-nb/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Lar appen lese og skrive konfigurasjon av Ikke forstyrr."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"start visning av bruk av tillatelser"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Lar innehaveren starte bruk av tillatelser for en app. Dette skal aldri være nødvendig for vanlige apper."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"starte visning av avgjørelser om tillatelser"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Lar innehaveren starte skjermen for å gjennomgå avgjørelser om tillatelser. Dette skal aldri være nødvendig for vanlige apper."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"starte visning av appfunksjoner"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Lar innehaveren se informasjon om funksjonene for en app."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"tilgang til sensordata ved høy samplingfrekvens"</string> diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml index bf8499e70f76..d4828b6ce942 100644 --- a/core/res/res/values-ne/strings.xml +++ b/core/res/res/values-ne/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"बाधा नपुर्याउँनुहोस् कन्फिगरेसन पढ्न र लेख्नको लागि एपलाई अनुमति दिनुहोस्।"</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"हेर्ने अनुमतिको प्रयोग सुरु गर्नुहोस्"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"वाहकलाई कुनै एपसम्बन्धी अनुमतिको प्रयोग सुरु गर्न दिन्छ। साधारण एपहरूलाई कहिल्यै आवश्यक नपर्नु पर्ने हो।"</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"अनुमतिसम्बन्धी निर्णयहरू हेर्न सुरु गर्नुहोस्"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"होल्डरलाई अनुमतिसम्बन्धी निर्णयहरू समीक्षा गर्ने स्क्रिन सुरु गर्न दिन्छ। सामान्य एपहरूलाई कहिल्यै पनि नचाहिनु पर्ने हो।"</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"एपका सुविधासम्बन्धी जानकारी हेर्न थाल्नुहोस्"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"होल्डरलाई एपका सुविधासम्बन्धी जानकारी हेर्न दिन्छ।"</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"नमुना लिने उच्च दरमा सेन्सरसम्बन्धी डेटा प्रयोग गर्ने"</string> diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml index 1dac9827df2c..3b6db066eb8f 100644 --- a/core/res/res/values-nl/strings.xml +++ b/core/res/res/values-nl/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Hiermee kan de app configuratie voor Niet storen lezen en schrijven."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"rechtengebruik starten"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Hiermee kan de houder het rechtengebruik voor een app starten. Nooit vereist voor normale apps."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"bekijken van rechtenbeslissingen starten"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Hiermee kan de houder het scherm starten om rechtenbeslissingen te bekijken. Nooit vereist voor normale apps."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"app-functies bekijken"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Hiermee kan de houder informatie over functies bekijken voor een app."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"toegang krijgen tot sensorgegevens met een hoge samplingsnelheid"</string> diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml index e825c2c7d13d..0220ce3c3540 100644 --- a/core/res/res/values-or/strings.xml +++ b/core/res/res/values-or/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"\"ବିରକ୍ତ କରନ୍ତୁ ନାହିଁ\" କନଫିଗରେଶନ୍ ପଢ଼ିବା ତଥା ଲେଖିବା ପାଇଁ ଆପକୁ ଅନୁମତି ଦେଇଥାଏ।"</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"ଅନୁମତି ବ୍ୟବହାର ଦେଖିବା ଆରମ୍ଭ କରନ୍ତୁ"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"ଏକ ଆପ୍ ପାଇଁ ଅନୁମତିର ବ୍ୟବହାର ଆରମ୍ଭ କରିବାକୁ ଧାରକକୁ ଅନୁମତି ଦେଇଥାଏ। ସାଧାରଣ ଆପ୍ଗୁଡ଼ିକ ପାଇଁ ଏହା ଆବଶ୍ୟକ ନୁହେଁ।"</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"ଅନୁମତି ନିଷ୍ପତ୍ତିଗୁଡ଼ିକ ଦେଖିବା ଆରମ୍ଭ କରନ୍ତୁ"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"ଅନୁମତି ନିଷ୍ପତ୍ତିଗୁଡ଼ିକର ସମୀକ୍ଷା କରିବାକୁ ସ୍କ୍ରିନ ଚାଲୁ କରିବା ପାଇଁ ହୋଲଡରଙ୍କୁ ଅନୁମତି ଦିଏ। ସାଧାରଣ ଆପଗୁଡ଼ିକ ପାଇଁ ଏହା କେବେ ବି ଆବଶ୍ୟକ ହେବ ନାହିଁ।"</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"ଆପର ଫିଚରଗୁଡ଼ିକୁ ଦେଖିବା ଆରମ୍ଭ କରନ୍ତୁ"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"କୌଣସି ଆପ ପାଇଁ ଫିଚରଗୁଡ଼ିକ ବିଷୟରେ ସୂଚନା ଦେଖିବା ଆରମ୍ଭ କରିବାକୁ ହୋଲଡରଙ୍କୁ ଅନୁମତି ଦିଏ।"</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"ଏକ ଉଚ୍ଚ ନମୁନାକରଣ ରେଟରେ ସେନ୍ସର୍ ଡାଟାକୁ ଆକ୍ସେସ୍ କରନ୍ତୁ"</string> diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml index fd822a3d2df2..7a3bc337bb82 100644 --- a/core/res/res/values-pa/strings.xml +++ b/core/res/res/values-pa/strings.xml @@ -174,7 +174,7 @@ <string name="httpErrorTooManyRequests" msgid="2149677715552037198">"ਬਹੁਤ ਜ਼ਿਆਦਾ ਬੇਨਤੀਆਂ ਦੀ ਪ੍ਰਕਿਰਿਆ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ। ਬਾਅਦ ਵਿੱਚ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।"</string> <string name="notification_title" msgid="5783748077084481121">"<xliff:g id="ACCOUNT">%1$s</xliff:g> ਲਈ ਸਾਈਨਇਨ ਅਸ਼ੁੱਧੀ"</string> <string name="contentServiceSync" msgid="2341041749565687871">"ਸਿੰਕ ਕਰੋ"</string> - <string name="contentServiceSyncNotificationTitle" msgid="5766411446676388623">"ਸਮਕਾਲੀਕਿਰਤ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ"</string> + <string name="contentServiceSyncNotificationTitle" msgid="5766411446676388623">"ਸਿੰਕ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ"</string> <string name="contentServiceTooManyDeletesNotificationDesc" msgid="4562226280528716090">"ਬਹੁਤ ਸਾਰੀਆਂ <xliff:g id="CONTENT_TYPE">%s</xliff:g> ਨੂੰ ਮਿਟਾਉਣ ਦੀ ਕੋਸ਼ਿਸ਼ ਕੀਤੀ ਗਈ।"</string> <string name="low_memory" product="tablet" msgid="5557552311566179924">"ਟੈਬਲੈੱਟ ਸਟੋਰੇਜ ਪੂਰੀ ਭਰੀ ਹੈ। ਜਗ੍ਹਾ ਖਾਲੀ ਕਰਨ ਲਈ ਕੁਝ ਫ਼ਾਈਲਾਂ ਮਿਟਾਓ।"</string> <string name="low_memory" product="watch" msgid="3479447988234030194">"ਘੜੀ ਸਟੋਰੇਜ ਪੂਰੀ ਭਰੀ ਹੈ। ਜਗ੍ਹਾ ਖਾਲੀ ਕਰਨ ਲਈ ਕੁਝ ਫ਼ਾਈਲਾਂ ਮਿਟਾਓ।"</string> @@ -676,11 +676,11 @@ <string name="face_error_vendor_unknown" msgid="7387005932083302070">"ਕੋਈ ਗੜਬੜ ਹੋ ਗਈ। ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।"</string> <string name="face_icon_content_description" msgid="465030547475916280">"ਚਿਹਰਾ ਪ੍ਰਤੀਕ"</string> <string name="permlab_readSyncSettings" msgid="6250532864893156277">"ਸਿੰਕ ਸੈਟਿੰਗਾਂ ਪੜ੍ਹੋ"</string> - <string name="permdesc_readSyncSettings" msgid="1325658466358779298">"ਐਪ ਨੂੰ ਕਿਸੇ ਖਾਤੇ ਲਈ ਸਮਕਾਲੀਕਰਨ ਸੈਟਿੰਗਾਂ ਪੜ੍ਹਨ ਦੇ ਯੋਗ ਬਣਾਉਂਦਾ ਹੈ। ਉਦਾਹਰਨ ਲਈ, ਇਹ ਪਤਾ ਕਰ ਸਕਦਾ ਹੈ ਕਿ People ਐਪ ਦਾ ਕਿਸੇ ਖਾਤੇ ਨਾਲ ਸਮਕਾਲੀਕਿਰਤ ਕੀਤਾ ਗਿਆ ਹੈ ਜਾਂ ਨਹੀਂ।"</string> - <string name="permlab_writeSyncSettings" msgid="6583154300780427399">"ਸਮਕਾਲੀਕਰਨ ਚਾਲੂ ਅਤੇ ਬੰਦ ਨੂੰ ਟੌਗਲ ਕਰੋ"</string> - <string name="permdesc_writeSyncSettings" msgid="6029151549667182687">"ਐਪ ਨੂੰ ਇੱਕ ਖਾਤੇ ਲਈ ਸਮਕਾਲੀਕਰਨ ਸੈਟਿੰਗਾਂ ਸੋਧਣ ਦੇ ਯੋਗ ਬਣਾਉਂਦਾ ਹੈ। ਉਦਾਹਰਨ ਲਈ, ਇਸਦੀ ਵਰਤੋਂ ਕਿਸੇ ਖਾਤੇ ਨਾਲ People ਐਪ ਦਾ ਸਮਕਾਲੀਕਰਨ ਚਾਲੂ ਕਰਨ ਲਈ ਕੀਤੀ ਜਾ ਸਕਦੀ ਹੈ।"</string> + <string name="permdesc_readSyncSettings" msgid="1325658466358779298">"ਐਪ ਨੂੰ ਕਿਸੇ ਖਾਤੇ ਲਈ ਸਿੰਕ ਸੈਟਿੰਗਾਂ ਪੜ੍ਹਨ ਦੇ ਯੋਗ ਬਣਾਉਂਦਾ ਹੈ। ਉਦਾਹਰਨ ਲਈ, ਇਹ ਪਤਾ ਕਰ ਸਕਦਾ ਹੈ ਕਿ People ਐਪ ਨੂੰ ਕਿਸੇ ਖਾਤੇ ਨਾਲ ਸਿੰਕ ਕੀਤਾ ਗਿਆ ਹੈ ਜਾਂ ਨਹੀਂ।"</string> + <string name="permlab_writeSyncSettings" msgid="6583154300780427399">"ਸਿੰਕ ਚਾਲੂ ਅਤੇ ਬੰਦ ਨੂੰ ਟੌਗਲ ਕਰੋ"</string> + <string name="permdesc_writeSyncSettings" msgid="6029151549667182687">"ਐਪ ਨੂੰ ਇੱਕ ਖਾਤੇ ਲਈ ਸਿੰਕ ਸੈਟਿੰਗਾਂ ਸੋਧਣ ਦੇ ਯੋਗ ਬਣਾਉਂਦਾ ਹੈ। ਉਦਾਹਰਨ ਲਈ, ਇਸਦੀ ਵਰਤੋਂ ਕਿਸੇ ਖਾਤੇ ਨਾਲ People ਐਪ ਦਾ ਸਿੰਕ ਚਾਲੂ ਕਰਨ ਲਈ ਕੀਤੀ ਜਾ ਸਕਦੀ ਹੈ।"</string> <string name="permlab_readSyncStats" msgid="3747407238320105332">"ਸਿੰਕ ਅੰਕੜੇ ਪੜ੍ਹੋ"</string> - <string name="permdesc_readSyncStats" msgid="3867809926567379434">"ਐਪ ਨੂੰ ਇੱਕ ਖਾਤੇ ਲਈ ਸਮਕਾਲੀਕਰਨ ਸਥਿਤੀ ਪੜ੍ਹਨ ਦੇ ਯੋਗ ਬਣਾਉਂਦਾ ਹੈ, ਇਸ ਵਿੱਚ ਸਮਕਾਲੀਕਰਨ ਵਰਤਾਰਿਆਂ ਦਾ ਇਤਿਹਾਸ ਅਤੇ ਕਿੰਨੇ ਡਾਟਾ ਦਾ ਸਮਕਾਲੀਕਿਰਤ ਕੀਤਾ ਜਾਂਦਾ ਹੈ, ਵੀ ਸ਼ਾਮਲ ਹੈ।"</string> + <string name="permdesc_readSyncStats" msgid="3867809926567379434">"ਐਪ ਨੂੰ ਇੱਕ ਖਾਤੇ ਲਈ ਸਿੰਕ ਸਥਿਤੀ ਪੜ੍ਹਨ ਦੇ ਯੋਗ ਬਣਾਉਂਦਾ ਹੈ, ਇਸ ਵਿੱਚ ਸਿੰਕ ਇਵੈਂਟਾਂ ਦਾ ਇਤਿਹਾਸ ਅਤੇ ਕਿੰਨਾ ਡਾਟਾ ਸਿੰਕ ਕੀਤਾ ਜਾਂਦਾ ਹੈ, ਇਹ ਵੀ ਸ਼ਾਮਲ ਹੈ।"</string> <string name="permlab_sdcardRead" msgid="5791467020950064920">"ਸਮੱਗਰੀਆਂ ਪੜ੍ਹੋ"</string> <string name="permdesc_sdcardRead" msgid="6872973242228240382">"ਐਪ ਨੂੰ ਸਮੱਗਰੀਆਂ ਪੜ੍ਹਨ ਦਿੰਦੀ ਹੈ।"</string> <string name="permlab_sdcardWrite" msgid="4863021819671416668">"ਸਮੱਗਰੀਆਂ ਦਾ ਸੰਸ਼ੋਧਨ ਕਰੋ ਜਾਂ ਮਿਟਾਓ"</string> @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"ਐਪ ਨੂੰ ਪਰੇਸ਼ਾਨ ਨਾ ਕਰੋ ਕੌਂਫਿਗਰੇਸ਼ਨ ਨੂੰ ਪੜ੍ਹਨ ਅਤੇ ਲਿਖਣ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ।"</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"ਇਜਾਜ਼ਤ ਵਰਤੋਂ ਦੇਖਣਾ ਸ਼ੁਰੂ ਕਰੋ"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"ਧਾਰਕ ਨੂੰ ਕਿਸੇ ਹੋਰ ਐਪ ਲਈ ਇਜਾਜ਼ਤ ਵਰਤੋਂ ਨੂੰ ਸ਼ੁਰੂ ਕਰਨ ਦਿੰਦਾ ਹੈ। ਸਧਾਰਨ ਐਪਾਂ ਲਈ ਕਦੇ ਵੀ ਲੋੜੀਂਦਾ ਨਹੀਂ ਹੋਵੇਗਾ।"</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"ਇਜਾਜ਼ਤ ਸੰਬੰਧੀ ਫ਼ੈਸਲਿਆਂ ਨੂੰ ਦੇਖਣਾ ਸ਼ੁਰੂ ਕਰੋ"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"ਇਜਾਜ਼ਤ ਸੰਬੰਧੀ ਫ਼ੈਸਲਿਆਂ ਦੀ ਸਮੀਖਿਆ ਕਰਨ ਲਈ ਹੋਲਡਰ ਨੂੰ ਸਕ੍ਰੀਨ ਨੂੰ ਸ਼ੁਰੂ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦੀ ਹੈ। ਸਧਾਰਨ ਐਪਾਂ ਲਈ ਕਦੇ ਵੀ ਲੋੜੀਂਦੀ ਨਹੀਂ ਹੋਵੇਗੀ।"</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"ਐਪ ਦੀਆਂ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਨੂੰ ਦੇਖਣਾ ਸ਼ੁਰੂ ਕਰੋ"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"ਇਸ ਨਾਲ ਹੋਲਡਰ ਨੂੰ ਕਿਸੇ ਐਪ ਦੀਆਂ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਬਾਰੇ ਜਾਣਕਾਰੀ ਦੇਖਣ ਦੀ ਆਗਿਆ ਮਿਲਦੀ ਹੈ।"</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"ਉੱਚ ਸੈਂਪਲਿੰਗ ਰੇਟ \'ਤੇ ਸੈਂਸਰ ਡਾਟਾ ਤੱਕ ਪਹੁੰਚ ਕਰੋ"</string> diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml index c2e8663fdf1e..bbc18679f089 100644 --- a/core/res/res/values-pl/strings.xml +++ b/core/res/res/values-pl/strings.xml @@ -739,10 +739,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Pozwala aplikacji na odczyt i zmianę konfiguracji trybu Nie przeszkadzać."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"rozpocząć wyświetlanie użycia uprawnień"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Umożliwia rozpoczęcie korzystania z uprawnienia dotyczącego danej aplikacji jego posiadaczowi. Zwykłe aplikacje nie powinny potrzebować tego uprawnienia."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"rozpoczęcie wyświetlania decyzji dotyczących uprawnień"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Umożliwia posiadaczowi uruchomienie ekranu w celu przeglądania decyzji dotyczących uprawnień. Zwykłe aplikacje nie powinny tego potrzebować."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"rozpoczęcie wyświetlania funkcji aplikacji"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Umożliwia posiadaczowi rozpoczęcie przeglądania informacji o funkcjach aplikacji."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"dostęp do danych czujnika z wysoką częstotliwością"</string> diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml index 2f894839a8ea..4469a67d3caa 100644 --- a/core/res/res/values-ro/strings.xml +++ b/core/res/res/values-ro/strings.xml @@ -736,10 +736,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Permite aplicației să citească și să scrie configurația Nu deranja."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"porniți folosirea permisiunii de vizualizare"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Permite proprietarului să pornească folosirea permisiunii pentru o aplicație. Nu ar trebui să fie necesară pentru aplicațiile obișnuite."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"să înceapă să examineze deciziile privind permisiunile"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Permite proprietarului să deschidă ecranul pentru a examina deciziile privind permisiunile. Nu ar trebui să fie necesară pentru aplicațiile obișnuite."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"începeți să vedeți funcțiile aplicației"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Permite proprietarului să înceapă să vadă informațiile despre funcții pentru o aplicație."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"să acceseze date de la senzori la o rată de eșantionare mare"</string> diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml index e4d6527d0e7b..6b874b9e4e40 100644 --- a/core/res/res/values-ru/strings.xml +++ b/core/res/res/values-ru/strings.xml @@ -739,10 +739,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Открывает приложению доступ к настройкам режима \"Не беспокоить\" и позволяет изменять их."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"Просмотр данных об используемых разрешениях"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Приложение получит доступ к данным об используемых разрешениях. Это разрешение не требуется обычным приложениям."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"Просмотр действий с разрешениями"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Позволяет просматривать действия с разрешениями. Не используется обычными приложениями."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"Просмотр функций приложения"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Позволяет просматривать информацию о функциях приложения."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"Доступ к данным датчиков при высокой частоте дискретизации"</string> diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml index 027fcd2f8983..34436810b101 100644 --- a/core/res/res/values-si/strings.xml +++ b/core/res/res/values-si/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"බාධා නොකරන්න වින්යාස කිරීම කියවීමට සහ ලිවීමට යෙදුමට ඉඩ දෙයි."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"අවසර භාවිතය බැලීමට ආරම්භ කරන්න"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"තබා සිටින්නාට යෙදුමක් සඳහා අවසර භාවිතය ආරම්භ කිරීමට ඉඩ දෙයි. සාමාන්ය යෙදුම් සඳහා කිසි විටෙක අවශ්ය නොවිය යුතු ය."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"නව අවසර තීරණ ආරම්භ කරන්න"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"අවසර තීරණ සමාලෝචනය කිරීමට තිරය ආරම්භ කිරීමට දරන්නාට ඉඩ දෙයි. සාමාන්ය යෙදුම් සඳහා කිසිදා අවශ්ය නොවිය යුතුය."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"යෙදුම බලන්න විශේෂාංග ආරම්භ කරන්න"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"යෙදුමක් සඳහා විශේෂාංග තොරතුරු බැලීම ආරම්භ කිරීමට දරන්නාට ඉඩ දෙන්න."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"ඉහළ නියැදි කිරීමේ වේගයකින් සංවේදක දත්ත වෙත පිවිසෙන්න"</string> diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml index eab7f733db65..07371009414e 100644 --- a/core/res/res/values-sq/strings.xml +++ b/core/res/res/values-sq/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Lejon aplikacionin të lexojë dhe shkruajë konfigurimin e \"Mos shqetëso\"."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"nis përdorimin e lejes për shikimin"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Lejon që mbajtësi të nisë përdorimin e lejeve për një aplikacion. Nuk duhet të nevojitet asnjëherë për aplikacionet normale."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"nisë shikimin e vendimeve për lejet"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Lejon që zotëruesi të nisë ekranin për shqyrtimin e vendimeve për lejet. Nuk duhet të nevojitet asnjëherë për aplikacionet normale."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"fillojë shikimin e veçorive të aplikacionit"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Lejon që zotëruesi të fillojë të shikojë informacionin e veçorive për një aplikacion."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"qasu te të dhënat e sensorit me një shpejtësi kampionimi më të lartë"</string> diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml index 8d49f2af4c10..e117bdaaefa9 100644 --- a/core/res/res/values-sr/strings.xml +++ b/core/res/res/values-sr/strings.xml @@ -736,10 +736,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Дозвољава апликацији да чита и уписује конфигурацију подешавања Не узнемиравај."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"почетак коришћења дозволе за преглед"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Дозвољава власнику да започне коришћење дозволе за апликацију. Никада не би требало да буде потребна за уобичајене апликације."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"покретање прегледа одлука о дозволама"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Дозвољава власнику да покрене екран за проверу одлука о дозволама. Никада не би требало да буде потребно за обичне апликације."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"покретање приказа функција апликације"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Дозвољава носиоцу дозволе да започне прегледање информација о функцијама апликације."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"приступ подацима сензора при великој брзини узорковања"</string> @@ -1552,7 +1550,7 @@ <string name="vpn_lockdown_disconnected" msgid="5573611651300764955">"Веза са увек укљученим VPN-ом је прекинута"</string> <string name="vpn_lockdown_error" msgid="4453048646854247947">"Повезивање на стално укључени VPN није успело"</string> <string name="vpn_lockdown_config" msgid="8331697329868252169">"Промените подешавања VPN-а"</string> - <string name="upload_file" msgid="8651942222301634271">"Одабери датотеку"</string> + <string name="upload_file" msgid="8651942222301634271">"Одабери фајл"</string> <string name="no_file_chosen" msgid="4146295695162318057">"Није изабрана ниједна датотека"</string> <string name="reset" msgid="3865826612628171429">"Ресетуј"</string> <string name="submit" msgid="862795280643405865">"Пошаљи"</string> diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml index af3586b387f7..90de24523802 100644 --- a/core/res/res/values-sv/strings.xml +++ b/core/res/res/values-sv/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Ger appen läs- och skrivbehörighet till konfigurationen för Stör ej."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"börja visa behörighetsanvändningen"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Gör att innehavaren kan öppna behörighetsanvändning för en app. Ska inte behövas för vanliga appar."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"börja visa behörighetsbeslut"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Tillåter att innehavaren öppnar skärmen för att granska behörighetsbeslut. Detta ska inte behövas för vanliga appar."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"börja visa appfunktioner"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Tillåter att innehavaren börjar visa information om funktioner för en app."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"åtkomst till sensordata med en hög samplingsfrekvens"</string> diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml index f70bfcd95ca7..ec241023ee35 100644 --- a/core/res/res/values-sw/strings.xml +++ b/core/res/res/values-sw/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Inaruhusu programu kusoma na kuandika usanidi wa kipengee cha Usinisumbue."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"anzisha kipengele cha kuona matumizi ya ruhusa"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Huruhusu kishikiliaji kuanzisha matumizi ya ruhusa ya programu. Haipaswi kuhitajika kwa ajili ya programu za kawaida."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"kuanzisha uamuzi wa ruhusa za kuangalia"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Humruhusu mmiliki kuanzisha skrini ili kukagua uamuzi wa ruhusa. Haipaswi kuhitajika kwenye programu za kawaida."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"anzisha kipengele cha kuangalia vipengele vya programu"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Huruhusu mmiliki kuanza kuangalia maelezo ya vipengele vya programu."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"fikia data ya vitambuzi kwa kasi ya juu ya sampuli"</string> diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml index 1114bf1e0c74..e7ea59d36387 100644 --- a/core/res/res/values-ta/strings.xml +++ b/core/res/res/values-ta/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"தொந்தரவு செய்ய வேண்டாம் உள்ளமைவைப் படிக்கவும் எழுதவும், ஆப்ஸை அனுமதிக்கிறது."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"அனுமதி உபயோகத்தை அணுகுதல்"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"ஆப்ஸிற்கான அனுமதி உபயோகத்தை ஹோல்டருக்கு வழங்கும். இயல்பான ஆப்ஸிற்கு இது எப்போதுமே தேவைப்படாது."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"அனுமதி முடிவுகளைப் பார்க்கத் தொடங்குதல்"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"அனுமதி தொடர்பான முடிவுகளை மதிப்பாய்வு செய்ய, திரையைத் தொடங்குவதற்கான அனுமதியை ஹோல்டருக்கு வழங்கும். இயல்பான ஆப்ஸிற்கு இது எப்போதுமே தேவைப்படாது."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"ஆப்ஸின் அம்சங்களைப் பார்க்கத் தொடங்குதல்"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"ஆப்ஸின் அம்சங்கள் குறித்த தகவல்களைப் பார்ப்பதற்கான அனுமதியை ஹோல்டருக்கு வழங்கும்."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"அதிகளவிலான சாம்பிளிங் ரேட்டில் சென்சார் தரவை அணுகுதல்"</string> diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml index 0d9cb8b86ac5..176d845f21dd 100644 --- a/core/res/res/values-te/strings.xml +++ b/core/res/res/values-te/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"అంతరాయం కలిగించవద్దు ఎంపిక కాన్ఫిగరేషన్ చదవడానికి మరియు రాయడానికి యాప్ను అనుమతిస్తుంది."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"వీక్షణ అనుమతి వినియోగాన్ని ప్రారంభించండి"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"యాప్నకు అనుమతి వినియోగాన్ని ప్రారంభించడానికి హోల్డర్ను అనుమతిస్తుంది. సాధారణ యాప్లకు ఎప్పటికీ ఇటువంటి అనుమతి అవసరం ఉండదు."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"వీక్షణ అనుమతి నిర్ణయాలను ప్రారంభించండి"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"అనుమతి నిర్ణయాలను రివ్యూ చేయడానికి స్క్రీన్ను ప్రారంభించడానికి హోల్డర్ను అనుమతిస్తుంది. సాధారణ యాప్లకు ఎప్పటికీ అవసరం ఉండదు."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"యాప్ ఫీచర్లను చూడటాన్ని ప్రారంభించండి"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"యాప్ ఫీచర్ల సమాచారాన్ని చూడటాన్ని ప్రారంభించడానికి హోల్డర్ను అనుమతిస్తుంది."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"అధిక శాంపిల్ రేటు వద్ద సెన్సార్ డేటాను యాక్సెస్ చేయండి"</string> diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml index b6b0345acc5d..30b3d0b3c127 100644 --- a/core/res/res/values-th/strings.xml +++ b/core/res/res/values-th/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"อนุญาตให้แอปอ่านและเขียนการกำหนดค่าโหมดห้ามรบกวน"</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"เริ่มการใช้สิทธิ์การดู"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"อนุญาตให้เจ้าของเริ่มการใช้สิทธิ์ของแอป ไม่จำเป็นสำหรับแอปทั่วไป"</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"เริ่มดูสิทธิ์ที่เลือกไว้"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"อนุญาตให้แอปเริ่มหน้าจอเพื่อดูสิทธิ์ที่เลือกไว้ แอปทั่วไปไม่จำเป็นต้องใช้"</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"เริ่มดูฟีเจอร์ของแอป"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"อนุญาตให้เจ้าของเริ่มดูข้อมูลฟีเจอร์สำหรับแอป"</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"เข้าถึงข้อมูลเซ็นเซอร์ที่อัตราการสุ่มตัวอย่างสูง"</string> diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml index fa73788174f7..c0bbc6958482 100644 --- a/core/res/res/values-tl/strings.xml +++ b/core/res/res/values-tl/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Nagbibigay-daan sa app na basahin at isulat ang configuration ng Huwag Istorbohin."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"simulan ang paggamit sa pahintulot sa pagtingin"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Binibigyang-daan ang may hawak na simulan ang paggamit ng pahintulot para sa isang app. Hindi dapat kailanganin kailanman para sa mga normal na app."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"simulan ang mga desisyon sa pahintulot na tumingin"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Nagbibigay-daan sa may hawak na simulan ang screen para masuri ang mga desisyon sa pahintulot. Hindi dapat kailanman kailanganin para sa mga karaniwang app."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"simulang tingnan ang mga feature ng app"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Nagbibigay-daan sa may hawak na simulang tingnan ang impormasyon ng mga feature para sa isang app."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"mag-access ng data ng sensor sa mataas na rate ng pag-sample"</string> diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml index adf96e077d58..8a8622715b2e 100644 --- a/core/res/res/values-tr/strings.xml +++ b/core/res/res/values-tr/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Uygulamaya, Rahatsız Etmeyin yapılandırmasını okuma ve yazma izni verir."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"izin kullanımı görüntülemeye başlama"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"İzin sahibinin bir uygulama için izin kullanımı başlatmasına olanak tanır. Normal uygulamalar için hiçbir zaman kullanılmamalıdır."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"izin kararlarını görüntülemeye başlama"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"İzin sahibinin, izin kararlarını incelemek için ekranı başlatmasına izin verir. Normal uygulamalarda hiçbir zaman gerek duyulmaz."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"uygulama özelliklerini görüntülemeye başlama"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"İzin sahibinin, bir uygulamanın özellik bilgilerini görüntülemeye başlamasına izin verir."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"sensör verilerine daha yüksek örnekleme hızında eriş"</string> diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml index b86402a462ff..f4fb575f836e 100644 --- a/core/res/res/values-uk/strings.xml +++ b/core/res/res/values-uk/strings.xml @@ -739,10 +739,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Додаток зможе переглядати та змінювати конфігурацію режиму \"Не турбувати\"."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"перегляньте дані про використання дозволів"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Власник зможе використовувати дозволи для цього додатка. Цей дозвіл не потрібен для звичайних додатків."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"почати перегляд рішень щодо дозволів"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"У додатку може відкриватися екран для перегляду рішень щодо дозволів. Звичайні додатки ніколи не використовують цей дозвіл."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"почати перегляд функцій додатка"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Дозволяє додатку почати перегляд інформації про функції іншого додатка."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"доступ до даних датчиків із високою частотою дикретизації"</string> diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml index 65fe847396bd..2c712f4018b2 100644 --- a/core/res/res/values-ur/strings.xml +++ b/core/res/res/values-ur/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"ایپ کو ڈسٹرب نہ کریں کنفیگریشن لکھنے اور پڑھنے کے قابل کرتا ہے۔"</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"اجازت کی استعمال کا ملاحظہ شروع کریں"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"حامل کو ایپ کی اجازت کے استعمال کو شروع کرنے کی اجازت دیتا ہے۔ عام ایپس کے لیے کبھی بھی درکار نہیں ہونا چاہیے۔"</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"اجازت کے فیصلوں کو دیکھنا شروع کریں"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"ہولڈر کو اجازت کے فیصلوں کے جائزے کے لیے اسکرین شروع کرنے کی اجازت دیتا ہے۔ عام ایپس کے لیے کبھی بھی اس کی ضرورت نہيں ہونی چاہیے۔"</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"ایپ کی خصوصیات کا ملاحظہ شروع کریں"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"ہولڈر کو ایپ کے لیے خصوصیات کی معلومات دیکھنے کی اجازت دیتا ہے۔"</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"نمونہ کاری کی اعلی شرح پر سینسر کے ڈیٹا تک رسائی حاصل کریں"</string> diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml index bbfadf6bf805..05899edf1e8c 100644 --- a/core/res/res/values-vi/strings.xml +++ b/core/res/res/values-vi/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Cho phép ứng dụng đọc và ghi cấu hình Không làm phiền."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"cấp quyền xem"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Cho phép chủ sở hữu cấp quyền cho một ứng dụng. Các ứng dụng thông thường sẽ không bao giờ cần quyền này."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"bắt đầu xem các quyết định cấp quyền"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Cho phép chủ sở hữu bắt đầu kiểm tra để xem xét các quyết định cấp quyền. Việc này hoàn toàn không cần thiết đối với các ứng dụng thông thường."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"bắt đầu xem các tính năng của ứng dụng"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Cho phép chủ sở hữu bắt đầu xem thông tin về tính năng của một ứng dụng."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"truy cập vào dữ liệu cảm biến ở tốc độ lấy mẫu cao"</string> diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml index b7e19725716f..6acd3e01d20f 100644 --- a/core/res/res/values-zh-rCN/strings.xml +++ b/core/res/res/values-zh-rCN/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"允许此应用读取和写入“勿扰”模式配置。"</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"授权使用“查看权限”"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"允许该应用开始查看应用的权限使用情况(普通应用绝不需要此权限)。"</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"开始查看权限决策"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"允许具有该权限的应用启动屏幕以查看权限决策。普通应用绝不需要此权限。"</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"开始查看应用功能"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"允许具有该权限的应用开始查看某个应用的功能信息。"</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"以高采样率访问传感器数据"</string> diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml index 86de22f3e3ff..bf51c1aae753 100644 --- a/core/res/res/values-zh-rHK/strings.xml +++ b/core/res/res/values-zh-rHK/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"允許應用程式讀取和寫入「請勿騷擾」設定。"</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"開始查看權限使用情況"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"允許應用程式開始查看應用程式的權限使用情況 (一般應用程式並不需要)。"</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"開始檢視權限決定"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"允許擁有者啟用螢幕以查看權限決定。不建議一般應用程式使用。"</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"開始查看應用程式功能"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"允許擁有者開始查看應用程式的功能資料。"</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"以高取樣率存取感應器資料"</string> diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml index a4e6fbba76d4..bd0b9a242d1a 100644 --- a/core/res/res/values-zh-rTW/strings.xml +++ b/core/res/res/values-zh-rTW/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"允許應用程式讀取及寫入「零打擾」設定。"</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"啟動檢視權限用途"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"允許應用程式開始使用其他應用程式 (一般應用程式並不需要)。"</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"開始檢視權限決定"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"允許應用程式啟動檢視權限決定的畫面 (一般應用程式並不需要)。"</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"開始查看應用程式功能"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"允許具有這項權限的應用程式開始查看其他應用程式的功能資訊。"</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"以高取樣率存取感應器資料"</string> diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml index 4e8cfd42aecd..bf024528f3e3 100644 --- a/core/res/res/values-zu/strings.xml +++ b/core/res/res/values-zu/strings.xml @@ -733,10 +733,8 @@ <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"Ivumela izinhlelo zokusebenza ukufunda nokubhala ukulungiswa kokuthi Ungaphazamisi."</string> <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"qala ukusetshenziswa kokubuka imvume"</string> <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"Ivumela umphathi ukuthi aqale ukusetshenziswa kwemvume kohlelo lokusebenza. Akumele idingelwe izinhlelo zokusebenza ezijwayelekile."</string> - <!-- no translation found for permlab_startReviewPermissionDecisions (8690578688476599284) --> - <skip /> - <!-- no translation found for permdesc_startReviewPermissionDecisions (2775556853503004236) --> - <skip /> + <string name="permlab_startReviewPermissionDecisions" msgid="8690578688476599284">"qala ukubuka izinqumo zemvume"</string> + <string name="permdesc_startReviewPermissionDecisions" msgid="2775556853503004236">"Ivumela obambile ukuthi aqale isikrini ukuze abuyekeze izinqumo zemvume. Akufanele idingeke ngama-app avamile."</string> <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"qala ukubuka izakhi ze-app"</string> <string name="permdesc_startViewAppFeatures" msgid="7207240860165206107">"Vumela isibambi ukuthi siqale ukubuka ulwazi lwezakhi lwe-app."</string> <string name="permlab_highSamplingRateSensors" msgid="3941068435726317070">"finyelela idatha yenzwa ngenani eliphezulu lokwenza isampuli"</string> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 226778163ea2..a5da1ba39cfa 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. --> @@ -5417,6 +5425,17 @@ ignores some hyphen character related typographic features, e.g. kerning. --> <enum name="fullFast" value="4" /> </attr> + <!-- Indicates the line break strategies can be used when calculating the text wrapping. --> + <attr name="lineBreakStyle"> + <!-- No line break style specific. --> + <enum name="none" value="0" /> + <!-- Use the least restrictive rule for line-breaking. --> + <enum name="loose" value="1" /> + <!-- Indicate breaking text with the most comment set of line-breaking rules. --> + <enum name="normal" value="2" /> + <!-- ndicates breaking text with the most strictest line-breaking rules. --> + <enum name="strict" value="3" /> + </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"> @@ -6884,6 +6903,9 @@ <!-- Special option for window animations: whether window should have rounded corners. @see ScreenDecorationsUtils#getWindowCornerRadius(Resources) --> <attr name="hasRoundedCorners" format="boolean" /> + <!-- Special option for window animations: whether the window's background should be used as + a background to the animation. --> + <attr name="showBackground" format="boolean" /> </declare-styleable> <declare-styleable name="AnimationSet"> @@ -9362,11 +9384,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. --> @@ -9665,4 +9688,11 @@ <attr name="iconfactoryBadgeSize" format="dimension"/> <!-- Perceptual luminance of a color, in accessibility friendly color space. From 0 to 100. --> <attr name="lStar" format="float"/> -</resources> + + <!-- The attributes of the {@code <locale>} tag within {@code <locale-config>}. --> + <declare-styleable name="LocaleConfig_Locale"> + <!-- The <a href="https://www.rfc-editor.org/rfc/bcp/bcp47.txt">IETF BCP47 language tag</a> + of the supported locale. {@link android.app.LocaleConfig} --> + <attr name="name" /> + </declare-styleable> + </resources> diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index f7e0fcf77c8f..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 @@ -1519,6 +1528,9 @@ <!-- An XML resource with the application's Network Security Config. --> <attr name="networkSecurityConfig" format="reference" /> + <!-- An XML resource with the application's {@link android.app.LocaleConfig} --> + <attr name="localeConfig" format="reference" /> + <!-- When an application is partitioned into splits, this is the name of the split that contains the defined component. --> <attr name="splitName" format="string" /> @@ -1674,6 +1686,7 @@ <attr name="sharedUserId" /> <attr name="sharedUserLabel" /> <attr name="sharedUserMaxSdkVersion" /> + <attr name="inheritKeyStoreKeys" /> <attr name="installLocation" /> <attr name="isolatedSplits" /> <attr name="isFeatureSplit" /> @@ -1801,6 +1814,7 @@ <attr name="maxAspectRatio" /> <attr name="minAspectRatio" /> <attr name="networkSecurityConfig" /> + <attr name="localeConfig" /> <!-- Declare the category of this app. Categories are used to cluster multiple apps together into meaningful groups, such as when summarizing battery, network, or disk usage. Apps should only define this value when they fit well into one of @@ -2825,14 +2839,6 @@ <attr name="path" /> <attr name="minSdkVersion" /> <attr name="maxSdkVersion" /> - <!-- The order in which the apex system services are initiated. When there are dependencies - among apex system services, setting this attribute for each of them ensures that they are - created in the order required by those dependencies. The apex-system-services that are - started manually within SystemServer ignore the initOrder and are not considered for - automatic starting of the other services. - The value is a simple integer, with higher number being initialized first. If not specified, - the default order is 0. --> - <attr name="initOrder" format="integer" /> </declare-styleable> <!-- The <code>receiver</code> tag declares an 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/public.xml b/core/res/res/values/public.xml index 574c2f7084b0..42386fcb76db 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -3211,6 +3211,7 @@ </staging-public-group-final> <public type="attr" name="shouldUseDefaultUnfoldTransition" id="0x0101064c" /> + <public type="attr" name="lineBreakStyle" id="0x0101064d" /> <staging-public-group-final type="id" first-id="0x01fe0000"> <public name="accessibilityActionDragStart" /> @@ -3253,6 +3254,10 @@ <public name="showClockAndComplications" /> <!-- @hide @SystemApi --> <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"> @@ -3276,6 +3281,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"> @@ -3319,6 +3326,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/styles_device_defaults.xml b/core/res/res/values/styles_device_defaults.xml index dcfca8497ec6..3b2f24409a88 100644 --- a/core/res/res/values/styles_device_defaults.xml +++ b/core/res/res/values/styles_device_defaults.xml @@ -42,7 +42,6 @@ easier. <item name="outlineSpotShadowColor">@color/btn_colored_background_material</item> <item name="textAppearance">?attr/textAppearanceButton</item> <item name="textColor">@color/btn_colored_text_material</item> - <item name="drawableTint">@color/btn_colored_text_material</item> </style> <style name="Widget.DeviceDefault.TextView" parent="Widget.Material.TextView" /> <style name="Widget.DeviceDefault.CheckedTextView" parent="Widget.Material.CheckedTextView"/> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 4e77563602ee..f6393503ebd5 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2221,6 +2221,7 @@ <java-symbol type="integer" name="config_dreamsBatteryLevelMinimumWhenNotPowered" /> <java-symbol type="integer" name="config_dreamsBatteryLevelDrainCutoff" /> <java-symbol type="string" name="config_dreamsDefaultComponent" /> + <java-symbol type="drawable" name="default_dream_preview" /> <java-symbol type="string" name="config_dozeComponent" /> <java-symbol type="string" name="enable_explore_by_touch_warning_title" /> <java-symbol type="string" name="enable_explore_by_touch_warning_message" /> @@ -3651,6 +3652,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" /> @@ -4645,4 +4647,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/batterystatstests/BatteryStatsViewer/AndroidManifest.xml b/core/tests/batterystatstests/BatteryStatsViewer/AndroidManifest.xml index 1b1f64afcbae..bd987a03b51f 100644 --- a/core/tests/batterystatstests/BatteryStatsViewer/AndroidManifest.xml +++ b/core/tests/batterystatstests/BatteryStatsViewer/AndroidManifest.xml @@ -16,8 +16,7 @@ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.frameworks.core.batterystatsviewer" - android:sharedUserId="android.uid.system"> + package="com.android.frameworks.core.batterystatsviewer"> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.BATTERY_STATS"/> diff --git a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java index eb378b92dd0a..6a53f6843d48 100644 --- a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java +++ b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java @@ -130,23 +130,20 @@ public class BatteryConsumerData { || powerModel == BatteryConsumer.POWER_MODEL_UNDEFINED) { addEntry(metricTitle, EntryType.UID_POWER_MODELED, requestedBatteryConsumer.getConsumedPower(component), - totalPowerByComponentMah[component] - ); + totalPowerByComponentMah[component]); + addProcessStateEntries(metricTitle, EntryType.UID_POWER_MODELED_PROCESS_STATE, + requestedBatteryConsumer, component); } else { addEntry(metricTitle + " (measured)", EntryType.UID_POWER_MEASURED, requestedBatteryConsumer.getConsumedPower(component), - totalPowerByComponentMah[component] - ); + totalPowerByComponentMah[component]); addProcessStateEntries(metricTitle, EntryType.UID_POWER_MEASURED_PROCESS_STATE, - requestedBatteryConsumer, component - ); + requestedBatteryConsumer, component); addEntry(metricTitle + " (modeled)", EntryType.UID_POWER_MODELED, requestedModeledBatteryConsumer.getConsumedPower(component), - totalModeledPowerByComponentMah[component] - ); + totalModeledPowerByComponentMah[component]); addProcessStateEntries(metricTitle, EntryType.UID_POWER_MODELED_PROCESS_STATE, - requestedModeledBatteryConsumer, component - ); + requestedModeledBatteryConsumer, component); } } diff --git a/core/tests/bluetoothtests/Android.bp b/core/tests/bluetoothtests/Android.bp deleted file mode 100644 index 68416dd65466..000000000000 --- a/core/tests/bluetoothtests/Android.bp +++ /dev/null @@ -1,24 +0,0 @@ -package { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "frameworks_base_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["frameworks_base_license"], -} - -android_test { - name: "BluetoothTests", - // Include all test java files. - srcs: ["src/**/*.java"], - libs: [ - "android.test.runner", - "android.test.base", - ], - static_libs: [ - "junit", - "modules-utils-bytesmatcher", - ], - platform_apis: true, - certificate: "platform", -} diff --git a/core/tests/bluetoothtests/AndroidManifest.xml b/core/tests/bluetoothtests/AndroidManifest.xml deleted file mode 100644 index 75583d5298cb..000000000000 --- a/core/tests/bluetoothtests/AndroidManifest.xml +++ /dev/null @@ -1,47 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2011 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> - -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.bluetooth.tests" - android:sharedUserId="android.uid.bluetooth" > - - <uses-permission android:name="android.permission.BLUETOOTH" /> - <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> - <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE"/> - <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> - <uses-permission android:name="android.permission.BLUETOOTH_SCAN" /> - <uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" /> - <uses-permission android:name="android.permission.BROADCAST_STICKY" /> - <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" /> - <uses-permission android:name="android.permission.LOCAL_MAC_ADDRESS" /> - <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/> - <uses-permission android:name="android.permission.RECEIVE_SMS" /> - <uses-permission android:name="android.permission.READ_SMS"/> - <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> - <uses-permission android:name="android.permission.WRITE_SETTINGS" /> - <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> - - <application > - <uses-library android:name="android.test.runner" /> - </application> - <instrumentation android:name="android.bluetooth.BluetoothTestRunner" - android:targetPackage="com.android.bluetooth.tests" - android:label="Bluetooth Tests" /> - <instrumentation android:name="android.bluetooth.BluetoothInstrumentation" - android:targetPackage="com.android.bluetooth.tests" - android:label="Bluetooth Test Utils" /> - -</manifest> diff --git a/core/tests/bluetoothtests/AndroidTest.xml b/core/tests/bluetoothtests/AndroidTest.xml deleted file mode 100644 index f93c4ebf5bf6..000000000000 --- a/core/tests/bluetoothtests/AndroidTest.xml +++ /dev/null @@ -1,32 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright 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. ---> -<configuration description="Config for Bluetooth test cases"> - <option name="test-suite-tag" value="apct"/> - <option name="test-suite-tag" value="apct-instrumentation"/> - <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> - <option name="cleanup-apks" value="true" /> - <option name="test-file-name" value="BluetoothTests.apk" /> - </target_preparer> - - <option name="test-suite-tag" value="apct"/> - <option name="test-tag" value="BluetoothTests"/> - - <test class="com.android.tradefed.testtype.AndroidJUnitTest" > - <option name="package" value="com.android.bluetooth.tests" /> - <option name="hidden-api-checks" value="false"/> - <option name="runner" value="android.bluetooth.BluetoothTestRunner"/> - </test> -</configuration> diff --git a/core/tests/bluetoothtests/OWNERS b/core/tests/bluetoothtests/OWNERS deleted file mode 100644 index 98bb87716207..000000000000 --- a/core/tests/bluetoothtests/OWNERS +++ /dev/null @@ -1 +0,0 @@ -include /core/java/android/bluetooth/OWNERS diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothCodecConfigTest.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothCodecConfigTest.java deleted file mode 100644 index bd55426601fc..000000000000 --- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothCodecConfigTest.java +++ /dev/null @@ -1,329 +0,0 @@ -/* - * Copyright 2018 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.bluetooth; - -import android.test.suitebuilder.annotation.SmallTest; - -import junit.framework.TestCase; - -/** - * Unit test cases for {@link BluetoothCodecConfig}. - * <p> - * To run this test, use: - * runtest --path core/tests/bluetoothtests/src/android/bluetooth/BluetoothCodecConfigTest.java - */ -public class BluetoothCodecConfigTest extends TestCase { - private static final int[] kCodecTypeArray = new int[] { - BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, - BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, - BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, - BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, - BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, - BluetoothCodecConfig.SOURCE_CODEC_TYPE_INVALID, - }; - private static final int[] kCodecPriorityArray = new int[] { - BluetoothCodecConfig.CODEC_PRIORITY_DISABLED, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST, - }; - private static final int[] kSampleRateArray = new int[] { - BluetoothCodecConfig.SAMPLE_RATE_NONE, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.SAMPLE_RATE_48000, - BluetoothCodecConfig.SAMPLE_RATE_88200, - BluetoothCodecConfig.SAMPLE_RATE_96000, - BluetoothCodecConfig.SAMPLE_RATE_176400, - BluetoothCodecConfig.SAMPLE_RATE_192000, - }; - private static final int[] kBitsPerSampleArray = new int[] { - BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.BITS_PER_SAMPLE_24, - BluetoothCodecConfig.BITS_PER_SAMPLE_32, - }; - private static final int[] kChannelModeArray = new int[] { - BluetoothCodecConfig.CHANNEL_MODE_NONE, - BluetoothCodecConfig.CHANNEL_MODE_MONO, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - }; - private static final long[] kCodecSpecific1Array = new long[] { 1000, 1001, 1002, 1003, }; - private static final long[] kCodecSpecific2Array = new long[] { 2000, 2001, 2002, 2003, }; - private static final long[] kCodecSpecific3Array = new long[] { 3000, 3001, 3002, 3003, }; - private static final long[] kCodecSpecific4Array = new long[] { 4000, 4001, 4002, 4003, }; - - private static final int kTotalConfigs = kCodecTypeArray.length * kCodecPriorityArray.length * - kSampleRateArray.length * kBitsPerSampleArray.length * kChannelModeArray.length * - kCodecSpecific1Array.length * kCodecSpecific2Array.length * kCodecSpecific3Array.length * - kCodecSpecific4Array.length; - - private int selectCodecType(int configId) { - int left = kCodecTypeArray.length; - int right = kTotalConfigs / left; - int index = configId / right; - index = index % kCodecTypeArray.length; - return kCodecTypeArray[index]; - } - - private int selectCodecPriority(int configId) { - int left = kCodecTypeArray.length * kCodecPriorityArray.length; - int right = kTotalConfigs / left; - int index = configId / right; - index = index % kCodecPriorityArray.length; - return kCodecPriorityArray[index]; - } - - private int selectSampleRate(int configId) { - int left = kCodecTypeArray.length * kCodecPriorityArray.length * kSampleRateArray.length; - int right = kTotalConfigs / left; - int index = configId / right; - index = index % kSampleRateArray.length; - return kSampleRateArray[index]; - } - - private int selectBitsPerSample(int configId) { - int left = kCodecTypeArray.length * kCodecPriorityArray.length * kSampleRateArray.length * - kBitsPerSampleArray.length; - int right = kTotalConfigs / left; - int index = configId / right; - index = index % kBitsPerSampleArray.length; - return kBitsPerSampleArray[index]; - } - - private int selectChannelMode(int configId) { - int left = kCodecTypeArray.length * kCodecPriorityArray.length * kSampleRateArray.length * - kBitsPerSampleArray.length * kChannelModeArray.length; - int right = kTotalConfigs / left; - int index = configId / right; - index = index % kChannelModeArray.length; - return kChannelModeArray[index]; - } - - private long selectCodecSpecific1(int configId) { - int left = kCodecTypeArray.length * kCodecPriorityArray.length * kSampleRateArray.length * - kBitsPerSampleArray.length * kChannelModeArray.length * kCodecSpecific1Array.length; - int right = kTotalConfigs / left; - int index = configId / right; - index = index % kCodecSpecific1Array.length; - return kCodecSpecific1Array[index]; - } - - private long selectCodecSpecific2(int configId) { - int left = kCodecTypeArray.length * kCodecPriorityArray.length * kSampleRateArray.length * - kBitsPerSampleArray.length * kChannelModeArray.length * kCodecSpecific1Array.length * - kCodecSpecific2Array.length; - int right = kTotalConfigs / left; - int index = configId / right; - index = index % kCodecSpecific2Array.length; - return kCodecSpecific2Array[index]; - } - - private long selectCodecSpecific3(int configId) { - int left = kCodecTypeArray.length * kCodecPriorityArray.length * kSampleRateArray.length * - kBitsPerSampleArray.length * kChannelModeArray.length * kCodecSpecific1Array.length * - kCodecSpecific2Array.length * kCodecSpecific3Array.length; - int right = kTotalConfigs / left; - int index = configId / right; - index = index % kCodecSpecific3Array.length; - return kCodecSpecific3Array[index]; - } - - private long selectCodecSpecific4(int configId) { - int left = kCodecTypeArray.length * kCodecPriorityArray.length * kSampleRateArray.length * - kBitsPerSampleArray.length * kChannelModeArray.length * kCodecSpecific1Array.length * - kCodecSpecific2Array.length * kCodecSpecific3Array.length * - kCodecSpecific4Array.length; - int right = kTotalConfigs / left; - int index = configId / right; - index = index % kCodecSpecific4Array.length; - return kCodecSpecific4Array[index]; - } - - @SmallTest - public void testBluetoothCodecConfig_valid_get_methods() { - - for (int config_id = 0; config_id < kTotalConfigs; config_id++) { - int codec_type = selectCodecType(config_id); - int codec_priority = selectCodecPriority(config_id); - int sample_rate = selectSampleRate(config_id); - int bits_per_sample = selectBitsPerSample(config_id); - int channel_mode = selectChannelMode(config_id); - long codec_specific1 = selectCodecSpecific1(config_id); - long codec_specific2 = selectCodecSpecific2(config_id); - long codec_specific3 = selectCodecSpecific3(config_id); - long codec_specific4 = selectCodecSpecific4(config_id); - - BluetoothCodecConfig bcc = buildBluetoothCodecConfig(codec_type, codec_priority, - sample_rate, bits_per_sample, - channel_mode, codec_specific1, - codec_specific2, codec_specific3, - codec_specific4); - - if (codec_type == BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC) { - assertTrue(bcc.isMandatoryCodec()); - } else { - assertFalse(bcc.isMandatoryCodec()); - } - - if (codec_type == BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC) { - assertEquals("SBC", bcc.getCodecName()); - } - if (codec_type == BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC) { - assertEquals("AAC", bcc.getCodecName()); - } - if (codec_type == BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX) { - assertEquals("aptX", bcc.getCodecName()); - } - if (codec_type == BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD) { - assertEquals("aptX HD", bcc.getCodecName()); - } - if (codec_type == BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC) { - assertEquals("LDAC", bcc.getCodecName()); - } - if (codec_type == BluetoothCodecConfig.SOURCE_CODEC_TYPE_INVALID) { - assertEquals("INVALID CODEC", bcc.getCodecName()); - } - - assertEquals(codec_type, bcc.getCodecType()); - assertEquals(codec_priority, bcc.getCodecPriority()); - assertEquals(sample_rate, bcc.getSampleRate()); - assertEquals(bits_per_sample, bcc.getBitsPerSample()); - assertEquals(channel_mode, bcc.getChannelMode()); - assertEquals(codec_specific1, bcc.getCodecSpecific1()); - assertEquals(codec_specific2, bcc.getCodecSpecific2()); - assertEquals(codec_specific3, bcc.getCodecSpecific3()); - assertEquals(codec_specific4, bcc.getCodecSpecific4()); - } - } - - @SmallTest - public void testBluetoothCodecConfig_equals() { - BluetoothCodecConfig bcc1 = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - - BluetoothCodecConfig bcc2_same = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - assertTrue(bcc1.equals(bcc2_same)); - - BluetoothCodecConfig bcc3_codec_type = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - assertFalse(bcc1.equals(bcc3_codec_type)); - - BluetoothCodecConfig bcc4_codec_priority = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, - BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - assertFalse(bcc1.equals(bcc4_codec_priority)); - - BluetoothCodecConfig bcc5_sample_rate = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_48000, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - assertFalse(bcc1.equals(bcc5_sample_rate)); - - BluetoothCodecConfig bcc6_bits_per_sample = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_24, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - assertFalse(bcc1.equals(bcc6_bits_per_sample)); - - BluetoothCodecConfig bcc7_channel_mode = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - assertFalse(bcc1.equals(bcc7_channel_mode)); - - BluetoothCodecConfig bcc8_codec_specific1 = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1001, 2000, 3000, 4000); - assertFalse(bcc1.equals(bcc8_codec_specific1)); - - BluetoothCodecConfig bcc9_codec_specific2 = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2002, 3000, 4000); - assertFalse(bcc1.equals(bcc9_codec_specific2)); - - BluetoothCodecConfig bcc10_codec_specific3 = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3003, 4000); - assertFalse(bcc1.equals(bcc10_codec_specific3)); - - BluetoothCodecConfig bcc11_codec_specific4 = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4004); - assertFalse(bcc1.equals(bcc11_codec_specific4)); - } - - private BluetoothCodecConfig buildBluetoothCodecConfig(int sourceCodecType, - int codecPriority, int sampleRate, int bitsPerSample, int channelMode, - long codecSpecific1, long codecSpecific2, long codecSpecific3, long codecSpecific4) { - return new BluetoothCodecConfig.Builder() - .setCodecType(sourceCodecType) - .setCodecPriority(codecPriority) - .setSampleRate(sampleRate) - .setBitsPerSample(bitsPerSample) - .setChannelMode(channelMode) - .setCodecSpecific1(codecSpecific1) - .setCodecSpecific2(codecSpecific2) - .setCodecSpecific3(codecSpecific3) - .setCodecSpecific4(codecSpecific4) - .build(); - - } -} diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothCodecStatusTest.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothCodecStatusTest.java deleted file mode 100644 index 1cb2dcae865c..000000000000 --- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothCodecStatusTest.java +++ /dev/null @@ -1,493 +0,0 @@ -/* - * Copyright 2018 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.bluetooth; - -import android.test.suitebuilder.annotation.SmallTest; - -import junit.framework.TestCase; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -/** - * Unit test cases for {@link BluetoothCodecStatus}. - * <p> - * To run this test, use: - * runtest --path core/tests/bluetoothtests/src/android/bluetooth/BluetoothCodecStatusTest.java - */ -public class BluetoothCodecStatusTest extends TestCase { - - // Codec configs: A and B are same; C is different - private static final BluetoothCodecConfig config_A = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig config_B = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig config_C = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - - // Local capabilities: A and B are same; C is different - private static final BluetoothCodecConfig local_capability1_A = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig local_capability1_B = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig local_capability1_C = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - - - private static final BluetoothCodecConfig local_capability2_A = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig local_capability2_B = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig local_capability2_C = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig local_capability3_A = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig local_capability3_B = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig local_capability3_C = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig local_capability4_A = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000, - BluetoothCodecConfig.BITS_PER_SAMPLE_24, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig local_capability4_B = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000, - BluetoothCodecConfig.BITS_PER_SAMPLE_24, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig local_capability4_C = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000, - BluetoothCodecConfig.BITS_PER_SAMPLE_24, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig local_capability5_A = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000 | - BluetoothCodecConfig.SAMPLE_RATE_88200 | - BluetoothCodecConfig.SAMPLE_RATE_96000, - BluetoothCodecConfig.BITS_PER_SAMPLE_16 | - BluetoothCodecConfig.BITS_PER_SAMPLE_24 | - BluetoothCodecConfig.BITS_PER_SAMPLE_32, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig local_capability5_B = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000 | - BluetoothCodecConfig.SAMPLE_RATE_88200 | - BluetoothCodecConfig.SAMPLE_RATE_96000, - BluetoothCodecConfig.BITS_PER_SAMPLE_16 | - BluetoothCodecConfig.BITS_PER_SAMPLE_24 | - BluetoothCodecConfig.BITS_PER_SAMPLE_32, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig local_capability5_C = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000 | - BluetoothCodecConfig.SAMPLE_RATE_88200 | - BluetoothCodecConfig.SAMPLE_RATE_96000, - BluetoothCodecConfig.BITS_PER_SAMPLE_16 | - BluetoothCodecConfig.BITS_PER_SAMPLE_24 | - BluetoothCodecConfig.BITS_PER_SAMPLE_32, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - - - // Selectable capabilities: A and B are same; C is different - private static final BluetoothCodecConfig selectable_capability1_A = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig selectable_capability1_B = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig selectable_capability1_C = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig selectable_capability2_A = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig selectable_capability2_B = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig selectable_capability2_C = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig selectable_capability3_A = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig selectable_capability3_B = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig selectable_capability3_C = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_16, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig selectable_capability4_A = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_24, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig selectable_capability4_B = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_24, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig selectable_capability4_C = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100, - BluetoothCodecConfig.BITS_PER_SAMPLE_24, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig selectable_capability5_A = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000 | - BluetoothCodecConfig.SAMPLE_RATE_88200 | - BluetoothCodecConfig.SAMPLE_RATE_96000, - BluetoothCodecConfig.BITS_PER_SAMPLE_16 | - BluetoothCodecConfig.BITS_PER_SAMPLE_24 | - BluetoothCodecConfig.BITS_PER_SAMPLE_32, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig selectable_capability5_B = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000 | - BluetoothCodecConfig.SAMPLE_RATE_88200 | - BluetoothCodecConfig.SAMPLE_RATE_96000, - BluetoothCodecConfig.BITS_PER_SAMPLE_16 | - BluetoothCodecConfig.BITS_PER_SAMPLE_24 | - BluetoothCodecConfig.BITS_PER_SAMPLE_32, - BluetoothCodecConfig.CHANNEL_MODE_STEREO | - BluetoothCodecConfig.CHANNEL_MODE_MONO, - 1000, 2000, 3000, 4000); - - private static final BluetoothCodecConfig selectable_capability5_C = - buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC, - BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, - BluetoothCodecConfig.SAMPLE_RATE_44100 | - BluetoothCodecConfig.SAMPLE_RATE_48000 | - BluetoothCodecConfig.SAMPLE_RATE_88200 | - BluetoothCodecConfig.SAMPLE_RATE_96000, - BluetoothCodecConfig.BITS_PER_SAMPLE_16 | - BluetoothCodecConfig.BITS_PER_SAMPLE_24 | - BluetoothCodecConfig.BITS_PER_SAMPLE_32, - BluetoothCodecConfig.CHANNEL_MODE_STEREO, - 1000, 2000, 3000, 4000); - - private static final List<BluetoothCodecConfig> LOCAL_CAPABILITY_A = - new ArrayList() {{ - add(local_capability1_A); - add(local_capability2_A); - add(local_capability3_A); - add(local_capability4_A); - add(local_capability5_A); - }}; - - private static final List<BluetoothCodecConfig> LOCAL_CAPABILITY_B = - new ArrayList() {{ - add(local_capability1_B); - add(local_capability2_B); - add(local_capability3_B); - add(local_capability4_B); - add(local_capability5_B); - }}; - - private static final List<BluetoothCodecConfig> LOCAL_CAPABILITY_B_REORDERED = - new ArrayList() {{ - add(local_capability5_B); - add(local_capability4_B); - add(local_capability2_B); - add(local_capability3_B); - add(local_capability1_B); - }}; - - private static final List<BluetoothCodecConfig> LOCAL_CAPABILITY_C = - new ArrayList() {{ - add(local_capability1_C); - add(local_capability2_C); - add(local_capability3_C); - add(local_capability4_C); - add(local_capability5_C); - }}; - - private static final List<BluetoothCodecConfig> SELECTABLE_CAPABILITY_A = - new ArrayList() {{ - add(selectable_capability1_A); - add(selectable_capability2_A); - add(selectable_capability3_A); - add(selectable_capability4_A); - add(selectable_capability5_A); - }}; - - private static final List<BluetoothCodecConfig> SELECTABLE_CAPABILITY_B = - new ArrayList() {{ - add(selectable_capability1_B); - add(selectable_capability2_B); - add(selectable_capability3_B); - add(selectable_capability4_B); - add(selectable_capability5_B); - }}; - - private static final List<BluetoothCodecConfig> SELECTABLE_CAPABILITY_B_REORDERED = - new ArrayList() {{ - add(selectable_capability5_B); - add(selectable_capability4_B); - add(selectable_capability2_B); - add(selectable_capability3_B); - add(selectable_capability1_B); - }}; - - private static final List<BluetoothCodecConfig> SELECTABLE_CAPABILITY_C = - new ArrayList() {{ - add(selectable_capability1_C); - add(selectable_capability2_C); - add(selectable_capability3_C); - add(selectable_capability4_C); - add(selectable_capability5_C); - }}; - - private static final BluetoothCodecStatus bcs_A = - new BluetoothCodecStatus(config_A, LOCAL_CAPABILITY_A, SELECTABLE_CAPABILITY_A); - private static final BluetoothCodecStatus bcs_B = - new BluetoothCodecStatus(config_B, LOCAL_CAPABILITY_B, SELECTABLE_CAPABILITY_B); - private static final BluetoothCodecStatus bcs_B_reordered = - new BluetoothCodecStatus(config_B, LOCAL_CAPABILITY_B_REORDERED, - SELECTABLE_CAPABILITY_B_REORDERED); - private static final BluetoothCodecStatus bcs_C = - new BluetoothCodecStatus(config_C, LOCAL_CAPABILITY_C, SELECTABLE_CAPABILITY_C); - - @SmallTest - public void testBluetoothCodecStatus_get_methods() { - - assertTrue(Objects.equals(bcs_A.getCodecConfig(), config_A)); - assertTrue(Objects.equals(bcs_A.getCodecConfig(), config_B)); - assertFalse(Objects.equals(bcs_A.getCodecConfig(), config_C)); - - assertTrue(bcs_A.getCodecsLocalCapabilities().equals(LOCAL_CAPABILITY_A)); - assertTrue(bcs_A.getCodecsLocalCapabilities().equals(LOCAL_CAPABILITY_B)); - assertFalse(bcs_A.getCodecsLocalCapabilities().equals(LOCAL_CAPABILITY_C)); - - assertTrue(bcs_A.getCodecsSelectableCapabilities() - .equals(SELECTABLE_CAPABILITY_A)); - assertTrue(bcs_A.getCodecsSelectableCapabilities() - .equals(SELECTABLE_CAPABILITY_B)); - assertFalse(bcs_A.getCodecsSelectableCapabilities() - .equals(SELECTABLE_CAPABILITY_C)); - } - - @SmallTest - public void testBluetoothCodecStatus_equals() { - assertTrue(bcs_A.equals(bcs_B)); - assertTrue(bcs_B.equals(bcs_A)); - assertTrue(bcs_A.equals(bcs_B_reordered)); - assertTrue(bcs_B_reordered.equals(bcs_A)); - assertFalse(bcs_A.equals(bcs_C)); - assertFalse(bcs_C.equals(bcs_A)); - } - - private static BluetoothCodecConfig buildBluetoothCodecConfig(int sourceCodecType, - int codecPriority, int sampleRate, int bitsPerSample, int channelMode, - long codecSpecific1, long codecSpecific2, long codecSpecific3, long codecSpecific4) { - return new BluetoothCodecConfig.Builder() - .setCodecType(sourceCodecType) - .setCodecPriority(codecPriority) - .setSampleRate(sampleRate) - .setBitsPerSample(bitsPerSample) - .setChannelMode(channelMode) - .setCodecSpecific1(codecSpecific1) - .setCodecSpecific2(codecSpecific2) - .setCodecSpecific3(codecSpecific3) - .setCodecSpecific4(codecSpecific4) - .build(); - - } -} diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothInstrumentation.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothInstrumentation.java deleted file mode 100644 index 37b2a50ed670..000000000000 --- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothInstrumentation.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (C) 2014 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.bluetooth; - -import android.app.Activity; -import android.app.Instrumentation; -import android.content.Context; -import android.os.Bundle; - -import junit.framework.Assert; - -import java.util.Set; - -public class BluetoothInstrumentation extends Instrumentation { - - private BluetoothTestUtils mUtils = null; - private BluetoothAdapter mAdapter = null; - private Bundle mArgs = null; - private Bundle mSuccessResult = null; - - private BluetoothTestUtils getBluetoothTestUtils() { - if (mUtils == null) { - mUtils = new BluetoothTestUtils(getContext(), - BluetoothInstrumentation.class.getSimpleName()); - } - return mUtils; - } - - private BluetoothAdapter getBluetoothAdapter() { - if (mAdapter == null) { - mAdapter = ((BluetoothManager)getContext().getSystemService( - Context.BLUETOOTH_SERVICE)).getAdapter(); - } - return mAdapter; - } - - @Override - public void onCreate(Bundle arguments) { - super.onCreate(arguments); - mArgs = arguments; - // create the default result response, but only use it in success code path - mSuccessResult = new Bundle(); - mSuccessResult.putString("result", "SUCCESS"); - start(); - } - - @Override - public void onStart() { - String command = mArgs.getString("command"); - if ("enable".equals(command)) { - enable(); - } else if ("disable".equals(command)) { - disable(); - } else if ("unpairAll".equals(command)) { - unpairAll(); - } else if ("getName".equals(command)) { - getName(); - } else if ("getAddress".equals(command)) { - getAddress(); - } else if ("getBondedDevices".equals(command)) { - getBondedDevices(); - } else { - finish(null); - } - } - - public void enable() { - getBluetoothTestUtils().enable(getBluetoothAdapter()); - finish(mSuccessResult); - } - - public void disable() { - getBluetoothTestUtils().disable(getBluetoothAdapter()); - finish(mSuccessResult); - } - - public void unpairAll() { - getBluetoothTestUtils().unpairAll(getBluetoothAdapter()); - finish(mSuccessResult); - } - - public void getName() { - String name = getBluetoothAdapter().getName(); - mSuccessResult.putString("name", name); - finish(mSuccessResult); - } - - public void getAddress() { - String name = getBluetoothAdapter().getAddress(); - mSuccessResult.putString("address", name); - finish(mSuccessResult); - } - - public void getBondedDevices() { - Set<BluetoothDevice> devices = getBluetoothAdapter().getBondedDevices(); - int i = 0; - for (BluetoothDevice device : devices) { - mSuccessResult.putString(String.format("device-%02d", i), device.getAddress()); - i++; - } - finish(mSuccessResult); - } - - public void finish(Bundle result) { - if (result == null) { - result = new Bundle(); - } - finish(Activity.RESULT_OK, result); - } -} diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeAudioCodecConfigTest.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeAudioCodecConfigTest.java deleted file mode 100644 index c3d707cd7596..000000000000 --- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeAudioCodecConfigTest.java +++ /dev/null @@ -1,58 +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 android.bluetooth; - -import android.test.suitebuilder.annotation.SmallTest; - -import junit.framework.TestCase; - -/** - * Unit test cases for {@link BluetoothLeAudioCodecConfig}. - */ -public class BluetoothLeAudioCodecConfigTest extends TestCase { - private int[] mCodecTypeArray = new int[] { - BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_LC3, - BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_INVALID, - }; - - @SmallTest - public void testBluetoothLeAudioCodecConfig_valid_get_methods() { - - for (int codecIdx = 0; codecIdx < mCodecTypeArray.length; codecIdx++) { - int codecType = mCodecTypeArray[codecIdx]; - - BluetoothLeAudioCodecConfig leAudioCodecConfig = - buildBluetoothLeAudioCodecConfig(codecType); - - if (codecType == BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_LC3) { - assertEquals("LC3", leAudioCodecConfig.getCodecName()); - } - if (codecType == BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_INVALID) { - assertEquals("INVALID CODEC", leAudioCodecConfig.getCodecName()); - } - - assertEquals(codecType, leAudioCodecConfig.getCodecType()); - } - } - - private BluetoothLeAudioCodecConfig buildBluetoothLeAudioCodecConfig(int sourceCodecType) { - return new BluetoothLeAudioCodecConfig.Builder() - .setCodecType(sourceCodecType) - .build(); - - } -} diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothRebootStressTest.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothRebootStressTest.java deleted file mode 100644 index 33e9dd7fabc6..000000000000 --- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothRebootStressTest.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2010 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.bluetooth; - -import android.content.Context; -import android.test.InstrumentationTestCase; - -/** - * Instrumentation test case for stress test involving rebooting the device. - * <p> - * This test case tests that bluetooth is enabled after a device reboot. Because - * the device will reboot, the instrumentation must be driven by a script on the - * host side. - */ -public class BluetoothRebootStressTest extends InstrumentationTestCase { - private static final String TAG = "BluetoothRebootStressTest"; - private static final String OUTPUT_FILE = "BluetoothRebootStressTestOutput.txt"; - - private BluetoothTestUtils mTestUtils; - - @Override - protected void setUp() throws Exception { - super.setUp(); - - Context context = getInstrumentation().getTargetContext(); - mTestUtils = new BluetoothTestUtils(context, TAG, OUTPUT_FILE); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - - mTestUtils.close(); - } - - /** - * Test method used to start the test by turning bluetooth on. - */ - public void testStart() { - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - mTestUtils.enable(adapter); - } - - /** - * Test method used in the middle iterations of the test to check if - * bluetooth is on. Does not toggle bluetooth after the check. Assumes that - * bluetooth has been turned on by {@code #testStart()} - */ - public void testMiddleNoToggle() { - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - - assertTrue(adapter.isEnabled()); - } - - /** - * Test method used in the middle iterations of the test to check if - * bluetooth is on. Toggles bluetooth after the check. Assumes that - * bluetooth has been turned on by {@code #testStart()} - */ - public void testMiddleToggle() { - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - - assertTrue(adapter.isEnabled()); - - mTestUtils.disable(adapter); - mTestUtils.enable(adapter); - } - - /** - * Test method used in the stop the test by turning bluetooth off. Assumes - * that bluetooth has been turned on by {@code #testStart()} - */ - public void testStop() { - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - - assertTrue(adapter.isEnabled()); - - mTestUtils.disable(adapter); - } -} diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothStressTest.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothStressTest.java deleted file mode 100644 index 89dbe3f75b56..000000000000 --- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothStressTest.java +++ /dev/null @@ -1,393 +0,0 @@ -/* - * Copyright (C) 2010 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.bluetooth; - -import android.content.Context; -import android.test.InstrumentationTestCase; - -/** - * Stress test suite for Bluetooth related functions. - * - * Includes tests for enabling/disabling bluetooth, enabling/disabling discoverable mode, - * starting/stopping scans, connecting/disconnecting to HFP, A2DP, HID, PAN profiles, and verifying - * that remote connections/disconnections occur for the PAN profile. - * <p> - * This test suite uses {@link android.bluetooth.BluetoothTestRunner} to for parameters such as the - * number of iterations and the addresses of remote Bluetooth devices. - */ -public class BluetoothStressTest extends InstrumentationTestCase { - private static final String TAG = "BluetoothStressTest"; - private static final String OUTPUT_FILE = "BluetoothStressTestOutput.txt"; - /** The amount of time to sleep between issuing start/stop SCO in ms. */ - private static final long SCO_SLEEP_TIME = 2 * 1000; - - private BluetoothAdapter mAdapter; - private BluetoothTestUtils mTestUtils; - - @Override - protected void setUp() throws Exception { - super.setUp(); - - Context context = getInstrumentation().getTargetContext(); - mAdapter = BluetoothAdapter.getDefaultAdapter(); - mTestUtils = new BluetoothTestUtils(context, TAG, OUTPUT_FILE); - - // Start all tests in a disabled state. - if (mAdapter.isEnabled()) { - mTestUtils.disable(mAdapter); - } - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - mTestUtils.close(); - } - - /** - * Stress test for enabling and disabling Bluetooth. - */ - public void testEnable() { - int iterations = BluetoothTestRunner.sEnableIterations; - if (iterations == 0) { - return; - } - - for (int i = 0; i < iterations; i++) { - mTestUtils.writeOutput("enable iteration " + (i + 1) + " of " + iterations); - mTestUtils.enable(mAdapter); - mTestUtils.disable(mAdapter); - } - } - - /** - * Stress test for putting the device in and taking the device out of discoverable mode. - */ - public void testDiscoverable() { - int iterations = BluetoothTestRunner.sDiscoverableIterations; - if (iterations == 0) { - return; - } - - mTestUtils.enable(mAdapter); - mTestUtils.undiscoverable(mAdapter); - - for (int i = 0; i < iterations; i++) { - mTestUtils.writeOutput("discoverable iteration " + (i + 1) + " of " + iterations); - mTestUtils.discoverable(mAdapter); - mTestUtils.undiscoverable(mAdapter); - } - } - - /** - * Stress test for starting and stopping Bluetooth scans. - */ - public void testScan() { - int iterations = BluetoothTestRunner.sScanIterations; - if (iterations == 0) { - return; - } - - mTestUtils.enable(mAdapter); - mTestUtils.stopScan(mAdapter); - - for (int i = 0; i < iterations; i++) { - mTestUtils.writeOutput("scan iteration " + (i + 1) + " of " + iterations); - mTestUtils.startScan(mAdapter); - mTestUtils.stopScan(mAdapter); - } - } - - /** - * Stress test for enabling and disabling the PAN NAP profile. - */ - public void testEnablePan() { - int iterations = BluetoothTestRunner.sEnablePanIterations; - if (iterations == 0) { - return; - } - - mTestUtils.enable(mAdapter); - mTestUtils.disablePan(mAdapter); - - for (int i = 0; i < iterations; i++) { - mTestUtils.writeOutput("testEnablePan iteration " + (i + 1) + " of " - + iterations); - mTestUtils.enablePan(mAdapter); - mTestUtils.disablePan(mAdapter); - } - } - - /** - * Stress test for pairing and unpairing with a remote device. - * <p> - * In this test, the local device initiates pairing with a remote device, and then unpairs with - * the device after the pairing has successfully completed. - */ - public void testPair() { - int iterations = BluetoothTestRunner.sPairIterations; - if (iterations == 0) { - return; - } - - BluetoothDevice device = mAdapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress); - mTestUtils.enable(mAdapter); - mTestUtils.unpair(mAdapter, device); - - for (int i = 0; i < iterations; i++) { - mTestUtils.writeOutput("pair iteration " + (i + 1) + " of " + iterations); - mTestUtils.pair(mAdapter, device, BluetoothTestRunner.sDevicePairPasskey, - BluetoothTestRunner.sDevicePairPin); - mTestUtils.unpair(mAdapter, device); - } - } - - /** - * Stress test for accepting a pairing request and unpairing with a remote device. - * <p> - * In this test, the local device waits for a pairing request from a remote device. It accepts - * the request and then unpairs after the paring has successfully completed. - */ - public void testAcceptPair() { - int iterations = BluetoothTestRunner.sPairIterations; - if (iterations == 0) { - return; - } - BluetoothDevice device = mAdapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress); - mTestUtils.enable(mAdapter); - mTestUtils.unpair(mAdapter, device); - - for (int i = 0; i < iterations; i++) { - mTestUtils.writeOutput("acceptPair iteration " + (i + 1) + " of " + iterations); - mTestUtils.acceptPair(mAdapter, device, BluetoothTestRunner.sDevicePairPasskey, - BluetoothTestRunner.sDevicePairPin); - mTestUtils.unpair(mAdapter, device); - } - } - - /** - * Stress test for connecting and disconnecting with an A2DP source. - * <p> - * In this test, the local device plays the role of an A2DP sink, and initiates connections and - * disconnections with an A2DP source. - */ - public void testConnectA2dp() { - int iterations = BluetoothTestRunner.sConnectA2dpIterations; - if (iterations == 0) { - return; - } - - BluetoothDevice device = mAdapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress); - mTestUtils.enable(mAdapter); - mTestUtils.unpair(mAdapter, device); - mTestUtils.pair(mAdapter, device, BluetoothTestRunner.sDevicePairPasskey, - BluetoothTestRunner.sDevicePairPin); - mTestUtils.disconnectProfile(mAdapter, device, BluetoothProfile.A2DP, null); - - for (int i = 0; i < iterations; i++) { - mTestUtils.writeOutput("connectA2dp iteration " + (i + 1) + " of " + iterations); - mTestUtils.connectProfile(mAdapter, device, BluetoothProfile.A2DP, - String.format("connectA2dp(device=%s)", device)); - mTestUtils.disconnectProfile(mAdapter, device, BluetoothProfile.A2DP, - String.format("disconnectA2dp(device=%s)", device)); - } - - mTestUtils.unpair(mAdapter, device); - } - - /** - * Stress test for connecting and disconnecting the HFP with a hands free device. - * <p> - * In this test, the local device plays the role of an HFP audio gateway, and initiates - * connections and disconnections with a hands free device. - */ - public void testConnectHeadset() { - int iterations = BluetoothTestRunner.sConnectHeadsetIterations; - if (iterations == 0) { - return; - } - - BluetoothDevice device = mAdapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress); - mTestUtils.enable(mAdapter); - mTestUtils.unpair(mAdapter, device); - mTestUtils.pair(mAdapter, device, BluetoothTestRunner.sDevicePairPasskey, - BluetoothTestRunner.sDevicePairPin); - mTestUtils.disconnectProfile(mAdapter, device, BluetoothProfile.HEADSET, null); - - for (int i = 0; i < iterations; i++) { - mTestUtils.writeOutput("connectHeadset iteration " + (i + 1) + " of " + iterations); - mTestUtils.connectProfile(mAdapter, device, BluetoothProfile.HEADSET, - String.format("connectHeadset(device=%s)", device)); - mTestUtils.disconnectProfile(mAdapter, device, BluetoothProfile.HEADSET, - String.format("disconnectHeadset(device=%s)", device)); - } - - mTestUtils.unpair(mAdapter, device); - } - - /** - * Stress test for connecting and disconnecting with a HID device. - * <p> - * In this test, the local device plays the role of a HID host, and initiates connections and - * disconnections with a HID device. - */ - public void testConnectInput() { - int iterations = BluetoothTestRunner.sConnectInputIterations; - if (iterations == 0) { - return; - } - - BluetoothDevice device = mAdapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress); - mTestUtils.enable(mAdapter); - mTestUtils.unpair(mAdapter, device); - mTestUtils.pair(mAdapter, device, BluetoothTestRunner.sDevicePairPasskey, - BluetoothTestRunner.sDevicePairPin); - mTestUtils.disconnectProfile(mAdapter, device, BluetoothProfile.HID_HOST, null); - - for (int i = 0; i < iterations; i++) { - mTestUtils.writeOutput("connectInput iteration " + (i + 1) + " of " + iterations); - mTestUtils.connectProfile(mAdapter, device, BluetoothProfile.HID_HOST, - String.format("connectInput(device=%s)", device)); - mTestUtils.disconnectProfile(mAdapter, device, BluetoothProfile.HID_HOST, - String.format("disconnectInput(device=%s)", device)); - } - - mTestUtils.unpair(mAdapter, device); - } - - /** - * Stress test for connecting and disconnecting with a PAN NAP. - * <p> - * In this test, the local device plays the role of a PANU, and initiates connections and - * disconnections with a NAP. - */ - public void testConnectPan() { - int iterations = BluetoothTestRunner.sConnectPanIterations; - if (iterations == 0) { - return; - } - - BluetoothDevice device = mAdapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress); - mTestUtils.enable(mAdapter); - mTestUtils.unpair(mAdapter, device); - mTestUtils.pair(mAdapter, device, BluetoothTestRunner.sDevicePairPasskey, - BluetoothTestRunner.sDevicePairPin); - - for (int i = 0; i < iterations; i++) { - mTestUtils.writeOutput("connectPan iteration " + (i + 1) + " of " + iterations); - mTestUtils.connectPan(mAdapter, device); - mTestUtils.disconnectPan(mAdapter, device); - } - - mTestUtils.unpair(mAdapter, device); - } - - /** - * Stress test for verifying a PANU connecting and disconnecting with the device. - * <p> - * In this test, the local device plays the role of a NAP which a remote PANU connects and - * disconnects from. - */ - public void testIncomingPanConnection() { - int iterations = BluetoothTestRunner.sConnectPanIterations; - if (iterations == 0) { - return; - } - - BluetoothDevice device = mAdapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress); - mTestUtils.enable(mAdapter); - mTestUtils.disablePan(mAdapter); - mTestUtils.enablePan(mAdapter); - mTestUtils.unpair(mAdapter, device); - mTestUtils.acceptPair(mAdapter, device, BluetoothTestRunner.sDevicePairPasskey, - BluetoothTestRunner.sDevicePairPin); - - for (int i = 0; i < iterations; i++) { - mTestUtils.writeOutput("incomingPanConnection iteration " + (i + 1) + " of " - + iterations); - mTestUtils.incomingPanConnection(mAdapter, device); - mTestUtils.incomingPanDisconnection(mAdapter, device); - } - - mTestUtils.unpair(mAdapter, device); - mTestUtils.disablePan(mAdapter); - } - - /** - * Stress test for verifying that AudioManager can open and close SCO connections. - * <p> - * In this test, a HSP connection is opened with an external headset and the SCO connection is - * repeatibly opened and closed. - */ - public void testStartStopSco() { - int iterations = BluetoothTestRunner.sStartStopScoIterations; - if (iterations == 0) { - return; - } - - BluetoothDevice device = mAdapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress); - mTestUtils.enable(mAdapter); - mTestUtils.unpair(mAdapter, device); - mTestUtils.pair(mAdapter, device, BluetoothTestRunner.sDevicePairPasskey, - BluetoothTestRunner.sDevicePairPin); - mTestUtils.disconnectProfile(mAdapter, device, BluetoothProfile.HEADSET, null); - mTestUtils.connectProfile(mAdapter, device, BluetoothProfile.HEADSET, null); - mTestUtils.stopSco(mAdapter, device); - - for (int i = 0; i < iterations; i++) { - mTestUtils.writeOutput("startStopSco iteration " + (i + 1) + " of " + iterations); - mTestUtils.startSco(mAdapter, device); - sleep(SCO_SLEEP_TIME); - mTestUtils.stopSco(mAdapter, device); - sleep(SCO_SLEEP_TIME); - } - - mTestUtils.disconnectProfile(mAdapter, device, BluetoothProfile.HEADSET, null); - mTestUtils.unpair(mAdapter, device); - } - - /* Make sure there is at least 1 unread message in the last week on remote device */ - public void testMceSetMessageStatus() { - int iterations = BluetoothTestRunner.sMceSetMessageStatusIterations; - if (iterations == 0) { - return; - } - - BluetoothDevice device = mAdapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress); - mTestUtils.enable(mAdapter); - mTestUtils.connectProfile(mAdapter, device, BluetoothProfile.MAP_CLIENT, null); - mTestUtils.mceGetUnreadMessage(mAdapter, device); - - for (int i = 0; i < iterations; i++) { - mTestUtils.mceSetMessageStatus(mAdapter, device, BluetoothMapClient.READ); - mTestUtils.mceSetMessageStatus(mAdapter, device, BluetoothMapClient.UNREAD); - } - - /** - * It is hard to find device to support set undeleted status, so just - * set deleted in 1 iteration - **/ - mTestUtils.mceSetMessageStatus(mAdapter, device, BluetoothMapClient.DELETED); - } - - private void sleep(long time) { - try { - Thread.sleep(time); - } catch (InterruptedException e) { - } - } -} diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestRunner.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestRunner.java deleted file mode 100644 index d19c2c3e7e24..000000000000 --- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestRunner.java +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright (C) 2010 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.bluetooth; - -import junit.framework.TestSuite; - -import android.os.Bundle; -import android.test.InstrumentationTestRunner; -import android.test.InstrumentationTestSuite; -import android.util.Log; - -/** - * Instrumentation test runner for Bluetooth tests. - * <p> - * To run: - * <pre> - * {@code - * adb shell am instrument \ - * [-e enable_iterations <iterations>] \ - * [-e discoverable_iterations <iterations>] \ - * [-e scan_iterations <iterations>] \ - * [-e enable_pan_iterations <iterations>] \ - * [-e pair_iterations <iterations>] \ - * [-e connect_a2dp_iterations <iterations>] \ - * [-e connect_headset_iterations <iterations>] \ - * [-e connect_input_iterations <iterations>] \ - * [-e connect_pan_iterations <iterations>] \ - * [-e start_stop_sco_iterations <iterations>] \ - * [-e mce_set_message_status_iterations <iterations>] \ - * [-e pair_address <address>] \ - * [-e headset_address <address>] \ - * [-e a2dp_address <address>] \ - * [-e input_address <address>] \ - * [-e pan_address <address>] \ - * [-e pair_pin <pin>] \ - * [-e pair_passkey <passkey>] \ - * -w com.android.bluetooth.tests/android.bluetooth.BluetoothTestRunner - * } - * </pre> - */ -public class BluetoothTestRunner extends InstrumentationTestRunner { - private static final String TAG = "BluetoothTestRunner"; - - public static int sEnableIterations = 100; - public static int sDiscoverableIterations = 1000; - public static int sScanIterations = 1000; - public static int sEnablePanIterations = 1000; - public static int sPairIterations = 100; - public static int sConnectHeadsetIterations = 100; - public static int sConnectA2dpIterations = 100; - public static int sConnectInputIterations = 100; - public static int sConnectPanIterations = 100; - public static int sStartStopScoIterations = 100; - public static int sMceSetMessageStatusIterations = 100; - - public static String sDeviceAddress = ""; - public static byte[] sDevicePairPin = {'1', '2', '3', '4'}; - public static int sDevicePairPasskey = 123456; - - @Override - public TestSuite getAllTests() { - TestSuite suite = new InstrumentationTestSuite(this); - suite.addTestSuite(BluetoothStressTest.class); - return suite; - } - - @Override - public ClassLoader getLoader() { - return BluetoothTestRunner.class.getClassLoader(); - } - - @Override - public void onCreate(Bundle arguments) { - String val = arguments.getString("enable_iterations"); - if (val != null) { - try { - sEnableIterations = Integer.parseInt(val); - } catch (NumberFormatException e) { - // Invalid argument, fall back to default value - } - } - - val = arguments.getString("discoverable_iterations"); - if (val != null) { - try { - sDiscoverableIterations = Integer.parseInt(val); - } catch (NumberFormatException e) { - // Invalid argument, fall back to default value - } - } - - val = arguments.getString("scan_iterations"); - if (val != null) { - try { - sScanIterations = Integer.parseInt(val); - } catch (NumberFormatException e) { - // Invalid argument, fall back to default value - } - } - - val = arguments.getString("enable_pan_iterations"); - if (val != null) { - try { - sEnablePanIterations = Integer.parseInt(val); - } catch (NumberFormatException e) { - // Invalid argument, fall back to default value - } - } - - val = arguments.getString("pair_iterations"); - if (val != null) { - try { - sPairIterations = Integer.parseInt(val); - } catch (NumberFormatException e) { - // Invalid argument, fall back to default value - } - } - - val = arguments.getString("connect_a2dp_iterations"); - if (val != null) { - try { - sConnectA2dpIterations = Integer.parseInt(val); - } catch (NumberFormatException e) { - // Invalid argument, fall back to default value - } - } - - val = arguments.getString("connect_headset_iterations"); - if (val != null) { - try { - sConnectHeadsetIterations = Integer.parseInt(val); - } catch (NumberFormatException e) { - // Invalid argument, fall back to default value - } - } - - val = arguments.getString("connect_input_iterations"); - if (val != null) { - try { - sConnectInputIterations = Integer.parseInt(val); - } catch (NumberFormatException e) { - // Invalid argument, fall back to default value - } - } - - val = arguments.getString("connect_pan_iterations"); - if (val != null) { - try { - sConnectPanIterations = Integer.parseInt(val); - } catch (NumberFormatException e) { - // Invalid argument, fall back to default value - } - } - - val = arguments.getString("start_stop_sco_iterations"); - if (val != null) { - try { - sStartStopScoIterations = Integer.parseInt(val); - } catch (NumberFormatException e) { - // Invalid argument, fall back to default value - } - } - - val = arguments.getString("mce_set_message_status_iterations"); - if (val != null) { - try { - sMceSetMessageStatusIterations = Integer.parseInt(val); - } catch (NumberFormatException e) { - // Invalid argument, fall back to default value - } - } - - val = arguments.getString("device_address"); - if (val != null) { - sDeviceAddress = val; - } - - val = arguments.getString("device_pair_pin"); - if (val != null) { - byte[] pin = BluetoothDevice.convertPinToBytes(val); - if (pin != null) { - sDevicePairPin = pin; - } - } - - val = arguments.getString("device_pair_passkey"); - if (val != null) { - try { - sDevicePairPasskey = Integer.parseInt(val); - } catch (NumberFormatException e) { - // Invalid argument, fall back to default value - } - } - - Log.i(TAG, String.format("enable_iterations=%d", sEnableIterations)); - Log.i(TAG, String.format("discoverable_iterations=%d", sDiscoverableIterations)); - Log.i(TAG, String.format("scan_iterations=%d", sScanIterations)); - Log.i(TAG, String.format("pair_iterations=%d", sPairIterations)); - Log.i(TAG, String.format("connect_a2dp_iterations=%d", sConnectA2dpIterations)); - Log.i(TAG, String.format("connect_headset_iterations=%d", sConnectHeadsetIterations)); - Log.i(TAG, String.format("connect_input_iterations=%d", sConnectInputIterations)); - Log.i(TAG, String.format("connect_pan_iterations=%d", sConnectPanIterations)); - Log.i(TAG, String.format("start_stop_sco_iterations=%d", sStartStopScoIterations)); - Log.i(TAG, String.format("device_address=%s", sDeviceAddress)); - Log.i(TAG, String.format("device_pair_pin=%s", new String(sDevicePairPin))); - Log.i(TAG, String.format("device_pair_passkey=%d", sDevicePairPasskey)); - - // Call onCreate last since we want to set the static variables first. - super.onCreate(arguments); - } -} diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java deleted file mode 100644 index 8eb6ebcda826..000000000000 --- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java +++ /dev/null @@ -1,1651 +0,0 @@ -/* - * Copyright (C) 2010 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.bluetooth; - -import android.bluetooth.BluetoothPan; -import android.bluetooth.BluetoothProfile; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.media.AudioManager; -import android.os.Environment; -import android.util.Log; - -import junit.framework.Assert; - -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; -import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; - -public class BluetoothTestUtils extends Assert { - - /** Timeout for enable/disable in ms. */ - private static final int ENABLE_DISABLE_TIMEOUT = 20000; - /** Timeout for discoverable/undiscoverable in ms. */ - private static final int DISCOVERABLE_UNDISCOVERABLE_TIMEOUT = 5000; - /** Timeout for starting/stopping a scan in ms. */ - private static final int START_STOP_SCAN_TIMEOUT = 5000; - /** Timeout for pair/unpair in ms. */ - private static final int PAIR_UNPAIR_TIMEOUT = 20000; - /** Timeout for connecting/disconnecting a profile in ms. */ - private static final int CONNECT_DISCONNECT_PROFILE_TIMEOUT = 20000; - /** Timeout to start or stop a SCO channel in ms. */ - private static final int START_STOP_SCO_TIMEOUT = 10000; - /** Timeout to connect a profile proxy in ms. */ - private static final int CONNECT_PROXY_TIMEOUT = 5000; - /** Time between polls in ms. */ - private static final int POLL_TIME = 100; - /** Timeout to get map message in ms. */ - private static final int GET_UNREAD_MESSAGE_TIMEOUT = 10000; - /** Timeout to set map message status in ms. */ - private static final int SET_MESSAGE_STATUS_TIMEOUT = 2000; - - private abstract class FlagReceiver extends BroadcastReceiver { - private int mExpectedFlags = 0; - private int mFiredFlags = 0; - private long mCompletedTime = -1; - - public FlagReceiver(int expectedFlags) { - mExpectedFlags = expectedFlags; - } - - public int getFiredFlags() { - synchronized (this) { - return mFiredFlags; - } - } - - public long getCompletedTime() { - synchronized (this) { - return mCompletedTime; - } - } - - protected void setFiredFlag(int flag) { - synchronized (this) { - mFiredFlags |= flag; - if ((mFiredFlags & mExpectedFlags) == mExpectedFlags) { - mCompletedTime = System.currentTimeMillis(); - } - } - } - } - - private class BluetoothReceiver extends FlagReceiver { - private static final int DISCOVERY_STARTED_FLAG = 1; - private static final int DISCOVERY_FINISHED_FLAG = 1 << 1; - private static final int SCAN_MODE_NONE_FLAG = 1 << 2; - private static final int SCAN_MODE_CONNECTABLE_FLAG = 1 << 3; - private static final int SCAN_MODE_CONNECTABLE_DISCOVERABLE_FLAG = 1 << 4; - private static final int STATE_OFF_FLAG = 1 << 5; - private static final int STATE_TURNING_ON_FLAG = 1 << 6; - private static final int STATE_ON_FLAG = 1 << 7; - private static final int STATE_TURNING_OFF_FLAG = 1 << 8; - private static final int STATE_GET_MESSAGE_FINISHED_FLAG = 1 << 9; - private static final int STATE_SET_MESSAGE_STATUS_FINISHED_FLAG = 1 << 10; - - public BluetoothReceiver(int expectedFlags) { - super(expectedFlags); - } - - @Override - public void onReceive(Context context, Intent intent) { - if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(intent.getAction())) { - setFiredFlag(DISCOVERY_STARTED_FLAG); - } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(intent.getAction())) { - setFiredFlag(DISCOVERY_FINISHED_FLAG); - } else if (BluetoothAdapter.ACTION_SCAN_MODE_CHANGED.equals(intent.getAction())) { - int mode = intent.getIntExtra(BluetoothAdapter.EXTRA_SCAN_MODE, -1); - assertNotSame(-1, mode); - switch (mode) { - case BluetoothAdapter.SCAN_MODE_NONE: - setFiredFlag(SCAN_MODE_NONE_FLAG); - break; - case BluetoothAdapter.SCAN_MODE_CONNECTABLE: - setFiredFlag(SCAN_MODE_CONNECTABLE_FLAG); - break; - case BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE: - setFiredFlag(SCAN_MODE_CONNECTABLE_DISCOVERABLE_FLAG); - break; - } - } else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())) { - int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1); - assertNotSame(-1, state); - switch (state) { - case BluetoothAdapter.STATE_OFF: - setFiredFlag(STATE_OFF_FLAG); - break; - case BluetoothAdapter.STATE_TURNING_ON: - setFiredFlag(STATE_TURNING_ON_FLAG); - break; - case BluetoothAdapter.STATE_ON: - setFiredFlag(STATE_ON_FLAG); - break; - case BluetoothAdapter.STATE_TURNING_OFF: - setFiredFlag(STATE_TURNING_OFF_FLAG); - break; - } - } - } - } - - private class PairReceiver extends FlagReceiver { - private static final int STATE_BONDED_FLAG = 1; - private static final int STATE_BONDING_FLAG = 1 << 1; - private static final int STATE_NONE_FLAG = 1 << 2; - - private BluetoothDevice mDevice; - private int mPasskey; - private byte[] mPin; - - public PairReceiver(BluetoothDevice device, int passkey, byte[] pin, int expectedFlags) { - super(expectedFlags); - - mDevice = device; - mPasskey = passkey; - mPin = pin; - } - - @Override - public void onReceive(Context context, Intent intent) { - if (!mDevice.equals(intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE))) { - return; - } - - if (BluetoothDevice.ACTION_PAIRING_REQUEST.equals(intent.getAction())) { - int varient = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, -1); - assertNotSame(-1, varient); - switch (varient) { - case BluetoothDevice.PAIRING_VARIANT_PIN: - case BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS: - mDevice.setPin(mPin); - break; - case BluetoothDevice.PAIRING_VARIANT_PASSKEY: - break; - case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION: - case BluetoothDevice.PAIRING_VARIANT_CONSENT: - mDevice.setPairingConfirmation(true); - break; - case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT: - break; - } - } else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())) { - int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1); - assertNotSame(-1, state); - switch (state) { - case BluetoothDevice.BOND_NONE: - setFiredFlag(STATE_NONE_FLAG); - break; - case BluetoothDevice.BOND_BONDING: - setFiredFlag(STATE_BONDING_FLAG); - break; - case BluetoothDevice.BOND_BONDED: - setFiredFlag(STATE_BONDED_FLAG); - break; - } - } - } - } - - private class ConnectProfileReceiver extends FlagReceiver { - private static final int STATE_DISCONNECTED_FLAG = 1; - private static final int STATE_CONNECTING_FLAG = 1 << 1; - private static final int STATE_CONNECTED_FLAG = 1 << 2; - private static final int STATE_DISCONNECTING_FLAG = 1 << 3; - - private BluetoothDevice mDevice; - private int mProfile; - private String mConnectionAction; - - public ConnectProfileReceiver(BluetoothDevice device, int profile, int expectedFlags) { - super(expectedFlags); - - mDevice = device; - mProfile = profile; - - switch (mProfile) { - case BluetoothProfile.A2DP: - mConnectionAction = BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED; - break; - case BluetoothProfile.HEADSET: - mConnectionAction = BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED; - break; - case BluetoothProfile.HID_HOST: - mConnectionAction = BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED; - break; - case BluetoothProfile.PAN: - mConnectionAction = BluetoothPan.ACTION_CONNECTION_STATE_CHANGED; - break; - case BluetoothProfile.MAP_CLIENT: - mConnectionAction = BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED; - break; - default: - mConnectionAction = null; - } - } - - @Override - public void onReceive(Context context, Intent intent) { - if (mConnectionAction != null && mConnectionAction.equals(intent.getAction())) { - if (!mDevice.equals(intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE))) { - return; - } - - int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); - assertNotSame(-1, state); - switch (state) { - case BluetoothProfile.STATE_DISCONNECTED: - setFiredFlag(STATE_DISCONNECTED_FLAG); - break; - case BluetoothProfile.STATE_CONNECTING: - setFiredFlag(STATE_CONNECTING_FLAG); - break; - case BluetoothProfile.STATE_CONNECTED: - setFiredFlag(STATE_CONNECTED_FLAG); - break; - case BluetoothProfile.STATE_DISCONNECTING: - setFiredFlag(STATE_DISCONNECTING_FLAG); - break; - } - } - } - } - - private class ConnectPanReceiver extends ConnectProfileReceiver { - private int mRole; - - public ConnectPanReceiver(BluetoothDevice device, int role, int expectedFlags) { - super(device, BluetoothProfile.PAN, expectedFlags); - - mRole = role; - } - - @Override - public void onReceive(Context context, Intent intent) { - if (mRole != intent.getIntExtra(BluetoothPan.EXTRA_LOCAL_ROLE, -1)) { - return; - } - - super.onReceive(context, intent); - } - } - - private class StartStopScoReceiver extends FlagReceiver { - private static final int STATE_CONNECTED_FLAG = 1; - private static final int STATE_DISCONNECTED_FLAG = 1 << 1; - - public StartStopScoReceiver(int expectedFlags) { - super(expectedFlags); - } - - @Override - public void onReceive(Context context, Intent intent) { - if (AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED.equals(intent.getAction())) { - int state = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, - AudioManager.SCO_AUDIO_STATE_ERROR); - assertNotSame(AudioManager.SCO_AUDIO_STATE_ERROR, state); - switch(state) { - case AudioManager.SCO_AUDIO_STATE_CONNECTED: - setFiredFlag(STATE_CONNECTED_FLAG); - break; - case AudioManager.SCO_AUDIO_STATE_DISCONNECTED: - setFiredFlag(STATE_DISCONNECTED_FLAG); - break; - } - } - } - } - - - private class MceSetMessageStatusReceiver extends FlagReceiver { - private static final int MESSAGE_RECEIVED_FLAG = 1; - private static final int STATUS_CHANGED_FLAG = 1 << 1; - - public MceSetMessageStatusReceiver(int expectedFlags) { - super(expectedFlags); - } - - @Override - public void onReceive(Context context, Intent intent) { - if (BluetoothMapClient.ACTION_MESSAGE_RECEIVED.equals(intent.getAction())) { - String handle = intent.getStringExtra(BluetoothMapClient.EXTRA_MESSAGE_HANDLE); - assertNotNull(handle); - setFiredFlag(MESSAGE_RECEIVED_FLAG); - mMsgHandle = handle; - } else if (BluetoothMapClient.ACTION_MESSAGE_DELETED_STATUS_CHANGED.equals(intent.getAction())) { - int result = intent.getIntExtra(BluetoothMapClient.EXTRA_RESULT_CODE, BluetoothMapClient.RESULT_FAILURE); - assertEquals(result, BluetoothMapClient.RESULT_SUCCESS); - setFiredFlag(STATUS_CHANGED_FLAG); - } else if (BluetoothMapClient.ACTION_MESSAGE_READ_STATUS_CHANGED.equals(intent.getAction())) { - int result = intent.getIntExtra(BluetoothMapClient.EXTRA_RESULT_CODE, BluetoothMapClient.RESULT_FAILURE); - assertEquals(result, BluetoothMapClient.RESULT_SUCCESS); - setFiredFlag(STATUS_CHANGED_FLAG); - } - } - } - - private BluetoothProfile.ServiceListener mServiceListener = - new BluetoothProfile.ServiceListener() { - @Override - public void onServiceConnected(int profile, BluetoothProfile proxy) { - synchronized (this) { - switch (profile) { - case BluetoothProfile.A2DP: - mA2dp = (BluetoothA2dp) proxy; - break; - case BluetoothProfile.HEADSET: - mHeadset = (BluetoothHeadset) proxy; - break; - case BluetoothProfile.HID_HOST: - mInput = (BluetoothHidHost) proxy; - break; - case BluetoothProfile.PAN: - mPan = (BluetoothPan) proxy; - break; - case BluetoothProfile.MAP_CLIENT: - mMce = (BluetoothMapClient) proxy; - break; - } - } - } - - @Override - public void onServiceDisconnected(int profile) { - synchronized (this) { - switch (profile) { - case BluetoothProfile.A2DP: - mA2dp = null; - break; - case BluetoothProfile.HEADSET: - mHeadset = null; - break; - case BluetoothProfile.HID_HOST: - mInput = null; - break; - case BluetoothProfile.PAN: - mPan = null; - break; - case BluetoothProfile.MAP_CLIENT: - mMce = null; - break; - } - } - } - }; - - private List<BroadcastReceiver> mReceivers = new ArrayList<BroadcastReceiver>(); - - private BufferedWriter mOutputWriter; - private String mTag; - private String mOutputFile; - - private Context mContext; - private BluetoothA2dp mA2dp = null; - private BluetoothHeadset mHeadset = null; - private BluetoothHidHost mInput = null; - private BluetoothPan mPan = null; - private BluetoothMapClient mMce = null; - private String mMsgHandle = null; - - /** - * Creates a utility instance for testing Bluetooth. - * - * @param context The context of the application using the utility. - * @param tag The log tag of the application using the utility. - */ - public BluetoothTestUtils(Context context, String tag) { - this(context, tag, null); - } - - /** - * Creates a utility instance for testing Bluetooth. - * - * @param context The context of the application using the utility. - * @param tag The log tag of the application using the utility. - * @param outputFile The path to an output file if the utility is to write results to a - * separate file. - */ - public BluetoothTestUtils(Context context, String tag, String outputFile) { - mContext = context; - mTag = tag; - mOutputFile = outputFile; - - if (mOutputFile == null) { - mOutputWriter = null; - } else { - try { - mOutputWriter = new BufferedWriter(new FileWriter(new File( - Environment.getExternalStorageDirectory(), mOutputFile), true)); - } catch (IOException e) { - Log.w(mTag, "Test output file could not be opened", e); - mOutputWriter = null; - } - } - } - - /** - * Closes the utility instance and unregisters any BroadcastReceivers. - */ - public void close() { - while (!mReceivers.isEmpty()) { - mContext.unregisterReceiver(mReceivers.remove(0)); - } - - if (mOutputWriter != null) { - try { - mOutputWriter.close(); - } catch (IOException e) { - Log.w(mTag, "Test output file could not be closed", e); - } - } - } - - /** - * Enables Bluetooth and checks to make sure that Bluetooth was turned on and that the correct - * actions were broadcast. - * - * @param adapter The BT adapter. - */ - public void enable(BluetoothAdapter adapter) { - writeOutput("Enabling Bluetooth adapter."); - assertFalse(adapter.isEnabled()); - int btState = adapter.getState(); - final Semaphore completionSemaphore = new Semaphore(0); - final BroadcastReceiver receiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - final String action = intent.getAction(); - if (!BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) { - return; - } - final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, - BluetoothAdapter.ERROR); - if (state == BluetoothAdapter.STATE_ON) { - completionSemaphore.release(); - } - } - }; - - final IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED); - mContext.registerReceiver(receiver, filter); - // Note: for Wear Local Edition builds, which have Permission Review Mode enabled to - // obey China CMIIT, BluetoothAdapter may not startup immediately on methods enable/disable. - // So no assertion applied here. - adapter.enable(); - boolean success = false; - try { - success = completionSemaphore.tryAcquire(ENABLE_DISABLE_TIMEOUT, TimeUnit.MILLISECONDS); - writeOutput(String.format("enable() completed in 0 ms")); - } catch (final InterruptedException e) { - // This should never happen but just in case it does, the test will fail anyway. - } - mContext.unregisterReceiver(receiver); - if (!success) { - fail(String.format("enable() timeout: state=%d (expected %d)", btState, - BluetoothAdapter.STATE_ON)); - } - } - - /** - * Disables Bluetooth and checks to make sure that Bluetooth was turned off and that the correct - * actions were broadcast. - * - * @param adapter The BT adapter. - */ - public void disable(BluetoothAdapter adapter) { - writeOutput("Disabling Bluetooth adapter."); - assertTrue(adapter.isEnabled()); - int btState = adapter.getState(); - final Semaphore completionSemaphore = new Semaphore(0); - final BroadcastReceiver receiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - final String action = intent.getAction(); - if (!BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) { - return; - } - final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, - BluetoothAdapter.ERROR); - if (state == BluetoothAdapter.STATE_OFF) { - completionSemaphore.release(); - } - } - }; - - final IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED); - mContext.registerReceiver(receiver, filter); - // Note: for Wear Local Edition builds, which have Permission Review Mode enabled to - // obey China CMIIT, BluetoothAdapter may not startup immediately on methods enable/disable. - // So no assertion applied here. - adapter.disable(); - boolean success = false; - try { - success = completionSemaphore.tryAcquire(ENABLE_DISABLE_TIMEOUT, TimeUnit.MILLISECONDS); - writeOutput(String.format("disable() completed in 0 ms")); - } catch (final InterruptedException e) { - // This should never happen but just in case it does, the test will fail anyway. - } - mContext.unregisterReceiver(receiver); - if (!success) { - fail(String.format("disable() timeout: state=%d (expected %d)", btState, - BluetoothAdapter.STATE_OFF)); - } - } - - /** - * Puts the local device into discoverable mode and checks to make sure that the local device - * is in discoverable mode and that the correct actions were broadcast. - * - * @param adapter The BT adapter. - */ - public void discoverable(BluetoothAdapter adapter) { - if (!adapter.isEnabled()) { - fail("discoverable() bluetooth not enabled"); - } - - int scanMode = adapter.getScanMode(); - if (scanMode != BluetoothAdapter.SCAN_MODE_CONNECTABLE) { - return; - } - - final Semaphore completionSemaphore = new Semaphore(0); - final BroadcastReceiver receiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - final String action = intent.getAction(); - if (!BluetoothAdapter.ACTION_SCAN_MODE_CHANGED.equals(action)) { - return; - } - final int mode = intent.getIntExtra(BluetoothAdapter.EXTRA_SCAN_MODE, - BluetoothAdapter.SCAN_MODE_NONE); - if (mode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) { - completionSemaphore.release(); - } - } - }; - - final IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED); - mContext.registerReceiver(receiver, filter); - assertEquals(adapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE), - BluetoothStatusCodes.SUCCESS); - boolean success = false; - try { - success = completionSemaphore.tryAcquire(DISCOVERABLE_UNDISCOVERABLE_TIMEOUT, - TimeUnit.MILLISECONDS); - writeOutput(String.format("discoverable() completed in 0 ms")); - } catch (final InterruptedException e) { - // This should never happen but just in case it does, the test will fail anyway. - } - mContext.unregisterReceiver(receiver); - if (!success) { - fail(String.format("discoverable() timeout: scanMode=%d (expected %d)", scanMode, - BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE)); - } - } - - /** - * Puts the local device into connectable only mode and checks to make sure that the local - * device is in in connectable mode and that the correct actions were broadcast. - * - * @param adapter The BT adapter. - */ - public void undiscoverable(BluetoothAdapter adapter) { - if (!adapter.isEnabled()) { - fail("undiscoverable() bluetooth not enabled"); - } - - int scanMode = adapter.getScanMode(); - if (scanMode != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) { - return; - } - - final Semaphore completionSemaphore = new Semaphore(0); - final BroadcastReceiver receiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - final String action = intent.getAction(); - if (!BluetoothAdapter.ACTION_SCAN_MODE_CHANGED.equals(action)) { - return; - } - final int mode = intent.getIntExtra(BluetoothAdapter.EXTRA_SCAN_MODE, - BluetoothAdapter.SCAN_MODE_NONE); - if (mode == BluetoothAdapter.SCAN_MODE_CONNECTABLE) { - completionSemaphore.release(); - } - } - }; - - final IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED); - mContext.registerReceiver(receiver, filter); - assertEquals(adapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE), - BluetoothStatusCodes.SUCCESS); - boolean success = false; - try { - success = completionSemaphore.tryAcquire(DISCOVERABLE_UNDISCOVERABLE_TIMEOUT, - TimeUnit.MILLISECONDS); - writeOutput(String.format("undiscoverable() completed in 0 ms")); - } catch (InterruptedException e) { - // This should never happen but just in case it does, the test will fail anyway. - } - mContext.unregisterReceiver(receiver); - if (!success) { - fail(String.format("undiscoverable() timeout: scanMode=%d (expected %d)", scanMode, - BluetoothAdapter.SCAN_MODE_CONNECTABLE)); - } - } - - /** - * Starts a scan for remote devices and checks to make sure that the local device is scanning - * and that the correct actions were broadcast. - * - * @param adapter The BT adapter. - */ - public void startScan(BluetoothAdapter adapter) { - int mask = BluetoothReceiver.DISCOVERY_STARTED_FLAG; - - if (!adapter.isEnabled()) { - fail("startScan() bluetooth not enabled"); - } - - if (adapter.isDiscovering()) { - return; - } - - BluetoothReceiver receiver = getBluetoothReceiver(mask); - - long start = System.currentTimeMillis(); - assertTrue(adapter.startDiscovery()); - - while (System.currentTimeMillis() - start < START_STOP_SCAN_TIMEOUT) { - if (adapter.isDiscovering() && ((receiver.getFiredFlags() & mask) == mask)) { - writeOutput(String.format("startScan() completed in %d ms", - (receiver.getCompletedTime() - start))); - removeReceiver(receiver); - return; - } - sleep(POLL_TIME); - } - - int firedFlags = receiver.getFiredFlags(); - removeReceiver(receiver); - fail(String.format("startScan() timeout: isDiscovering=%b, flags=0x%x (expected 0x%x)", - adapter.isDiscovering(), firedFlags, mask)); - } - - /** - * Stops a scan for remote devices and checks to make sure that the local device is not scanning - * and that the correct actions were broadcast. - * - * @param adapter The BT adapter. - */ - public void stopScan(BluetoothAdapter adapter) { - int mask = BluetoothReceiver.DISCOVERY_FINISHED_FLAG; - - if (!adapter.isEnabled()) { - fail("stopScan() bluetooth not enabled"); - } - - if (!adapter.isDiscovering()) { - return; - } - - BluetoothReceiver receiver = getBluetoothReceiver(mask); - - long start = System.currentTimeMillis(); - assertTrue(adapter.cancelDiscovery()); - - while (System.currentTimeMillis() - start < START_STOP_SCAN_TIMEOUT) { - if (!adapter.isDiscovering() && ((receiver.getFiredFlags() & mask) == mask)) { - writeOutput(String.format("stopScan() completed in %d ms", - (receiver.getCompletedTime() - start))); - removeReceiver(receiver); - return; - } - sleep(POLL_TIME); - } - - int firedFlags = receiver.getFiredFlags(); - removeReceiver(receiver); - fail(String.format("stopScan() timeout: isDiscovering=%b, flags=0x%x (expected 0x%x)", - adapter.isDiscovering(), firedFlags, mask)); - - } - - /** - * Enables PAN tethering on the local device and checks to make sure that tethering is enabled. - * - * @param adapter The BT adapter. - */ - public void enablePan(BluetoothAdapter adapter) { - if (mPan == null) mPan = (BluetoothPan) connectProxy(adapter, BluetoothProfile.PAN); - assertNotNull(mPan); - - long start = System.currentTimeMillis(); - mPan.setBluetoothTethering(true); - long stop = System.currentTimeMillis(); - assertTrue(mPan.isTetheringOn()); - - writeOutput(String.format("enablePan() completed in %d ms", (stop - start))); - } - - /** - * Disables PAN tethering on the local device and checks to make sure that tethering is - * disabled. - * - * @param adapter The BT adapter. - */ - public void disablePan(BluetoothAdapter adapter) { - if (mPan == null) mPan = (BluetoothPan) connectProxy(adapter, BluetoothProfile.PAN); - assertNotNull(mPan); - - long start = System.currentTimeMillis(); - mPan.setBluetoothTethering(false); - long stop = System.currentTimeMillis(); - assertFalse(mPan.isTetheringOn()); - - writeOutput(String.format("disablePan() completed in %d ms", (stop - start))); - } - - /** - * Initiates a pairing with a remote device and checks to make sure that the devices are paired - * and that the correct actions were broadcast. - * - * @param adapter The BT adapter. - * @param device The remote device. - * @param passkey The pairing passkey if pairing requires a passkey. Any value if not. - * @param pin The pairing pin if pairing requires a pin. Any value if not. - */ - public void pair(BluetoothAdapter adapter, BluetoothDevice device, int passkey, byte[] pin) { - pairOrAcceptPair(adapter, device, passkey, pin, true); - } - - /** - * Accepts a pairing with a remote device and checks to make sure that the devices are paired - * and that the correct actions were broadcast. - * - * @param adapter The BT adapter. - * @param device The remote device. - * @param passkey The pairing passkey if pairing requires a passkey. Any value if not. - * @param pin The pairing pin if pairing requires a pin. Any value if not. - */ - public void acceptPair(BluetoothAdapter adapter, BluetoothDevice device, int passkey, - byte[] pin) { - pairOrAcceptPair(adapter, device, passkey, pin, false); - } - - /** - * Helper method used by {@link #pair(BluetoothAdapter, BluetoothDevice, int, byte[])} and - * {@link #acceptPair(BluetoothAdapter, BluetoothDevice, int, byte[])} to either pair or accept - * a pairing request. - * - * @param adapter The BT adapter. - * @param device The remote device. - * @param passkey The pairing passkey if pairing requires a passkey. Any value if not. - * @param pin The pairing pin if pairing requires a pin. Any value if not. - * @param shouldPair Whether to pair or accept the pair. - */ - private void pairOrAcceptPair(BluetoothAdapter adapter, BluetoothDevice device, int passkey, - byte[] pin, boolean shouldPair) { - int mask = PairReceiver.STATE_BONDING_FLAG | PairReceiver.STATE_BONDED_FLAG; - long start = -1; - String methodName; - if (shouldPair) { - methodName = String.format("pair(device=%s)", device); - } else { - methodName = String.format("acceptPair(device=%s)", device); - } - - if (!adapter.isEnabled()) { - fail(String.format("%s bluetooth not enabled", methodName)); - } - - PairReceiver receiver = getPairReceiver(device, passkey, pin, mask); - - int state = device.getBondState(); - switch (state) { - case BluetoothDevice.BOND_NONE: - assertFalse(adapter.getBondedDevices().contains(device)); - start = System.currentTimeMillis(); - if (shouldPair) { - assertTrue(device.createBond()); - } - break; - case BluetoothDevice.BOND_BONDING: - mask = 0; // Don't check for received intents since we might have missed them. - break; - case BluetoothDevice.BOND_BONDED: - assertTrue(adapter.getBondedDevices().contains(device)); - return; - default: - removeReceiver(receiver); - fail(String.format("%s invalid state: state=%d", methodName, state)); - } - - long s = System.currentTimeMillis(); - while (System.currentTimeMillis() - s < PAIR_UNPAIR_TIMEOUT) { - state = device.getBondState(); - if (state == BluetoothDevice.BOND_BONDED && (receiver.getFiredFlags() & mask) == mask) { - assertTrue(adapter.getBondedDevices().contains(device)); - long finish = receiver.getCompletedTime(); - if (start != -1 && finish != -1) { - writeOutput(String.format("%s completed in %d ms", methodName, - (finish - start))); - } else { - writeOutput(String.format("%s completed", methodName)); - } - removeReceiver(receiver); - return; - } - sleep(POLL_TIME); - } - - int firedFlags = receiver.getFiredFlags(); - removeReceiver(receiver); - fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)", - methodName, state, BluetoothDevice.BOND_BONDED, firedFlags, mask)); - } - - /** - * Deletes a pairing with a remote device and checks to make sure that the devices are unpaired - * and that the correct actions were broadcast. - * - * @param adapter The BT adapter. - * @param device The remote device. - */ - public void unpair(BluetoothAdapter adapter, BluetoothDevice device) { - int mask = PairReceiver.STATE_NONE_FLAG; - long start = -1; - String methodName = String.format("unpair(device=%s)", device); - - if (!adapter.isEnabled()) { - fail(String.format("%s bluetooth not enabled", methodName)); - } - - PairReceiver receiver = getPairReceiver(device, 0, null, mask); - - int state = device.getBondState(); - switch (state) { - case BluetoothDevice.BOND_NONE: - assertFalse(adapter.getBondedDevices().contains(device)); - removeReceiver(receiver); - return; - case BluetoothDevice.BOND_BONDING: - start = System.currentTimeMillis(); - assertTrue(device.removeBond()); - break; - case BluetoothDevice.BOND_BONDED: - assertTrue(adapter.getBondedDevices().contains(device)); - start = System.currentTimeMillis(); - assertTrue(device.removeBond()); - break; - default: - removeReceiver(receiver); - fail(String.format("%s invalid state: state=%d", methodName, state)); - } - - long s = System.currentTimeMillis(); - while (System.currentTimeMillis() - s < PAIR_UNPAIR_TIMEOUT) { - if (device.getBondState() == BluetoothDevice.BOND_NONE - && (receiver.getFiredFlags() & mask) == mask) { - assertFalse(adapter.getBondedDevices().contains(device)); - long finish = receiver.getCompletedTime(); - if (start != -1 && finish != -1) { - writeOutput(String.format("%s completed in %d ms", methodName, - (finish - start))); - } else { - writeOutput(String.format("%s completed", methodName)); - } - removeReceiver(receiver); - return; - } - } - - int firedFlags = receiver.getFiredFlags(); - removeReceiver(receiver); - fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)", - methodName, state, BluetoothDevice.BOND_BONDED, firedFlags, mask)); - } - - /** - * Deletes all pairings of remote devices - * @param adapter the BT adapter - */ - public void unpairAll(BluetoothAdapter adapter) { - Set<BluetoothDevice> devices = adapter.getBondedDevices(); - for (BluetoothDevice device : devices) { - unpair(adapter, device); - } - } - - /** - * Connects a profile from the local device to a remote device and checks to make sure that the - * profile is connected and that the correct actions were broadcast. - * - * @param adapter The BT adapter. - * @param device The remote device. - * @param profile The profile to connect. One of {@link BluetoothProfile#A2DP}, - * {@link BluetoothProfile#HEADSET}, {@link BluetoothProfile#HID_HOST} or {@link BluetoothProfile#MAP_CLIENT}.. - * @param methodName The method name to printed in the logs. If null, will be - * "connectProfile(profile=<profile>, device=<device>)" - */ - public void connectProfile(BluetoothAdapter adapter, BluetoothDevice device, int profile, - String methodName) { - if (methodName == null) { - methodName = String.format("connectProfile(profile=%d, device=%s)", profile, device); - } - int mask = (ConnectProfileReceiver.STATE_CONNECTING_FLAG - | ConnectProfileReceiver.STATE_CONNECTED_FLAG); - long start = -1; - - if (!adapter.isEnabled()) { - fail(String.format("%s bluetooth not enabled", methodName)); - } - - if (!adapter.getBondedDevices().contains(device)) { - fail(String.format("%s device not paired", methodName)); - } - - BluetoothProfile proxy = connectProxy(adapter, profile); - assertNotNull(proxy); - - ConnectProfileReceiver receiver = getConnectProfileReceiver(device, profile, mask); - - int state = proxy.getConnectionState(device); - switch (state) { - case BluetoothProfile.STATE_CONNECTED: - removeReceiver(receiver); - return; - case BluetoothProfile.STATE_CONNECTING: - mask = 0; // Don't check for received intents since we might have missed them. - break; - case BluetoothProfile.STATE_DISCONNECTED: - case BluetoothProfile.STATE_DISCONNECTING: - start = System.currentTimeMillis(); - if (profile == BluetoothProfile.A2DP) { - assertTrue(((BluetoothA2dp)proxy).connect(device)); - } else if (profile == BluetoothProfile.HEADSET) { - assertTrue(((BluetoothHeadset)proxy).connect(device)); - } else if (profile == BluetoothProfile.HID_HOST) { - assertTrue(((BluetoothHidHost)proxy).connect(device)); - } else if (profile == BluetoothProfile.MAP_CLIENT) { - assertTrue(((BluetoothMapClient)proxy).connect(device)); - } - break; - default: - removeReceiver(receiver); - fail(String.format("%s invalid state: state=%d", methodName, state)); - } - - long s = System.currentTimeMillis(); - while (System.currentTimeMillis() - s < CONNECT_DISCONNECT_PROFILE_TIMEOUT) { - state = proxy.getConnectionState(device); - if (state == BluetoothProfile.STATE_CONNECTED - && (receiver.getFiredFlags() & mask) == mask) { - long finish = receiver.getCompletedTime(); - if (start != -1 && finish != -1) { - writeOutput(String.format("%s completed in %d ms", methodName, - (finish - start))); - } else { - writeOutput(String.format("%s completed", methodName)); - } - removeReceiver(receiver); - return; - } - sleep(POLL_TIME); - } - - int firedFlags = receiver.getFiredFlags(); - removeReceiver(receiver); - fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)", - methodName, state, BluetoothProfile.STATE_CONNECTED, firedFlags, mask)); - } - - /** - * Disconnects a profile between the local device and a remote device and checks to make sure - * that the profile is disconnected and that the correct actions were broadcast. - * - * @param adapter The BT adapter. - * @param device The remote device. - * @param profile The profile to disconnect. One of {@link BluetoothProfile#A2DP}, - * {@link BluetoothProfile#HEADSET}, or {@link BluetoothProfile#HID_HOST}. - * @param methodName The method name to printed in the logs. If null, will be - * "connectProfile(profile=<profile>, device=<device>)" - */ - public void disconnectProfile(BluetoothAdapter adapter, BluetoothDevice device, int profile, - String methodName) { - if (methodName == null) { - methodName = String.format("disconnectProfile(profile=%d, device=%s)", profile, device); - } - int mask = (ConnectProfileReceiver.STATE_DISCONNECTING_FLAG - | ConnectProfileReceiver.STATE_DISCONNECTED_FLAG); - long start = -1; - - if (!adapter.isEnabled()) { - fail(String.format("%s bluetooth not enabled", methodName)); - } - - if (!adapter.getBondedDevices().contains(device)) { - fail(String.format("%s device not paired", methodName)); - } - - BluetoothProfile proxy = connectProxy(adapter, profile); - assertNotNull(proxy); - - ConnectProfileReceiver receiver = getConnectProfileReceiver(device, profile, mask); - - int state = proxy.getConnectionState(device); - switch (state) { - case BluetoothProfile.STATE_CONNECTED: - case BluetoothProfile.STATE_CONNECTING: - start = System.currentTimeMillis(); - if (profile == BluetoothProfile.A2DP) { - assertTrue(((BluetoothA2dp)proxy).disconnect(device)); - } else if (profile == BluetoothProfile.HEADSET) { - assertTrue(((BluetoothHeadset)proxy).disconnect(device)); - } else if (profile == BluetoothProfile.HID_HOST) { - assertTrue(((BluetoothHidHost)proxy).disconnect(device)); - } else if (profile == BluetoothProfile.MAP_CLIENT) { - assertTrue(((BluetoothMapClient)proxy).disconnect(device)); - } - break; - case BluetoothProfile.STATE_DISCONNECTED: - removeReceiver(receiver); - return; - case BluetoothProfile.STATE_DISCONNECTING: - mask = 0; // Don't check for received intents since we might have missed them. - break; - default: - removeReceiver(receiver); - fail(String.format("%s invalid state: state=%d", methodName, state)); - } - - long s = System.currentTimeMillis(); - while (System.currentTimeMillis() - s < CONNECT_DISCONNECT_PROFILE_TIMEOUT) { - state = proxy.getConnectionState(device); - if (state == BluetoothProfile.STATE_DISCONNECTED - && (receiver.getFiredFlags() & mask) == mask) { - long finish = receiver.getCompletedTime(); - if (start != -1 && finish != -1) { - writeOutput(String.format("%s completed in %d ms", methodName, - (finish - start))); - } else { - writeOutput(String.format("%s completed", methodName)); - } - removeReceiver(receiver); - return; - } - sleep(POLL_TIME); - } - - int firedFlags = receiver.getFiredFlags(); - removeReceiver(receiver); - fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)", - methodName, state, BluetoothProfile.STATE_DISCONNECTED, firedFlags, mask)); - } - - /** - * Connects the PANU to a remote NAP and checks to make sure that the PANU is connected and that - * the correct actions were broadcast. - * - * @param adapter The BT adapter. - * @param device The remote device. - */ - public void connectPan(BluetoothAdapter adapter, BluetoothDevice device) { - connectPanOrIncomingPanConnection(adapter, device, true); - } - - /** - * Checks that a remote PANU connects to the local NAP correctly and that the correct actions - * were broadcast. - * - * @param adapter The BT adapter. - * @param device The remote device. - */ - public void incomingPanConnection(BluetoothAdapter adapter, BluetoothDevice device) { - connectPanOrIncomingPanConnection(adapter, device, false); - } - - /** - * Helper method used by {@link #connectPan(BluetoothAdapter, BluetoothDevice)} and - * {@link #incomingPanConnection(BluetoothAdapter, BluetoothDevice)} to either connect to a - * remote NAP or verify that a remote device connected to the local NAP. - * - * @param adapter The BT adapter. - * @param device The remote device. - * @param connect If the method should initiate the connection (is PANU) - */ - private void connectPanOrIncomingPanConnection(BluetoothAdapter adapter, BluetoothDevice device, - boolean connect) { - long start = -1; - int mask, role; - String methodName; - - if (connect) { - methodName = String.format("connectPan(device=%s)", device); - mask = (ConnectProfileReceiver.STATE_CONNECTED_FLAG | - ConnectProfileReceiver.STATE_CONNECTING_FLAG); - role = BluetoothPan.LOCAL_PANU_ROLE; - } else { - methodName = String.format("incomingPanConnection(device=%s)", device); - mask = ConnectProfileReceiver.STATE_CONNECTED_FLAG; - role = BluetoothPan.LOCAL_NAP_ROLE; - } - - if (!adapter.isEnabled()) { - fail(String.format("%s bluetooth not enabled", methodName)); - } - - if (!adapter.getBondedDevices().contains(device)) { - fail(String.format("%s device not paired", methodName)); - } - - mPan = (BluetoothPan) connectProxy(adapter, BluetoothProfile.PAN); - assertNotNull(mPan); - ConnectPanReceiver receiver = getConnectPanReceiver(device, role, mask); - - int state = mPan.getConnectionState(device); - switch (state) { - case BluetoothPan.STATE_CONNECTED: - removeReceiver(receiver); - return; - case BluetoothPan.STATE_CONNECTING: - mask = 0; // Don't check for received intents since we might have missed them. - break; - case BluetoothPan.STATE_DISCONNECTED: - case BluetoothPan.STATE_DISCONNECTING: - start = System.currentTimeMillis(); - if (role == BluetoothPan.LOCAL_PANU_ROLE) { - Log.i("BT", "connect to pan"); - assertTrue(mPan.connect(device)); - } - break; - default: - removeReceiver(receiver); - fail(String.format("%s invalid state: state=%d", methodName, state)); - } - - long s = System.currentTimeMillis(); - while (System.currentTimeMillis() - s < CONNECT_DISCONNECT_PROFILE_TIMEOUT) { - state = mPan.getConnectionState(device); - if (state == BluetoothPan.STATE_CONNECTED - && (receiver.getFiredFlags() & mask) == mask) { - long finish = receiver.getCompletedTime(); - if (start != -1 && finish != -1) { - writeOutput(String.format("%s completed in %d ms", methodName, - (finish - start))); - } else { - writeOutput(String.format("%s completed", methodName)); - } - removeReceiver(receiver); - return; - } - sleep(POLL_TIME); - } - - int firedFlags = receiver.getFiredFlags(); - removeReceiver(receiver); - fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%s)", - methodName, state, BluetoothPan.STATE_CONNECTED, firedFlags, mask)); - } - - /** - * Disconnects the PANU from a remote NAP and checks to make sure that the PANU is disconnected - * and that the correct actions were broadcast. - * - * @param adapter The BT adapter. - * @param device The remote device. - */ - public void disconnectPan(BluetoothAdapter adapter, BluetoothDevice device) { - disconnectFromRemoteOrVerifyConnectNap(adapter, device, true); - } - - /** - * Checks that a remote PANU disconnects from the local NAP correctly and that the correct - * actions were broadcast. - * - * @param adapter The BT adapter. - * @param device The remote device. - */ - public void incomingPanDisconnection(BluetoothAdapter adapter, BluetoothDevice device) { - disconnectFromRemoteOrVerifyConnectNap(adapter, device, false); - } - - /** - * Helper method used by {@link #disconnectPan(BluetoothAdapter, BluetoothDevice)} and - * {@link #incomingPanDisconnection(BluetoothAdapter, BluetoothDevice)} to either disconnect - * from a remote NAP or verify that a remote device disconnected from the local NAP. - * - * @param adapter The BT adapter. - * @param device The remote device. - * @param disconnect Whether the method should connect or verify. - */ - private void disconnectFromRemoteOrVerifyConnectNap(BluetoothAdapter adapter, - BluetoothDevice device, boolean disconnect) { - long start = -1; - int mask, role; - String methodName; - - if (disconnect) { - methodName = String.format("disconnectPan(device=%s)", device); - mask = (ConnectProfileReceiver.STATE_DISCONNECTED_FLAG | - ConnectProfileReceiver.STATE_DISCONNECTING_FLAG); - role = BluetoothPan.LOCAL_PANU_ROLE; - } else { - methodName = String.format("incomingPanDisconnection(device=%s)", device); - mask = ConnectProfileReceiver.STATE_DISCONNECTED_FLAG; - role = BluetoothPan.LOCAL_NAP_ROLE; - } - - if (!adapter.isEnabled()) { - fail(String.format("%s bluetooth not enabled", methodName)); - } - - if (!adapter.getBondedDevices().contains(device)) { - fail(String.format("%s device not paired", methodName)); - } - - mPan = (BluetoothPan) connectProxy(adapter, BluetoothProfile.PAN); - assertNotNull(mPan); - ConnectPanReceiver receiver = getConnectPanReceiver(device, role, mask); - - int state = mPan.getConnectionState(device); - switch (state) { - case BluetoothPan.STATE_CONNECTED: - case BluetoothPan.STATE_CONNECTING: - start = System.currentTimeMillis(); - if (role == BluetoothPan.LOCAL_PANU_ROLE) { - assertTrue(mPan.disconnect(device)); - } - break; - case BluetoothPan.STATE_DISCONNECTED: - removeReceiver(receiver); - return; - case BluetoothPan.STATE_DISCONNECTING: - mask = 0; // Don't check for received intents since we might have missed them. - break; - default: - removeReceiver(receiver); - fail(String.format("%s invalid state: state=%d", methodName, state)); - } - - long s = System.currentTimeMillis(); - while (System.currentTimeMillis() - s < CONNECT_DISCONNECT_PROFILE_TIMEOUT) { - state = mPan.getConnectionState(device); - if (state == BluetoothHidHost.STATE_DISCONNECTED - && (receiver.getFiredFlags() & mask) == mask) { - long finish = receiver.getCompletedTime(); - if (start != -1 && finish != -1) { - writeOutput(String.format("%s completed in %d ms", methodName, - (finish - start))); - } else { - writeOutput(String.format("%s completed", methodName)); - } - removeReceiver(receiver); - return; - } - sleep(POLL_TIME); - } - - int firedFlags = receiver.getFiredFlags(); - removeReceiver(receiver); - fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%s)", - methodName, state, BluetoothHidHost.STATE_DISCONNECTED, firedFlags, mask)); - } - - /** - * Opens a SCO channel using {@link android.media.AudioManager#startBluetoothSco()} and checks - * to make sure that the channel is opened and that the correct actions were broadcast. - * - * @param adapter The BT adapter. - * @param device The remote device. - */ - public void startSco(BluetoothAdapter adapter, BluetoothDevice device) { - startStopSco(adapter, device, true); - } - - /** - * Closes a SCO channel using {@link android.media.AudioManager#stopBluetoothSco()} and checks - * to make sure that the channel is closed and that the correct actions were broadcast. - * - * @param adapter The BT adapter. - * @param device The remote device. - */ - public void stopSco(BluetoothAdapter adapter, BluetoothDevice device) { - startStopSco(adapter, device, false); - } - /** - * Helper method for {@link #startSco(BluetoothAdapter, BluetoothDevice)} and - * {@link #stopSco(BluetoothAdapter, BluetoothDevice)}. - * - * @param adapter The BT adapter. - * @param device The remote device. - * @param isStart Whether the SCO channel should be opened. - */ - private void startStopSco(BluetoothAdapter adapter, BluetoothDevice device, boolean isStart) { - long start = -1; - int mask; - String methodName; - - if (isStart) { - methodName = String.format("startSco(device=%s)", device); - mask = StartStopScoReceiver.STATE_CONNECTED_FLAG; - } else { - methodName = String.format("stopSco(device=%s)", device); - mask = StartStopScoReceiver.STATE_DISCONNECTED_FLAG; - } - - if (!adapter.isEnabled()) { - fail(String.format("%s bluetooth not enabled", methodName)); - } - - if (!adapter.getBondedDevices().contains(device)) { - fail(String.format("%s device not paired", methodName)); - } - - AudioManager manager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); - assertNotNull(manager); - - if (!manager.isBluetoothScoAvailableOffCall()) { - fail(String.format("%s device does not support SCO", methodName)); - } - - boolean isScoOn = manager.isBluetoothScoOn(); - if (isStart == isScoOn) { - return; - } - - StartStopScoReceiver receiver = getStartStopScoReceiver(mask); - start = System.currentTimeMillis(); - if (isStart) { - manager.startBluetoothSco(); - } else { - manager.stopBluetoothSco(); - } - - long s = System.currentTimeMillis(); - while (System.currentTimeMillis() - s < START_STOP_SCO_TIMEOUT) { - isScoOn = manager.isBluetoothScoOn(); - if (isStart == isScoOn && (receiver.getFiredFlags() & mask) == mask) { - long finish = receiver.getCompletedTime(); - if (start != -1 && finish != -1) { - writeOutput(String.format("%s completed in %d ms", methodName, - (finish - start))); - } else { - writeOutput(String.format("%s completed", methodName)); - } - removeReceiver(receiver); - return; - } - sleep(POLL_TIME); - } - - int firedFlags = receiver.getFiredFlags(); - removeReceiver(receiver); - fail(String.format("%s timeout: on=%b (expected %b), flags=0x%x (expected 0x%x)", - methodName, isScoOn, isStart, firedFlags, mask)); - } - - /** - * Writes a string to the logcat and a file if a file has been specified in the constructor. - * - * @param s The string to be written. - */ - public void writeOutput(String s) { - Log.i(mTag, s); - if (mOutputWriter == null) { - return; - } - try { - mOutputWriter.write(s + "\n"); - mOutputWriter.flush(); - } catch (IOException e) { - Log.w(mTag, "Could not write to output file", e); - } - } - - public void mceGetUnreadMessage(BluetoothAdapter adapter, BluetoothDevice device) { - int mask; - String methodName = "getUnreadMessage"; - - if (!adapter.isEnabled()) { - fail(String.format("%s bluetooth not enabled", methodName)); - } - - if (!adapter.getBondedDevices().contains(device)) { - fail(String.format("%s device not paired", methodName)); - } - - mMce = (BluetoothMapClient) connectProxy(adapter, BluetoothProfile.MAP_CLIENT); - assertNotNull(mMce); - - if (mMce.getConnectionState(device) != BluetoothProfile.STATE_CONNECTED) { - fail(String.format("%s device is not connected", methodName)); - } - - mMsgHandle = null; - mask = MceSetMessageStatusReceiver.MESSAGE_RECEIVED_FLAG; - MceSetMessageStatusReceiver receiver = getMceSetMessageStatusReceiver(device, mask); - assertTrue(mMce.getUnreadMessages(device)); - - long s = System.currentTimeMillis(); - while (System.currentTimeMillis() - s < GET_UNREAD_MESSAGE_TIMEOUT) { - if ((receiver.getFiredFlags() & mask) == mask) { - writeOutput(String.format("%s completed", methodName)); - removeReceiver(receiver); - return; - } - sleep(POLL_TIME); - } - int firedFlags = receiver.getFiredFlags(); - removeReceiver(receiver); - fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%s)", - methodName, mMce.getConnectionState(device), BluetoothMapClient.STATE_CONNECTED, firedFlags, mask)); - } - - /** - * Set a message to read/unread/deleted/undeleted - */ - public void mceSetMessageStatus(BluetoothAdapter adapter, BluetoothDevice device, int status) { - int mask; - String methodName = "setMessageStatus"; - - if (!adapter.isEnabled()) { - fail(String.format("%s bluetooth not enabled", methodName)); - } - - if (!adapter.getBondedDevices().contains(device)) { - fail(String.format("%s device not paired", methodName)); - } - - mMce = (BluetoothMapClient) connectProxy(adapter, BluetoothProfile.MAP_CLIENT); - assertNotNull(mMce); - - if (mMce.getConnectionState(device) != BluetoothProfile.STATE_CONNECTED) { - fail(String.format("%s device is not connected", methodName)); - } - - assertNotNull(mMsgHandle); - mask = MceSetMessageStatusReceiver.STATUS_CHANGED_FLAG; - MceSetMessageStatusReceiver receiver = getMceSetMessageStatusReceiver(device, mask); - - assertTrue(mMce.setMessageStatus(device, mMsgHandle, status)); - - long s = System.currentTimeMillis(); - while (System.currentTimeMillis() - s < SET_MESSAGE_STATUS_TIMEOUT) { - if ((receiver.getFiredFlags() & mask) == mask) { - writeOutput(String.format("%s completed", methodName)); - removeReceiver(receiver); - return; - } - sleep(POLL_TIME); - } - - int firedFlags = receiver.getFiredFlags(); - removeReceiver(receiver); - fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%s)", - methodName, mMce.getConnectionState(device), BluetoothPan.STATE_CONNECTED, firedFlags, mask)); - } - - private void addReceiver(BroadcastReceiver receiver, String[] actions) { - IntentFilter filter = new IntentFilter(); - for (String action: actions) { - filter.addAction(action); - } - mContext.registerReceiver(receiver, filter); - mReceivers.add(receiver); - } - - private BluetoothReceiver getBluetoothReceiver(int expectedFlags) { - String[] actions = { - BluetoothAdapter.ACTION_DISCOVERY_FINISHED, - BluetoothAdapter.ACTION_DISCOVERY_STARTED, - BluetoothAdapter.ACTION_SCAN_MODE_CHANGED, - BluetoothAdapter.ACTION_STATE_CHANGED}; - BluetoothReceiver receiver = new BluetoothReceiver(expectedFlags); - addReceiver(receiver, actions); - return receiver; - } - - private PairReceiver getPairReceiver(BluetoothDevice device, int passkey, byte[] pin, - int expectedFlags) { - String[] actions = { - BluetoothDevice.ACTION_PAIRING_REQUEST, - BluetoothDevice.ACTION_BOND_STATE_CHANGED}; - PairReceiver receiver = new PairReceiver(device, passkey, pin, expectedFlags); - addReceiver(receiver, actions); - return receiver; - } - - private ConnectProfileReceiver getConnectProfileReceiver(BluetoothDevice device, int profile, - int expectedFlags) { - String[] actions = { - BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED, - BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED, - BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED, - BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED}; - ConnectProfileReceiver receiver = new ConnectProfileReceiver(device, profile, - expectedFlags); - addReceiver(receiver, actions); - return receiver; - } - - private ConnectPanReceiver getConnectPanReceiver(BluetoothDevice device, int role, - int expectedFlags) { - String[] actions = {BluetoothPan.ACTION_CONNECTION_STATE_CHANGED}; - ConnectPanReceiver receiver = new ConnectPanReceiver(device, role, expectedFlags); - addReceiver(receiver, actions); - return receiver; - } - - private StartStopScoReceiver getStartStopScoReceiver(int expectedFlags) { - String[] actions = {AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED}; - StartStopScoReceiver receiver = new StartStopScoReceiver(expectedFlags); - addReceiver(receiver, actions); - return receiver; - } - - private MceSetMessageStatusReceiver getMceSetMessageStatusReceiver(BluetoothDevice device, - int expectedFlags) { - String[] actions = {BluetoothMapClient.ACTION_MESSAGE_RECEIVED, - BluetoothMapClient.ACTION_MESSAGE_READ_STATUS_CHANGED, - BluetoothMapClient.ACTION_MESSAGE_DELETED_STATUS_CHANGED}; - MceSetMessageStatusReceiver receiver = new MceSetMessageStatusReceiver(expectedFlags); - addReceiver(receiver, actions); - return receiver; - } - - private void removeReceiver(BroadcastReceiver receiver) { - mContext.unregisterReceiver(receiver); - mReceivers.remove(receiver); - } - - private BluetoothProfile connectProxy(BluetoothAdapter adapter, int profile) { - switch (profile) { - case BluetoothProfile.A2DP: - if (mA2dp != null) { - return mA2dp; - } - break; - case BluetoothProfile.HEADSET: - if (mHeadset != null) { - return mHeadset; - } - break; - case BluetoothProfile.HID_HOST: - if (mInput != null) { - return mInput; - } - break; - case BluetoothProfile.PAN: - if (mPan != null) { - return mPan; - } - case BluetoothProfile.MAP_CLIENT: - if (mMce != null) { - return mMce; - } - break; - default: - return null; - } - adapter.getProfileProxy(mContext, mServiceListener, profile); - long s = System.currentTimeMillis(); - switch (profile) { - case BluetoothProfile.A2DP: - while (mA2dp == null && System.currentTimeMillis() - s < CONNECT_PROXY_TIMEOUT) { - sleep(POLL_TIME); - } - return mA2dp; - case BluetoothProfile.HEADSET: - while (mHeadset == null && System.currentTimeMillis() - s < CONNECT_PROXY_TIMEOUT) { - sleep(POLL_TIME); - } - return mHeadset; - case BluetoothProfile.HID_HOST: - while (mInput == null && System.currentTimeMillis() - s < CONNECT_PROXY_TIMEOUT) { - sleep(POLL_TIME); - } - return mInput; - case BluetoothProfile.PAN: - while (mPan == null && System.currentTimeMillis() - s < CONNECT_PROXY_TIMEOUT) { - sleep(POLL_TIME); - } - return mPan; - case BluetoothProfile.MAP_CLIENT: - while (mMce == null && System.currentTimeMillis() - s < CONNECT_PROXY_TIMEOUT) { - sleep(POLL_TIME); - } - return mMce; - default: - return null; - } - } - - private void sleep(long time) { - try { - Thread.sleep(time); - } catch (InterruptedException e) { - } - } -} diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothUuidTest.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothUuidTest.java deleted file mode 100644 index 536d722679b6..000000000000 --- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothUuidTest.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2014 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.bluetooth; - -import android.os.ParcelUuid; -import android.test.suitebuilder.annotation.SmallTest; - -import junit.framework.TestCase; - -/** - * Unit test cases for {@link BluetoothUuid}. - * <p> - * To run this test, use adb shell am instrument -e class 'android.bluetooth.BluetoothUuidTest' -w - * 'com.android.bluetooth.tests/android.bluetooth.BluetoothTestRunner' - */ -public class BluetoothUuidTest extends TestCase { - - @SmallTest - public void testUuidParser() { - byte[] uuid16 = new byte[] { - 0x0B, 0x11 }; - assertEquals(ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB"), - BluetoothUuid.parseUuidFrom(uuid16)); - - byte[] uuid32 = new byte[] { - 0x0B, 0x11, 0x33, (byte) 0xFE }; - assertEquals(ParcelUuid.fromString("FE33110B-0000-1000-8000-00805F9B34FB"), - BluetoothUuid.parseUuidFrom(uuid32)); - - byte[] uuid128 = new byte[] { - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, - 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, (byte) 0xFF }; - assertEquals(ParcelUuid.fromString("FF0F0E0D-0C0B-0A09-0807-0060504030201"), - BluetoothUuid.parseUuidFrom(uuid128)); - } - - @SmallTest - public void testUuidType() { - assertTrue(BluetoothUuid.is16BitUuid( - ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB"))); - assertFalse(BluetoothUuid.is32BitUuid( - ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB"))); - - assertFalse(BluetoothUuid.is16BitUuid( - ParcelUuid.fromString("FE33110B-0000-1000-8000-00805F9B34FB"))); - assertTrue(BluetoothUuid.is32BitUuid( - ParcelUuid.fromString("FE33110B-0000-1000-8000-00805F9B34FB"))); - assertFalse(BluetoothUuid.is32BitUuid( - ParcelUuid.fromString("FE33110B-1000-1000-8000-00805F9B34FB"))); - - } -} diff --git a/core/tests/bluetoothtests/src/android/bluetooth/le/AdvertiseDataTest.java b/core/tests/bluetoothtests/src/android/bluetooth/le/AdvertiseDataTest.java deleted file mode 100644 index e58d905357e6..000000000000 --- a/core/tests/bluetoothtests/src/android/bluetooth/le/AdvertiseDataTest.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (C) 2014 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.bluetooth.le; - -import android.os.Parcel; -import android.os.ParcelUuid; -import android.test.suitebuilder.annotation.SmallTest; - -import junit.framework.TestCase; - -/** - * Unit test cases for {@link AdvertiseData}. - * <p> - * To run the test, use adb shell am instrument -e class 'android.bluetooth.le.AdvertiseDataTest' -w - * 'com.android.bluetooth.tests/android.bluetooth.BluetoothTestRunner' - */ -public class AdvertiseDataTest extends TestCase { - - private AdvertiseData.Builder mAdvertiseDataBuilder; - - @Override - protected void setUp() throws Exception { - mAdvertiseDataBuilder = new AdvertiseData.Builder(); - } - - @SmallTest - public void testEmptyData() { - Parcel parcel = Parcel.obtain(); - AdvertiseData data = mAdvertiseDataBuilder.build(); - data.writeToParcel(parcel, 0); - parcel.setDataPosition(0); - AdvertiseData dataFromParcel = - AdvertiseData.CREATOR.createFromParcel(parcel); - assertEquals(data, dataFromParcel); - } - - @SmallTest - public void testEmptyServiceUuid() { - Parcel parcel = Parcel.obtain(); - AdvertiseData data = mAdvertiseDataBuilder.setIncludeDeviceName(true).build(); - data.writeToParcel(parcel, 0); - parcel.setDataPosition(0); - AdvertiseData dataFromParcel = - AdvertiseData.CREATOR.createFromParcel(parcel); - assertEquals(data, dataFromParcel); - } - - @SmallTest - public void testEmptyManufacturerData() { - Parcel parcel = Parcel.obtain(); - int manufacturerId = 50; - byte[] manufacturerData = new byte[0]; - AdvertiseData data = - mAdvertiseDataBuilder.setIncludeDeviceName(true) - .addManufacturerData(manufacturerId, manufacturerData).build(); - data.writeToParcel(parcel, 0); - parcel.setDataPosition(0); - AdvertiseData dataFromParcel = - AdvertiseData.CREATOR.createFromParcel(parcel); - assertEquals(data, dataFromParcel); - } - - @SmallTest - public void testEmptyServiceData() { - Parcel parcel = Parcel.obtain(); - ParcelUuid uuid = ParcelUuid.fromString("0000110A-0000-1000-8000-00805F9B34FB"); - byte[] serviceData = new byte[0]; - AdvertiseData data = - mAdvertiseDataBuilder.setIncludeDeviceName(true) - .addServiceData(uuid, serviceData).build(); - data.writeToParcel(parcel, 0); - parcel.setDataPosition(0); - AdvertiseData dataFromParcel = - AdvertiseData.CREATOR.createFromParcel(parcel); - assertEquals(data, dataFromParcel); - } - - @SmallTest - public void testServiceUuid() { - Parcel parcel = Parcel.obtain(); - ParcelUuid uuid = ParcelUuid.fromString("0000110A-0000-1000-8000-00805F9B34FB"); - ParcelUuid uuid2 = ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB"); - - AdvertiseData data = - mAdvertiseDataBuilder.setIncludeDeviceName(true) - .addServiceUuid(uuid).addServiceUuid(uuid2).build(); - data.writeToParcel(parcel, 0); - parcel.setDataPosition(0); - AdvertiseData dataFromParcel = - AdvertiseData.CREATOR.createFromParcel(parcel); - assertEquals(data, dataFromParcel); - } - - @SmallTest - public void testManufacturerData() { - Parcel parcel = Parcel.obtain(); - ParcelUuid uuid = ParcelUuid.fromString("0000110A-0000-1000-8000-00805F9B34FB"); - ParcelUuid uuid2 = ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB"); - - int manufacturerId = 50; - byte[] manufacturerData = new byte[] { - (byte) 0xF0, 0x00, 0x02, 0x15 }; - AdvertiseData data = - mAdvertiseDataBuilder.setIncludeDeviceName(true) - .addServiceUuid(uuid).addServiceUuid(uuid2) - .addManufacturerData(manufacturerId, manufacturerData).build(); - - data.writeToParcel(parcel, 0); - parcel.setDataPosition(0); - AdvertiseData dataFromParcel = - AdvertiseData.CREATOR.createFromParcel(parcel); - assertEquals(data, dataFromParcel); - } - - @SmallTest - public void testServiceData() { - Parcel parcel = Parcel.obtain(); - ParcelUuid uuid = ParcelUuid.fromString("0000110A-0000-1000-8000-00805F9B34FB"); - byte[] serviceData = new byte[] { - (byte) 0xF0, 0x00, 0x02, 0x15 }; - AdvertiseData data = - mAdvertiseDataBuilder.setIncludeDeviceName(true) - .addServiceData(uuid, serviceData).build(); - data.writeToParcel(parcel, 0); - parcel.setDataPosition(0); - AdvertiseData dataFromParcel = - AdvertiseData.CREATOR.createFromParcel(parcel); - assertEquals(data, dataFromParcel); - } -} diff --git a/core/tests/bluetoothtests/src/android/bluetooth/le/ScanFilterTest.java b/core/tests/bluetoothtests/src/android/bluetooth/le/ScanFilterTest.java deleted file mode 100644 index 35da4bceb620..000000000000 --- a/core/tests/bluetoothtests/src/android/bluetooth/le/ScanFilterTest.java +++ /dev/null @@ -1,215 +0,0 @@ -/* - * Copyright (C) 2014 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.bluetooth.le; - -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.le.ScanFilter; -import android.bluetooth.le.ScanRecord; -import android.os.Parcel; -import android.os.ParcelUuid; -import android.test.suitebuilder.annotation.SmallTest; - -import junit.framework.TestCase; - -/** - * Unit test cases for Bluetooth LE scan filters. - * <p> - * To run this test, use adb shell am instrument -e class 'android.bluetooth.ScanFilterTest' -w - * 'com.android.bluetooth.tests/android.bluetooth.BluetoothTestRunner' - */ -public class ScanFilterTest extends TestCase { - - private static final String DEVICE_MAC = "01:02:03:04:05:AB"; - private ScanResult mScanResult; - private ScanFilter.Builder mFilterBuilder; - - @Override - protected void setUp() throws Exception { - byte[] scanRecord = new byte[] { - 0x02, 0x01, 0x1a, // advertising flags - 0x05, 0x02, 0x0b, 0x11, 0x0a, 0x11, // 16 bit service uuids - 0x04, 0x09, 0x50, 0x65, 0x64, // setName - 0x02, 0x0A, (byte) 0xec, // tx power level - 0x05, 0x16, 0x0b, 0x11, 0x50, 0x64, // service data - 0x05, (byte) 0xff, (byte) 0xe0, 0x00, 0x02, 0x15, // manufacturer specific data - 0x03, 0x50, 0x01, 0x02, // an unknown data type won't cause trouble - }; - - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - BluetoothDevice device = adapter.getRemoteDevice(DEVICE_MAC); - mScanResult = new ScanResult(device, ScanRecord.parseFromBytes(scanRecord), - -10, 1397545200000000L); - mFilterBuilder = new ScanFilter.Builder(); - } - - @SmallTest - public void testsetNameFilter() { - ScanFilter filter = mFilterBuilder.setDeviceName("Ped").build(); - assertTrue("setName filter fails", filter.matches(mScanResult)); - - filter = mFilterBuilder.setDeviceName("Pem").build(); - assertFalse("setName filter fails", filter.matches(mScanResult)); - - } - - @SmallTest - public void testDeviceFilter() { - ScanFilter filter = mFilterBuilder.setDeviceAddress(DEVICE_MAC).build(); - assertTrue("device filter fails", filter.matches(mScanResult)); - - filter = mFilterBuilder.setDeviceAddress("11:22:33:44:55:66").build(); - assertFalse("device filter fails", filter.matches(mScanResult)); - } - - @SmallTest - public void testsetServiceUuidFilter() { - ScanFilter filter = mFilterBuilder.setServiceUuid( - ParcelUuid.fromString("0000110A-0000-1000-8000-00805F9B34FB")).build(); - assertTrue("uuid filter fails", filter.matches(mScanResult)); - - filter = mFilterBuilder.setServiceUuid( - ParcelUuid.fromString("0000110C-0000-1000-8000-00805F9B34FB")).build(); - assertFalse("uuid filter fails", filter.matches(mScanResult)); - - filter = mFilterBuilder - .setServiceUuid(ParcelUuid.fromString("0000110C-0000-1000-8000-00805F9B34FB"), - ParcelUuid.fromString("FFFFFFF0-FFFF-FFFF-FFFF-FFFFFFFFFFFF")) - .build(); - assertTrue("uuid filter fails", filter.matches(mScanResult)); - } - - @SmallTest - public void testsetServiceDataFilter() { - byte[] setServiceData = new byte[] { - 0x50, 0x64 }; - ParcelUuid serviceDataUuid = ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB"); - ScanFilter filter = mFilterBuilder.setServiceData(serviceDataUuid, setServiceData).build(); - assertTrue("service data filter fails", filter.matches(mScanResult)); - - byte[] emptyData = new byte[0]; - filter = mFilterBuilder.setServiceData(serviceDataUuid, emptyData).build(); - assertTrue("service data filter fails", filter.matches(mScanResult)); - - byte[] prefixData = new byte[] { - 0x50 }; - filter = mFilterBuilder.setServiceData(serviceDataUuid, prefixData).build(); - assertTrue("service data filter fails", filter.matches(mScanResult)); - - byte[] nonMatchData = new byte[] { - 0x51, 0x64 }; - byte[] mask = new byte[] { - (byte) 0x00, (byte) 0xFF }; - filter = mFilterBuilder.setServiceData(serviceDataUuid, nonMatchData, mask).build(); - assertTrue("partial service data filter fails", filter.matches(mScanResult)); - - filter = mFilterBuilder.setServiceData(serviceDataUuid, nonMatchData).build(); - assertFalse("service data filter fails", filter.matches(mScanResult)); - } - - @SmallTest - public void testManufacturerSpecificData() { - byte[] setManufacturerData = new byte[] { - 0x02, 0x15 }; - int manufacturerId = 0xE0; - ScanFilter filter = - mFilterBuilder.setManufacturerData(manufacturerId, setManufacturerData).build(); - assertTrue("manufacturer data filter fails", filter.matches(mScanResult)); - - byte[] emptyData = new byte[0]; - filter = mFilterBuilder.setManufacturerData(manufacturerId, emptyData).build(); - assertTrue("manufacturer data filter fails", filter.matches(mScanResult)); - - byte[] prefixData = new byte[] { - 0x02 }; - filter = mFilterBuilder.setManufacturerData(manufacturerId, prefixData).build(); - assertTrue("manufacturer data filter fails", filter.matches(mScanResult)); - - // Test data mask - byte[] nonMatchData = new byte[] { - 0x02, 0x14 }; - filter = mFilterBuilder.setManufacturerData(manufacturerId, nonMatchData).build(); - assertFalse("manufacturer data filter fails", filter.matches(mScanResult)); - byte[] mask = new byte[] { - (byte) 0xFF, (byte) 0x00 - }; - filter = mFilterBuilder.setManufacturerData(manufacturerId, nonMatchData, mask).build(); - assertTrue("partial setManufacturerData filter fails", filter.matches(mScanResult)); - } - - @SmallTest - public void testReadWriteParcel() { - ScanFilter filter = mFilterBuilder.build(); - testReadWriteParcelForFilter(filter); - - filter = mFilterBuilder.setDeviceName("Ped").build(); - testReadWriteParcelForFilter(filter); - - filter = mFilterBuilder.setDeviceAddress("11:22:33:44:55:66").build(); - testReadWriteParcelForFilter(filter); - - filter = mFilterBuilder.setServiceUuid( - ParcelUuid.fromString("0000110C-0000-1000-8000-00805F9B34FB")).build(); - testReadWriteParcelForFilter(filter); - - filter = mFilterBuilder.setServiceUuid( - ParcelUuid.fromString("0000110C-0000-1000-8000-00805F9B34FB"), - ParcelUuid.fromString("FFFFFFF0-FFFF-FFFF-FFFF-FFFFFFFFFFFF")).build(); - testReadWriteParcelForFilter(filter); - - byte[] serviceData = new byte[] { - 0x50, 0x64 }; - - ParcelUuid serviceDataUuid = ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB"); - filter = mFilterBuilder.setServiceData(serviceDataUuid, serviceData).build(); - testReadWriteParcelForFilter(filter); - - filter = mFilterBuilder.setServiceData(serviceDataUuid, new byte[0]).build(); - testReadWriteParcelForFilter(filter); - - byte[] serviceDataMask = new byte[] { - (byte) 0xFF, (byte) 0xFF }; - filter = mFilterBuilder.setServiceData(serviceDataUuid, serviceData, serviceDataMask) - .build(); - testReadWriteParcelForFilter(filter); - - byte[] manufacturerData = new byte[] { - 0x02, 0x15 }; - int manufacturerId = 0xE0; - filter = mFilterBuilder.setManufacturerData(manufacturerId, manufacturerData).build(); - testReadWriteParcelForFilter(filter); - - filter = mFilterBuilder.setServiceData(serviceDataUuid, new byte[0]).build(); - testReadWriteParcelForFilter(filter); - - byte[] manufacturerDataMask = new byte[] { - (byte) 0xFF, (byte) 0xFF - }; - filter = mFilterBuilder.setManufacturerData(manufacturerId, manufacturerData, - manufacturerDataMask).build(); - testReadWriteParcelForFilter(filter); - } - - private void testReadWriteParcelForFilter(ScanFilter filter) { - Parcel parcel = Parcel.obtain(); - filter.writeToParcel(parcel, 0); - parcel.setDataPosition(0); - ScanFilter filterFromParcel = - ScanFilter.CREATOR.createFromParcel(parcel); - assertEquals(filter, filterFromParcel); - } -} diff --git a/core/tests/bluetoothtests/src/android/bluetooth/le/ScanRecordTest.java b/core/tests/bluetoothtests/src/android/bluetooth/le/ScanRecordTest.java deleted file mode 100644 index 4e817d4a0d91..000000000000 --- a/core/tests/bluetoothtests/src/android/bluetooth/le/ScanRecordTest.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (C) 2014 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.bluetooth.le; - -import android.os.ParcelUuid; -import android.test.suitebuilder.annotation.SmallTest; - -import com.android.internal.util.HexDump; -import com.android.modules.utils.BytesMatcher; - -import junit.framework.TestCase; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.function.Predicate; - -/** - * Unit test cases for {@link ScanRecord}. - * <p> - * To run this test, use adb shell am instrument -e class 'android.bluetooth.ScanRecordTest' -w - * 'com.android.bluetooth.tests/android.bluetooth.BluetoothTestRunner' - */ -public class ScanRecordTest extends TestCase { - /** - * Example raw beacons captured from a Blue Charm BC011 - */ - private static final String RECORD_URL = "0201060303AAFE1716AAFE10EE01626C7565636861726D626561636F6E730009168020691E0EFE13551109426C7565436861726D5F313639363835000000"; - private static final String RECORD_UUID = "0201060303AAFE1716AAFE00EE626C7565636861726D31000000000001000009168020691E0EFE13551109426C7565436861726D5F313639363835000000"; - private static final String RECORD_TLM = "0201060303AAFE1116AAFE20000BF017000008874803FB93540916802069080EFE13551109426C7565436861726D5F313639363835000000000000000000"; - private static final String RECORD_IBEACON = "0201061AFF4C000215426C7565436861726D426561636F6E730EFE1355C509168020691E0EFE13551109426C7565436861726D5F31363936383500000000"; - - @SmallTest - public void testMatchesAnyField_Eddystone_Parser() { - final List<String> found = new ArrayList<>(); - final Predicate<byte[]> matcher = (v) -> { - found.add(HexDump.toHexString(v)); - return false; - }; - ScanRecord.parseFromBytes(HexDump.hexStringToByteArray(RECORD_URL)) - .matchesAnyField(matcher); - - assertEquals(Arrays.asList( - "020106", - "0303AAFE", - "1716AAFE10EE01626C7565636861726D626561636F6E7300", - "09168020691E0EFE1355", - "1109426C7565436861726D5F313639363835"), found); - } - - @SmallTest - public void testMatchesAnyField_Eddystone() { - final BytesMatcher matcher = BytesMatcher.decode("⊆0016AAFE/00FFFFFF"); - assertMatchesAnyField(RECORD_URL, matcher); - assertMatchesAnyField(RECORD_UUID, matcher); - assertMatchesAnyField(RECORD_TLM, matcher); - assertNotMatchesAnyField(RECORD_IBEACON, matcher); - } - - @SmallTest - public void testMatchesAnyField_iBeacon_Parser() { - final List<String> found = new ArrayList<>(); - final Predicate<byte[]> matcher = (v) -> { - found.add(HexDump.toHexString(v)); - return false; - }; - ScanRecord.parseFromBytes(HexDump.hexStringToByteArray(RECORD_IBEACON)) - .matchesAnyField(matcher); - - assertEquals(Arrays.asList( - "020106", - "1AFF4C000215426C7565436861726D426561636F6E730EFE1355C5", - "09168020691E0EFE1355", - "1109426C7565436861726D5F313639363835"), found); - } - - @SmallTest - public void testMatchesAnyField_iBeacon() { - final BytesMatcher matcher = BytesMatcher.decode("⊆00FF4C0002/00FFFFFFFF"); - assertNotMatchesAnyField(RECORD_URL, matcher); - assertNotMatchesAnyField(RECORD_UUID, matcher); - assertNotMatchesAnyField(RECORD_TLM, matcher); - assertMatchesAnyField(RECORD_IBEACON, matcher); - } - - @SmallTest - public void testParser() { - byte[] scanRecord = new byte[] { - 0x02, 0x01, 0x1a, // advertising flags - 0x05, 0x02, 0x0b, 0x11, 0x0a, 0x11, // 16 bit service uuids - 0x04, 0x09, 0x50, 0x65, 0x64, // name - 0x02, 0x0A, (byte) 0xec, // tx power level - 0x05, 0x16, 0x0b, 0x11, 0x50, 0x64, // service data - 0x05, (byte) 0xff, (byte) 0xe0, 0x00, 0x02, 0x15, // manufacturer specific data - 0x03, 0x50, 0x01, 0x02, // an unknown data type won't cause trouble - }; - ScanRecord data = ScanRecord.parseFromBytes(scanRecord); - assertEquals(0x1a, data.getAdvertiseFlags()); - ParcelUuid uuid1 = ParcelUuid.fromString("0000110A-0000-1000-8000-00805F9B34FB"); - ParcelUuid uuid2 = ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB"); - assertTrue(data.getServiceUuids().contains(uuid1)); - assertTrue(data.getServiceUuids().contains(uuid2)); - - assertEquals("Ped", data.getDeviceName()); - assertEquals(-20, data.getTxPowerLevel()); - - assertTrue(data.getManufacturerSpecificData().get(0x00E0) != null); - assertArrayEquals(new byte[] { - 0x02, 0x15 }, data.getManufacturerSpecificData().get(0x00E0)); - - assertTrue(data.getServiceData().containsKey(uuid2)); - assertArrayEquals(new byte[] { - 0x50, 0x64 }, data.getServiceData().get(uuid2)); - } - - // Assert two byte arrays are equal. - private static void assertArrayEquals(byte[] expected, byte[] actual) { - if (!Arrays.equals(expected, actual)) { - fail("expected:<" + Arrays.toString(expected) + - "> but was:<" + Arrays.toString(actual) + ">"); - } - - } - - private static void assertMatchesAnyField(String record, BytesMatcher matcher) { - assertTrue(ScanRecord.parseFromBytes(HexDump.hexStringToByteArray(record)) - .matchesAnyField(matcher)); - } - - private static void assertNotMatchesAnyField(String record, BytesMatcher matcher) { - assertFalse(ScanRecord.parseFromBytes(HexDump.hexStringToByteArray(record)) - .matchesAnyField(matcher)); - } -} diff --git a/core/tests/bluetoothtests/src/android/bluetooth/le/ScanResultTest.java b/core/tests/bluetoothtests/src/android/bluetooth/le/ScanResultTest.java deleted file mode 100644 index 01d5c593bf27..000000000000 --- a/core/tests/bluetoothtests/src/android/bluetooth/le/ScanResultTest.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2014 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.bluetooth.le; - -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.os.Parcel; -import android.test.suitebuilder.annotation.SmallTest; - -import junit.framework.TestCase; - -/** - * Unit test cases for Bluetooth LE scans. - * <p> - * To run this test, use adb shell am instrument -e class 'android.bluetooth.ScanResultTest' -w - * 'com.android.bluetooth.tests/android.bluetooth.BluetoothTestRunner' - */ -public class ScanResultTest extends TestCase { - - /** - * Test read and write parcel of ScanResult - */ - @SmallTest - public void testScanResultParceling() { - BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice( - "01:02:03:04:05:06"); - byte[] scanRecord = new byte[] { - 1, 2, 3 }; - int rssi = -10; - long timestampMicros = 10000L; - - ScanResult result = new ScanResult(device, ScanRecord.parseFromBytes(scanRecord), rssi, - timestampMicros); - Parcel parcel = Parcel.obtain(); - result.writeToParcel(parcel, 0); - // Need to reset parcel data position to the beginning. - parcel.setDataPosition(0); - ScanResult resultFromParcel = ScanResult.CREATOR.createFromParcel(parcel); - assertEquals(result, resultFromParcel); - } - -} diff --git a/core/tests/bluetoothtests/src/android/bluetooth/le/ScanSettingsTest.java b/core/tests/bluetoothtests/src/android/bluetooth/le/ScanSettingsTest.java deleted file mode 100644 index 7c42c3b46775..000000000000 --- a/core/tests/bluetoothtests/src/android/bluetooth/le/ScanSettingsTest.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2014 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.bluetooth.le; - -import android.test.suitebuilder.annotation.SmallTest; - -import junit.framework.TestCase; - -/** - * Test for Bluetooth LE {@link ScanSettings}. - */ -public class ScanSettingsTest extends TestCase { - - @SmallTest - public void testCallbackType() { - ScanSettings.Builder builder = new ScanSettings.Builder(); - builder.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES); - builder.setCallbackType(ScanSettings.CALLBACK_TYPE_FIRST_MATCH); - builder.setCallbackType(ScanSettings.CALLBACK_TYPE_MATCH_LOST); - builder.setCallbackType( - ScanSettings.CALLBACK_TYPE_FIRST_MATCH | ScanSettings.CALLBACK_TYPE_MATCH_LOST); - try { - builder.setCallbackType( - ScanSettings.CALLBACK_TYPE_ALL_MATCHES | ScanSettings.CALLBACK_TYPE_MATCH_LOST); - fail("should have thrown IllegalArgumentException!"); - } catch (IllegalArgumentException e) { - // nothing to do - } - - try { - builder.setCallbackType( - ScanSettings.CALLBACK_TYPE_ALL_MATCHES | - ScanSettings.CALLBACK_TYPE_FIRST_MATCH); - fail("should have thrown IllegalArgumentException!"); - } catch (IllegalArgumentException e) { - // nothing to do - } - - try { - builder.setCallbackType( - ScanSettings.CALLBACK_TYPE_ALL_MATCHES | - ScanSettings.CALLBACK_TYPE_FIRST_MATCH | - ScanSettings.CALLBACK_TYPE_MATCH_LOST); - fail("should have thrown IllegalArgumentException!"); - } catch (IllegalArgumentException e) { - // nothing to do - } - - } -} diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp index c18a70c5dd2a..c1f3c4fc12c7 100644 --- a/core/tests/coretests/Android.bp +++ b/core/tests/coretests/Android.bp @@ -93,33 +93,9 @@ android_test { java_genrule { name: "FrameworksCoreTests_apks_as_resources", srcs: [ - ":FrameworksCoreTests_install", - ":FrameworksCoreTests_install_bad_dex", - ":FrameworksCoreTests_install_complete_package_info", - ":FrameworksCoreTests_install_decl_perm", ":FrameworksCoreTests_install_jni_lib_open_from_apk", - ":FrameworksCoreTests_install_loc_auto", - ":FrameworksCoreTests_install_loc_internal", - ":FrameworksCoreTests_install_loc_sdcard", - ":FrameworksCoreTests_install_loc_unspecified", - ":FrameworksCoreTests_install_use_perm_good", - ":FrameworksCoreTests_install_uses_feature", ":FrameworksCoreTests_install_verifier_bad", ":FrameworksCoreTests_install_verifier_good", - ":FrameworksCoreTests_keyset_permdef_sa_unone", - ":FrameworksCoreTests_keyset_permuse_sa_ua_ub", - ":FrameworksCoreTests_keyset_permuse_sb_ua_ub", - ":FrameworksCoreTests_keyset_sab_ua", - ":FrameworksCoreTests_keyset_sa_ua", - ":FrameworksCoreTests_keyset_sa_uab", - ":FrameworksCoreTests_keyset_sa_ua_ub", - ":FrameworksCoreTests_keyset_sa_ub", - ":FrameworksCoreTests_keyset_sa_unone", - ":FrameworksCoreTests_keyset_sau_ub", - ":FrameworksCoreTests_keyset_sb_ua", - ":FrameworksCoreTests_keyset_sb_ub", - ":FrameworksCoreTests_keyset_splata_api", - ":FrameworksCoreTests_keyset_splat_api", ":FrameworksCoreTests_locales", ":FrameworksCoreTests_overlay_config", ":FrameworksCoreTests_version_1", @@ -173,4 +149,4 @@ android_library { "framework", "framework-res", ], -}
\ No newline at end of file +} diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml index 14a3a01630ab..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"/> @@ -1670,11 +1672,4 @@ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" android:targetPackage="com.android.frameworks.coretests" android:label="Frameworks Core Tests" /> - <key-sets> - <key-set android:name="A" > - <public-key android:name="keyA" - android:value="MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsMpNthdOxud7roPDZMMomOqXgJJdRfIWpkKEqmC61Mv+Nf6QY3TorEwJeghjSmqj7IbBKrtvfQq4E2XJO1HuspmQO4Ng2gvn+r+6EwNfKc9k55d6s+27SR867jKurBbHNtZMG+tjL1yH4r+tNzcuJCsgyAFqLmxFdcxEwzNvREyRpoYc5RDR0mmTwkMCUhJ6CId1EYEKiCEdNzxv+fWPEb21u+/MWpleGCILs8kglRVb2q/WOzAAvGr4FY5plfaE6N+lr7+UschQ+aMi1+uqewo2o0qPFVmZP5hnwj55K4UMzu/NhhDqQQsX4cSGES1KgHo5MTqRqZjN/I7emw5pFQIDAQAB"/> - </key-set> - <upgrade-key-set android:name="A"/> - </key-sets> </manifest> diff --git a/core/tests/coretests/apks/install_loc_sdcard/Android.bp b/core/tests/coretests/apks/install_loc_sdcard/Android.bp deleted file mode 100644 index 708e655e07db..000000000000 --- a/core/tests/coretests/apks/install_loc_sdcard/Android.bp +++ /dev/null @@ -1,15 +0,0 @@ -package { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "frameworks_base_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["frameworks_base_license"], -} - -android_test_helper_app { - name: "FrameworksCoreTests_install_loc_sdcard", - defaults: ["FrameworksCoreTests_apks_defaults"], - - srcs: ["**/*.java"], -} diff --git a/core/tests/coretests/apks/install_loc_unspecified/Android.bp b/core/tests/coretests/apks/install_loc_unspecified/Android.bp deleted file mode 100644 index 76869e9b9ed5..000000000000 --- a/core/tests/coretests/apks/install_loc_unspecified/Android.bp +++ /dev/null @@ -1,15 +0,0 @@ -package { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "frameworks_base_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["frameworks_base_license"], -} - -android_test_helper_app { - name: "FrameworksCoreTests_install_loc_unspecified", - defaults: ["FrameworksCoreTests_apks_defaults"], - - srcs: ["**/*.java"], -} diff --git a/core/tests/coretests/apks/install_use_perm_good/Android.bp b/core/tests/coretests/apks/install_use_perm_good/Android.bp deleted file mode 100644 index 89700ddb94be..000000000000 --- a/core/tests/coretests/apks/install_use_perm_good/Android.bp +++ /dev/null @@ -1,15 +0,0 @@ -package { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "frameworks_base_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["frameworks_base_license"], -} - -android_test_helper_app { - name: "FrameworksCoreTests_install_use_perm_good", - defaults: ["FrameworksCoreTests_apks_defaults"], - - srcs: ["**/*.java"], -} diff --git a/core/tests/coretests/apks/install_uses_feature/Android.bp b/core/tests/coretests/apks/install_uses_feature/Android.bp deleted file mode 100644 index 913a96a1cd27..000000000000 --- a/core/tests/coretests/apks/install_uses_feature/Android.bp +++ /dev/null @@ -1,15 +0,0 @@ -package { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "frameworks_base_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["frameworks_base_license"], -} - -android_test_helper_app { - name: "FrameworksCoreTests_install_uses_feature", - defaults: ["FrameworksCoreTests_apks_defaults"], - - srcs: ["**/*.java"], -} diff --git a/core/tests/coretests/apks/keyset/Android.bp b/core/tests/coretests/apks/keyset/Android.bp deleted file mode 100644 index 93c3b1f60327..000000000000 --- a/core/tests/coretests/apks/keyset/Android.bp +++ /dev/null @@ -1,129 +0,0 @@ -//apks signed by keyset_A -package { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "frameworks_base_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["frameworks_base_license"], -} - -android_test_helper_app { - name: "FrameworksCoreTests_keyset_sa_unone", - defaults: ["FrameworksCoreTests_apks_defaults"], - srcs: ["**/*.java"], - certificate: ":FrameworksCoreTests_keyset_A_cert", - manifest: "uNone/AndroidManifest.xml", -} - -android_test_helper_app { - name: "FrameworksCoreTests_keyset_sa_ua", - defaults: ["FrameworksCoreTests_apks_defaults"], - srcs: ["**/*.java"], - certificate: ":FrameworksCoreTests_keyset_A_cert", - manifest: "uA/AndroidManifest.xml", -} - -android_test_helper_app { - name: "FrameworksCoreTests_keyset_sa_ub", - defaults: ["FrameworksCoreTests_apks_defaults"], - srcs: ["**/*.java"], - certificate: ":FrameworksCoreTests_keyset_A_cert", - manifest: "uB/AndroidManifest.xml", -} - -android_test_helper_app { - name: "FrameworksCoreTests_keyset_sa_uab", - defaults: ["FrameworksCoreTests_apks_defaults"], - srcs: ["**/*.java"], - certificate: ":FrameworksCoreTests_keyset_A_cert", - manifest: "uAB/AndroidManifest.xml", -} - -android_test_helper_app { - name: "FrameworksCoreTests_keyset_sa_ua_ub", - defaults: ["FrameworksCoreTests_apks_defaults"], - srcs: ["**/*.java"], - certificate: ":FrameworksCoreTests_keyset_A_cert", - manifest: "uAuB/AndroidManifest.xml", -} - -android_test_helper_app { - name: "FrameworksCoreTests_keyset_permdef_sa_unone", - defaults: ["FrameworksCoreTests_apks_defaults"], - srcs: ["**/*.java"], - certificate: ":FrameworksCoreTests_keyset_A_cert", - manifest: "permDef/AndroidManifest.xml", -} - -android_test_helper_app { - name: "FrameworksCoreTests_keyset_permuse_sa_ua_ub", - defaults: ["FrameworksCoreTests_apks_defaults"], - srcs: ["**/*.java"], - certificate: ":FrameworksCoreTests_keyset_A_cert", - manifest: "permUse/AndroidManifest.xml", -} - -//apks signed by keyset_B -android_test_helper_app { - name: "FrameworksCoreTests_keyset_sb_ua", - defaults: ["FrameworksCoreTests_apks_defaults"], - srcs: ["**/*.java"], - certificate: ":FrameworksCoreTests_keyset_B_cert", - manifest: "uA/AndroidManifest.xml", -} - -android_test_helper_app { - name: "FrameworksCoreTests_keyset_sb_ub", - defaults: ["FrameworksCoreTests_apks_defaults"], - srcs: ["**/*.java"], - certificate: ":FrameworksCoreTests_keyset_B_cert", - manifest: "uB/AndroidManifest.xml", -} - -android_test_helper_app { - name: "FrameworksCoreTests_keyset_permuse_sb_ua_ub", - defaults: ["FrameworksCoreTests_apks_defaults"], - srcs: ["**/*.java"], - certificate: ":FrameworksCoreTests_keyset_B_cert", - manifest: "permUse/AndroidManifest.xml", -} - -//apks signed by keyset_A and keyset_B -android_test_helper_app { - name: "FrameworksCoreTests_keyset_sab_ua", - defaults: ["FrameworksCoreTests_apks_defaults"], - srcs: ["**/*.java"], - certificate: ":FrameworksCoreTests_keyset_A_cert", - additional_certificates: [":FrameworksCoreTests_keyset_B_cert"], - manifest: "uA/AndroidManifest.xml", -} - -//apks signed by keyset_A and unit_test -android_test_helper_app { - name: "FrameworksCoreTests_keyset_sau_ub", - defaults: ["FrameworksCoreTests_apks_defaults"], - srcs: ["**/*.java"], - certificate: ":FrameworksCoreTests_keyset_A_cert", - additional_certificates: [":FrameworksCoreTests_keyset_B_cert"], - manifest: "uB/AndroidManifest.xml", -} - -//apks signed by platform only -android_test_helper_app { - name: "FrameworksCoreTests_keyset_splat_api", - defaults: ["FrameworksCoreTests_apks_defaults"], - srcs: ["**/*.java"], - certificate: "platform", - manifest: "api_test/AndroidManifest.xml", -} - -//apks signed by platform and keyset_A -android_test_helper_app { - name: "FrameworksCoreTests_keyset_splata_api", - defaults: ["FrameworksCoreTests_apks_defaults"], - srcs: ["**/*.java"], - certificate: "platform", - additional_certificates: [":FrameworksCoreTests_keyset_A_cert"], - manifest: "api_test/AndroidManifest.xml", -} diff --git a/core/tests/coretests/certs/Android.bp b/core/tests/coretests/certs/Android.bp index 8411183c3335..8d4ecf4253c3 100644 --- a/core/tests/coretests/certs/Android.bp +++ b/core/tests/coretests/certs/Android.bp @@ -10,16 +10,6 @@ package { } android_app_certificate { - name: "FrameworksCoreTests_keyset_A_cert", - certificate: "keyset_A", -} - -android_app_certificate { - name: "FrameworksCoreTests_keyset_B_cert", - certificate: "keyset_B", -} - -android_app_certificate { name: "FrameworksCoreTests_unit_test_cert", certificate: "unit_test", } 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/app/PropertyInvalidatedCacheTests.java b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java index fd3079fd295d..d3e8bb0ec317 100644 --- a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java +++ b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java @@ -17,6 +17,8 @@ package android.app; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertSame; import androidx.test.filters.SmallTest; @@ -25,7 +27,9 @@ import org.junit.Test; /** * Test for verifying the behavior of {@link PropertyInvalidatedCache}. This test does - * not use any actual binder calls - it is entirely self-contained. + * not use any actual binder calls - it is entirely self-contained. This test also relies + * on the test mode of {@link PropertyInvalidatedCache} because Android SELinux rules do + * not grant test processes the permission to set system properties. * <p> * Build/Install/Run: * atest FrameworksCoreTests:PropertyInvalidatedCacheTests @@ -33,6 +37,8 @@ import org.junit.Test; @SmallTest public class PropertyInvalidatedCacheTests { + // This property is never set. The test process does not have permission to set any + // properties. static final String CACHE_PROPERTY = "cache_key.cache_test_a"; // This class is a proxy for binder calls. It contains a counter that increments @@ -58,7 +64,8 @@ public class PropertyInvalidatedCacheTests { } } - // Clear the test mode after every test, in case this process is used for other tests. + // Clear the test mode after every test, in case this process is used for other + // tests. This also resets the test property map. @After public void tearDown() throws Exception { PropertyInvalidatedCache.setTestMode(false); @@ -176,5 +183,161 @@ public class PropertyInvalidatedCacheTests { } }; assertEquals(true, cache1.getDisabledState()); + + // Remove the record of caches being locally disabled. This is a clean-up step. + cache1.clearDisableLocal(); + assertEquals(true, cache1.getDisabledState()); + assertEquals(true, cache2.getDisabledState()); + assertEquals(false, cache3.getDisabledState()); + + // Create a new cache1. Verify that the new instance is not disabled. + cache1 = new PropertyInvalidatedCache<>(4, CACHE_PROPERTY) { + @Override + public Boolean recompute(Integer x) { + return tester.query(x); + } + }; + assertEquals(false, cache1.getDisabledState()); + } + + private static final String UNSET_KEY = "Aiw7woh6ie4toh7W"; + + private static class TestCache extends PropertyInvalidatedCache<Integer, String> { + TestCache() { + this(CACHE_PROPERTY); + } + + TestCache(String key) { + super(4, key); + setTestMode(true); + testPropertyName(key); + } + + @Override + public String recompute(Integer qv) { + mRecomputeCount += 1; + return "foo" + qv.toString(); + } + + int getRecomputeCount() { + return mRecomputeCount; + } + + private int mRecomputeCount = 0; + } + + @Test + public void testCacheRecompute() { + TestCache cache = new TestCache(); + cache.invalidateCache(); + assertEquals(cache.isDisabledLocal(), false); + assertEquals("foo5", cache.query(5)); + assertEquals(1, cache.getRecomputeCount()); + assertEquals("foo5", cache.query(5)); + assertEquals(1, cache.getRecomputeCount()); + assertEquals("foo6", cache.query(6)); + assertEquals(2, cache.getRecomputeCount()); + cache.invalidateCache(); + assertEquals("foo5", cache.query(5)); + assertEquals("foo5", cache.query(5)); + assertEquals(3, cache.getRecomputeCount()); + } + + @Test + public void testCacheInitialState() { + TestCache cache = new TestCache(); + assertEquals("foo5", cache.query(5)); + assertEquals("foo5", cache.query(5)); + assertEquals(2, cache.getRecomputeCount()); + cache.invalidateCache(); + assertEquals("foo5", cache.query(5)); + assertEquals("foo5", cache.query(5)); + assertEquals(3, cache.getRecomputeCount()); + } + + @Test + public void testCachePropertyUnset() { + TestCache cache = new TestCache(UNSET_KEY); + assertEquals("foo5", cache.query(5)); + assertEquals("foo5", cache.query(5)); + assertEquals(2, cache.getRecomputeCount()); + } + + @Test + public void testCacheDisableState() { + TestCache cache = new TestCache(); + assertEquals("foo5", cache.query(5)); + assertEquals("foo5", cache.query(5)); + assertEquals(2, cache.getRecomputeCount()); + cache.invalidateCache(); + assertEquals("foo5", cache.query(5)); + assertEquals("foo5", cache.query(5)); + assertEquals(3, cache.getRecomputeCount()); + cache.disableSystemWide(); + assertEquals("foo5", cache.query(5)); + assertEquals("foo5", cache.query(5)); + assertEquals(5, cache.getRecomputeCount()); + cache.invalidateCache(); // Should not reenable + assertEquals("foo5", cache.query(5)); + assertEquals("foo5", cache.query(5)); + assertEquals(7, cache.getRecomputeCount()); + } + + @Test + public void testRefreshSameObject() { + int[] refreshCount = new int[1]; + TestCache cache = new TestCache() { + @Override + public String refresh(String oldResult, Integer query) { + refreshCount[0] += 1; + return oldResult; + } + }; + cache.invalidateCache(); + String result1 = cache.query(5); + assertEquals("foo5", result1); + String result2 = cache.query(5); + assertSame(result1, result2); + assertEquals(1, cache.getRecomputeCount()); + assertEquals(1, refreshCount[0]); + assertEquals("foo5", cache.query(5)); + assertEquals(2, refreshCount[0]); + } + + @Test + public void testRefreshInvalidateRace() { + int[] refreshCount = new int[1]; + TestCache cache = new TestCache() { + @Override + public String refresh(String oldResult, Integer query) { + refreshCount[0] += 1; + invalidateCache(); + return new String(oldResult); + } + }; + cache.invalidateCache(); + String result1 = cache.query(5); + assertEquals("foo5", result1); + String result2 = cache.query(5); + assertEquals(result1, result2); + assertNotSame(result1, result2); + assertEquals(2, cache.getRecomputeCount()); + } + + @Test + public void testLocalProcessDisable() { + TestCache cache = new TestCache(); + assertEquals(cache.isDisabledLocal(), false); + cache.invalidateCache(); + assertEquals("foo5", cache.query(5)); + assertEquals(1, cache.getRecomputeCount()); + assertEquals("foo5", cache.query(5)); + assertEquals(1, cache.getRecomputeCount()); + assertEquals(cache.isDisabledLocal(), false); + cache.disableLocal(); + assertEquals(cache.isDisabledLocal(), true); + assertEquals("foo5", cache.query(5)); + assertEquals("foo5", cache.query(5)); + assertEquals(3, cache.getRecomputeCount()); } } diff --git a/core/tests/coretests/src/android/text/MeasuredParagraphTest.java b/core/tests/coretests/src/android/text/MeasuredParagraphTest.java index d6a7682475f2..045e746281a6 100644 --- a/core/tests/coretests/src/android/text/MeasuredParagraphTest.java +++ b/core/tests/coretests/src/android/text/MeasuredParagraphTest.java @@ -136,8 +136,8 @@ public class MeasuredParagraphTest { MeasuredParagraph mt = null; mt = MeasuredParagraph.buildForStaticLayout( - PAINT, "XXX", 0, 3, LTR, MeasuredText.Builder.HYPHENATION_MODE_NONE, false, - null /* no hint */, null); + PAINT, null /* line break config */, "XXX", 0, 3, LTR, + MeasuredText.Builder.HYPHENATION_MODE_NONE, false, null /* no hint */, null); assertNotNull(mt); assertNotNull(mt.getChars()); assertEquals("XXX", charsToString(mt.getChars())); @@ -152,8 +152,8 @@ public class MeasuredParagraphTest { // Recycle it MeasuredParagraph mt2 = MeasuredParagraph.buildForStaticLayout( - PAINT, "_VVV_", 1, 4, RTL, MeasuredText.Builder.HYPHENATION_MODE_NONE, false, - null /* no hint */, mt); + PAINT, null /* line break config */, "_VVV_", 1, 4, RTL, + MeasuredText.Builder.HYPHENATION_MODE_NONE, false, null /* no hint */, mt); assertEquals(mt2, mt); assertNotNull(mt2.getChars()); assertEquals("VVV", charsToString(mt.getChars())); 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/com/android/internal/content/res/OverlayConfigIterationRule.java b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigIterationRule.java index 2c31b08bebdc..187803c90084 100644 --- a/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigIterationRule.java +++ b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigIterationRule.java @@ -20,8 +20,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.when; -import android.content.pm.parsing.ParsingPackageRead; -import android.content.pm.parsing.ParsingPackageUtils; +import android.content.pm.parsing.FrameworkParsingPackageUtils; import android.os.Build; import android.text.TextUtils; import android.util.ArrayMap; @@ -80,7 +79,7 @@ public class OverlayConfigIterationRule implements TestRule { } public boolean isMatchRequiredSystemProperty() { - return ParsingPackageUtils.checkRequiredSystemProperties( + return FrameworkParsingPackageUtils.checkRequiredSystemProperties( requiredSystemPropertyName, requiredSystemPropertyValue); } } @@ -174,11 +173,12 @@ public class OverlayConfigIterationRule implements TestRule { mIteration = Iteration.SYSTEM_SERVER; doAnswer((InvocationOnMock invocation) -> { final Object[] args = invocation.getArguments(); - final TriConsumer<ParsingPackageRead, Boolean, File> f = - (TriConsumer<ParsingPackageRead, Boolean, File>) args[0]; + final TriConsumer<PackageProvider.Package, Boolean, File> f = + (TriConsumer<PackageProvider.Package, Boolean, File>) args[0]; for (Map.Entry<File, TestOverlayInfo> overlay : mTestOverlayInfos.entrySet()) { - final ParsingPackageRead a = Mockito.mock(ParsingPackageRead.class); + final PackageProvider.Package a = + Mockito.mock(PackageProvider.Package.class); final TestOverlayInfo info = overlay.getValue(); if ((!TextUtils.isEmpty(info.requiredSystemPropertyName) || !TextUtils.isEmpty(info.requiredSystemPropertyValue)) 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 69e617aaac19..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(5500); + 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..1efd78bc13fc 100644 --- a/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java +++ b/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java @@ -21,25 +21,43 @@ 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, true); } @Test @@ -67,4 +85,396 @@ public class PowerProfileTest extends TestCase { assertEquals(150.0, mProfile.getAveragePower(PowerProfile.POWER_VIDEO)); } + @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 e7ce9a03f18a..a36839910742 100644 --- a/core/tests/coretests/src/com/android/internal/os/WifiPowerCalculatorTest.java +++ b/core/tests/coretests/src/com/android/internal/os/WifiPowerCalculatorTest.java @@ -25,6 +25,8 @@ import android.app.usage.NetworkStatsManager; import android.net.NetworkCapabilities; import android.net.NetworkStats; import android.os.BatteryConsumer; +import android.os.BatteryStats; +import android.os.BatteryUsageStatsQuery; import android.os.Process; import android.os.UidBatteryConsumer; import android.os.WorkSource; @@ -40,6 +42,7 @@ import org.mockito.Mock; @RunWith(AndroidJUnit4.class) @SmallTest +@SuppressWarnings("GuardedBy") public class WifiPowerCalculatorTest { private static final double PRECISION = 0.00001; @@ -66,14 +69,18 @@ public class WifiPowerCalculatorTest { batteryStats.noteNetworkInterfaceForTransports("wifi", new int[]{NetworkCapabilities.TRANSPORT_WIFI}); - NetworkStats networkStats = new NetworkStats(10000, 1) - .insertEntry("wifi", APP_UID, 0, 0, 1000, 100, 2000, 20, 100) - .insertEntry("wifi", Process.WIFI_UID, 0, 0, 1111, 111, 2222, 22, 111); - mStatsRule.setNetworkStats(networkStats); + mStatsRule.setNetworkStats(buildNetworkStats(10000, 1000, 100, 2000, 20)); return batteryStats; } + private NetworkStats buildNetworkStats(long elapsedRealtime, int rxBytes, int rxPackets, + int txBytes, int txPackets) { + return new NetworkStats(elapsedRealtime, 1) + .insertEntry("wifi", APP_UID, 0, 0, rxBytes, rxPackets, txBytes, txPackets, 100) + .insertEntry("wifi", Process.WIFI_UID, 0, 0, 1111, 111, 2222, 22, 111); + } + /** Sets up an WifiActivityEnergyInfo for ActivityController-model-based tests. */ private WifiActivityEnergyInfo setupPowerControllerBasedModelEnergyNumbersInfo() { return new WifiActivityEnergyInfo(10000, @@ -85,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()); @@ -93,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)) @@ -115,6 +125,61 @@ public class WifiPowerCalculatorTest { } @Test + public void testPowerControllerBasedModel_powerProfile_byProcessState() { + final BatteryStatsImpl batteryStats = setupTestNetworkNumbers(); + + mStatsRule.setTime(1000, 1000); + + BatteryStatsImpl.Uid uid = batteryStats.getUidStatsLocked(APP_UID); + uid.setProcessStateForTest( + BatteryStats.Uid.PROCESS_STATE_FOREGROUND, 1000); + + batteryStats.updateWifiState(new WifiActivityEnergyInfo(2000, + WifiActivityEnergyInfo.STACK_STATE_STATE_ACTIVE, 1000, 2000, 3000, 4000), + POWER_DATA_UNAVAILABLE, 2000, 2000, + mNetworkStatsManager); + + uid.setProcessStateForTest( + BatteryStats.Uid.PROCESS_STATE_BACKGROUND, 3000); + + mStatsRule.setNetworkStats(buildNetworkStats(4000, 5000, 200, 7000, 80)); + + batteryStats.updateWifiState(new WifiActivityEnergyInfo(4000, + WifiActivityEnergyInfo.STACK_STATE_STATE_ACTIVE, 5000, 6000, 7000, 8000), + POWER_DATA_UNAVAILABLE, 4000, 4000, + mNetworkStatsManager); + + WifiPowerCalculator calculator = new WifiPowerCalculator(mStatsRule.getPowerProfile()); + mStatsRule.apply(new BatteryUsageStatsQuery.Builder() + .powerProfileModeledOnly() + .includePowerModels() + .includeProcessStateData() + .build(), calculator); + + UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID); + assertThat(uidConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_WIFI)) + .isEqualTo(12423); + assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI)) + .isWithin(PRECISION).of(2.0214666); + assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI)) + .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE); + + final BatteryConsumer.Key foreground = uidConsumer.getKey( + BatteryConsumer.POWER_COMPONENT_WIFI, + BatteryConsumer.PROCESS_STATE_FOREGROUND); + final BatteryConsumer.Key background = uidConsumer.getKey( + BatteryConsumer.POWER_COMPONENT_WIFI, + BatteryConsumer.PROCESS_STATE_BACKGROUND); + final BatteryConsumer.Key fgs = uidConsumer.getKey( + BatteryConsumer.POWER_COMPONENT_WIFI, + BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE); + + assertThat(uidConsumer.getConsumedPower(foreground)).isWithin(PRECISION).of(1.1214666); + assertThat(uidConsumer.getConsumedPower(background)).isWithin(PRECISION).of(0.9); + assertThat(uidConsumer.getConsumedPower(fgs)).isWithin(PRECISION).of(0); + } + + @Test public void testPowerControllerBasedModel_measured() { final BatteryStatsImpl batteryStats = setupTestNetworkNumbers(); final WifiActivityEnergyInfo energyInfo = setupPowerControllerBasedModelEnergyNumbersInfo(); @@ -148,6 +213,60 @@ public class WifiPowerCalculatorTest { .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY); } + @Test + public void testPowerControllerBasedModel_measured_byProcessState() { + final BatteryStatsImpl batteryStats = setupTestNetworkNumbers(); + + mStatsRule.setTime(1000, 1000); + + BatteryStatsImpl.Uid uid = batteryStats.getUidStatsLocked(APP_UID); + uid.setProcessStateForTest( + BatteryStats.Uid.PROCESS_STATE_FOREGROUND, 1000); + + batteryStats.updateWifiState(new WifiActivityEnergyInfo(2000, + WifiActivityEnergyInfo.STACK_STATE_STATE_ACTIVE, 1000, 2000, 3000, 4000), + 1_000_000, 2000, 2000, + mNetworkStatsManager); + + uid.setProcessStateForTest( + BatteryStats.Uid.PROCESS_STATE_BACKGROUND, 3000); + + mStatsRule.setNetworkStats(buildNetworkStats(4000, 5000, 200, 7000, 80)); + + batteryStats.updateWifiState(new WifiActivityEnergyInfo(4000, + WifiActivityEnergyInfo.STACK_STATE_STATE_ACTIVE, 5000, 6000, 7000, 8000), + 5_000_000, 4000, 4000, + mNetworkStatsManager); + + WifiPowerCalculator calculator = new WifiPowerCalculator(mStatsRule.getPowerProfile()); + mStatsRule.apply(new BatteryUsageStatsQuery.Builder() + .includePowerModels() + .includeProcessStateData() + .build(), calculator); + + UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID); + assertThat(uidConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_WIFI)) + .isEqualTo(12423); + assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI)) + .isWithin(PRECISION).of(1.0325211); + assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI)) + .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY); + + final BatteryConsumer.Key foreground = uidConsumer.getKey( + BatteryConsumer.POWER_COMPONENT_WIFI, + BatteryConsumer.PROCESS_STATE_FOREGROUND); + final BatteryConsumer.Key background = uidConsumer.getKey( + BatteryConsumer.POWER_COMPONENT_WIFI, + BatteryConsumer.PROCESS_STATE_BACKGROUND); + final BatteryConsumer.Key fgs = uidConsumer.getKey( + BatteryConsumer.POWER_COMPONENT_WIFI, + BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE); + + assertThat(uidConsumer.getConsumedPower(foreground)).isWithin(PRECISION).of(0.5517519); + assertThat(uidConsumer.getConsumedPower(background)).isWithin(PRECISION).of(0.4807691); + assertThat(uidConsumer.getConsumedPower(fgs)).isWithin(PRECISION).of(0); + } + /** Sets up batterystats object with prepopulated network & timer data for Timer-model tests. */ private BatteryStatsImpl setupTimerBasedModelTestNumbers() { final BatteryStatsImpl batteryStats = setupTestNetworkNumbers(); diff --git a/core/tests/hdmitests/src/android/hardware/hdmi/DeviceFeaturesTest.java b/core/tests/hdmitests/src/android/hardware/hdmi/DeviceFeaturesTest.java new file mode 100644 index 000000000000..875ab3862354 --- /dev/null +++ b/core/tests/hdmitests/src/android/hardware/hdmi/DeviceFeaturesTest.java @@ -0,0 +1,114 @@ +/* + * 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 android.hardware.hdmi; + +import static android.hardware.hdmi.DeviceFeatures.FEATURE_NOT_SUPPORTED; +import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORTED; +import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORT_UNKNOWN; + +import static com.google.common.truth.Truth.assertThat; + +import androidx.test.filters.SmallTest; + +import com.google.common.testing.EqualsTester; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for {@link DeviceFeatures} */ +@RunWith(JUnit4.class) +@SmallTest +public class DeviceFeaturesTest { + + @Test + public void testEquals() { + new EqualsTester() + .addEqualityGroup(DeviceFeatures.ALL_FEATURES_SUPPORT_UNKNOWN) + .addEqualityGroup(DeviceFeatures.NO_FEATURES_SUPPORTED) + .addEqualityGroup( + DeviceFeatures.fromOperand( + new byte[]{(byte) 0b0111_0000}), + DeviceFeatures.fromOperand( + new byte[]{(byte) 0b1111_0000}), + DeviceFeatures.fromOperand( + new byte[]{(byte) 0b1111_0000, (byte) 0b0101_0101}), + DeviceFeatures.ALL_FEATURES_SUPPORT_UNKNOWN.toBuilder() + .setRecordTvScreenSupport(FEATURE_SUPPORTED) + .setSetOsdStringSupport(FEATURE_SUPPORTED) + .setDeckControlSupport(FEATURE_SUPPORTED) + .setSetAudioRateSupport(FEATURE_NOT_SUPPORTED) + .setArcTxSupport(FEATURE_NOT_SUPPORTED) + .setArcRxSupport(FEATURE_NOT_SUPPORTED) + .setSetAudioVolumeLevelSupport(FEATURE_NOT_SUPPORTED) + .build() + ) + .testEquals(); + } + + @Test + public void testDeviceFeaturesOperandConversion() { + DeviceFeatures info = DeviceFeatures.fromOperand( + new byte[]{(byte) 0b0111_0000}); + + assertThat(info.getRecordTvScreenSupport()).isEqualTo(FEATURE_SUPPORTED); + assertThat(info.getSetOsdStringSupport()).isEqualTo(FEATURE_SUPPORTED); + assertThat(info.getDeckControlSupport()).isEqualTo(FEATURE_SUPPORTED); + assertThat(info.getSetAudioRateSupport()).isEqualTo(FEATURE_NOT_SUPPORTED); + assertThat(info.getArcTxSupport()).isEqualTo(FEATURE_NOT_SUPPORTED); + assertThat(info.getArcRxSupport()).isEqualTo(FEATURE_NOT_SUPPORTED); + assertThat(info.getSetAudioVolumeLevelSupport()).isEqualTo(FEATURE_NOT_SUPPORTED); + + assertThat(info.toOperand()).isEqualTo(new byte[]{(byte) 0b0111_0000}); + } + + @Test + public void testUpdate() { + DeviceFeatures oldFeatures = DeviceFeatures.ALL_FEATURES_SUPPORT_UNKNOWN.toBuilder() + .setRecordTvScreenSupport(FEATURE_SUPPORTED) + .setSetOsdStringSupport(FEATURE_SUPPORTED) + .setDeckControlSupport(FEATURE_NOT_SUPPORTED) + .setSetAudioRateSupport(FEATURE_NOT_SUPPORTED) + .setArcTxSupport(FEATURE_SUPPORT_UNKNOWN) + .setArcRxSupport(FEATURE_SUPPORT_UNKNOWN) + .setSetAudioVolumeLevelSupport(FEATURE_SUPPORT_UNKNOWN) + .build(); + + DeviceFeatures newFeatures = DeviceFeatures.ALL_FEATURES_SUPPORT_UNKNOWN.toBuilder() + .setRecordTvScreenSupport(FEATURE_NOT_SUPPORTED) + .setSetOsdStringSupport(FEATURE_SUPPORT_UNKNOWN) + .setDeckControlSupport(FEATURE_SUPPORTED) + .setSetAudioRateSupport(FEATURE_SUPPORT_UNKNOWN) + .setArcTxSupport(FEATURE_SUPPORTED) + .setArcRxSupport(FEATURE_NOT_SUPPORTED) + .setSetAudioVolumeLevelSupport(FEATURE_SUPPORT_UNKNOWN) + .build(); + + // Always take the field from newFeatures, unless it's FEATURE_SUPPORT_UNKNOWN + DeviceFeatures updatedFeatures = DeviceFeatures.ALL_FEATURES_SUPPORT_UNKNOWN.toBuilder() + .setRecordTvScreenSupport(FEATURE_NOT_SUPPORTED) + .setSetOsdStringSupport(FEATURE_SUPPORTED) + .setDeckControlSupport(FEATURE_SUPPORTED) + .setSetAudioRateSupport(FEATURE_NOT_SUPPORTED) + .setArcTxSupport(FEATURE_SUPPORTED) + .setArcRxSupport(FEATURE_NOT_SUPPORTED) + .setSetAudioVolumeLevelSupport(FEATURE_SUPPORT_UNKNOWN) + .build(); + + assertThat(oldFeatures.toBuilder().update(newFeatures).build()).isEqualTo(updatedFeatures); + } +} diff --git a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiDeviceInfoTest.java b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiDeviceInfoTest.java index 4ce072ccc894..5f7468e084bf 100755 --- a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiDeviceInfoTest.java +++ b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiDeviceInfoTest.java @@ -43,44 +43,32 @@ public class HdmiDeviceInfoTest { int adopterId = 2; new EqualsTester() - .addEqualityGroup(new HdmiDeviceInfo()) + .addEqualityGroup(HdmiDeviceInfo.INACTIVE_DEVICE) .addEqualityGroup( - new HdmiDeviceInfo(phyAddr, portId), new HdmiDeviceInfo(phyAddr, portId)) + HdmiDeviceInfo.hardwarePort(phyAddr, portId), + HdmiDeviceInfo.hardwarePort(phyAddr, portId)) .addEqualityGroup( - new HdmiDeviceInfo(phyAddr, portId, adopterId, deviceId), - new HdmiDeviceInfo(phyAddr, portId, adopterId, deviceId)) + HdmiDeviceInfo.mhlDevice(phyAddr, portId, adopterId, deviceId), + HdmiDeviceInfo.mhlDevice(phyAddr, portId, adopterId, deviceId)) .addEqualityGroup( - new HdmiDeviceInfo( - logicalAddr, phyAddr, portId, deviceType, vendorId, displayName), - new HdmiDeviceInfo( - logicalAddr, phyAddr, portId, deviceType, vendorId, displayName)) - .addEqualityGroup( - new HdmiDeviceInfo( - logicalAddr, - phyAddr, - portId, - deviceType, - vendorId, - displayName, - powerStatus), - new HdmiDeviceInfo( - logicalAddr, - phyAddr, - portId, - deviceType, - vendorId, - displayName, - powerStatus)) - .addEqualityGroup( - new HdmiDeviceInfo( - logicalAddr, - phyAddr, - portId, - deviceType, - vendorId, - displayName, - powerStatus, - cecVersion)) + HdmiDeviceInfo.cecDeviceBuilder() + .setLogicalAddress(logicalAddr) + .setPhysicalAddress(phyAddr) + .setPortId(portId) + .setDeviceType(deviceType) + .setVendorId(vendorId) + .setDisplayName(displayName) + .setDevicePowerStatus(powerStatus) + .setCecVersion(cecVersion).build(), + HdmiDeviceInfo.cecDeviceBuilder() + .setLogicalAddress(logicalAddr) + .setPhysicalAddress(phyAddr) + .setPortId(portId) + .setDeviceType(deviceType) + .setVendorId(vendorId) + .setDisplayName(displayName) + .setDevicePowerStatus(powerStatus) + .setCecVersion(cecVersion).build()) .testEquals(); } } diff --git a/core/tests/systemproperties/src/android/os/PropertyInvalidatedCacheTest.java b/core/tests/systemproperties/src/android/os/PropertyInvalidatedCacheTest.java deleted file mode 100644 index 182bf6d165a0..000000000000 --- a/core/tests/systemproperties/src/android/os/PropertyInvalidatedCacheTest.java +++ /dev/null @@ -1,168 +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.os; - -import android.app.PropertyInvalidatedCache; -import android.test.suitebuilder.annotation.SmallTest; - -import junit.framework.TestCase; - -public class PropertyInvalidatedCacheTest extends TestCase { - private static final String KEY = "sys.testkey"; - private static final String UNSET_KEY = "Aiw7woh6ie4toh7W"; - - private static class TestCache extends PropertyInvalidatedCache<Integer, String> { - TestCache() { - this(KEY); - } - - TestCache(String key) { - super(4, key); - } - - @Override - public String recompute(Integer qv) { - mRecomputeCount += 1; - return "foo" + qv.toString(); - } - - int getRecomputeCount() { - return mRecomputeCount; - } - - private int mRecomputeCount = 0; - } - - @Override - protected void setUp() { - SystemProperties.set(KEY, ""); - } - - @SmallTest - public void testCacheRecompute() throws Exception { - TestCache cache = new TestCache(); - cache.invalidateCache(); - assertEquals("foo5", cache.query(5)); - assertEquals(1, cache.getRecomputeCount()); - assertEquals("foo5", cache.query(5)); - assertEquals(1, cache.getRecomputeCount()); - assertEquals("foo6", cache.query(6)); - assertEquals(2, cache.getRecomputeCount()); - cache.invalidateCache(); - assertEquals("foo5", cache.query(5)); - assertEquals("foo5", cache.query(5)); - assertEquals(3, cache.getRecomputeCount()); - } - - @SmallTest - public void testCacheInitialState() throws Exception { - TestCache cache = new TestCache(); - assertEquals("foo5", cache.query(5)); - assertEquals("foo5", cache.query(5)); - assertEquals(2, cache.getRecomputeCount()); - cache.invalidateCache(); - assertEquals("foo5", cache.query(5)); - assertEquals("foo5", cache.query(5)); - assertEquals(3, cache.getRecomputeCount()); - } - - @SmallTest - public void testCachePropertyUnset() throws Exception { - TestCache cache = new TestCache(UNSET_KEY); - assertEquals("foo5", cache.query(5)); - assertEquals("foo5", cache.query(5)); - assertEquals(2, cache.getRecomputeCount()); - } - - @SmallTest - public void testCacheDisableState() throws Exception { - TestCache cache = new TestCache(); - assertEquals("foo5", cache.query(5)); - assertEquals("foo5", cache.query(5)); - assertEquals(2, cache.getRecomputeCount()); - cache.invalidateCache(); - assertEquals("foo5", cache.query(5)); - assertEquals("foo5", cache.query(5)); - assertEquals(3, cache.getRecomputeCount()); - cache.disableSystemWide(); - assertEquals("foo5", cache.query(5)); - assertEquals("foo5", cache.query(5)); - assertEquals(5, cache.getRecomputeCount()); - cache.invalidateCache(); // Should not reenable - assertEquals("foo5", cache.query(5)); - assertEquals("foo5", cache.query(5)); - assertEquals(7, cache.getRecomputeCount()); - } - - @SmallTest - public void testRefreshSameObject() throws Exception { - int[] refreshCount = new int[1]; - TestCache cache = new TestCache() { - @Override - protected String refresh(String oldResult, Integer query) { - refreshCount[0] += 1; - return oldResult; - } - }; - cache.invalidateCache(); - String result1 = cache.query(5); - assertEquals("foo5", result1); - String result2 = cache.query(5); - assertSame(result1, result2); - assertEquals(1, cache.getRecomputeCount()); - assertEquals(1, refreshCount[0]); - assertEquals("foo5", cache.query(5)); - assertEquals(2, refreshCount[0]); - } - - @SmallTest - public void testRefreshInvalidateRace() throws Exception { - int[] refreshCount = new int[1]; - TestCache cache = new TestCache() { - @Override - protected String refresh(String oldResult, Integer query) { - refreshCount[0] += 1; - invalidateCache(); - return new String(oldResult); - } - }; - cache.invalidateCache(); - String result1 = cache.query(5); - assertEquals("foo5", result1); - String result2 = cache.query(5); - assertEquals(result1, result2); - assertNotSame(result1, result2); - assertEquals(2, cache.getRecomputeCount()); - } - - @SmallTest - public void testLocalProcessDisable() throws Exception { - TestCache cache = new TestCache(); - cache.invalidateCache(); - assertEquals("foo5", cache.query(5)); - assertEquals(1, cache.getRecomputeCount()); - assertEquals("foo5", cache.query(5)); - assertEquals(1, cache.getRecomputeCount()); - assertEquals(cache.isDisabledLocal(), false); - cache.disableLocal(); - assertEquals(cache.isDisabledLocal(), true); - assertEquals("foo5", cache.query(5)); - assertEquals("foo5", cache.query(5)); - assertEquals(3, cache.getRecomputeCount()); - } - -} diff --git a/data/etc/com.android.settings.xml b/data/etc/com.android.settings.xml index 3fdb0da80875..c5710e3e9a8b 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" /> 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 ee0fb4456336..2029de6cca56 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"/> @@ -392,7 +393,11 @@ applications that come with the platform <permission name="android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS"/> <permission name="android.permission.SET_WALLPAPER" /> <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"/> 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/BitmapShader.java b/graphics/java/android/graphics/BitmapShader.java index e6ff187bd99d..43cb5ee8b5c0 100644 --- a/graphics/java/android/graphics/BitmapShader.java +++ b/graphics/java/android/graphics/BitmapShader.java @@ -16,8 +16,12 @@ package android.graphics; +import android.annotation.IntDef; import android.annotation.NonNull; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Shader used to draw a bitmap as a texture. The bitmap can be repeated or * mirrored by setting the tiling mode. @@ -31,6 +35,47 @@ public class BitmapShader extends Shader { private int mTileX; private int mTileY; + /** @hide */ + @IntDef(prefix = {"FILTER_MODE"}, value = { + FILTER_MODE_DEFAULT, + FILTER_MODE_NEAREST, + FILTER_MODE_LINEAR + }) + @Retention(RetentionPolicy.SOURCE) + public @interface FilterMode {} + + /** + * This FilterMode value will respect the value of the Paint#isFilterBitmap flag while the + * shader is attached to the Paint. + * + * <p>The exception to this rule is when a Shader is attached as input to a RuntimeShader. In + * that case this mode will default to FILTER_MODE_NEAREST.</p> + * + * @see #setFilterMode(int) + */ + public static final int FILTER_MODE_DEFAULT = 0; + /** + * This FilterMode value will cause the shader to sample from the nearest pixel to the requested + * sample point. + * + * <p>This value will override the effect of Paint#isFilterBitmap.</p> + * + * @see #setFilterMode(int) + */ + public static final int FILTER_MODE_NEAREST = 1; + /** + * This FilterMode value will cause the shader to interpolate the output of the shader from a + * 2x2 grid of pixels nearest to the sample point (i.e. bilinear interpolation). + * + * <p>This value will override the effect of Paint#isFilterBitmap.</p> + * + * @see #setFilterMode(int) + */ + public static final int FILTER_MODE_LINEAR = 2; + + @FilterMode + private int mFilterMode; + /* * This is cache of the last value from the Paint of bitmap-filtering. * In the future, BitmapShaders will carry their own (expanded) data for this @@ -49,6 +94,15 @@ public class BitmapShader extends Shader { private boolean mFilterFromPaint; /** + * Stores whether or not the contents of this shader's bitmap will be sampled + * without modification or if the bitmap's properties, like colorspace and + * premultiplied alpha, will be respected when sampling from the bitmap's buffer. + */ + private boolean mIsDirectSampled; + + private boolean mRequestDirectSampling; + + /** * Call this to create a new shader that will draw with a bitmap. * * @param bitmap The bitmap to use inside the shader @@ -66,24 +120,60 @@ public class BitmapShader extends Shader { mBitmap = bitmap; mTileX = tileX; mTileY = tileY; + mFilterMode = FILTER_MODE_DEFAULT; mFilterFromPaint = false; + mIsDirectSampled = false; + mRequestDirectSampling = false; + } + + /** + * Returns the filter mode used when sampling from this shader + */ + @FilterMode + public int getFilterMode() { + return mFilterMode; + } + + /** + * Set the filter mode to be used when sampling from this shader + */ + public void setFilterMode(@FilterMode int mode) { + if (mode != mFilterMode) { + mFilterMode = mode; + discardNativeInstance(); + } + } + + /** @hide */ + /* package */ synchronized long getNativeInstanceWithDirectSampling() { + mRequestDirectSampling = true; + return getNativeInstance(); } /** @hide */ @Override protected long createNativeInstance(long nativeMatrix, boolean filterFromPaint) { - mFilterFromPaint = filterFromPaint; + boolean enableLinearFilter = mFilterMode == FILTER_MODE_LINEAR; + if (mFilterMode == FILTER_MODE_DEFAULT) { + mFilterFromPaint = filterFromPaint; + enableLinearFilter = mFilterFromPaint; + } + + mIsDirectSampled = mRequestDirectSampling; + mRequestDirectSampling = false; + return nativeCreate(nativeMatrix, mBitmap.getNativeInstance(), mTileX, mTileY, - mFilterFromPaint); + enableLinearFilter, mIsDirectSampled); } /** @hide */ @Override protected boolean shouldDiscardNativeInstance(boolean filterFromPaint) { - return mFilterFromPaint != filterFromPaint; + return mIsDirectSampled != mRequestDirectSampling + || (mFilterMode == FILTER_MODE_DEFAULT && mFilterFromPaint != filterFromPaint); } private static native long nativeCreate(long nativeMatrix, long bitmapHandle, - int shaderTileModeX, int shaderTileModeY, boolean filter); + int shaderTileModeX, int shaderTileModeY, boolean filter, boolean isDirectSampled); } diff --git a/graphics/java/android/graphics/RuntimeShader.java b/graphics/java/android/graphics/RuntimeShader.java index ef57f4a76007..57046f5de61d 100644 --- a/graphics/java/android/graphics/RuntimeShader.java +++ b/graphics/java/android/graphics/RuntimeShader.java @@ -279,11 +279,11 @@ public class RuntimeShader extends Shader { } /** - * Sets the uniform shader that is declares as input to this shader. If the shader does not + * Assigns the uniform shader to the provided shader parameter. If the shader program does not * have a uniform shader with that name then an IllegalArgumentException is thrown. * - * @param shaderName name matching the uniform declared in the SKSL shader - * @param shader shader passed into the SKSL shader for sampling + * @param shaderName name matching the uniform declared in the AGSL shader program + * @param shader shader passed into the AGSL shader program for sampling */ public void setInputShader(@NonNull String shaderName, @NonNull Shader shader) { if (shaderName == null) { @@ -297,6 +297,28 @@ public class RuntimeShader extends Shader { discardNativeInstance(); } + /** + * Assigns the uniform shader to the provided shader parameter. If the shader program does not + * have a uniform shader with that name then an IllegalArgumentException is thrown. + * + * Unlike setInputShader this method returns samples directly from the bitmap's buffer. This + * means that there will be no transformation of the sampled pixels, such as colorspace + * conversion or alpha premultiplication. + */ + public void setInputBuffer(@NonNull String shaderName, @NonNull BitmapShader shader) { + if (shaderName == null) { + throw new NullPointerException("The shaderName parameter must not be null"); + } + if (shader == null) { + throw new NullPointerException("The shader parameter must not be null"); + } + + nativeUpdateShader(mNativeInstanceRuntimeShaderBuilder, shaderName, + shader.getNativeInstanceWithDirectSampling()); + discardNativeInstance(); + } + + /** @hide */ @Override protected long createNativeInstance(long nativeMatrix, boolean filterFromPaint) { diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java new file mode 100644 index 000000000000..4d818583fd23 --- /dev/null +++ b/graphics/java/android/graphics/text/LineBreakConfig.java @@ -0,0 +1,110 @@ +/* + * 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.graphics.text; + +import android.annotation.IntDef; +import android.annotation.Nullable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + +/** + * Indicates the strategies can be used when calculating the text wrapping. + * + * See <a href="https://drafts.csswg.org/css-text/#line-break-property">the line-break property</a> + */ +public final class LineBreakConfig { + + /** + * No line break style specified. + */ + public static final int LINE_BREAK_STYLE_NONE = 0; + + /** + * Use the least restrictive rule for line-breaking. This is usually used for short lines. + */ + public static final int LINE_BREAK_STYLE_LOOSE = 1; + + /** + * Indicate breaking text with the most comment set of line-breaking rules. + */ + public static final int LINE_BREAK_STYLE_NORMAL = 2; + + /** + * Indicates breaking text with the most strictest line-breaking rules. + */ + public static final int LINE_BREAK_STYLE_STRICT = 3; + + /** @hide */ + @IntDef(prefix = { "LINE_BREAK_STYLE_" }, value = { + LINE_BREAK_STYLE_NONE, LINE_BREAK_STYLE_LOOSE, LINE_BREAK_STYLE_NORMAL, + LINE_BREAK_STYLE_STRICT + }) + @Retention(RetentionPolicy.SOURCE) + public @interface LineBreakStyle {} + + private @LineBreakStyle int mLineBreakStyle = LINE_BREAK_STYLE_NONE; + + public LineBreakConfig() { + } + + /** + * Set the line break configuration. + * + * @param config the new line break configuration. + */ + public void set(@Nullable LineBreakConfig config) { + if (config != null) { + mLineBreakStyle = config.getLineBreakStyle(); + } else { + mLineBreakStyle = LineBreakConfig.LINE_BREAK_STYLE_NONE; + } + } + + /** + * Get the line break style. + * + * @return The current line break style to be used for the text wrapping. + */ + public @LineBreakStyle int getLineBreakStyle() { + return mLineBreakStyle; + } + + /** + * Set the line break style. + * + * @param lineBreakStyle the new line break style. + */ + public void setLineBreakStyle(@LineBreakStyle int lineBreakStyle) { + mLineBreakStyle = lineBreakStyle; + } + + @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; + } + + @Override + public int hashCode() { + return Objects.hash(mLineBreakStyle); + } +} diff --git a/graphics/java/android/graphics/text/MeasuredText.java b/graphics/java/android/graphics/text/MeasuredText.java index a34d0abcf753..5f4afb7b9888 100644 --- a/graphics/java/android/graphics/text/MeasuredText.java +++ b/graphics/java/android/graphics/text/MeasuredText.java @@ -239,11 +239,33 @@ public class MeasuredText { */ public @NonNull Builder appendStyleRun(@NonNull Paint paint, @IntRange(from = 0) int length, boolean isRtl) { + return appendStyleRun(paint, null, length, isRtl); + } + + /** + * Apply styles to the given length. + * + * Keeps an internal offset which increases at every append. The initial value for this + * offset is zero. After the style is applied the internal offset is moved to {@code offset + * + length}, and next call will start from this new position. + * + * @param paint a paint + * @param lineBreakConfig a line break configuration. + * @param length a length to be applied with a given paint, can not exceed the length of the + * text + * @param isRtl true if the text is in RTL context, otherwise false. + */ + public @NonNull Builder appendStyleRun(@NonNull Paint paint, + @Nullable LineBreakConfig lineBreakConfig, @IntRange(from = 0) int length, + boolean isRtl) { Preconditions.checkNotNull(paint); Preconditions.checkArgument(length > 0, "length can not be negative"); final int end = mCurrentOffset + length; Preconditions.checkArgument(end <= mText.length, "Style exceeds the text length"); - nAddStyleRun(mNativePtr, paint.getNativeInstance(), mCurrentOffset, end, isRtl); + int lbStyle = (lineBreakConfig != null) ? lineBreakConfig.getLineBreakStyle() : + LineBreakConfig.LINE_BREAK_STYLE_NONE; + nAddStyleRun(mNativePtr, paint.getNativeInstance(), lbStyle, mCurrentOffset, end, + isRtl); mCurrentOffset = end; return this; } @@ -423,12 +445,14 @@ 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 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. */ private static native void nAddStyleRun(/* Non Zero */ long nativeBuilderPtr, /* Non Zero */ long paintPtr, + int lineBreakStyle, @IntRange(from = 0) int start, @IntRange(from = 0) int end, boolean isRtl); diff --git a/keystore/java/android/security/AndroidKeyStoreMaintenance.java b/keystore/java/android/security/AndroidKeyStoreMaintenance.java index 919a93b8f107..05fb4c3cf76f 100644 --- a/keystore/java/android/security/AndroidKeyStoreMaintenance.java +++ b/keystore/java/android/security/AndroidKeyStoreMaintenance.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.os.ServiceManager; import android.os.ServiceSpecificException; +import android.security.keystore.KeyProperties; import android.security.maintenance.IKeystoreMaintenance; import android.system.keystore2.Domain; import android.system.keystore2.KeyDescriptor; @@ -157,6 +158,11 @@ public class AndroidKeyStoreMaintenance { * Migrates a key given by the source descriptor to the location designated by the destination * descriptor. * + * If Domain::APP is selected in either source or destination, nspace must be set to + * {@link KeyProperties#NAMESPACE_APPLICATION}, implying the caller's UID. + * If the caller has the MIGRATE_ANY_KEY permission, Domain::APP may be used with + * other nspace values which then indicates the UID of a different application. + * * @param source - The key to migrate may be specified by Domain.APP, Domain.SELINUX, or * Domain.KEY_ID. The caller needs the permissions use, delete, and grant for the * source namespace. @@ -183,4 +189,20 @@ public class AndroidKeyStoreMaintenance { return SYSTEM_ERROR; } } + + /** + * @see IKeystoreMaintenance#listEntries(int, long) + */ + @Nullable + public static KeyDescriptor[] listEntries(int domain, long nspace) { + try { + return getService().listEntries(domain, nspace); + } catch (ServiceSpecificException e) { + Log.e(TAG, "listEntries failed", e); + return null; + } catch (Exception e) { + Log.e(TAG, "Can not connect to keystore", e); + return null; + } + } } 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/core/java/android/bluetooth/BluetoothUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java index 867469241f84..b310dd638e6c 100644 --- a/core/java/android/bluetooth/BluetoothUtils.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java @@ -14,28 +14,22 @@ * limitations under the License. */ -package android.bluetooth; +package com.android.wm.shell.back; -import java.time.Duration; +import android.view.MotionEvent; /** - * {@hide} + * Interface for SysUI to get access to the Back animation related methods. */ -public final class BluetoothUtils { - /** - * This utility class cannot be instantiated - */ - private BluetoothUtils() {} +public interface BackAnimation { /** - * Timeout value for synchronous binder call + * Called when a {@link MotionEvent} is generated by a back gesture. */ - private static final Duration SYNC_CALLS_TIMEOUT = Duration.ofSeconds(5); + void onBackMotion(MotionEvent event); /** - * @return timeout value for synchronous binder call + * Sets whether the back gesture is past the trigger threshold or not. */ - static Duration getSyncTimeout() { - return SYNC_CALLS_TIMEOUT; - } + 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/back/BackPreviewHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackPreviewHandler.java deleted file mode 100644 index dc20f7b72a2b..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackPreviewHandler.java +++ /dev/null @@ -1,41 +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.wm.shell.back; - -import android.os.SystemProperties; -import android.view.IWindowManager; - -import javax.inject.Inject; - -/** - * Handle the preview of what a back gesture will lead to. - */ -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; - } - - private final IWindowManager mWmService; - - @Inject - public BackPreviewHandler(IWindowManager windowManagerService) { - mWmService = windowManagerService; - } -} 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 0aa8d7e2f1a3..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 @@ -44,6 +44,7 @@ import android.window.RemoteTransition; import android.window.TransitionInfo; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; +import android.window.WindowContainerTransactionCallback; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.common.TransactionPool; @@ -66,28 +67,27 @@ class SplitScreenTransitions { DismissTransition mPendingDismiss = null; IBinder mPendingEnter = null; + IBinder mPendingRecent = null; private IBinder mAnimatingTransition = null; - private OneShotRemoteHandler mRemoteHandler = null; + private OneShotRemoteHandler mPendingRemoteHandler = null; + private OneShotRemoteHandler mActiveRemoteHandler = null; - private Transitions.TransitionFinishCallback mRemoteFinishCB = (wct, wctCB) -> { - if (wct != null || wctCB != null) { - throw new UnsupportedOperationException("finish transactions not supported yet."); - } - onFinish(); - }; + private final Transitions.TransitionFinishCallback mRemoteFinishCB = this::onFinish; /** Keeps track of currently running animations */ private final ArrayList<Animator> mAnimations = new ArrayList<>(); + private final StageCoordinator mStageCoordinator; private Transitions.TransitionFinishCallback mFinishCallback = null; private SurfaceControl.Transaction mFinishTransaction; SplitScreenTransitions(@NonNull TransactionPool pool, @NonNull Transitions transitions, - @NonNull Runnable onFinishCallback) { + @NonNull Runnable onFinishCallback, StageCoordinator stageCoordinator) { mTransactionPool = pool; mTransitions = transitions; mOnFinish = onFinishCallback; + mStageCoordinator = stageCoordinator; } void playAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @@ -97,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); @@ -166,22 +167,21 @@ class SplitScreenTransitions { } } t.apply(); - onFinish(); + onFinish(null /* wct */, null /* wctCB */); } /** Starts a transition to enter split with a remote transition animator. */ 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; } @@ -203,7 +203,33 @@ class SplitScreenTransitions { return transition; } - void onFinish() { + IBinder startRecentTransition(@Nullable IBinder transition, WindowContainerTransaction wct, + Transitions.TransitionHandler handler, @Nullable RemoteTransition remoteTransition) { + if (transition == null) { + transition = mTransitions.startTransition(TRANSIT_OPEN, wct, handler); + } + mPendingRecent = transition; + + if (remoteTransition != null) { + // Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff) + mPendingRemoteHandler = new OneShotRemoteHandler( + mTransitions.getMainExecutor(), remoteTransition); + mPendingRemoteHandler.setTransition(transition); + } + + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition " + + " deduced Enter recent panel"); + 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(); if (mFinishTransaction != null) { @@ -211,14 +237,25 @@ class SplitScreenTransitions { mTransactionPool.release(mFinishTransaction); mFinishTransaction = null; } - mFinishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); - mFinishCallback = null; + if (mFinishCallback != null) { + mFinishCallback.onTransitionFinished(wct /* wct */, wctCB /* wctCB */); + mFinishCallback = null; + } if (mAnimatingTransition == mPendingEnter) { mPendingEnter = null; } if (mPendingDismiss != null && mPendingDismiss.mTransition == mAnimatingTransition) { mPendingDismiss = null; } + if (mAnimatingTransition == mPendingRecent) { + // If the wct is not null while finishing recent transition, it indicates it's not + // 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; } @@ -240,7 +277,7 @@ class SplitScreenTransitions { mTransactionPool.release(transaction); mTransitions.getMainExecutor().execute(() -> { mAnimations.remove(va); - onFinish(); + onFinish(null /* wct */, null /* wctCB */); }); }; va.addListener(new Animator.AnimatorListener() { @@ -288,7 +325,7 @@ class SplitScreenTransitions { mTransactionPool.release(transaction); mTransitions.getMainExecutor().execute(() -> { mAnimations.remove(va); - onFinish(); + onFinish(null /* wct */, null /* wctCB */); }); }; va.addListener(new AnimatorListenerAdapter() { 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 83830ec56c32..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 @@ -20,6 +20,7 @@ import static android.app.ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN; import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; @@ -165,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 @@ -236,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::onTransitionAnimationComplete, this); mDisplayController.addDisplayWindowListener(this); mDisplayLayout = new DisplayLayout(displayController.getDisplayLayout(displayId)); transitions.addHandler(this); @@ -266,7 +256,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, mRootTDAOrganizer.registerListener(displayId, this); mSplitLayout = splitLayout; mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions, - mOnTransitionAnimationComplete); + this::onTransitionAnimationComplete, this); mMainUnfoldController = unfoldControllerProvider.get().orElse(null); mSideUnfoldController = unfoldControllerProvider.get().orElse(null); mLogger = logger; @@ -1233,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; @@ -1249,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" @@ -1271,13 +1261,16 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, final int activityType = triggerTask.getActivityType(); if (activityType == ACTIVITY_TYPE_ASSISTANT) { // We don't want assistant panel to dismiss split screen, so do nothing. + } else if (activityType == ACTIVITY_TYPE_HOME + || activityType == ACTIVITY_TYPE_RECENTS) { + // Enter overview panel, so start recent transition. + mSplitTransitions.startRecentTransition(transition, out, this, + request.getRemoteTransition()); } else { - // Going home or occluded by the other fullscreen task, so dismiss both. + // Occluded by the other fullscreen task, so dismiss both. prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, out); - final int exitReason = activityType == ACTIVITY_TYPE_HOME - ? EXIT_REASON_RETURN_HOME : EXIT_REASON_UNKNOWN; mSplitTransitions.startDismissTransition(transition, out, this, - STAGE_TYPE_UNDEFINED, exitReason); + STAGE_TYPE_UNDEFINED, EXIT_REASON_UNKNOWN); } } } else { @@ -1292,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. @@ -1307,13 +1307,14 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { - if (transition != mSplitTransitions.mPendingEnter && ( - mSplitTransitions.mPendingDismiss == null + if (transition != mSplitTransitions.mPendingEnter + && transition != mSplitTransitions.mPendingRecent + && (mSplitTransitions.mPendingDismiss == null || mSplitTransitions.mPendingDismiss.mTransition != transition)) { // Not entering or exiting, so just do some house-keeping and validation. // If we're not in split-mode, just abort so something else can handle it. - if (!isSplitScreenVisible()) return false; + if (!mMainStage.isActive()) return false; for (int iC = 0; iC < info.getChanges().size(); ++iC) { final TransitionInfo.Change change = info.getChanges().get(iC); @@ -1356,6 +1357,8 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, boolean shouldAnimate = true; if (mSplitTransitions.mPendingEnter == transition) { shouldAnimate = startPendingEnterAnimation(transition, info, startTransaction); + } else if (mSplitTransitions.mPendingRecent == transition) { + shouldAnimate = startPendingRecentAnimation(transition, info, startTransaction); } else if (mSplitTransitions.mPendingDismiss != null && mSplitTransitions.mPendingDismiss.mTransition == transition) { shouldAnimate = startPendingDismissAnimation( @@ -1368,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. @@ -1500,6 +1514,32 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, return true; } + private boolean startPendingRecentAnimation(@NonNull IBinder transition, + @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) { + setDividerVisibility(false, t); + return true; + } + + 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, + STAGE_TYPE_UNDEFINED, EXIT_REASON_RETURN_HOME); + } else { + setDividerVisibility(true, null /* t */); + } + } + private void addDividerBarToTransition(@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, boolean show) { final SurfaceControl leash = mSplitLayout.getDividerLeash(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java index 73f65b08a7eb..f8902c6d2cc6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java @@ -269,6 +269,8 @@ public class StartingSurfaceDrawer { // touchable or focusable by the user. We also add in the ALT_FOCUSABLE_IM // flag because we do know that the next window will take input // focus, so we want to get the IME window up on top of us right away. + // Touches will only pass through to the host activity window and will be blocked from + // passing to any other windows. windowFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; @@ -276,9 +278,6 @@ public class StartingSurfaceDrawer { params.token = appToken; params.packageName = activityInfo.packageName; params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; - // Setting as trusted overlay to let touches pass through. This is safe because this - // window is controlled by the system. - params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; if (!context.getResources().getCompatibilityInfo().supportsScreen()) { params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW; 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 11b23875437f..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; @@ -291,17 +290,13 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); }; - boolean requireBackgroundForTransition = false; - + @ColorInt int backgroundColorForTransition = 0; final int wallpaperTransit = getWallpaperTransitType(info); for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); 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); @@ -315,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); } } @@ -352,8 +346,19 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { Animation a = loadAnimation(info, change, wallpaperTransit); if (a != null) { - if (changeRequiresBackground(info, change)) { - requireBackgroundForTransition = true; + if (isTask) { + final @TransitionType int type = info.getType(); + final boolean isOpenOrCloseTransition = type == TRANSIT_OPEN + || type == TRANSIT_CLOSE + || type == TRANSIT_TO_FRONT + || type == TRANSIT_TO_BACK; + if (isOpenOrCloseTransition) { + // Use the overview background as the background for the animation + final Context uiContext = ActivityThread.currentActivityThread() + .getSystemUiContext(); + backgroundColorForTransition = + uiContext.getColor(R.color.overview_background); + } } float cornerRadius = 0; @@ -365,9 +370,21 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { ScreenDecorationsUtils.getWindowCornerRadius(displayContext); } + if (a.getShowBackground()) { + // use the window's background color if provided as the background color for the + // animation - the top most window with a valid background color and + // showBackground set takes precedence. + if (change.getBackgroundColor() != 0) { + backgroundColorForTransition = change.getBackgroundColor(); + } + } + + 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(), @@ -376,36 +393,26 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { } } - if (requireBackgroundForTransition) { - addBackgroundToTransition(info.getRootLeash(), startTransaction, finishTransaction); + if (backgroundColorForTransition != 0) { + addBackgroundToTransition(info.getRootLeash(), backgroundColorForTransition, + startTransaction, finishTransaction); } startTransaction.apply(); - rotator.cleanUp(finishTransaction); + mRotator.cleanUp(finishTransaction); TransitionMetrics.getInstance().reportAnimationStart(transition); // run finish now in-case there are no animations onAnimFinish.run(); return true; } - private boolean changeRequiresBackground(TransitionInfo info, - TransitionInfo.Change change) { - final boolean isTask = change.getTaskInfo() != null; - final @TransitionType int type = info.getType(); - final boolean isOpenOrCloseTransition = type == TRANSIT_OPEN || type == TRANSIT_CLOSE - || type == TRANSIT_TO_FRONT || type == TRANSIT_TO_BACK; - return isTask && isOpenOrCloseTransition; - } - private void addBackgroundToTransition( @NonNull SurfaceControl rootLeash, + @ColorInt int color, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction ) { - final Context uiContext = ActivityThread.currentActivityThread().getSystemUiContext(); - final @ColorInt int overviewBackgroundColor = - uiContext.getColor(R.color.overview_background); - final Color bgColor = Color.valueOf(overviewBackgroundColor); + final Color bgColor = Color.valueOf(color); final float[] colorArray = new float[] { bgColor.red(), bgColor.green(), bgColor.blue() }; final SurfaceControl animationBackgroundSurface = new SurfaceControl.Builder() @@ -449,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, @@ -466,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 @@ -476,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 @@ -558,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/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/PipAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt index 8e6fa5f1314c..7e232ea32181 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt @@ -66,7 +66,7 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper( stringExtras: Map<String, String> ) { super.launchViaIntent(wmHelper, expectedWindowName, action, stringExtras) - wmHelper.waitFor("hasPipWindow") { it.wmState.hasPipWindow() } + wmHelper.waitPipShown() } private fun focusOnObject(selector: BySelector): Boolean { @@ -88,7 +88,7 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper( clickObject(ENTER_PIP_BUTTON_ID) // Wait on WMHelper or simply wait for 3 seconds - wmHelper?.waitFor("hasPipWindow") { it.wmState.hasPipWindow() } ?: SystemClock.sleep(3_000) + wmHelper?.waitPipShown() ?: SystemClock.sleep(3_000) // when entering pip, the dismiss button is visible at the start. to ensure the pip // animation is complete, wait until the pip dismiss button is no longer visible. // b/176822698: dismiss-only state will be removed in the future @@ -148,7 +148,7 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper( } // Wait for animation to complete. - wmHelper.waitFor("!hasPipWindow") { !it.wmState.hasPipWindow() } + wmHelper.waitPipGone() wmHelper.waitForHomeActivityVisible() } @@ -165,7 +165,7 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper( ?: error("PIP window expand button not found") val expandButtonBounds = expandPipObject.visibleBounds uiDevice.click(expandButtonBounds.centerX(), expandButtonBounds.centerY()) - wmHelper.waitFor("!hasPipWindow") { !it.wmState.hasPipWindow() } + wmHelper.waitPipGone() wmHelper.waitForAppTransitionIdle() } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt index 2c02d2c183c6..fb1004bda0cb 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt @@ -27,7 +27,6 @@ import com.android.server.wm.flicker.annotation.Group2 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.entireScreenCovered import com.android.server.wm.flicker.helpers.exitSplitScreen -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.helpers.launchSplitScreen import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview import com.android.server.wm.flicker.helpers.setRotation @@ -41,7 +40,6 @@ import com.android.server.wm.flicker.statusBarWindowIsVisible import com.android.server.wm.traces.common.FlickerComponentName import com.android.wm.shell.flicker.dockedStackDividerBecomesInvisible import com.android.wm.shell.flicker.helpers.SimpleAppHelper -import org.junit.Assume.assumeFalse import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -112,11 +110,7 @@ class LegacySplitScreenToLauncher( @FlakyTest(bugId = 206753786) @Test - fun statusBarLayerRotatesScales() { - // This test doesn't work in shell transitions because of b/206753786 - assumeFalse(isShellTransitionsEnabled) - testSpec.statusBarLayerRotatesScales() - } + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @Presubmit @Test 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 7d7add48c898..264d482426cb 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 @@ -29,7 +29,6 @@ import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.entireScreenCovered import com.android.server.wm.flicker.helpers.ImeAppHelper import com.android.server.wm.flicker.helpers.WindowUtils -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.helpers.launchSplitScreen import com.android.server.wm.flicker.helpers.resizeSplitScreen import com.android.server.wm.flicker.helpers.setRotation @@ -45,7 +44,6 @@ import com.android.server.wm.traces.parser.toFlickerComponent import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER_COMPONENT import com.android.wm.shell.flicker.helpers.SimpleAppHelper import com.android.wm.shell.flicker.testapp.Components -import org.junit.Assume.assumeFalse import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -137,11 +135,7 @@ class ResizeLegacySplitScreen( @FlakyTest(bugId = 206753786) @Test - fun statusBarLayerRotatesScales() { - // This test doesn't work in shell transitions because of b/206753786 - assumeFalse(isShellTransitionsEnabled) - testSpec.statusBarLayerRotatesScales() - } + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @Test fun topAppLayerIsAlwaysVisible() { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt index 5678899dbbae..d703ea082c87 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt @@ -25,7 +25,6 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group2 import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.helpers.launchSplitScreen import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.navBarLayerRotatesAndScales @@ -35,7 +34,6 @@ import com.android.server.wm.flicker.statusBarWindowIsVisible import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd import com.android.wm.shell.flicker.helpers.SplitScreenHelper -import org.junit.Assume.assumeFalse import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -79,11 +77,7 @@ class RotateOneLaunchedAppAndEnterSplitScreen( @FlakyTest(bugId = 206753786) @Test - fun statusBarLayerRotatesScales() { - // This test doesn't work in shell transitions because of b/206753786 - assumeFalse(isShellTransitionsEnabled) - testSpec.statusBarLayerRotatesScales() - } + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt index c2edf9dd5db2..6b1883914e59 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt @@ -25,7 +25,6 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group2 import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.helpers.launchSplitScreen import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.navBarLayerRotatesAndScales @@ -35,7 +34,6 @@ import com.android.server.wm.flicker.statusBarWindowIsVisible import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd import com.android.wm.shell.flicker.helpers.SplitScreenHelper -import org.junit.Assume.assumeFalse import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -78,11 +76,7 @@ class RotateOneLaunchedAppInSplitScreenMode( @FlakyTest(bugId = 206753786) @Test - fun statusBarLayerRotatesScales() { - // This test doesn't work in shell transitions because of b/206753786 - assumeFalse(isShellTransitionsEnabled) - testSpec.statusBarLayerRotatesScales() - } + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt index 777998c99e75..acd658b5ba56 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt @@ -25,7 +25,6 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group2 import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.helpers.launchSplitScreen import com.android.server.wm.flicker.helpers.reopenAppFromOverview import com.android.server.wm.flicker.helpers.setRotation @@ -37,7 +36,6 @@ import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisibleAtEnd import com.android.wm.shell.flicker.helpers.SplitScreenHelper -import org.junit.Assume.assumeFalse import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -88,11 +86,7 @@ class RotateTwoLaunchedAppAndEnterSplitScreen( @FlakyTest(bugId = 206753786) @Test - fun statusBarLayerRotatesScales() { - // This test doesn't work in shell transitions because of b/206753786 - assumeFalse(isShellTransitionsEnabled) - testSpec.statusBarLayerRotatesScales() - } + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt index 914b11d51529..b40be8b5f401 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt @@ -25,7 +25,6 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group2 import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.helpers.launchSplitScreen import com.android.server.wm.flicker.helpers.reopenAppFromOverview import com.android.server.wm.flicker.helpers.setRotation @@ -37,7 +36,6 @@ import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisibleAtEnd import com.android.wm.shell.flicker.helpers.SplitScreenHelper -import org.junit.Assume.assumeFalse import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -93,11 +91,7 @@ class RotateTwoLaunchedAppInSplitScreenMode( @FlakyTest(bugId = 206753786) @Test - fun statusBarLayerRotatesScales() { - // This test doesn't work in shell transitions because of b/206753786 - assumeFalse(isShellTransitionsEnabled) - testSpec.statusBarLayerRotatesScales() - } + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @FlakyTest @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt index d3bb0082be07..db94de238a5d 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt @@ -16,6 +16,7 @@ package com.android.wm.shell.flicker.pip +import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest @@ -26,9 +27,7 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.LAUNCHER_COMPONENT import com.android.server.wm.flicker.annotation.Group3 import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.rules.WMFlickerServiceRuleForTestSpec -import org.junit.Assume.assumeFalse import org.junit.FixMethodOrder import org.junit.Rule import org.junit.Test @@ -93,11 +92,7 @@ class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { /** {@inheritDoc} */ @FlakyTest(bugId = 206753786) @Test - override fun statusBarLayerRotatesScales() { - // This test doesn't work in shell transitions because of b/206753786 - assumeFalse(isShellTransitionsEnabled) - super.statusBarLayerRotatesScales() - } + override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() /** * Checks [pipApp] window remains visible throughout the animation @@ -187,13 +182,14 @@ class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { } /** - * Checks the focus doesn't change during the animation + * Checks that the focus changes between the [pipApp] window and the launcher when + * closing the pip window */ - @FlakyTest + @Postsubmit @Test - fun focusDoesNotChange() { + fun focusChanges() { testSpec.assertEventLog { - this.focusDoesNotChange() + this.focusChanges(pipApp.`package`, "NexusLauncherActivity") } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt index fa9fbcd53ed1..dee13c182a95 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt @@ -27,7 +27,6 @@ import com.android.server.wm.flicker.annotation.Group3 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.entireScreenCovered import com.android.server.wm.flicker.helpers.WindowUtils -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.navBarLayerRotatesAndScales import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.server.wm.traces.common.FlickerComponentName @@ -36,7 +35,6 @@ import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Com import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_PORTRAIT import com.android.wm.shell.flicker.testapp.Components.PipActivity.ACTION_ENTER_PIP import com.android.wm.shell.flicker.testapp.Components.FixedActivity.EXTRA_FIXED_ORIENTATION -import org.junit.Assume.assumeFalse import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -100,7 +98,7 @@ class EnterPipToOtherOrientationTest( // Enter PiP, and assert that the PiP is within bounds now that the device is back // in portrait broadcastActionTrigger.doAction(ACTION_ENTER_PIP) - wmHelper.waitFor { it.wmState.hasPipWindow() } + wmHelper.waitPipShown() wmHelper.waitForAppTransitionIdle() // during rotation the status bar becomes invisible and reappears at the end wmHelper.waitForNavBarStatusBarVisible() @@ -121,11 +119,7 @@ class EnterPipToOtherOrientationTest( */ @FlakyTest(bugId = 206753786) @Test - override fun statusBarLayerRotatesScales() { - // This test doesn't work in shell transitions because of b/206753786 - assumeFalse(isShellTransitionsEnabled) - testSpec.statusBarLayerRotatesScales() - } + override fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() /** * Checks that all parts of the screen are covered at the start and end of the transition diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt index f8a3aff98276..990872f58dc1 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt @@ -18,9 +18,7 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.wm.shell.flicker.helpers.FixedAppHelper -import org.junit.Assume.assumeFalse import org.junit.Test /** @@ -29,15 +27,6 @@ import org.junit.Test abstract class ExitPipToAppTransition(testSpec: FlickerTestParameter) : PipTransition(testSpec) { protected val testApp = FixedAppHelper(instrumentation) - /** {@inheritDoc} */ - @Presubmit - @Test - override fun statusBarLayerRotatesScales() { - // This test doesn't work in shell transitions because of b/206753786 - assumeFalse(isShellTransitionsEnabled) - super.statusBarLayerRotatesScales() - } - /** * Checks that the pip app window remains inside the display bounds throughout the whole * animation diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt index 9a220070db01..173140d77cb6 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt @@ -16,9 +16,9 @@ package com.android.wm.shell.flicker.pip +import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import android.view.Surface -import androidx.test.filters.FlakyTest import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.LAUNCHER_COMPONENT import com.android.server.wm.flicker.dsl.FlickerBuilder @@ -96,14 +96,13 @@ abstract class ExitPipTransition(testSpec: FlickerTestParameter) : PipTransition } /** - * Checks that the focus changes between the [pipApp] window and the launcher when - * closing the pip window + * Checks that the focus doesn't change between windows during the transition */ - @FlakyTest(bugId = 151179149) + @Postsubmit @Test - open fun focusChanges() { + open fun focusDoesNotChange() { testSpec.assertEventLog { - this.focusChanges(pipApp.launcherName, "NexusLauncherActivity") + this.focusDoesNotChange() } } }
\ No newline at end of file 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 2231d8864f14..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 @@ -24,8 +24,6 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group3 import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled -import org.junit.Assume.assumeFalse import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -82,11 +80,12 @@ class ExitPipViaExpandButtonClickTest( /** {@inheritDoc} */ @FlakyTest(bugId = 206753786) @Test - override fun statusBarLayerRotatesScales() { - // This test doesn't work in shell transitions because of b/206753786 - assumeFalse(isShellTransitionsEnabled) - super.statusBarLayerRotatesScales() - } + override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() + + /** {@inheritDoc} */ + @FlakyTest(bugId = 197726610) + @Test + override fun pipLayerExpands() = super.pipLayerExpands() companion object { /** 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 fcac2c782d07..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 @@ -24,9 +24,7 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group3 import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.rules.WMFlickerServiceRuleForTestSpec -import org.junit.Assume.assumeFalse import org.junit.FixMethodOrder import org.junit.Rule import org.junit.Test @@ -101,11 +99,12 @@ class ExitPipViaIntentTest(testSpec: FlickerTestParameter) : ExitPipToAppTransit /** {@inheritDoc} */ @FlakyTest(bugId = 206753786) @Test - override fun statusBarLayerRotatesScales() { - // This test doesn't work in shell transitions because of b/206753786 - assumeFalse(isShellTransitionsEnabled) - super.statusBarLayerRotatesScales() - } + override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() + + /** {@inheritDoc} */ + @FlakyTest(bugId = 197726610) + @Test + override fun pipLayerExpands() = super.pipLayerExpands() companion object { /** 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/ExitPipWithSwipeDownTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt index 8e6603b3cc30..6524182e9082 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt @@ -24,9 +24,7 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group3 import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.statusBarLayerRotatesScales -import org.junit.Assume.assumeFalse import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -65,7 +63,7 @@ class ExitPipWithSwipeDownTest(testSpec: FlickerTestParameter) : ExitPipTransiti val pipCenterY = pipRegion.centerY() val displayCenterX = device.displayWidth / 2 device.swipe(pipCenterX, pipCenterY, displayCenterX, device.displayHeight, 10) - wmHelper.waitFor("!hasPipWindow") { !it.wmState.hasPipWindow() } + wmHelper.waitPipGone() wmHelper.waitForWindowSurfaceDisappeared(pipApp.component) wmHelper.waitForAppTransitionIdle() } @@ -81,11 +79,7 @@ class ExitPipWithSwipeDownTest(testSpec: FlickerTestParameter) : ExitPipTransiti @FlakyTest(bugId = 206753786) @Test - override fun statusBarLayerRotatesScales() { - // This test doesn't work in shell transitions because of b/206753786 - assumeFalse(isShellTransitionsEnabled) - testSpec.statusBarLayerRotatesScales() - } + override fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() companion object { /** diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt index ef9ff4fc63c2..8d14f70357b1 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt @@ -16,6 +16,7 @@ package com.android.wm.shell.flicker.pip +import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest @@ -148,7 +149,7 @@ class ExpandPipOnDoubleClickTest(testSpec: FlickerTestParameter) : PipTransition /** * Checks that the focus doesn't change between windows during the transition */ - @FlakyTest + @Postsubmit @Test fun focusDoesNotChange() { testSpec.assertEventLog { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt index f9e180e8ec61..e4150241d42c 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt @@ -24,8 +24,6 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group3 import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled -import org.junit.Assume.assumeFalse import com.android.server.wm.flicker.traces.region.RegionSubject import org.junit.FixMethodOrder import org.junit.Test @@ -85,11 +83,7 @@ class MovePipDownShelfHeightChangeTest( /** {@inheritDoc} */ @FlakyTest(bugId = 206753786) @Test - override fun statusBarLayerRotatesScales() { - // This test doesn't work in shell transitions because of b/206753786 - assumeFalse(isShellTransitionsEnabled) - super.statusBarLayerRotatesScales() - } + override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() companion object { /** diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt index b7bfa1b2df88..4a4c46c596a2 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt @@ -25,8 +25,6 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group3 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.traces.region.RegionSubject -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled -import org.junit.Assume.assumeFalse import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -85,11 +83,7 @@ class MovePipUpShelfHeightChangeTest( /** {@inheritDoc} */ @FlakyTest(bugId = 206753786) @Test - override fun statusBarLayerRotatesScales() { - // This test doesn't work in shell transitions because of b/206753786 - assumeFalse(isShellTransitionsEnabled) - super.statusBarLayerRotatesScales() - } + override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() companion object { /** 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/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt index 93a4e1be3bb7..bb66f7bbc01f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt @@ -125,13 +125,13 @@ abstract class PipTransition(protected val testSpec: FlickerTestParameter) { removeAllTasksButHome() if (!eachRun) { pipApp.launchViaIntent(wmHelper, stringExtras = stringExtras) - wmHelper.waitFor { it.wmState.hasPipWindow() } + wmHelper.waitPipShown() } } eachRun { if (eachRun) { pipApp.launchViaIntent(wmHelper, stringExtras = stringExtras) - wmHelper.waitFor { it.wmState.hasPipWindow() } + wmHelper.waitPipShown() } } } 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/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java index ea94cf0f7597..59c377a3e13d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java @@ -251,7 +251,7 @@ public class SplitTransitionTests extends ShellTestCase { } @Test - public void testDismissToHome() { + public void testEnterRecents() { enterSplit(); ActivityManager.RunningTaskInfo homeTask = new TestRunningTaskInfoBuilder() @@ -264,7 +264,7 @@ public class SplitTransitionTests extends ShellTestCase { IBinder transition = mock(IBinder.class); WindowContainerTransaction result = mStageCoordinator.handleRequest(transition, request); - assertTrue(containsSplitExit(result)); + assertTrue(result.isEmpty()); // make sure we haven't made any local changes yet (need to wait until transition is ready) assertTrue(mStageCoordinator.isSplitScreenVisible()); @@ -284,7 +284,7 @@ public class SplitTransitionTests extends ShellTestCase { mock(SurfaceControl.Transaction.class), mock(SurfaceControl.Transaction.class), mock(Transitions.TransitionFinishCallback.class)); - assertFalse(mStageCoordinator.isSplitScreenVisible()); + assertTrue(mStageCoordinator.isSplitScreenVisible()); } @Test 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/Shader.cpp b/libs/hwui/jni/Shader.cpp index c4366f756a21..c505b53b2df1 100644 --- a/libs/hwui/jni/Shader.cpp +++ b/libs/hwui/jni/Shader.cpp @@ -64,7 +64,8 @@ static jlong Shader_getNativeFinalizer(JNIEnv*, jobject) { /////////////////////////////////////////////////////////////////////////////////////////////// static jlong BitmapShader_constructor(JNIEnv* env, jobject o, jlong matrixPtr, jlong bitmapHandle, - jint tileModeX, jint tileModeY, bool filter) { + jint tileModeX, jint tileModeY, bool filter, + bool isDirectSampled) { const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr); sk_sp<SkImage> image; if (bitmapHandle) { @@ -79,8 +80,12 @@ static jlong BitmapShader_constructor(JNIEnv* env, jobject o, jlong matrixPtr, j } SkSamplingOptions sampling(filter ? SkFilterMode::kLinear : SkFilterMode::kNearest, SkMipmapMode::kNone); - sk_sp<SkShader> shader = image->makeShader( - (SkTileMode)tileModeX, (SkTileMode)tileModeY, sampling); + sk_sp<SkShader> shader; + if (isDirectSampled) { + shader = image->makeRawShader((SkTileMode)tileModeX, (SkTileMode)tileModeY, sampling); + } else { + shader = image->makeShader((SkTileMode)tileModeX, (SkTileMode)tileModeY, sampling); + } ThrowIAE_IfNull(env, shader.get()); if (matrix) { @@ -393,7 +398,7 @@ static const JNINativeMethod gShaderMethods[] = { }; static const JNINativeMethod gBitmapShaderMethods[] = { - { "nativeCreate", "(JJIIZ)J", (void*)BitmapShader_constructor }, + {"nativeCreate", "(JJIIZZ)J", (void*)BitmapShader_constructor}, }; static const JNINativeMethod gLinearGradientMethods[] = { diff --git a/libs/hwui/jni/text/MeasuredText.cpp b/libs/hwui/jni/text/MeasuredText.cpp index bd9bd7121f8a..09539ecc34b0 100644 --- a/libs/hwui/jni/text/MeasuredText.cpp +++ b/libs/hwui/jni/text/MeasuredText.cpp @@ -65,11 +65,11 @@ static jlong nInitBuilder(CRITICAL_JNI_PARAMS) { // Regular JNI static void nAddStyleRun(JNIEnv* /* unused */, jclass /* unused */, jlong builderPtr, - jlong paintPtr, jint start, jint end, jboolean isRtl) { + jlong paintPtr, jint lbStyle, 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), isRtl); + toBuilder(builderPtr)->addStyleRun(start, end, std::move(minikinPaint), lbStyle, isRtl); } // Regular JNI @@ -144,7 +144,7 @@ static jint nGetMemoryUsage(CRITICAL_JNI_PARAMS_COMMA jlong ptr) { static const JNINativeMethod gMTBuilderMethods[] = { // MeasuredParagraphBuilder native functions. {"nInitBuilder", "()J", (void*)nInitBuilder}, - {"nAddStyleRun", "(JJIIZ)V", (void*)nAddStyleRun}, + {"nAddStyleRun", "(JJIIIZ)V", (void*)nAddStyleRun}, {"nAddReplacementRun", "(JJIIF)V", (void*)nAddReplacementRun}, {"nBuildMeasuredText", "(JJ[CZZZ)J", (void*)nBuildMeasuredText}, {"nFreeBuilder", "(J)V", (void*)nFreeBuilder}, diff --git a/libs/input/Android.bp b/libs/input/Android.bp index 55f932dffff1..6c0fd5f65359 100644 --- a/libs/input/Android.bp +++ b/libs/input/Android.bp @@ -55,6 +55,7 @@ cc_library_shared { "-Wall", "-Wextra", "-Werror", + "-Wthread-safety", ], } diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp index f43586f8d9d0..1dc74e5f7740 100644 --- a/libs/input/PointerController.cpp +++ b/libs/input/PointerController.cpp @@ -18,11 +18,13 @@ //#define LOG_NDEBUG 0 #include "PointerController.h" -#include "PointerControllerContext.h" #include <SkBlendMode.h> #include <SkCanvas.h> #include <SkColor.h> +#include <android-base/thread_annotations.h> + +#include "PointerControllerContext.h" namespace android { @@ -36,8 +38,18 @@ const ui::Transform kIdentityTransform; void PointerController::DisplayInfoListener::onWindowInfosChanged( const std::vector<android::gui::WindowInfo>&, - const std::vector<android::gui::DisplayInfo>& displayInfo) { - mPointerController.onDisplayInfosChanged(displayInfo); + const std::vector<android::gui::DisplayInfo>& displayInfos) { + std::scoped_lock lock(mLock); + if (mPointerController == nullptr) return; + + // PointerController uses DisplayInfoListener's lock. + base::ScopedLockAssertion assumeLocked(mPointerController->getLock()); + mPointerController->onDisplayInfosChangedLocked(displayInfos); +} + +void PointerController::DisplayInfoListener::onPointerControllerDestroyed() { + std::scoped_lock lock(mLock); + mPointerController = nullptr; } // --- PointerController --- @@ -68,16 +80,36 @@ std::shared_ptr<PointerController> PointerController::create( PointerController::PointerController(const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper, const sp<SpriteController>& spriteController) + : PointerController( + policy, looper, spriteController, + [](const sp<android::gui::WindowInfosListener>& listener) { + SurfaceComposerClient::getDefault()->addWindowInfosListener(listener); + }, + [](const sp<android::gui::WindowInfosListener>& listener) { + SurfaceComposerClient::getDefault()->removeWindowInfosListener(listener); + }) {} + +PointerController::PointerController(const sp<PointerControllerPolicyInterface>& policy, + const sp<Looper>& looper, + const sp<SpriteController>& spriteController, + WindowListenerConsumer registerListener, + WindowListenerConsumer unregisterListener) : mContext(policy, looper, spriteController, *this), mCursorController(mContext), - mDisplayInfoListener(new DisplayInfoListener(*this)) { - std::scoped_lock lock(mLock); + mDisplayInfoListener(new DisplayInfoListener(this)), + mUnregisterWindowInfosListener(std::move(unregisterListener)) { + std::scoped_lock lock(getLock()); mLocked.presentation = Presentation::SPOT; - SurfaceComposerClient::getDefault()->addWindowInfosListener(mDisplayInfoListener); + registerListener(mDisplayInfoListener); } PointerController::~PointerController() { - SurfaceComposerClient::getDefault()->removeWindowInfosListener(mDisplayInfoListener); + mDisplayInfoListener->onPointerControllerDestroyed(); + mUnregisterWindowInfosListener(mDisplayInfoListener); +} + +std::mutex& PointerController::getLock() const { + return mDisplayInfoListener->mLock; } bool PointerController::getBounds(float* outMinX, float* outMinY, float* outMaxX, @@ -89,7 +121,7 @@ void PointerController::move(float deltaX, float deltaY) { const int32_t displayId = mCursorController.getDisplayId(); vec2 transformed; { - std::scoped_lock lock(mLock); + std::scoped_lock lock(getLock()); const auto& transform = getTransformForDisplayLocked(displayId); transformed = transformWithoutTranslation(transform, {deltaX, deltaY}); } @@ -108,7 +140,7 @@ void PointerController::setPosition(float x, float y) { const int32_t displayId = mCursorController.getDisplayId(); vec2 transformed; { - std::scoped_lock lock(mLock); + std::scoped_lock lock(getLock()); const auto& transform = getTransformForDisplayLocked(displayId); transformed = transform.transform(x, y); } @@ -119,7 +151,7 @@ void PointerController::getPosition(float* outX, float* outY) const { const int32_t displayId = mCursorController.getDisplayId(); mCursorController.getPosition(outX, outY); { - std::scoped_lock lock(mLock); + std::scoped_lock lock(getLock()); const auto& transform = getTransformForDisplayLocked(displayId); const auto xy = transform.inverse().transform(*outX, *outY); *outX = xy.x; @@ -132,17 +164,17 @@ int32_t PointerController::getDisplayId() const { } void PointerController::fade(Transition transition) { - std::scoped_lock lock(mLock); + std::scoped_lock lock(getLock()); mCursorController.fade(transition); } void PointerController::unfade(Transition transition) { - std::scoped_lock lock(mLock); + std::scoped_lock lock(getLock()); mCursorController.unfade(transition); } void PointerController::setPresentation(Presentation presentation) { - std::scoped_lock lock(mLock); + std::scoped_lock lock(getLock()); if (mLocked.presentation == presentation) { return; @@ -162,7 +194,7 @@ void PointerController::setPresentation(Presentation presentation) { void PointerController::setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex, BitSet32 spotIdBits, int32_t displayId) { - std::scoped_lock lock(mLock); + std::scoped_lock lock(getLock()); std::array<PointerCoords, MAX_POINTERS> outSpotCoords{}; const ui::Transform& transform = getTransformForDisplayLocked(displayId); @@ -185,11 +217,11 @@ void PointerController::setSpots(const PointerCoords* spotCoords, const uint32_t } void PointerController::clearSpots() { - std::scoped_lock lock(mLock); + std::scoped_lock lock(getLock()); clearSpotsLocked(); } -void PointerController::clearSpotsLocked() REQUIRES(mLock) { +void PointerController::clearSpotsLocked() { for (auto& [displayID, spotController] : mLocked.spotControllers) { spotController.clearSpots(); } @@ -200,7 +232,7 @@ void PointerController::setInactivityTimeout(InactivityTimeout inactivityTimeout } void PointerController::reloadPointerResources() { - std::scoped_lock lock(mLock); + std::scoped_lock lock(getLock()); for (auto& [displayID, spotController] : mLocked.spotControllers) { spotController.reloadSpotResources(); @@ -216,7 +248,7 @@ void PointerController::reloadPointerResources() { } void PointerController::setDisplayViewport(const DisplayViewport& viewport) { - std::scoped_lock lock(mLock); + std::scoped_lock lock(getLock()); bool getAdditionalMouseResources = false; if (mLocked.presentation == PointerController::Presentation::POINTER) { @@ -226,12 +258,12 @@ void PointerController::setDisplayViewport(const DisplayViewport& viewport) { } void PointerController::updatePointerIcon(int32_t iconId) { - std::scoped_lock lock(mLock); + std::scoped_lock lock(getLock()); mCursorController.updatePointerIcon(iconId); } void PointerController::setCustomPointerIcon(const SpriteIcon& icon) { - std::scoped_lock lock(mLock); + std::scoped_lock lock(getLock()); mCursorController.setCustomPointerIcon(icon); } @@ -245,7 +277,7 @@ void PointerController::onDisplayViewportsUpdated(std::vector<DisplayViewport>& displayIdSet.insert(viewport.displayId); } - std::scoped_lock lock(mLock); + std::scoped_lock lock(getLock()); for (auto it = mLocked.spotControllers.begin(); it != mLocked.spotControllers.end();) { int32_t displayID = it->first; if (!displayIdSet.count(displayID)) { @@ -261,8 +293,8 @@ void PointerController::onDisplayViewportsUpdated(std::vector<DisplayViewport>& } } -void PointerController::onDisplayInfosChanged(const std::vector<gui::DisplayInfo>& displayInfo) { - std::scoped_lock lock(mLock); +void PointerController::onDisplayInfosChangedLocked( + const std::vector<gui::DisplayInfo>& displayInfo) { mLocked.mDisplayInfos = displayInfo; } diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h index 796077f6c38c..2e6e851ee15a 100644 --- a/libs/input/PointerController.h +++ b/libs/input/PointerController.h @@ -72,13 +72,31 @@ public: void reloadPointerResources(); void onDisplayViewportsUpdated(std::vector<DisplayViewport>& viewports); - void onDisplayInfosChanged(const std::vector<gui::DisplayInfo>& displayInfos); + void onDisplayInfosChangedLocked(const std::vector<gui::DisplayInfo>& displayInfos) + REQUIRES(getLock()); + +protected: + using WindowListenerConsumer = + std::function<void(const sp<android::gui::WindowInfosListener>&)>; + + // Constructor used to test WindowInfosListener registration. + PointerController(const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper, + const sp<SpriteController>& spriteController, + WindowListenerConsumer registerListener, + WindowListenerConsumer unregisterListener); private: + PointerController(const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper, + const sp<SpriteController>& spriteController); + friend PointerControllerContext::LooperCallback; friend PointerControllerContext::MessageHandler; - mutable std::mutex mLock; + // PointerController's DisplayInfoListener can outlive the PointerController because when the + // listener is registered, a strong pointer to the listener (which can extend its lifecycle) + // is given away. To avoid the small overhead of using two separate locks in these two objects, + // we use the DisplayInfoListener's lock in PointerController. + std::mutex& getLock() const; PointerControllerContext mContext; @@ -89,24 +107,28 @@ private: std::vector<gui::DisplayInfo> mDisplayInfos; std::unordered_map<int32_t /* displayId */, TouchSpotController> spotControllers; - } mLocked GUARDED_BY(mLock); + } mLocked GUARDED_BY(getLock()); class DisplayInfoListener : public gui::WindowInfosListener { public: - explicit DisplayInfoListener(PointerController& pc) : mPointerController(pc){}; + explicit DisplayInfoListener(PointerController* pc) : mPointerController(pc){}; void onWindowInfosChanged(const std::vector<android::gui::WindowInfo>&, const std::vector<android::gui::DisplayInfo>&) override; + void onPointerControllerDestroyed(); + + // This lock is also used by PointerController. See PointerController::getLock(). + std::mutex mLock; private: - PointerController& mPointerController; + PointerController* mPointerController GUARDED_BY(mLock); }; + sp<DisplayInfoListener> mDisplayInfoListener; + const WindowListenerConsumer mUnregisterWindowInfosListener; - const ui::Transform& getTransformForDisplayLocked(int displayId) const REQUIRES(mLock); + const ui::Transform& getTransformForDisplayLocked(int displayId) const REQUIRES(getLock()); - PointerController(const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper, - const sp<SpriteController>& spriteController); - void clearSpotsLocked(); + void clearSpotsLocked() REQUIRES(getLock()); }; } // namespace android diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp index b67088a389b6..dae1fccec804 100644 --- a/libs/input/tests/PointerController_test.cpp +++ b/libs/input/tests/PointerController_test.cpp @@ -255,4 +255,36 @@ TEST_F(PointerControllerTest, doesNotGetResourcesBeforeSettingViewport) { ensureDisplayViewportIsSet(); } +class PointerControllerWindowInfoListenerTest : public Test {}; + +class TestPointerController : public PointerController { +public: + TestPointerController(sp<android::gui::WindowInfosListener>& registeredListener, + const sp<Looper>& looper) + : PointerController( + new MockPointerControllerPolicyInterface(), looper, + new NiceMock<MockSpriteController>(looper), + [®isteredListener](const sp<android::gui::WindowInfosListener>& listener) { + // Register listener + registeredListener = listener; + }, + [®isteredListener](const sp<android::gui::WindowInfosListener>& listener) { + // Unregister listener + if (registeredListener == listener) registeredListener = nullptr; + }) {} +}; + +TEST_F(PointerControllerWindowInfoListenerTest, + doesNotCrashIfListenerCalledAfterPointerControllerDestroyed) { + sp<android::gui::WindowInfosListener> registeredListener; + sp<android::gui::WindowInfosListener> localListenerCopy; + { + TestPointerController pointerController(registeredListener, new Looper(false)); + ASSERT_NE(nullptr, registeredListener) << "WindowInfosListener was not registered"; + localListenerCopy = registeredListener; + } + EXPECT_EQ(nullptr, registeredListener) << "WindowInfosListener was not unregistered"; + localListenerCopy->onWindowInfosChanged({}, {}); +} + } // namespace android diff --git a/libs/usb/tests/accessorytest/f_accessory.h b/libs/usb/tests/accessorytest/f_accessory.h index 312f4ba6eed3..75e017c16674 100644 --- a/libs/usb/tests/accessorytest/f_accessory.h +++ b/libs/usb/tests/accessorytest/f_accessory.h @@ -1,148 +1,53 @@ -/* - * Gadget Function Driver for Android USB accessories - * - * Copyright (C) 2011 Google, Inc. - * Author: Mike Lockwood <lockwood@android.com> - * - * This software is licensed under the terms of the GNU General Public - * License version 2, as published by the Free Software Foundation, and - * may be copied, distributed, and modified under those terms. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - */ - -#ifndef __LINUX_USB_F_ACCESSORY_H -#define __LINUX_USB_F_ACCESSORY_H - -/* Use Google Vendor ID when in accessory mode */ +/**************************************************************************** + **************************************************************************** + *** + *** This header was automatically generated from a Linux kernel header + *** of the same name, to make information necessary for userspace to + *** call into the kernel available to libc. It contains only constants, + *** structures, and macros generated from the original header, and thus, + *** contains no copyrightable information. + *** + *** To edit the content of this header, modify the corresponding + *** source file (e.g. under external/kernel-headers/original/) then + *** run bionic/libc/kernel/tools/update_all.py + *** + *** Any manual change here will be lost the next time this script will + *** be run. You've been warned! + *** + **************************************************************************** + ****************************************************************************/ +#ifndef _UAPI_LINUX_USB_F_ACCESSORY_H +#define _UAPI_LINUX_USB_F_ACCESSORY_H #define USB_ACCESSORY_VENDOR_ID 0x18D1 - - -/* Product ID to use when in accessory mode */ #define USB_ACCESSORY_PRODUCT_ID 0x2D00 - -/* Product ID to use when in accessory mode and adb is enabled */ +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ #define USB_ACCESSORY_ADB_PRODUCT_ID 0x2D01 - -/* Indexes for strings sent by the host via ACCESSORY_SEND_STRING */ -#define ACCESSORY_STRING_MANUFACTURER 0 -#define ACCESSORY_STRING_MODEL 1 -#define ACCESSORY_STRING_DESCRIPTION 2 -#define ACCESSORY_STRING_VERSION 3 -#define ACCESSORY_STRING_URI 4 -#define ACCESSORY_STRING_SERIAL 5 - -/* Control request for retrieving device's protocol version - * - * requestType: USB_DIR_IN | USB_TYPE_VENDOR - * request: ACCESSORY_GET_PROTOCOL - * value: 0 - * index: 0 - * data version number (16 bits little endian) - * 1 for original accessory support - * 2 adds audio and HID support - */ -#define ACCESSORY_GET_PROTOCOL 51 - -/* Control request for host to send a string to the device - * - * requestType: USB_DIR_OUT | USB_TYPE_VENDOR - * request: ACCESSORY_SEND_STRING - * value: 0 - * index: string ID - * data zero terminated UTF8 string - * - * The device can later retrieve these strings via the - * ACCESSORY_GET_STRING_* ioctls - */ -#define ACCESSORY_SEND_STRING 52 - -/* Control request for starting device in accessory mode. - * The host sends this after setting all its strings to the device. - * - * requestType: USB_DIR_OUT | USB_TYPE_VENDOR - * request: ACCESSORY_START - * value: 0 - * index: 0 - * data none - */ -#define ACCESSORY_START 53 - -/* Control request for registering a HID device. - * Upon registering, a unique ID is sent by the accessory in the - * value parameter. This ID will be used for future commands for - * the device - * - * requestType: USB_DIR_OUT | USB_TYPE_VENDOR - * request: ACCESSORY_REGISTER_HID_DEVICE - * value: Accessory assigned ID for the HID device - * index: total length of the HID report descriptor - * data none - */ -#define ACCESSORY_REGISTER_HID 54 - -/* Control request for unregistering a HID device. - * - * requestType: USB_DIR_OUT | USB_TYPE_VENDOR - * request: ACCESSORY_REGISTER_HID - * value: Accessory assigned ID for the HID device - * index: 0 - * data none - */ -#define ACCESSORY_UNREGISTER_HID 55 - -/* Control request for sending the HID report descriptor. - * If the HID descriptor is longer than the endpoint zero max packet size, - * the descriptor will be sent in multiple ACCESSORY_SET_HID_REPORT_DESC - * commands. The data for the descriptor must be sent sequentially - * if multiple packets are needed. - * - * requestType: USB_DIR_OUT | USB_TYPE_VENDOR - * request: ACCESSORY_SET_HID_REPORT_DESC - * value: Accessory assigned ID for the HID device - * index: offset of data in descriptor - * (needed when HID descriptor is too big for one packet) - * data the HID report descriptor - */ -#define ACCESSORY_SET_HID_REPORT_DESC 56 - -/* Control request for sending HID events. - * - * requestType: USB_DIR_OUT | USB_TYPE_VENDOR - * request: ACCESSORY_SEND_HID_EVENT - * value: Accessory assigned ID for the HID device - * index: 0 - * data the HID report for the event - */ -#define ACCESSORY_SEND_HID_EVENT 57 - -/* Control request for setting the audio mode. - * - * requestType: USB_DIR_OUT | USB_TYPE_VENDOR - * request: ACCESSORY_SET_AUDIO_MODE - * value: 0 - no audio - * 1 - device to host, 44100 16-bit stereo PCM - * index: 0 - * data the HID report for the event - */ -#define ACCESSORY_SET_AUDIO_MODE 58 - - - -/* ioctls for retrieving strings set by the host */ -#define ACCESSORY_GET_STRING_MANUFACTURER _IOW('M', 1, char[256]) -#define ACCESSORY_GET_STRING_MODEL _IOW('M', 2, char[256]) -#define ACCESSORY_GET_STRING_DESCRIPTION _IOW('M', 3, char[256]) -#define ACCESSORY_GET_STRING_VERSION _IOW('M', 4, char[256]) -#define ACCESSORY_GET_STRING_URI _IOW('M', 5, char[256]) -#define ACCESSORY_GET_STRING_SERIAL _IOW('M', 6, char[256]) -/* returns 1 if there is a start request pending */ -#define ACCESSORY_IS_START_REQUESTED _IO('M', 7) -/* returns audio mode (set via the ACCESSORY_SET_AUDIO_MODE control request) */ -#define ACCESSORY_GET_AUDIO_MODE _IO('M', 8) - -#endif /* __LINUX_USB_F_ACCESSORY_H */ +#define ACCESSORY_STRING_MANUFACTURER 0 +#define ACCESSORY_STRING_MODEL 1 +#define ACCESSORY_STRING_DESCRIPTION 2 +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +#define ACCESSORY_STRING_VERSION 3 +#define ACCESSORY_STRING_URI 4 +#define ACCESSORY_STRING_SERIAL 5 +#define ACCESSORY_GET_PROTOCOL 51 +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +#define ACCESSORY_SEND_STRING 52 +#define ACCESSORY_START 53 +#define ACCESSORY_REGISTER_HID 54 +#define ACCESSORY_UNREGISTER_HID 55 +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +#define ACCESSORY_SET_HID_REPORT_DESC 56 +#define ACCESSORY_SEND_HID_EVENT 57 +#define ACCESSORY_SET_AUDIO_MODE 58 +#define ACCESSORY_GET_STRING_MANUFACTURER _IOW('M', 1, char[256]) +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +#define ACCESSORY_GET_STRING_MODEL _IOW('M', 2, char[256]) +#define ACCESSORY_GET_STRING_DESCRIPTION _IOW('M', 3, char[256]) +#define ACCESSORY_GET_STRING_VERSION _IOW('M', 4, char[256]) +#define ACCESSORY_GET_STRING_URI _IOW('M', 5, char[256]) +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ +#define ACCESSORY_GET_STRING_SERIAL _IOW('M', 6, char[256]) +#define ACCESSORY_IS_START_REQUESTED _IO('M', 7) +#define ACCESSORY_GET_AUDIO_MODE _IO('M', 8) +#endif +/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */ 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/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..a46229da76ce 100644 --- a/media/aidl/android/media/audio/common/AudioOutputFlags.aidl +++ b/media/aidl/android/media/audio/common/AudioOutputFlags.aidl @@ -97,4 +97,8 @@ enum AudioOutputFlags { * tracks. */ GAPLESS_OFFLOAD = 15, + /** + * Output is used for transmitting ultrasound audio. + */ + ULTRASOUND = 16, } 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/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..e2f286e775d2 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,5 @@ enum AudioOutputFlags { VOIP_RX = 13, INCALL_MUSIC = 14, GAPLESS_OFFLOAD = 15, + ULTRASOUND = 16, } 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.bp b/media/java/Android.bp index eeaf6e9015ac..c7c1d54cd3c6 100644 --- a/media/java/Android.bp +++ b/media/java/Android.bp @@ -8,7 +8,7 @@ package { } filegroup { - name: "framework-media-sources", + name: "framework-media-non-updatable-sources", srcs: [ "**/*.java", "**/*.aidl", 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/AudioManager.java b/media/java/android/media/AudioManager.java index ffd5eaaccf61..68e5d94f7559 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -79,6 +79,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -5648,6 +5649,43 @@ public class AudioManager { } /** + * Get the audio devices that would be used for the routing of the given audio attributes. + * These are the devices anticipated to play sound from an {@link AudioTrack} created with + * the specified {@link AudioAttributes}. + * The audio routing can change if audio devices are physically connected or disconnected or + * concurrently through {@link AudioRouting} or {@link MediaRouter}. + * @param attributes the {@link AudioAttributes} for which the routing is being queried + * @return an empty list if there was an issue with the request, a list of + * {@link AudioDeviceInfo} otherwise (typically one device, except for duplicated paths). + */ + public @NonNull List<AudioDeviceInfo> getAudioDevicesForAttributes( + @NonNull AudioAttributes attributes) { + final List<AudioDeviceAttributes> devicesForAttributes; + try { + Objects.requireNonNull(attributes); + final IAudioService service = getService(); + devicesForAttributes = service.getDevicesForAttributesUnprotected(attributes); + } catch (Exception e) { + Log.i(TAG, "No audio devices available for specified attributes."); + return Collections.emptyList(); + } + + // Map from AudioDeviceAttributes to AudioDeviceInfo + AudioDeviceInfo[] outputDeviceInfos = getDevicesStatic(GET_DEVICES_OUTPUTS); + List<AudioDeviceInfo> deviceInfosForAttributes = new ArrayList<>(); + for (AudioDeviceAttributes deviceForAttributes : devicesForAttributes) { + for (AudioDeviceInfo deviceInfo : outputDeviceInfos) { + if (deviceForAttributes.getType() == deviceInfo.getType() + && TextUtils.equals(deviceForAttributes.getAddress(), + deviceInfo.getAddress())) { + deviceInfosForAttributes.add(deviceInfo); + } + } + } + return Collections.unmodifiableList(deviceInfosForAttributes); + } + + /** * @hide * Volume behavior for an audio device that has no particular volume behavior set. Invalid as * an argument to {@link #setDeviceVolumeBehavior(AudioDeviceAttributes, int)} and should not 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..50bf1e5e0a26 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: diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 7f6fb90297b3..96199a988704 100755 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -308,6 +308,8 @@ interface IAudioService { List<AudioDeviceAttributes> getDevicesForAttributes(in AudioAttributes attributes); + List<AudioDeviceAttributes> getDevicesForAttributesUnprotected(in AudioAttributes attributes); + int setAllowedCapturePolicy(in int capturePolicy); int getAllowedCapturePolicy(); 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/MediaMetadataRetriever.java b/media/java/android/media/MediaMetadataRetriever.java index 60d21c0b3b97..ad8fc0707f3e 100644 --- a/media/java/android/media/MediaMetadataRetriever.java +++ b/media/java/android/media/MediaMetadataRetriever.java @@ -1077,7 +1077,9 @@ public class MediaMetadataRetriever implements AutoCloseable { * Releases any acquired resources. Call it when done with the object. * * @throws IOException When an {@link IOException} is thrown while closing a {@link - * MediaDataSource} passed to {@link #setDataSource(MediaDataSource)}. + * MediaDataSource} passed to {@link #setDataSource(MediaDataSource)}. This throws clause exists + * since API 33, but this method can throw in earlier API versions where the exception is not + * declared. */ @Override public void close() throws IOException { @@ -1088,7 +1090,9 @@ public class MediaMetadataRetriever implements AutoCloseable { * Releases any acquired resources. Call it when done with the object. * * @throws IOException When an {@link IOException} is thrown while closing a {@link - * MediaDataSource} passed to {@link #setDataSource(MediaDataSource)}. + * MediaDataSource} passed to {@link #setDataSource(MediaDataSource)}. This throws clause exists + * since API 33, but this method can throw in earlier API versions where the exception is not + * declared. */ public native void release() throws IOException; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) 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/Spatializer.java b/media/java/android/media/Spatializer.java index f3f8bbe22637..030d212825ee 100644 --- a/media/java/android/media/Spatializer.java +++ b/media/java/android/media/Spatializer.java @@ -215,6 +215,29 @@ public class Spatializer { public static final int HEAD_TRACKING_MODE_RELATIVE_DEVICE = 2; /** + * @hide + * Head tracking mode to string conversion + * @param mode a valid head tracking mode + * @return a string containing the matching constant name + */ + public static final String headtrackingModeToString(int mode) { + switch(mode) { + case HEAD_TRACKING_MODE_UNSUPPORTED: + return "HEAD_TRACKING_MODE_UNSUPPORTED"; + case HEAD_TRACKING_MODE_DISABLED: + return "HEAD_TRACKING_MODE_DISABLED"; + case HEAD_TRACKING_MODE_OTHER: + return "HEAD_TRACKING_MODE_OTHER"; + case HEAD_TRACKING_MODE_RELATIVE_WORLD: + return "HEAD_TRACKING_MODE_RELATIVE_WORLD"; + case HEAD_TRACKING_MODE_RELATIVE_DEVICE: + return "HEAD_TRACKING_MODE_RELATIVE_DEVICE"; + default: + return "head tracking mode unknown " + mode; + } + } + + /** * Return the level of support for the spatialization feature on this device. * This level of support is independent of whether the {@code Spatializer} is currently * enabled or available and will not change over time. 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/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/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..a8ef0957286c 100644 --- a/media/java/android/media/tv/interactive/ITvIAppManager.aidl +++ b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl @@ -31,7 +31,7 @@ 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); 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..68fae2d96c98 100644 --- a/media/java/android/media/tv/interactive/ITvInteractiveAppService.aidl +++ b/media/java/android/media/tv/interactive/ITvInteractiveAppService.aidl @@ -23,7 +23,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 { 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..15a5f823e144 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() { @@ -699,15 +794,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 +838,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 +855,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 +1238,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 +1253,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 +1268,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 +1283,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 +1574,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 +1649,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 +1683,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 +1714,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 +1810,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..c27aeb98a2ed 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 = @@ -196,8 +203,7 @@ 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) { } /** @@ -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/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/BtProfileConnectionInfoTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/BtProfileConnectionInfoTest.java new file mode 100644 index 000000000000..fd66d3b9904e --- /dev/null +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/BtProfileConnectionInfoTest.java @@ -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. + */ + +package com.android.mediaframeworktest.unit; + +import static org.junit.Assert.assertEquals; + +import android.bluetooth.BluetoothProfile; +import android.media.BtProfileConnectionInfo; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class BtProfileConnectionInfoTest { + + @Test + public void testCoverageA2dp() { + final boolean supprNoisy = false; + final int volume = 42; + final BtProfileConnectionInfo info = BtProfileConnectionInfo.a2dpInfo(supprNoisy, volume); + assertEquals(info.getProfile(), BluetoothProfile.A2DP); + assertEquals(info.getSuppressNoisyIntent(), supprNoisy); + assertEquals(info.getVolume(), volume); + } + + @Test + public void testCoverageA2dpSink() { + final int volume = 42; + final BtProfileConnectionInfo info = BtProfileConnectionInfo.a2dpSinkInfo(volume); + assertEquals(info.getProfile(), BluetoothProfile.A2DP_SINK); + assertEquals(info.getVolume(), volume); + } + + @Test + public void testCoveragehearingAid() { + final boolean supprNoisy = true; + final BtProfileConnectionInfo info = BtProfileConnectionInfo.hearingAidInfo(supprNoisy); + assertEquals(info.getProfile(), BluetoothProfile.HEARING_AID); + assertEquals(info.getSuppressNoisyIntent(), supprNoisy); + } + + @Test + public void testCoverageLeAudio() { + final boolean supprNoisy = false; + final boolean isLeOutput = true; + final BtProfileConnectionInfo info = BtProfileConnectionInfo.leAudio(supprNoisy, + isLeOutput); + assertEquals(info.getProfile(), BluetoothProfile.LE_AUDIO); + assertEquals(info.getSuppressNoisyIntent(), supprNoisy); + assertEquals(info.getIsLeOutput(), isLeOutput); + } +} + 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/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 cc7b2a5a30e2..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 */ @@ -143,6 +158,25 @@ public class NetworkStatsManager { } /** @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 + * be omitted in case of rate limiting. + * + * @param pollOnOpen true if poll is needed. + * @hide + */ + // 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; @@ -196,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); @@ -370,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); @@ -403,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); @@ -419,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. * @@ -484,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. @@ -499,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. } /** @@ -570,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. * - * @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 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 + @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 getMobileUidStats() { try { - return mService.getDetailedUidStats(requiredIfaces.toArray(new String[0])); + return mService.getUidStatsForTransport(TRANSPORT_CELLULAR); } catch (RemoteException e) { - if (DBG) Log.d(TAG, "Remote exception when get detailed uid stats"); + if (DBG) Log.d(TAG, "Remote exception when get Mobile uid stats"); + throw e.rethrowFromSystemServer(); + } + } + + /** + * Query realtime Wi-Fi network usage statistics. + * + * 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 + @RequiresPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) + @NonNull public android.net.NetworkStats getWifiUidStats() { + try { + return mService.getUidStatsForTransport(TRANSPORT_WIFI); + } catch (RemoteException e) { + if (DBG) Log.d(TAG, "Remote exception when get WiFi uid stats"); throw e.rethrowFromSystemServer(); } } @@ -863,4 +992,83 @@ public class NetworkStatsManager { return msg.getData().getParcelable(key); } } + + /** + * Mark given UID as being in foreground for stats purposes. + * + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_STACK}) + public void setUidForeground(int uid, boolean uidForeground) { + try { + mService.setUidForeground(uid, uidForeground); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Set default value of global alert bytes, the value will be clamped to [128kB, 2MB]. + * + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + Manifest.permission.NETWORK_STACK}) + public void setDefaultGlobalAlert(long alertBytes) { + try { + // TODO: Sync internal naming with the API surface. + mService.advisePersistThreshold(alertBytes); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Force update of statistics. + * + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_STACK}) + public void forceUpdate() { + try { + mService.forceUpdate(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * 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(client = MODULE_LIBRARIES) + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_STACK}) + public void setStatsProviderWarningAndLimitAsync(@NonNull String iface, long warning, + long limit) { + try { + mService.setStatsProviderWarningAndLimitAsync(iface, warning, limit); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } 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/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsService.aidl b/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsService.aidl index 12937b5cb2c7..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 @@ -94,4 +88,16 @@ interface INetworkStatsService { /** Registers a network stats provider */ INetworkStatsProviderCallback registerNetworkStatsProvider(String tag, in INetworkStatsProvider provider); + + /** Mark given UID as being in foreground for stats purposes. */ + void setUidForeground(int uid, boolean uidForeground); + + /** Advise persistence threshold; may be overridden internally. */ + void advisePersistThreshold(long thresholdBytes); + + /** + * Set the warning and limit to all registered custom network stats providers. + * Note that invocation of any interface will be sent to all providers. + */ + void setStatsProviderWarningAndLimitAsync(String iface, long warning, long limit); } 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/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..a62aa519a909 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java +++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java @@ -17,17 +17,24 @@ package android.net; 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.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.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 +44,24 @@ import java.util.Objects; * * @hide */ +// @SystemApi(client = MODULE_LIBRARIES) public class NetworkIdentity implements Comparable<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_" }, 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 @@ -59,21 +79,22 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> { public static final int OEM_PRIVATE = 0x2; 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 +103,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 +111,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 +125,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 +174,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 +190,95 @@ 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 the roaming status of this instance. */ + public boolean isRoaming() { + return mRoaming; + } + + /** @hide */ + // TODO: Remove this function after all callers are removed. public boolean getMetered() { return mMetered; } + /** Return the meteredness of this instance. */ + public boolean isMetered() { + return mMetered; + } + + /** @hide */ + // TODO: Remove this function after all callers are removed. public boolean getDefaultNetwork() { return mDefaultNetwork; } + /** Return the default network status of this instance. */ + 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; - } - } - - return new NetworkIdentity(legacyType, subType, subscriberId, networkId, roaming, metered, - defaultNetwork, oemManaged); + @NonNull NetworkStateSnapshot snapshot, + boolean defaultNetwork, @Annotation.NetworkType int ratType) { + return new NetworkIdentity.Builder().setNetworkStateSnapshot(snapshot) + .setDefaultNetwork(defaultNetwork).setRatType(ratType).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)) { @@ -257,16 +292,17 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> { } @Override - public int compareTo(NetworkIdentity another) { + public int compareTo(@NonNull NetworkIdentity another) { + Objects.requireNonNull(another); int res = Integer.compare(mType, another.mType); if (res == 0) { - res = Integer.compare(mSubType, another.mSubType); + res = Integer.compare(mRatType, another.mRatType); } if (res == 0 && mSubscriberId != null && another.mSubscriberId != null) { res = mSubscriberId.compareTo(another.mSubscriberId); } - if (res == 0 && mNetworkId != null && another.mNetworkId != null) { - res = mNetworkId.compareTo(another.mNetworkId); + if (res == 0 && mWifiNetworkKey != null && another.mWifiNetworkKey != null) { + res = mWifiNetworkKey.compareTo(another.mWifiNetworkKey); } if (res == 0) { res = Boolean.compare(mRoaming, another.mRoaming); @@ -282,4 +318,192 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> { } return res; } + + /** + * Builder class for {@link NetworkIdentity}. + */ + public static final class Builder { + 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 to read roaming, metered, wifikey... from the snapshot for convenience. + * + * @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; + if (info != null) { + 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) { + mType = type; + return this; + } + + /** + * Set the Radio Access Technology(RAT) type of the network. + * + * @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) { + 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 the roaming. + * + * @param roaming the roaming status of the network. + * @return this builder. + */ + @NonNull + public Builder setRoaming(boolean roaming) { + mRoaming = roaming; + return this; + } + + /** + * Set the meteredness. + * + * @param metered the meteredness of the network. + * @return this builder. + */ + @NonNull + public Builder setMetered(boolean metered) { + mMetered = metered; + return this; + } + + /** + * Set the default network status. + * + * @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) { + mOemManaged = oemManaged; + return this; + } + + /** + * Builds the instance of the {@link NetworkIdentity}. + * + * @return the built instance of {@link NetworkIdentity}. + */ + @NonNull + public NetworkIdentity build() { + 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..041f070512b0 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,7 @@ import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.util.HashSet; +import java.util.Objects; /** * Identity of a {@code iface}, defined by the set of {@link NetworkIdentity} @@ -32,6 +34,7 @@ import java.util.HashSet; * * @hide */ +// @SystemApi(client = MODULE_LIBRARIES) public class NetworkIdentitySet extends HashSet<NetworkIdentity> implements Comparable<NetworkIdentitySet> { private static final int VERSION_INIT = 1; @@ -41,9 +44,14 @@ 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(DataInput in) throws IOException { final int version = in.readInt(); final int size = in.readInt(); @@ -52,7 +60,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 +99,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; } } @@ -172,7 +190,8 @@ public class NetworkIdentitySet extends HashSet<NetworkIdentity> implements } @Override - public int compareTo(NetworkIdentitySet another) { + public int compareTo(@NonNull NetworkIdentitySet another) { + Objects.requireNonNull(another); if (isEmpty()) return -1; if (another.isEmpty()) return 1; @@ -183,6 +202,7 @@ public class NetworkIdentitySet extends HashSet<NetworkIdentity> implements /** * 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 b00fea4de269..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"; /** @@ -383,6 +384,95 @@ public final class NetworkStats implements Parcelable { this.operations += another.operations; } + /** + * @return interface name of this entry. + * @hide + */ + @Nullable public String getIface() { + return iface; + } + + /** + * @return the uid of this entry. + */ + public int getUid() { + return uid; + } + + /** + * @return the set state of this entry. Should be one of the following + * values: {@link #SET_DEFAULT}, {@link #SET_FOREGROUND}. + */ + @State public int getSet() { + return set; + } + + /** + * @return the tag value of this entry. + */ + public int getTag() { + return tag; + } + + /** + * @return the metered state. Should be one of the following + * values: {link #METERED_YES}, {link #METERED_NO}. + */ + @Meteredness public int getMetered() { + return metered; + } + + /** + * @return the roaming state. Should be one of the following + * values: {link #ROAMING_YES}, {link #ROAMING_NO}. + */ + @Roaming public int getRoaming() { + return roaming; + } + + /** + * @return the default network state. Should be one of the following + * values: {link #DEFAULT_NETWORK_YES}, {link #DEFAULT_NETWORK_NO}. + */ + @DefaultNetwork public int getDefaultNetwork() { + return defaultNetwork; + } + + /** + * @return the number of received bytes. + */ + public long getRxBytes() { + return rxBytes; + } + + /** + * @return the number of received packets. + */ + public long getRxPackets() { + return rxPackets; + } + + /** + * @return the number of transmitted bytes. + */ + public long getTxBytes() { + return txBytes; + } + + /** + * @return the number of transmitted packets. + */ + public long getTxPackets() { + return txPackets; + } + + /** + * @return the count of network operations performed for this entry. + */ + public long getOperations() { + return operations; + } + @Override public String toString() { final StringBuilder builder = new StringBuilder(); @@ -589,11 +679,40 @@ 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 */ @UnsupportedAppUsage - public Entry getValues(int i, Entry recycle) { + public Entry getValues(int i, @Nullable Entry recycle) { final Entry entry = recycle != null ? recycle : new Entry(); entry.iface = iface[i]; entry.uid = uid[i]; diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java index 9f9d73f88851..f169fed6b9b3 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; @@ -77,6 +79,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 +103,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 +128,7 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W mDirty = false; } + /** @hide */ public long getStartMillis() { return mStartMillis; } @@ -124,6 +136,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 +146,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 +187,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 +202,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 +228,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 +354,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 +401,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 +412,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 +432,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 +465,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 +505,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 +548,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 +594,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 +665,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<>(); @@ -669,6 +706,7 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W return keys; } + /** @hide */ public void dump(IndentingPrintWriter pw) { for (Key key : getSortedKeys()) { pw.print("ident="); pw.print(key.ident.toString()); @@ -683,6 +721,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 +745,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 +808,32 @@ 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 implements Comparable<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 NetworkIdentitySet ident, int uid, int set, int tag) { + this.ident = Objects.requireNonNull(ident); this.uid = uid; this.set = set; this.tag = tag; @@ -790,7 +846,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 @@ -800,7 +856,8 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W } @Override - public int compareTo(Key another) { + public int compareTo(@NonNull Key another) { + Objects.requireNonNull(another); int res = 0; if (ident != null && another.ident != null) { res = ident.compareTo(another.ident); diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsHistory.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsHistory.java index 428bc6df266a..90054c683de5 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsHistory.java +++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsHistory.java @@ -30,6 +30,7 @@ import static android.text.format.DateUtils.SECOND_IN_MILLIS; import static com.android.net.module.util.NetworkStatsUtils.multiplySafeByRational; +import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; import android.os.Parcel; @@ -64,18 +65,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; @@ -108,15 +116,18 @@ public class NetworkStatsHistory implements Parcelable { public long 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 +141,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 +163,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 +175,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 +218,7 @@ public class NetworkStatsHistory implements Parcelable { } } + /** @hide */ public void writeToStream(DataOutput out) throws IOException { out.writeInt(VERSION_ADD_ACTIVE); out.writeLong(bucketDuration); @@ -221,15 +236,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 +257,7 @@ public class NetworkStatsHistory implements Parcelable { } } + /** @hide */ @UnsupportedAppUsage public long getEnd() { if (bucketCount > 0) { @@ -250,6 +269,7 @@ public class NetworkStatsHistory implements Parcelable { /** * Return total bytes represented by this history. + * @hide */ public long getTotalBytes() { return totalBytes; @@ -258,6 +278,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 +294,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 +308,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 +324,7 @@ public class NetworkStatsHistory implements Parcelable { return entry; } + /** @hide */ public void setValues(int i, Entry entry) { // Unwind old values if (rxBytes != null) totalBytes -= rxBytes[i]; @@ -322,6 +346,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 +357,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 +418,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 +429,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 +511,7 @@ public class NetworkStatsHistory implements Parcelable { /** * Clear all data stored in this object. + * @hide */ public void clear() { bucketStart = EmptyArray.LONG; @@ -498,9 +527,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 +552,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 +568,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 +583,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 +647,7 @@ public class NetworkStatsHistory implements Parcelable { /** * @deprecated only for temporary testing + * @hide */ @Deprecated public void generateRandom(long start, long end, long bytes) { @@ -631,6 +666,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 +696,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 +715,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 +739,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 +757,7 @@ public class NetworkStatsHistory implements Parcelable { } } + /** @hide */ public void dumpDebug(ProtoOutputStream proto, long tag) { final long start = proto.start(tag); @@ -776,6 +817,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 +825,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 +900,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) { 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 d8feb88f0fe4..c803a723ba83 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java +++ b/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java @@ -26,9 +26,9 @@ 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.RemoteException; -import android.os.ServiceManager; import com.android.server.NetworkManagementSocketTagger; @@ -53,6 +53,7 @@ import java.net.SocketException; * use {@link NetworkStatsManager} instead. */ public class TrafficStats { + private static final String TAG = TrafficStats.class.getSimpleName(); /** * The return value to indicate that the device does not support the statistic. */ @@ -173,8 +174,8 @@ public class TrafficStats { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562) private synchronized static INetworkStatsService getStatsService() { if (sStatsService == null) { - sStatsService = INetworkStatsService.Stub.asInterface( - ServiceManager.getService(Context.NETWORK_STATS_SERVICE)); + throw new IllegalStateException("TrafficStats not initialized, uid=" + + Binder.getCallingUid()); } return sStatsService; } @@ -193,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> @@ -265,6 +286,18 @@ public class TrafficStats { } /** + * Set active tag to use when accounting {@link Socket} traffic originating + * from the current thread. The tag used internally is well-defined to + * distinguish all download provider traffic. + * + * @hide + */ + @SystemApi + public static void setThreadStatsTagDownload() { + setThreadStatsTag(TAG_SYSTEM_DOWNLOAD); + } + + /** * Get the active tag used when accounting {@link Socket} traffic originating * from the current thread. Only one active tag per thread is supported. * {@link #tagSocket(Socket)}. 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/framework-t/src/com/android/server/NetworkManagementSocketTagger.java b/packages/ConnectivityT/framework-t/src/com/android/server/NetworkManagementSocketTagger.java index e35f6a648b77..1eb52fb44629 100644 --- a/packages/ConnectivityT/framework-t/src/com/android/server/NetworkManagementSocketTagger.java +++ b/packages/ConnectivityT/framework-t/src/com/android/server/NetworkManagementSocketTagger.java @@ -26,6 +26,7 @@ import java.net.SocketException; /** * Assigns tags to sockets for traffic stats. + * @hide */ public final class NetworkManagementSocketTagger extends SocketTagger { private static final String TAG = "NetworkManagementSocketTagger"; diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsManagerInternal.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsManagerInternal.java deleted file mode 100644 index 0e9a9da6804b..000000000000 --- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsManagerInternal.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2018 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.annotation.NonNull; -import android.net.NetworkStats; -import android.net.NetworkTemplate; - -public abstract class NetworkStatsManagerInternal { - /** Return network layer usage total for traffic that matches template. */ - public abstract long getNetworkTotalBytes(NetworkTemplate template, long start, long end); - - /** Return network layer usage per-UID for traffic that matches template. */ - public abstract NetworkStats getNetworkUidBytes(NetworkTemplate template, long start, long end); - - /** Mark given UID as being in foreground for stats purposes. */ - public abstract void setUidForeground(int uid, boolean uidForeground); - - /** Advise persistance threshold; may be overridden internally. */ - public abstract void advisePersistThreshold(long thresholdBytes); - - /** Force update of statistics. */ - public abstract void forceUpdate(); - - /** - * Set the warning and limit to all registered custom network stats providers. - * Note that invocation of any interface will be sent to all providers. - */ - public abstract void setStatsProviderWarningAndLimitAsync(@NonNull String iface, long warning, - long limit); -} 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 efc8c555c3c7..9b90f3b54542 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; @@ -154,9 +141,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 +204,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 +234,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 +243,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 +292,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 +355,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @NonNull private final NetworkStatsSubscriptionsMonitor mNetworkStatsSubscriptionsMonitor; + @NonNull + private final LocationPermissionChecker mLocationPermissionChecker; + private static @NonNull File getDefaultSystemDir() { return new File(Environment.getDataDirectory(), "system"); } @@ -428,10 +420,9 @@ 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()); - service.registerLocalService(); return service; } @@ -463,6 +454,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { mContentResolver = mContext.getContentResolver(); mContentObserver = mDeps.makeContentObserver(mHandler, mSettings, mNetworkStatsSubscriptionsMonitor); + mLocationPermissionChecker = mDeps.makeLocationPermissionChecker(mContext); } /** @@ -510,11 +502,13 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } }; } - } - private void registerLocalService() { - LocalServices.addService(NetworkStatsManagerInternal.class, - new NetworkStatsManagerInternalImpl()); + /** + * @see LocationPermissionChecker + */ + public LocationPermissionChecker makeLocationPermissionChecker(final Context context) { + return new LocationPermissionChecker(context); + } } /** @@ -597,13 +591,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(); } @@ -714,12 +708,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 { @@ -733,7 +740,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); @@ -767,26 +774,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); @@ -804,6 +826,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); @@ -816,6 +839,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, @@ -830,6 +854,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, @@ -851,6 +878,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); @@ -887,7 +934,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); @@ -904,14 +951,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); } } @@ -962,11 +1009,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); @@ -1007,7 +1058,8 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } @VisibleForTesting - void setUidForeground(int uid, boolean uidForeground) { + public void setUidForeground(int uid, boolean uidForeground) { + PermissionUtils.enforceNetworkStackPermission(mContext); synchronized (mStatsLock) { final int set = uidForeground ? SET_FOREGROUND : SET_DEFAULT; final int oldSet = mActiveUidCounterSet.get(uid, SET_DEFAULT); @@ -1043,7 +1095,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @Override public void forceUpdate() { - mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG); + PermissionUtils.enforceNetworkStackPermission(mContext); final long token = Binder.clearCallingIdentity(); try { @@ -1053,7 +1105,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } } - private void advisePersistThreshold(long thresholdBytes) { + /** Advise persistence threshold; may be overridden internally. */ + public void advisePersistThreshold(long thresholdBytes) { + PermissionUtils.enforceNetworkStackPermission(mContext); // clamp threshold into safe range mPersistThreshold = NetworkStatsUtils.constrain(thresholdBytes, 128 * KB_IN_BYTES, 2 * MB_IN_BYTES); @@ -1322,16 +1376,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. @@ -1346,12 +1402,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); @@ -1361,6 +1417,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. @@ -1402,6 +1461,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub { if (isMobile) { mobileIfaces.add(iface); } + if (isWifi) { + wifiIfaces.add(iface); + } mStatsFactory.noteStackedIface(iface, baseIface); } @@ -1409,11 +1471,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) { @@ -1431,11 +1498,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; } @@ -1690,52 +1757,19 @@ public class NetworkStatsService extends INetworkStatsService.Stub { removeUidsLocked(CollectionUtils.toIntArray(uids)); } - private class NetworkStatsManagerInternalImpl extends NetworkStatsManagerInternal { - @Override - public long getNetworkTotalBytes(NetworkTemplate template, long start, long end) { - Trace.traceBegin(TRACE_TAG_NETWORK, "getNetworkTotalBytes"); - try { - return NetworkStatsService.this.getNetworkTotalBytes(template, start, end); - } finally { - Trace.traceEnd(TRACE_TAG_NETWORK); - } - } - - @Override - public NetworkStats getNetworkUidBytes(NetworkTemplate template, long start, long end) { - Trace.traceBegin(TRACE_TAG_NETWORK, "getNetworkUidBytes"); - try { - return NetworkStatsService.this.getNetworkUidBytes(template, start, end); - } finally { - Trace.traceEnd(TRACE_TAG_NETWORK); - } - } - - @Override - public void setUidForeground(int uid, boolean uidForeground) { - NetworkStatsService.this.setUidForeground(uid, uidForeground); - } - - @Override - public void advisePersistThreshold(long thresholdBytes) { - NetworkStatsService.this.advisePersistThreshold(thresholdBytes); - } - - @Override - public void forceUpdate() { - NetworkStatsService.this.forceUpdate(); - } - - @Override - public void setStatsProviderWarningAndLimitAsync( - @NonNull String iface, long warning, long limit) { - if (LOGV) { - Log.v(TAG, "setStatsProviderWarningAndLimitAsync(" - + iface + "," + warning + "," + limit + ")"); - } - invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.onSetWarningAndLimit(iface, - warning, limit)); + /** + * Set the warning and limit to all registered custom network stats providers. + * Note that invocation of any interface will be sent to all providers. + */ + public void setStatsProviderWarningAndLimitAsync( + @NonNull String iface, long warning, long limit) { + PermissionUtils.enforceNetworkStackPermission(mContext); + if (LOGV) { + Log.v(TAG, "setStatsProviderWarningAndLimitAsync(" + + iface + "," + warning + "," + limit + ")"); } + invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.onSetWarningAndLimit(iface, + warning, limit)); } @Override @@ -2156,7 +2190,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { public void notifyWarningOrLimitReached() { Log.d(TAG, mTag + ": notifyWarningOrLimitReached"); BinderUtils.withCleanCallingIdentity(() -> - mNetworkPolicyManager.onStatsProviderWarningOrLimitReached()); + mNetworkPolicyManager.notifyStatsProviderWarningOrLimitReached()); } @Override @@ -2215,24 +2249,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() { @@ -2240,25 +2261,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() { @@ -2266,31 +2285,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/PrintSpooler/Android.bp b/packages/PrintSpooler/Android.bp index 772c69fe079b..6af3c6624f62 100644 --- a/packages/PrintSpooler/Android.bp +++ b/packages/PrintSpooler/Android.bp @@ -34,18 +34,23 @@ license { android_app { name: "PrintSpooler", defaults: ["platform_app_defaults"], + resource_dirs: [], + platform_apis: true, + jni_libs: ["libprintspooler_jni"], + static_libs: [ + "PrintSpoolerLib", + ], +} +android_library { + name: "PrintSpoolerLib", resource_dirs: ["res"], - srcs: [ "src/**/*.java", "src/com/android/printspooler/renderer/IPdfRenderer.aidl", "src/com/android/printspooler/renderer/IPdfEditor.aidl", ], - platform_apis: true, - - jni_libs: ["libprintspooler_jni"], static_libs: [ "android-support-v7-recyclerview", "android-support-compat", @@ -55,4 +60,5 @@ android_app { "android-support-fragment", "android-support-annotations", ], + manifest: "AndroidManifest.xml", } diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java index cf73aacb9b55..0c4cb8e4e8f6 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java +++ b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java @@ -314,16 +314,15 @@ public final class SelectPrinterActivity extends Activity implements @Override public boolean onContextItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.string.print_select_printer: { - PrinterInfo printer = item.getIntent().getParcelableExtra(EXTRA_PRINTER); - onPrinterSelected(printer); - } return true; - - case R.string.print_forget_printer: { - PrinterId printerId = item.getIntent().getParcelableExtra(EXTRA_PRINTER_ID); - mPrinterRegistry.forgetFavoritePrinter(printerId); - } return true; + final int itemId = item.getItemId(); + if (itemId == R.string.print_select_printer) { + PrinterInfo printer = item.getIntent().getParcelableExtra(EXTRA_PRINTER); + onPrinterSelected(printer); + return true; + } else if (itemId == R.string.print_forget_printer) { + PrinterId printerId = item.getIntent().getParcelableExtra(EXTRA_PRINTER_ID); + mPrinterRegistry.forgetFavoritePrinter(printerId); + return true; } return false; } 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/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 47b0744497ff..f9ac01db5454 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -1271,10 +1271,13 @@ <string name="wifi_status_mac_randomized">MAC is randomized</string> <!-- Summary to show how many devices are connected in wifi hotspot [CHAR LIMIT=NONE] --> - <plurals name="wifi_tether_connected_summary"> - <item quantity="one">%1$d device connected</item> - <item quantity="other">%1$d devices connected</item> - </plurals> + <string name="wifi_tether_connected_summary"> + {count, plural, + =0 {0 device connected} + =1 {1 device connected} + other {# devices connected} + } + </string> <!-- Content description of zen mode time condition plus button (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_manual_zen_more_time">More time.</string> 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..2c862e685035 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java @@ -176,6 +176,12 @@ public class BluetoothEventManager { } @VisibleForTesting + void registerProfileIntentReceiverForTest() { + mContext.registerReceiverAsUser(mProfileBroadcastReceiver, mUserHandle, + mProfileIntentFilter, 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 6b9b75011f5b..3c444f2b95b3 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,13 +47,17 @@ 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"; private static final boolean DEBUG = false; + private final Drawable mDreamPreviewDefault; public static class DreamInfo { public CharSequence caption; @@ -77,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; @@ -102,15 +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); + 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() { @@ -133,11 +177,11 @@ public class DreamBackend { dreamInfo.isActive = dreamInfo.componentName.equals(activeDream); final DreamMetadata dreamMetadata = getDreamMetadata(pm, resolveInfo); - if (dreamMetadata != null) { - dreamInfo.settingsComponentName = dreamMetadata.mSettingsActivity; - dreamInfo.previewImage = dreamMetadata.mPreviewImage; + dreamInfo.settingsComponentName = dreamMetadata.mSettingsActivity; + dreamInfo.previewImage = dreamMetadata.mPreviewImage; + if (dreamInfo.previewImage == null) { + dreamInfo.previewImage = mDreamPreviewDefault; } - dreamInfos.add(dreamInfo); } Collections.sort(dreamInfos, mComparator); @@ -239,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() { 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/src/com/android/settingslib/wifi/WifiUtils.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java index 56454e975370..4ab6542d567a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java @@ -22,6 +22,7 @@ import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.getMaxNe import android.content.Context; import android.content.Intent; import android.graphics.drawable.Drawable; +import android.icu.text.MessageFormat; import android.net.wifi.ScanResult; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiConfiguration.NetworkSelectionStatus; @@ -33,6 +34,8 @@ import androidx.annotation.VisibleForTesting; import com.android.settingslib.R; +import java.util.HashMap; +import java.util.Locale; import java.util.Map; public class WifiUtils { @@ -333,4 +336,20 @@ public class WifiUtils { intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, bundle); return intent; } + + /** + * Returns the string of Wi-Fi tethering summary for connected devices. + * + * @param context The application context + * @param connectedDevices The count of connected devices + */ + public static String getWifiTetherSummaryForConnectedDevices(Context context, + int connectedDevices) { + MessageFormat msgFormat = new MessageFormat( + context.getResources().getString(R.string.wifi_tether_connected_summary), + Locale.getDefault()); + Map<String, Object> arguments = new HashMap<>(); + arguments.put("count", connectedDevices); + return msgFormat.format(arguments); + } } 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/LocalBluetoothProfileManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java index 4f8fa2fdb96e..09540d1373ee 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java @@ -40,6 +40,7 @@ import android.os.ParcelUuid; import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -84,6 +85,7 @@ public class LocalBluetoothProfileManagerTest { when(mCachedBluetoothDevice.getDevice()).thenReturn(mDevice); mProfileManager = new LocalBluetoothProfileManager(mContext, mLocalBluetoothAdapter, mDeviceManager, mEventManager); + mEventManager.registerProfileIntentReceiverForTest(); } /** @@ -150,6 +152,7 @@ public class LocalBluetoothProfileManagerTest { * profile connection state changed callback */ @Test + @Ignore public void stateChangedHandler_receiveA2dpConnectionStateChanged_shouldDispatchCallback() { mShadowBluetoothAdapter.setSupportedProfiles(generateList( new int[] {BluetoothProfile.A2DP})); @@ -171,6 +174,7 @@ public class LocalBluetoothProfileManagerTest { * profile connection state changed callback */ @Test + @Ignore public void stateChangedHandler_receiveHeadsetConnectionStateChanged_shouldDispatchCallback() { mShadowBluetoothAdapter.setSupportedProfiles(generateList( new int[] {BluetoothProfile.HEADSET})); @@ -192,6 +196,7 @@ public class LocalBluetoothProfileManagerTest { * CachedBluetoothDeviceManager method */ @Test + @Ignore public void stateChangedHandler_receiveHAPConnectionStateChanged_shouldDispatchDeviceManager() { mShadowBluetoothAdapter.setSupportedProfiles(generateList( new int[] {BluetoothProfile.HEARING_AID})); @@ -214,6 +219,7 @@ public class LocalBluetoothProfileManagerTest { * profile connection state changed callback */ @Test + @Ignore public void stateChangedHandler_receivePanConnectionStateChanged_shouldNotDispatchCallback() { mShadowBluetoothAdapter.setSupportedProfiles(generateList( new int[] {BluetoothProfile.PAN})); @@ -255,6 +261,7 @@ public class LocalBluetoothProfileManagerTest { * handler and refresh CachedBluetoothDevice */ @Test + @Ignore public void stateChangedHandler_receivePanConnectionStateChangedWithProfile_shouldRefresh() { mShadowBluetoothAdapter.setSupportedProfiles(generateList( new int[] {BluetoothProfile.PAN})); 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 1303a62ff13d..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" /> @@ -341,6 +342,9 @@ <uses-permission android:name="android.permission.SET_WALLPAPER" /> <uses-permission android:name="android.permission.SET_WALLPAPER_COMPONENT" /> + <!-- Permission needed to test wallpaper dimming --> + <uses-permission android:name="android.permission.SET_WALLPAPER_DIM_AMOUNT" /> + <!-- Permission required to test ContentResolver caching. --> <uses-permission android:name="android.permission.CACHE_CONTENT" /> @@ -545,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" /> @@ -620,6 +628,9 @@ <!-- Permission required for CTS test - Notification test suite --> <uses-permission android:name="android.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL" /> + <!-- Permission required for CTS test - CaptioningManagerTest --> + <uses-permission android:name="android.permission.SET_SYSTEM_AUDIO_CAPTION" /> + <application android:label="@string/app_label" android:theme="@android:style/Theme.DeviceDefault.DayNight" android:defaultToDeviceProtectedStorage="true" diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index e9e85f156d6d..f35f5dd0c3ae 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -150,12 +150,12 @@ <uses-permission android:name="android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS" /> <uses-permission android:name="android.permission.GET_RUNTIME_PERMISSIONS" /> - <!-- Communal mode --> - <uses-permission android:name="android.permission.WRITE_COMMUNAL_STATE" /> - <!-- 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/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/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml index e69582f52ebf..f72a8dc08c9c 100644 --- a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml +++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml @@ -378,7 +378,8 @@ android:clickable="true"/> </LinearLayout> </LinearLayout> - <FrameLayout + + <LinearLayout android:id="@+id/button_layout" android:orientation="horizontal" android:layout_width="match_parent" @@ -390,9 +391,10 @@ android:clickable="false" android:focusable="false"> - <FrameLayout + <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_weight="1" android:layout_gravity="start|center_vertical" android:orientation="vertical"> <Button @@ -401,12 +403,13 @@ android:layout_height="wrap_content" android:text="@string/turn_off_airplane_mode" android:ellipsize="end" + android:maxLines="1" style="@style/Widget.Dialog.Button.BorderButton" android:clickable="true" android:focusable="true"/> - </FrameLayout> + </LinearLayout> - <FrameLayout + <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="16dp" @@ -417,10 +420,13 @@ android:layout_height="wrap_content" android:text="@string/inline_done_button" style="@style/Widget.Dialog.Button" + android:maxLines="1" + android:ellipsize="end" android:clickable="true" android:focusable="true"/> - </FrameLayout> - </FrameLayout> + </LinearLayout> + </LinearLayout> + </LinearLayout> </androidx.core.widget.NestedScrollView> </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/config.xml b/packages/SystemUI/res/values/config.xml index 9c35fea6438d..fc2756ecc8e5 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -716,26 +716,12 @@ <!-- 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> <!-- Class for the communal source connector to be used --> <string name="config_communalSourceConnector" translatable="false"></string> + <!-- How often in milliseconds to jitter the dream overlay in order to avoid burn-in. --> + <integer name="config_dreamOverlayBurnInProtectionUpdateIntervalMillis">500</integer> </resources> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 08fb2c66e043..b22ad66ab67a 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> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 1b528110d188..ff5699bbc199 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -976,8 +976,9 @@ </style> <style name="InternetDialog.NetworkSummary"> - <item name="android:layout_marginEnd">34dp</item> + <item name="android:layout_marginEnd">7dp</item> <item name="android:ellipsize">end</item> + <item name="android:maxLines">2</item> <item name="android:textAppearance">@style/TextAppearance.InternetDialog.Secondary</item> </style> 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/plugins/PluginManagerImpl.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java index 7539f995dab4..851ea6558697 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java @@ -137,7 +137,8 @@ public class PluginManagerImpl extends BroadcastReceiver implements PluginManage filter.addAction(PLUGIN_CHANGED); filter.addAction(DISABLE_PLUGIN); filter.addDataScheme("package"); - mContext.registerReceiver(this, filter, PluginActionManager.PLUGIN_PERMISSION, null); + mContext.registerReceiver(this, filter, PluginActionManager.PLUGIN_PERMISSION, null, + Context.RECEIVER_EXPORTED_UNAUDITED); filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED); mContext.registerReceiver(this, filter); } 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/RemoteAnimationTargetCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java index 72e3e181aaf1..4d0c443ca5a4 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java @@ -17,7 +17,6 @@ package com.android.systemui.shared.system; import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE; -import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; @@ -140,22 +139,11 @@ public class RemoteAnimationTargetCompat { // changes should be ordered top-to-bottom in z final int mode = change.getMode(); - // Don't move anything that isn't independent within its parents - if (!TransitionInfo.isIndependent(change, info)) { - if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT || mode == TRANSIT_CHANGE) { - t.show(leash); - t.setPosition(leash, change.getEndRelOffset().x, change.getEndRelOffset().y); - } - return; - } - - boolean hasParent = change.getParent() != null; + // Launcher animates leaf tasks directly, so always reparent all task leashes to root leash. + t.reparent(leash, info.getRootLeash()); + t.setPosition(leash, change.getStartAbsBounds().left - info.getRootOffset().x, + change.getStartAbsBounds().top - info.getRootOffset().y); - if (!hasParent) { - t.reparent(leash, info.getRootLeash()); - t.setPosition(leash, change.getStartAbsBounds().left - info.getRootOffset().x, - change.getStartAbsBounds().top - info.getRootOffset().y); - } t.show(leash); // Put all the OPEN/SHOW on top if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) { @@ -266,14 +254,15 @@ public class RemoteAnimationTargetCompat { SurfaceControl.Transaction t, ArrayMap<SurfaceControl, SurfaceControl> leashMap) { final ArrayList<RemoteAnimationTargetCompat> out = new ArrayList<>(); for (int i = 0; i < info.getChanges().size(); i++) { - boolean changeIsWallpaper = - (info.getChanges().get(i).getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0; + final TransitionInfo.Change change = info.getChanges().get(i); + final boolean changeIsWallpaper = + (change.getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0; if (wallpapers != changeIsWallpaper) continue; - out.add(new RemoteAnimationTargetCompat(info.getChanges().get(i), - info.getChanges().size() - i, info, t)); - if (leashMap == null) continue; - leashMap.put(info.getChanges().get(i).getLeash(), - out.get(out.size() - 1).leash); + + out.add(new RemoteAnimationTargetCompat(change, info.getChanges().size() - i, info, t)); + if (leashMap != null) { + leashMap.put(change.getLeash(), out.get(out.size() - 1).leash); + } } return out.toArray(new RemoteAnimationTargetCompat[out.size()]); } 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/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/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/KeyguardEsimArea.java b/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java index 26c227dc8929..8bcb7c9e29c2 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java @@ -91,7 +91,8 @@ class KeyguardEsimArea extends Button implements View.OnClickListener { protected void onAttachedToWindow() { super.onAttachedToWindow(); mContext.registerReceiver(mReceiver, new IntentFilter(ACTION_DISABLE_ESIM), - PERMISSION_SELF, null /* scheduler */); + PERMISSION_SELF, null /* scheduler */, + Context.RECEIVER_EXPORTED_UNAUDITED); } public static boolean isEsimLocked(Context context, int subId) { 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/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 5276679ea104..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 @@ -3294,11 +3278,19 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } public void clearBiometricRecognized() { + clearBiometricRecognized(UserHandle.USER_NULL); + } + + public void clearBiometricRecognizedWhenKeyguardDone(int unlockedUser) { + clearBiometricRecognized(unlockedUser); + } + + private void clearBiometricRecognized(int unlockedUser) { Assert.isMainThread(); mUserFingerprintAuthenticated.clear(); mUserFaceAuthenticated.clear(); - mTrustManager.clearAllBiometricRecognized(BiometricSourceType.FINGERPRINT); - mTrustManager.clearAllBiometricRecognized(BiometricSourceType.FACE); + mTrustManager.clearAllBiometricRecognized(BiometricSourceType.FINGERPRINT, unlockedUser); + mTrustManager.clearAllBiometricRecognized(BiometricSourceType.FACE, unlockedUser); for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); 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/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index a100cb8caf98..9c2971cda493 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -96,6 +96,8 @@ import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.concurrency.ThreadFactory; import com.android.systemui.util.settings.SecureSettings; +import java.io.FileDescriptor; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executor; @@ -107,7 +109,7 @@ import javax.inject.Inject; * for antialiasing and emulation purposes. */ @SysUISingleton -public class ScreenDecorations extends CoreStartable implements Tunable { +public class ScreenDecorations extends CoreStartable implements Tunable , Dumpable{ private static final boolean DEBUG = false; private static final String TAG = "ScreenDecorations"; @@ -677,6 +679,20 @@ public class ScreenDecorations extends CoreStartable implements Tunable { }); } + @Override + public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { + pw.println("ScreenDecorations state:"); + pw.println(" DEBUG_DISABLE_SCREEN_DECORATIONS:" + DEBUG_DISABLE_SCREEN_DECORATIONS); + pw.println(" mIsRoundedCornerMultipleRadius:" + mIsRoundedCornerMultipleRadius); + pw.println(" mIsPrivacyDotEnabled:" + mIsPrivacyDotEnabled); + pw.println(" mPendingRotationChange:" + mPendingRotationChange); + pw.println(" mRoundedDefault(x,y)=(" + mRoundedDefault.x + "," + mRoundedDefault.y + ")"); + pw.println(" mRoundedDefaultTop(x,y)=(" + mRoundedDefaultTop.x + "," + mRoundedDefaultTop.y + + ")"); + pw.println(" mRoundedDefaultBottom(x,y)=(" + mRoundedDefaultBottom.x + "," + + mRoundedDefaultBottom.y + ")"); + } + private void updateOrientation() { Preconditions.checkState(mHandler.getLooper().getThread() == Thread.currentThread(), "must call on " + mHandler.getLooper().getThread() diff --git a/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java b/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java index d7da63b9e262..1f2de4cfc346 100644 --- a/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java +++ b/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java @@ -112,7 +112,8 @@ public class SliceBroadcastRelayHandler extends CoreStartable { public void register(Context context, ComponentName receiver, IntentFilter filter) { mReceivers.add(receiver); - context.registerReceiver(this, filter); + context.registerReceiver(this, filter, + Context.RECEIVER_EXPORTED_UNAUDITED); } public void unregister(Context context) { 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/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index 1ac9016a1ee8..2b12f67acd5b 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -518,7 +518,7 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); - context.registerReceiver(mBroadcastReceiver, filter); + context.registerReceiver(mBroadcastReceiver, filter, Context.RECEIVER_EXPORTED_UNAUDITED); mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class); } 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/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index d20844143ad6..6581490030dc 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -539,7 +539,8 @@ public class UdfpsController implements DozeReceiver { final IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); - context.registerReceiver(mBroadcastReceiver, filter); + context.registerReceiver(mBroadcastReceiver, filter, + Context.RECEIVER_EXPORTED_UNAUDITED); udfpsHapticsSimulator.setUdfpsController(this); } 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/communal/CommunalManagerUpdater.java b/packages/SystemUI/src/com/android/systemui/communal/CommunalManagerUpdater.java deleted file mode 100644 index ebe804af15e8..000000000000 --- a/packages/SystemUI/src/com/android/systemui/communal/CommunalManagerUpdater.java +++ /dev/null @@ -1,67 +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.communal; - -import android.app.communal.CommunalManager; -import android.content.Context; -import android.util.Log; - -import com.android.systemui.CoreStartable; -import com.android.systemui.dagger.SysUISingleton; - -import java.lang.ref.WeakReference; - -import javax.inject.Inject; - -/** - * The {@link CommunalManagerUpdater} is responsible for forwarding state from SystemUI to - * the {@link CommunalManager} system service. - */ -@SysUISingleton -public class CommunalManagerUpdater extends CoreStartable { - private static final String TAG = "CommunalManagerUpdater"; - - private final CommunalManager mCommunalManager; - private final CommunalSourceMonitor mMonitor; - - private final CommunalSourceMonitor.Callback mSourceCallback = - new CommunalSourceMonitor.Callback() { - @Override - public void onSourceAvailable(WeakReference<CommunalSource> source) { - try { - mCommunalManager.setCommunalViewShowing( - source != null && source.get() != null); - } catch (RuntimeException e) { - Log.e(TAG, "Error updating communal manager service state", e); - } - } - }; - - @Inject - public CommunalManagerUpdater(Context context, CommunalSourceMonitor monitor) { - super(context); - mCommunalManager = context.getSystemService(CommunalManager.class); - mMonitor = monitor; - } - - @Override - public void start() { - if (mCommunalManager != null) { - mMonitor.addCallback(mSourceCallback); - } - } -} 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 00491da38280..bbe9dbd57f53 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java @@ -25,9 +25,7 @@ import com.android.systemui.accessibility.SystemActions; import com.android.systemui.accessibility.WindowMagnification; import com.android.systemui.biometrics.AuthController; import com.android.systemui.clipboardoverlay.ClipboardListener; -import com.android.systemui.communal.CommunalManagerUpdater; 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; @@ -213,18 +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); - - /** Inject into CommunalManagerUpdater. */ - @Binds - @IntoMap - @ClassKey(CommunalManagerUpdater.class) - public abstract CoreStartable bindCommunalManagerUpdater( - CommunalManagerUpdater communalManagerUpdater); } 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/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java index 572bb4467c97..5b46079c87cc 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java @@ -16,8 +16,11 @@ package com.android.systemui.dreams; +import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset; + import android.graphics.Rect; import android.graphics.Region; +import android.os.Handler; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; @@ -26,6 +29,7 @@ import androidx.constraintlayout.widget.ConstraintLayout; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.R; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dreams.dagger.DreamOverlayComponent; import com.android.systemui.dreams.dagger.DreamOverlayModule; import com.android.systemui.util.ViewController; @@ -47,6 +51,15 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve // the space into which widgets are placed. private final ViewGroup mDreamOverlayContentView; + // The maximum translation offset to apply to the overlay container to avoid screen burn-in. + private final int mMaxBurnInOffset; + + // The interval in milliseconds between burn-in protection updates. + private final long mBurnInProtectionUpdateInterval; + + // Main thread handler used to schedule periodic tasks (e.g. burn-in protection updates). + private final Handler mHandler; + // A hook into the internal inset calculation where we declare the overlays as the only // touchable regions. private final ViewTreeObserver.OnComputeInternalInsetsListener @@ -81,13 +94,21 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve public DreamOverlayContainerViewController( DreamOverlayContainerView containerView, @Named(DreamOverlayModule.DREAM_OVERLAY_CONTENT_VIEW) ViewGroup contentView, - DreamOverlayStatusBarViewController statusBarViewController) { + DreamOverlayStatusBarViewController statusBarViewController, + @Main Handler handler, + @Named(DreamOverlayModule.MAX_BURN_IN_OFFSET) int maxBurnInOffset, + @Named(DreamOverlayModule.BURN_IN_PROTECTION_UPDATE_INTERVAL) long + burnInProtectionUpdateInterval) { super(containerView); mDreamOverlayContentView = contentView; mStatusBarViewController = statusBarViewController; mDreamOverlayNotificationsDragAreaHeight = mView.getResources().getDimensionPixelSize( R.dimen.dream_overlay_notifications_drag_area_height); + + mHandler = handler; + mMaxBurnInOffset = maxBurnInOffset; + mBurnInProtectionUpdateInterval = burnInProtectionUpdateInterval; } @Override @@ -99,10 +120,12 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve protected void onViewAttached() { mView.getViewTreeObserver() .addOnComputeInternalInsetsListener(mOnComputeInternalInsetsListener); + mHandler.postDelayed(this::updateBurnInOffsets, mBurnInProtectionUpdateInterval); } @Override protected void onViewDetached() { + mHandler.removeCallbacks(this::updateBurnInOffsets); mView.getViewTreeObserver() .removeOnComputeInternalInsetsListener(mOnComputeInternalInsetsListener); } @@ -123,4 +146,15 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve int getDreamOverlayNotificationsDragAreaHeight() { return mDreamOverlayNotificationsDragAreaHeight; } + + private void updateBurnInOffsets() { + // These translation values change slowly, and the set translation methods are idempotent, + // so no translation occurs when the values don't change. + mView.setTranslationX(getBurnInOffset(mMaxBurnInOffset * 2, true) + - mMaxBurnInOffset); + mView.setTranslationY(getBurnInOffset(mMaxBurnInOffset * 2, false) + - mMaxBurnInOffset); + + mHandler.postDelayed(this::updateBurnInOffsets, mBurnInProtectionUpdateInterval); + } } 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/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java index 5b588a9d9023..d2912032ed39 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java @@ -17,6 +17,7 @@ package com.android.systemui.dreams.dagger; import android.content.ContentResolver; +import android.content.res.Resources; import android.os.Handler; import android.view.LayoutInflater; import android.view.ViewGroup; @@ -45,6 +46,9 @@ public abstract class DreamOverlayModule { public static final String DREAM_OVERLAY_BATTERY_CONTROLLER = "dream_overlay_battery_controller"; public static final String DREAM_OVERLAY_CONTENT_VIEW = "dream_overlay_content_view"; + public static final String MAX_BURN_IN_OFFSET = "max_burn_in_offset"; + public static final String BURN_IN_PROTECTION_UPDATE_INTERVAL = + "burn_in_protection_update_interval"; /** */ @Provides @@ -104,4 +108,20 @@ public abstract class DreamOverlayModule { contentResolver, batteryController); } + + /** */ + @Provides + @DreamOverlayComponent.DreamOverlayScope + @Named(MAX_BURN_IN_OFFSET) + static int providesMaxBurnInOffset(@Main Resources resources) { + return resources.getDimensionPixelSize(R.dimen.default_burn_in_prevention_offset); + } + + /** */ + @Provides + @Named(BURN_IN_PROTECTION_UPDATE_INTERVAL) + static long providesBurnInProtectionUpdateInterval(@Main Resources resources) { + return resources.getInteger( + R.integer.config_dreamOverlayBurnInProtectionUpdateIntervalMillis); + } } diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java index 89623f4566bd..f3b721c02635 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java +++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java @@ -88,7 +88,8 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable { filter.addAction(ACTION_GET_FLAGS); flagManager.setRestartAction(this::restartSystemUI); flagManager.setClearCacheAction(this::removeFromCache); - context.registerReceiver(mReceiver, filter, null, null); + context.registerReceiver(mReceiver, filter, null, null, + Context.RECEIVER_EXPORTED_UNAUDITED); dumpManager.registerDumpable(TAG, this); } diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java index 5d6c2a247df3..4be819a49772 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 = diff --git a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java index c46ffa077746..3ae11ff28345 100644 --- a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java +++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java @@ -28,6 +28,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Parcelable; +import android.os.Trace; import android.util.ArrayMap; import android.view.LayoutInflater; import android.view.View; @@ -53,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(); @@ -224,10 +225,12 @@ public class FragmentHostManager { } public void reloadFragments() { + Trace.beginSection("FrargmentHostManager#reloadFragments"); // Save the old state. Parcelable p = destroyFragmentHost(); // Generate a new fragment host and restore its state. createFragmentHost(p); + Trace.endSection(); } class HostCallbacks extends FragmentHostCallback<FragmentHostManager> { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java index d73d9cdb7d40..0ad2807bed55 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java @@ -119,14 +119,13 @@ public class KeyguardIndicationRotateTextViewController extends return; } long minShowDuration = getMinVisibilityMillis(mIndicationMessages.get(mCurrIndicationType)); - final boolean hasPreviousIndication = mIndicationMessages.get(type) != null - && !TextUtils.isEmpty(mIndicationMessages.get(type).getMessage()); - final boolean hasNewIndication = newIndication != null; + final boolean hasNewIndication = newIndication != null + && !TextUtils.isEmpty(newIndication.getMessage()); if (!hasNewIndication) { mIndicationMessages.remove(type); mIndicationQueue.removeIf(x -> x == type); } else { - if (!hasPreviousIndication) { + if (!mIndicationQueue.contains(type)) { mIndicationQueue.add(type); } @@ -230,6 +229,7 @@ public class KeyguardIndicationRotateTextViewController extends public void clearMessages() { mCurrIndicationType = INDICATION_TYPE_NONE; mIndicationQueue.clear(); + mIndicationMessages.clear(); mView.clearMessages(); } @@ -310,7 +310,7 @@ public class KeyguardIndicationRotateTextViewController extends if (mIsDozing) { showIndication(INDICATION_TYPE_NONE); } else if (mIndicationQueue.size() > 0) { - showIndication(mIndicationQueue.remove(0)); + showIndication(mIndicationQueue.get(0)); } } }; @@ -327,7 +327,7 @@ public class KeyguardIndicationRotateTextViewController extends ShowNextIndication(long delay) { mShowIndicationRunnable = () -> { int type = mIndicationQueue.size() == 0 - ? INDICATION_TYPE_NONE : mIndicationQueue.remove(0); + ? INDICATION_TYPE_NONE : mIndicationQueue.get(0); showIndication(type); }; mCancelDelayedRunnable = mExecutor.executeDelayed(mShowIndicationRunnable, delay); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 5ca253910823..8376681e7bd4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -894,7 +894,8 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, delayedActionFilter.addAction(DELAYED_KEYGUARD_ACTION); delayedActionFilter.addAction(DELAYED_LOCK_PROFILE_ACTION); mContext.registerReceiver(mDelayedLockBroadcastReceiver, delayedActionFilter, - SYSTEMUI_PERMISSION, null /* scheduler */); + SYSTEMUI_PERMISSION, null /* scheduler */, + Context.RECEIVER_EXPORTED_UNAUDITED); mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); @@ -1760,7 +1761,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, } }; - public void keyguardDone() { + private void keyguardDone() { Trace.beginSection("KeyguardViewMediator#keyguardDone"); if (DEBUG) Log.d(TAG, "keyguardDone()"); userActivity(); @@ -1894,9 +1895,8 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, resetKeyguardDonePendingLocked(); } - if (mGoingToSleep) { - mUpdateMonitor.clearBiometricRecognized(); + mUpdateMonitor.clearBiometricRecognizedWhenKeyguardDone(currentUser); Log.i(TAG, "Device is going to sleep, aborting keyguardDone"); return; } @@ -1917,7 +1917,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, } handleHide(); - mUpdateMonitor.clearBiometricRecognized(); + mUpdateMonitor.clearBiometricRecognizedWhenKeyguardDone(currentUser); Trace.endSection(); } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java index 66c51d278dab..e2716e992c48 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java @@ -159,7 +159,9 @@ public class MediaProjectionPermissionActivity extends Activity mDialog.getButton(DialogInterface.BUTTON_POSITIVE).setFilterTouchesWhenObscured(true); final Window w = mDialog.getWindow(); - w.setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); + // QS is not closed when pressing CastTile. Match the type of the dialog shown from the + // tile. + w.setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); w.addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); mDialog.show(); diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java index 4e0392790c5c..83d581fb7897 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java @@ -539,7 +539,8 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback { } boolean isVolumeControlEnabled(@NonNull MediaDevice device) { - return !device.getFeatures().contains(MediaRoute2Info.FEATURE_REMOTE_GROUP_PLAYBACK) + // TODO(b/202500642): Also enable volume control for remote non-group sessions. + return !isActiveRemoteDevice(device) || mVolumeAdjustmentForRemoteGroupSessions; } 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..9e350ee60bff 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -79,6 +79,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 +232,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 +496,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 +511,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) { @@ -930,6 +932,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/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java index e06b768dbe6f..1dba536d52fd 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java @@ -28,6 +28,7 @@ import android.view.View.OnLayoutChangeListener; import androidx.annotation.Nullable; +import com.android.systemui.animation.Interpolators; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.qs.QS; import com.android.systemui.plugins.qs.QSTile; @@ -41,7 +42,6 @@ import com.android.systemui.qs.dagger.QSScope; import com.android.systemui.qs.tileimpl.HeightOverrideable; import com.android.systemui.tuner.TunerService; import com.android.systemui.tuner.TunerService.Tunable; -import com.android.wm.shell.animation.Interpolators; import java.util.ArrayList; import java.util.Collection; @@ -301,6 +301,8 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha TouchAnimator.Builder qqsTranslationYBuilder = new Builder(); TouchAnimator.Builder translationXBuilder = new Builder(); TouchAnimator.Builder nonFirstPageAlphaBuilder = new Builder(); + TouchAnimator.Builder quadraticInterpolatorBuilder = new Builder() + .setInterpolator(Interpolators.ACCELERATE); Collection<QSTile> tiles = mHost.getTiles(); int count = 0; @@ -413,7 +415,13 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha qqsTranslationYBuilder ); - firstPageBuilder.addFloat(quickTileView.getSecondaryLabel(), "alpha", 0, 1); + // Secondary labels on tiles not in QQS have two alpha animation applied: + // * on the tile themselves + // * on TileLayout + // Therefore, we use a quadratic interpolator animator to animate the alpha + // for tiles in QQS to match. + quadraticInterpolatorBuilder + .addFloat(quickTileView.getSecondaryLabel(), "alpha", 0, 1); nonFirstPageAlphaBuilder .addFloat(quickTileView.getSecondaryLabel(), "alpha", 0, 0); @@ -461,6 +469,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha mFirstPageAnimator = firstPageBuilder // Fade in the tiles/labels as we reach the final position. .addFloat(tileLayout, "alpha", 0, 1) + .addFloat(quadraticInterpolatorBuilder.build(), "position", 0, 1) .setListener(this) .build(); @@ -535,7 +544,11 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha builder.addFloat(tileView.getSecondaryIcon(), "translationY", -centerDiff, 0); // The labels have different apparent size in QQS vs QS (no secondary label), so the // translation needs to account for that. - int labelDiff = centerDiff - tileView.getSecondaryLabel().getMeasuredHeight() / 2; + int secondaryLabelOffset = 0; + if (tileView.getSecondaryLabel().getVisibility() == View.VISIBLE) { + secondaryLabelOffset = tileView.getSecondaryLabel().getMeasuredHeight() / 2; + } + int labelDiff = centerDiff - secondaryLabelOffset; builder.addFloat(tileView.getLabelContainer(), "translationY", -labelDiff, 0); builder.addFloat(tileView.getSecondaryLabel(), "alpha", 0, 0.3f, 1); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index 41dced6bffeb..259b786dafb7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -25,6 +25,7 @@ import android.animation.AnimatorListenerAdapter; import android.content.res.Configuration; import android.graphics.Rect; import android.os.Bundle; +import android.os.Trace; import android.util.Log; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; @@ -172,9 +173,14 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { - inflater = inflater.cloneInContext(new ContextThemeWrapper(getContext(), - R.style.Theme_SystemUI_QuickSettings)); - return inflater.inflate(R.layout.qs_panel, container, false); + try { + Trace.beginSection("QSFragment#onCreateView"); + inflater = inflater.cloneInContext(new ContextThemeWrapper(getContext(), + R.style.Theme_SystemUI_QuickSettings)); + return inflater.inflate(R.layout.qs_panel, container, false); + } finally { + Trace.endSection(); + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java index 9acd3eb4afc3..d3bad167b660 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java @@ -414,9 +414,9 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { holder.mTileView.removeOnLayoutChangeListener(this); - holder.mTileView.requestFocus(); + holder.mTileView.requestAccessibilityFocus(); if (mAccessibilityAction == ACTION_NONE) { - holder.mTileView.clearFocus(); + holder.mTileView.clearAccessibilityFocus(); } } }); @@ -449,12 +449,13 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta // Update the tile divider position mTileDividerIndex++; mFocusIndex = mEditIndex - 1; + final int focus = mFocusIndex; mNeedsFocus = true; if (mRecyclerView != null) { mRecyclerView.post(() -> { final RecyclerView recyclerView = mRecyclerView; if (recyclerView != null) { - recyclerView.smoothScrollToPosition(mFocusIndex); + recyclerView.smoothScrollToPosition(focus); } }); } 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/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index cd4b74514937..963a0d709dac 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -34,8 +34,6 @@ import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewCont import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_ON; import static com.android.systemui.plugins.FalsingManager.LOW_PENALTY; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; import android.app.IActivityManager; import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; @@ -72,7 +70,6 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.settingslib.Utils; import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.systemui.R; -import com.android.systemui.animation.Interpolators; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; @@ -149,6 +146,7 @@ public class KeyguardIndicationController { private CharSequence mBiometricMessage; protected ColorStateList mInitialTextColorState; private boolean mVisible; + private boolean mOrganizationOwnedDevice; private boolean mPowerPluggedIn; private boolean mPowerPluggedInWired; @@ -256,13 +254,13 @@ public class KeyguardIndicationController { mExecutor, mStatusBarStateController); updateIndication(false /* animate */); - updateDisclosure(); + updateOrganizedOwnedDevice(); if (mBroadcastReceiver == null) { // Update the disclosure proactively to avoid IPC on the critical path. mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - updateDisclosure(); + updateOrganizedOwnedDevice(); } }; IntentFilter intentFilter = new IntentFilter(); @@ -305,12 +303,11 @@ public class KeyguardIndicationController { } /** - * Doesn't include disclosure (also a persistent indication) which gets triggered separately. - * * This method also doesn't update transient messages like biometrics since those messages * are also updated separately. */ private void updatePersistentIndications(boolean animate, int userId) { + updateDisclosure(); updateOwnerInfo(); updateBattery(animate); updateUserLocked(userId); @@ -320,9 +317,14 @@ public class KeyguardIndicationController { updateResting(); } - private void updateDisclosure() { + private void updateOrganizedOwnedDevice() { // avoid calling this method since it has an IPC - if (whitelistIpcs(this::isOrganizationOwnedDevice)) { + mOrganizationOwnedDevice = whitelistIpcs(this::isOrganizationOwnedDevice); + updatePersistentIndications(false, KeyguardUpdateMonitor.getCurrentUser()); + } + + private void updateDisclosure() { + if (mOrganizationOwnedDevice) { final CharSequence organizationName = getOrganizationOwnedDeviceOrganizationName(); final CharSequence disclosure = getDisclosureText(organizationName); mRotateTextViewController.updateIndication( @@ -335,8 +337,6 @@ public class KeyguardIndicationController { } else { mRotateTextViewController.hideIndication(INDICATION_TYPE_DISCLOSURE); } - - updateResting(); } private CharSequence getDisclosureText(@Nullable CharSequence organizationName) { @@ -753,60 +753,6 @@ public class KeyguardIndicationController { updatePersistentIndications(animate, KeyguardUpdateMonitor.getCurrentUser()); } - // animates textView - textView moves up and bounces down - private void animateText(KeyguardIndicationTextView textView, String indication) { - int yTranslation = mContext.getResources().getInteger( - R.integer.wired_charging_keyguard_text_animation_distance); - int animateUpDuration = mContext.getResources().getInteger( - R.integer.wired_charging_keyguard_text_animation_duration_up); - int animateDownDuration = mContext.getResources().getInteger( - R.integer.wired_charging_keyguard_text_animation_duration_down); - textView.animate().cancel(); - ViewClippingUtil.setClippingDeactivated(textView, true, mClippingParams); - textView.animate() - .translationYBy(yTranslation) - .setInterpolator(Interpolators.LINEAR) - .setDuration(animateUpDuration) - .setListener(new AnimatorListenerAdapter() { - private boolean mCancelled; - - @Override - public void onAnimationStart(Animator animation) { - textView.switchIndication(indication, null); - } - - @Override - public void onAnimationCancel(Animator animation) { - textView.setTranslationY(BOUNCE_ANIMATION_FINAL_Y); - mCancelled = true; - } - - @Override - public void onAnimationEnd(Animator animation) { - if (mCancelled) { - ViewClippingUtil.setClippingDeactivated(textView, false, - mClippingParams); - return; - } - textView.animate() - .setDuration(animateDownDuration) - .setInterpolator(Interpolators.BOUNCE) - .translationY(BOUNCE_ANIMATION_FINAL_Y) - .setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - textView.setTranslationY(BOUNCE_ANIMATION_FINAL_Y); - ViewClippingUtil.setClippingDeactivated(textView, false, - mClippingParams); - // Unset the listener, otherwise this may persist for - // another view property animation - textView.animate().setListener(null); - } - }); - } - }); - } - protected String computePowerIndication() { int chargingId; if (mBatteryOverheated) { @@ -1182,9 +1128,12 @@ public class KeyguardIndicationController { @Override public void onKeyguardShowingChanged() { + // All transient messages are gone the next time keyguard is shown if (!mKeyguardStateController.isShowing()) { mTopIndicationView.clearMessages(); mRotateTextViewController.clearMessages(); + } else { + updatePersistentIndications(false, KeyguardUpdateMonitor.getCurrentUser()); } } }; 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/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java index 9dc823b63290..d785059e3de7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java @@ -293,7 +293,8 @@ public class NotificationLockscreenUserManagerImpl implements IntentFilter internalFilter = new IntentFilter(); internalFilter.addAction(NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION); - mContext.registerReceiver(mBaseBroadcastReceiver, internalFilter, PERMISSION_SELF, null); + mContext.registerReceiver(mBaseBroadcastReceiver, internalFilter, PERMISSION_SELF, null, + Context.RECEIVER_EXPORTED_UNAUDITED); mCurrentUserId = ActivityManager.getCurrentUser(); // in case we reg'd receiver too late updateCurrentProfilesCache(); 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/NotificationUtils.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java index cbc113ba91bf..c3cc97bc14a3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java @@ -24,6 +24,7 @@ import android.widget.ImageView; import com.android.internal.util.ContrastColorUtil; import com.android.systemui.R; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; /** * A util class for various reusable functions @@ -73,4 +74,20 @@ public class NotificationUtils { return (int) (dimensionPixelSize * factor); } + /** Get the notification key, reformatted for logging, for the (optional) entry */ + public static String logKey(NotificationEntry entry) { + if (entry == null) { + return "null"; + } + return logKey(entry.getKey()); + } + + /** Removes newlines from the notification key to prettify apps that have these in the tag */ + public static String logKey(String key) { + if (key == null) { + return "null"; + } + return key.replace("\n", ""); + } + } 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 bf81ea5c264c..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; @@ -56,7 +57,6 @@ import android.service.notification.NotificationListenerService.RankingMap; import android.service.notification.StatusBarNotification; import android.util.ArrayMap; import android.util.Pair; -import android.util.Slog; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -73,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; @@ -425,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(); } @@ -622,7 +633,7 @@ public class NotifCollection implements Dumpable { entry.mLifetimeExtenders.clear(); mAmDispatchingToOtherCode = true; for (NotifLifetimeExtender extender : mLifetimeExtenders) { - if (extender.shouldExtendLifetime(entry, entry.mCancellationReason)) { + if (extender.maybeExtendLifetime(entry, entry.mCancellationReason)) { mLogger.logLifetimeExtended(entry.getKey(), extender); entry.mLifetimeExtenders.add(extender); } @@ -756,6 +767,7 @@ public class NotifCollection implements Dumpable { && !entry.getSbn().getNotification().isGroupSummary() && !hasFlag(entry, Notification.FLAG_ONGOING_EVENT) && !hasFlag(entry, Notification.FLAG_BUBBLE) + && !hasFlag(entry, Notification.FLAG_NO_CLEAR) && entry.getDismissState() != DISMISSED; } @@ -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/GutsCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinator.kt index dbecf1cc28d7..ac00581c7c8b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinator.kt @@ -84,7 +84,7 @@ class GutsCoordinator @Inject constructor( onEndLifetimeExtensionCallback = callback } - override fun shouldExtendLifetime(entry: NotificationEntry, reason: Int): Boolean { + override fun maybeExtendLifetime(entry: NotificationEntry, reason: Int): Boolean { val isShowingGuts = isCurrentlyShowingGuts(entry) if (isShowingGuts) { notifsExtendingLifetime.add(entry.key) 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 f9f0b9da8778..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 shouldExtendLifetime(@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/legacy/NotificationGroupManagerLegacy.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java index 3b93020f024f..cd2affead92a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java @@ -16,6 +16,10 @@ package com.android.systemui.statusbar.notification.collection.legacy; +import static com.android.systemui.statusbar.notification.NotificationUtils.logKey; + +import static java.util.Objects.requireNonNull; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Notification; @@ -49,6 +53,7 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.TreeSet; +import java.util.function.Function; import javax.inject.Inject; @@ -79,10 +84,9 @@ public class NotificationGroupManagerLegacy implements private final HashMap<String, NotificationGroup> mGroupMap = new HashMap<>(); private final ArraySet<OnGroupExpansionChangeListener> mExpansionChangeListeners = new ArraySet<>(); - private final ArraySet<OnGroupChangeListener> mGroupChangeListeners = new ArraySet<>(); private final Lazy<PeopleNotificationIdentifier> mPeopleNotificationIdentifier; private final Optional<Bubbles> mBubblesOptional; - private final EventBuffer mEventBuffer = new EventBuffer(); + private final GroupEventDispatcher mEventDispatcher = new GroupEventDispatcher(mGroupMap::get); private int mBarState = -1; private HashMap<String, StatusBarNotification> mIsolatedEntries = new HashMap<>(); private HeadsUpManager mHeadsUpManager; @@ -105,7 +109,7 @@ public class NotificationGroupManagerLegacy implements * Add a listener for changes to groups. */ public void registerGroupChangeListener(OnGroupChangeListener listener) { - mGroupChangeListeners.add(listener); + mEventDispatcher.registerGroupChangeListener(listener); } @Override @@ -156,13 +160,15 @@ public class NotificationGroupManagerLegacy implements */ public void onEntryRemoved(NotificationEntry removed) { if (SPEW) { - Log.d(TAG, "onEntryRemoved: entry=" + removed); + Log.d(TAG, "onEntryRemoved: entry=" + logKey(removed)); } + mEventDispatcher.openBufferScope(); onEntryRemovedInternal(removed, removed.getSbn()); StatusBarNotification oldSbn = mIsolatedEntries.remove(removed.getKey()); if (oldSbn != null) { updateSuppression(mGroupMap.get(oldSbn.getGroupKey())); } + mEventDispatcher.closeBufferScope(); } /** @@ -190,7 +196,8 @@ public class NotificationGroupManagerLegacy implements return; } if (SPEW) { - Log.d(TAG, "onEntryRemovedInternal: entry=" + removed + " group=" + group.groupKey); + Log.d(TAG, "onEntryRemovedInternal: entry=" + logKey(removed) + + " group=" + logGroupKey(group)); } if (isGroupChild(removed.getKey(), isGroup, isGroupSummary)) { group.children.remove(removed.getKey()); @@ -201,9 +208,7 @@ public class NotificationGroupManagerLegacy implements if (group.children.isEmpty()) { if (group.summary == null) { mGroupMap.remove(groupKey); - for (OnGroupChangeListener listener : mGroupChangeListeners) { - listener.onGroupRemoved(group, groupKey); - } + mEventDispatcher.notifyGroupRemoved(group); } } } @@ -213,10 +218,12 @@ public class NotificationGroupManagerLegacy implements */ public void onEntryAdded(final NotificationEntry added) { if (SPEW) { - Log.d(TAG, "onEntryAdded: entry=" + added); + Log.d(TAG, "onEntryAdded: entry=" + logKey(added)); } + mEventDispatcher.openBufferScope(); updateIsolation(added); onEntryAddedInternal(added); + mEventDispatcher.closeBufferScope(); } private void onEntryAddedInternal(final NotificationEntry added) { @@ -230,19 +237,17 @@ public class NotificationGroupManagerLegacy implements if (group == null) { group = new NotificationGroup(groupKey); mGroupMap.put(groupKey, group); - - for (OnGroupChangeListener listener : mGroupChangeListeners) { - listener.onGroupCreated(group, groupKey); - } + mEventDispatcher.notifyGroupCreated(group); } if (SPEW) { - Log.d(TAG, "onEntryAddedInternal: entry=" + added + " group=" + group.groupKey); + Log.d(TAG, "onEntryAddedInternal: entry=" + logKey(added) + + " group=" + logGroupKey(group)); } if (isGroupChild) { NotificationEntry existing = group.children.get(added.getKey()); if (existing != null && existing != added) { Throwable existingThrowable = existing.getDebugThrowable(); - Log.wtf(TAG, "Inconsistent entries found with the same key " + added.getKey() + Log.wtf(TAG, "Inconsistent entries found with the same key " + logKey(added) + "existing removed: " + existing.isRowRemoved() + (existingThrowable != null ? Log.getStackTraceString(existingThrowable) + "\n" : "") @@ -262,9 +267,7 @@ public class NotificationGroupManagerLegacy implements for (NotificationEntry child : childrenCopy) { onEntryBecomingChild(child); } - for (OnGroupChangeListener listener : mGroupChangeListeners) { - listener.onGroupCreatedFromChildren(group); - } + mEventDispatcher.notifyGroupsChanged(); } } } @@ -323,30 +326,27 @@ public class NotificationGroupManagerLegacy implements boolean alertOverrideChanged = prevAlertOverride != group.alertOverride; boolean suppressionChanged = prevSuppressed != group.suppressed; if (alertOverrideChanged || suppressionChanged) { - if (DEBUG && alertOverrideChanged) { - Log.d(TAG, "updateSuppression: alertOverride was=" + prevAlertOverride - + " now=" + group.alertOverride + " group:\n" + group); - } - if (DEBUG && suppressionChanged) { - Log.d(TAG, - "updateSuppression: suppressed changed to " + group.suppressed - + " group:\n" + group); - } - if (!mIsUpdatingUnchangedGroup) { + if (DEBUG) { + Log.d(TAG, "updateSuppression:" + + " willNotifyListeners=" + !mIsUpdatingUnchangedGroup + + " changes for group:\n" + group); if (alertOverrideChanged) { - mEventBuffer.notifyAlertOverrideChanged(group, prevAlertOverride); + Log.d(TAG, "updateSuppression: alertOverride was=" + logKey(prevAlertOverride) + + " now=" + logKey(group.alertOverride)); } if (suppressionChanged) { - for (OnGroupChangeListener listener : mGroupChangeListeners) { - listener.onGroupSuppressionChanged(group, group.suppressed); - } - } - mEventBuffer.notifyGroupsChanged(); - } else { - if (DEBUG) { - Log.d(TAG, group + " did not notify listeners of above change(s)"); + Log.d(TAG, "updateSuppression: suppressed changed to " + group.suppressed); } } + if (alertOverrideChanged) { + mEventDispatcher.notifyAlertOverrideChanged(group, prevAlertOverride); + } + if (suppressionChanged) { + mEventDispatcher.notifySuppressedChanged(group); + } + if (!mIsUpdatingUnchangedGroup) { + mEventDispatcher.notifyGroupsChanged(); + } } } @@ -369,13 +369,15 @@ public class NotificationGroupManagerLegacy implements // but which should be alerting (because priority conversations are isolated), find it. if (group == null || group.summary == null) { if (SPEW) { - Log.d(TAG, "getPriorityConversationAlertOverride: null group or summary"); + Log.d(TAG, "getPriorityConversationAlertOverride: null group or summary" + + " group=" + logGroupKey(group)); } return null; } if (isIsolated(group.summary.getKey())) { if (SPEW) { - Log.d(TAG, "getPriorityConversationAlertOverride: isolated group"); + Log.d(TAG, "getPriorityConversationAlertOverride: isolated group" + + " group=" + logGroupKey(group)); } return null; } @@ -386,7 +388,8 @@ public class NotificationGroupManagerLegacy implements if (group.summary.getSbn().getNotification().getGroupAlertBehavior() == Notification.GROUP_ALERT_CHILDREN) { if (SPEW) { - Log.d(TAG, "getPriorityConversationAlertOverride: summary == GROUP_ALERT_CHILDREN"); + Log.d(TAG, "getPriorityConversationAlertOverride: summary == GROUP_ALERT_CHILDREN" + + " group=" + logGroupKey(group)); } return null; } @@ -396,7 +399,8 @@ public class NotificationGroupManagerLegacy implements HashMap<String, NotificationEntry> children = getImportantConversations(group); if (children == null || children.isEmpty()) { if (SPEW) { - Log.d(TAG, "getPriorityConversationAlertOverride: no important conversations"); + Log.d(TAG, "getPriorityConversationAlertOverride: no important conversations" + + " group=" + logGroupKey(group)); } return null; } @@ -408,8 +412,8 @@ public class NotificationGroupManagerLegacy implements if (child.getSbn().getNotification().getGroupAlertBehavior() != Notification.GROUP_ALERT_SUMMARY) { if (SPEW) { - Log.d(TAG, "getPriorityConversationAlertOverride: " - + "child != GROUP_ALERT_SUMMARY"); + Log.d(TAG, "getPriorityConversationAlertOverride: child != GROUP_ALERT_SUMMARY" + + " group=" + logGroupKey(group)); } return null; } @@ -450,13 +454,16 @@ public class NotificationGroupManagerLegacy implements } if (newestChild != null && importantChildKeys.contains(newestChild.getKey())) { if (SPEW) { - Log.d(TAG, "getPriorityConversationAlertOverride: result=" + newestChild); + Log.d(TAG, "getPriorityConversationAlertOverride:" + + " result=" + logKey(newestChild) + + " group=" + logGroupKey(group)); } return newestChild; } if (SPEW) { - Log.d(TAG, "getPriorityConversationAlertOverride: result=null, newestChild=" - + newestChild); + Log.d(TAG, "getPriorityConversationAlertOverride:" + + " result=null newestChild=" + logKey(newestChild) + + " group=" + logGroupKey(group)); } return null; } @@ -500,7 +507,7 @@ public class NotificationGroupManagerLegacy implements */ public void onEntryUpdated(NotificationEntry entry, StatusBarNotification oldNotification) { if (SPEW) { - Log.d(TAG, "onEntryUpdated: entry=" + entry); + Log.d(TAG, "onEntryUpdated: entry=" + logKey(entry)); } onEntryUpdated(entry, oldNotification.getGroupKey(), oldNotification.isGroup(), oldNotification.getNotification().isGroupSummary()); @@ -519,6 +526,7 @@ public class NotificationGroupManagerLegacy implements boolean groupKeysChanged = !oldGroupKey.equals(newGroupKey); boolean wasGroupChild = isGroupChild(entry.getKey(), oldIsGroup, oldIsGroupSummary); boolean isGroupChild = isGroupChild(entry.getSbn()); + mEventDispatcher.openBufferScope(); mIsUpdatingUnchangedGroup = !groupKeysChanged && wasGroupChild == isGroupChild; if (mGroupMap.get(getGroupKey(entry.getKey(), oldGroupKey)) != null) { onEntryRemovedInternal(entry, oldGroupKey, oldIsGroup, oldIsGroupSummary); @@ -536,6 +544,7 @@ public class NotificationGroupManagerLegacy implements } else if (!wasGroupChild && isGroupChild) { onEntryBecomingChild(entry); } + mEventDispatcher.closeBufferScope(); } /** @@ -798,7 +807,7 @@ public class NotificationGroupManagerLegacy implements */ private void isolateNotification(NotificationEntry entry) { if (SPEW) { - Log.d(TAG, "isolateNotification: entry=" + entry); + Log.d(TAG, "isolateNotification: entry=" + logKey(entry)); } // We will be isolated now, so lets update the groups onEntryRemovedInternal(entry, entry.getSbn()); @@ -811,9 +820,7 @@ public class NotificationGroupManagerLegacy implements // When the notification gets added afterwards it is already isolated and therefore // it doesn't lead to an update. updateSuppression(mGroupMap.get(entry.getSbn().getGroupKey())); - for (OnGroupChangeListener listener : mGroupChangeListeners) { - listener.onGroupsChanged(); - } + mEventDispatcher.notifyGroupsChanged(); } /** @@ -827,7 +834,7 @@ public class NotificationGroupManagerLegacy implements // listener may be unable to correctly determine the true state of the group. By delaying // the alertOverride change until after the add phase, we can ensure that listeners only // have to handle a consistent state. - mEventBuffer.startBuffering(); + mEventDispatcher.openBufferScope(); boolean isIsolated = isIsolated(entry.getSbn().getKey()); if (shouldIsolate(entry)) { if (!isIsolated) { @@ -836,7 +843,7 @@ public class NotificationGroupManagerLegacy implements } else if (isIsolated) { stopIsolatingNotification(entry); } - mEventBuffer.flushAndStopBuffering(); + mEventDispatcher.closeBufferScope(); } /** @@ -846,15 +853,13 @@ public class NotificationGroupManagerLegacy implements */ private void stopIsolatingNotification(NotificationEntry entry) { if (SPEW) { - Log.d(TAG, "stopIsolatingNotification: entry=" + entry); + Log.d(TAG, "stopIsolatingNotification: entry=" + logKey(entry)); } // not isolated anymore, we need to update the groups onEntryRemovedInternal(entry, entry.getSbn()); mIsolatedEntries.remove(entry.getKey()); onEntryAddedInternal(entry); - for (OnGroupChangeListener listener : mGroupChangeListeners) { - listener.onGroupsChanged(); - } + mEventDispatcher.notifyGroupsChanged(); } private boolean isGroupNotFullyVisible(NotificationGroup notificationGroup) { @@ -874,11 +879,11 @@ public class NotificationGroupManagerLegacy implements pw.println("GroupManagerLegacy state:"); pw.println(" number of groups: " + mGroupMap.size()); for (Map.Entry<String, NotificationGroup> entry : mGroupMap.entrySet()) { - pw.println("\n key: " + entry.getKey()); pw.println(entry.getValue()); + pw.println("\n key: " + logKey(entry.getKey())); pw.println(entry.getValue()); } pw.println("\n isolated entries: " + mIsolatedEntries.size()); for (Map.Entry<String, StatusBarNotification> entry : mIsolatedEntries.entrySet()) { - pw.print(" "); pw.print(entry.getKey()); + pw.print(" "); pw.print(logKey(entry.getKey())); pw.print(", "); pw.println(entry.getValue()); } } @@ -888,6 +893,14 @@ public class NotificationGroupManagerLegacy implements setStatusBarState(newState); } + /** Get the group key, reformatted for logging, for the (optional) group */ + public static String logGroupKey(NotificationGroup group) { + if (group == null) { + return "null"; + } + return logKey(group.groupKey); + } + /** * A record of a notification being posted, containing the time of the post and the key of the * notification entry. These are stored in a TreeSet by the NotificationGroup and used to @@ -977,18 +990,35 @@ public class NotificationGroupManagerLegacy implements * When buffering, instead of notifying the listeners it will set internal state that will allow * it to notify listeners of those events later */ - private class EventBuffer { + static class GroupEventDispatcher { + private final Function<String, NotificationGroup> mGroupMapGetter; + private final ArraySet<OnGroupChangeListener> mGroupChangeListeners = new ArraySet<>(); private final HashMap<String, NotificationEntry> mOldAlertOverrideByGroup = new HashMap<>(); - private boolean mIsBuffering = false; + private final HashMap<String, Boolean> mOldSuppressedByGroup = new HashMap<>(); + private int mBufferScopeDepth = 0; private boolean mDidGroupsChange = false; + GroupEventDispatcher(Function<String, NotificationGroup> groupMapGetter) { + mGroupMapGetter = requireNonNull(groupMapGetter); + } + + void registerGroupChangeListener(OnGroupChangeListener listener) { + mGroupChangeListeners.add(listener); + } + + private boolean isBuffering() { + return mBufferScopeDepth > 0; + } + void notifyAlertOverrideChanged(NotificationGroup group, NotificationEntry oldAlertOverride) { - if (mIsBuffering) { + if (isBuffering()) { // The value in this map is the override before the event. If there is an entry // already in the map, then we are effectively coalescing two events, which means // we need to preserve the original initial value. - mOldAlertOverrideByGroup.putIfAbsent(group.groupKey, oldAlertOverride); + if (!mOldAlertOverrideByGroup.containsKey(group.groupKey)) { + mOldAlertOverrideByGroup.put(group.groupKey, oldAlertOverride); + } } else { for (OnGroupChangeListener listener : mGroupChangeListeners) { listener.onGroupAlertOverrideChanged(group, oldAlertOverride, @@ -997,8 +1027,21 @@ public class NotificationGroupManagerLegacy implements } } + void notifySuppressedChanged(NotificationGroup group) { + if (isBuffering()) { + // The value in this map is the 'suppressed' before the event. If there is a value + // already in the map, then we are effectively coalescing two events, which means + // we need to preserve the original initial value. + mOldSuppressedByGroup.putIfAbsent(group.groupKey, !group.suppressed); + } else { + for (OnGroupChangeListener listener : mGroupChangeListeners) { + listener.onGroupSuppressionChanged(group, group.suppressed); + } + } + } + void notifyGroupsChanged() { - if (mIsBuffering) { + if (isBuffering()) { mDidGroupsChange = true; } else { for (OnGroupChangeListener listener : mGroupChangeListeners) { @@ -1007,26 +1050,94 @@ public class NotificationGroupManagerLegacy implements } } - void startBuffering() { - mIsBuffering = true; + void notifyGroupCreated(NotificationGroup group) { + // NOTE: given how this event is used, it doesn't need to be buffered right now + final String groupKey = group.groupKey; + for (OnGroupChangeListener listener : mGroupChangeListeners) { + listener.onGroupCreated(group, groupKey); + } + } + + void notifyGroupRemoved(NotificationGroup group) { + // NOTE: given how this event is used, it doesn't need to be buffered right now + final String groupKey = group.groupKey; + for (OnGroupChangeListener listener : mGroupChangeListeners) { + listener.onGroupRemoved(group, groupKey); + } + } + + void openBufferScope() { + mBufferScopeDepth++; + if (SPEW) { + Log.d(TAG, "openBufferScope: scopeDepth=" + mBufferScopeDepth); + } + } + + void closeBufferScope() { + mBufferScopeDepth--; + if (SPEW) { + Log.d(TAG, "closeBufferScope: scopeDepth=" + mBufferScopeDepth); + } + // Flush buffered events if the last buffer scope has closed + if (!isBuffering()) { + flushBuffer(); + } } - void flushAndStopBuffering() { - // stop buffering so that we can call our own helpers - mIsBuffering = false; + private void flushBuffer() { + if (SPEW) { + Log.d(TAG, "flushBuffer: " + + " suppressed.size=" + mOldSuppressedByGroup.size() + + " alertOverride.size=" + mOldAlertOverrideByGroup.size() + + " mDidGroupsChange=" + mDidGroupsChange); + } + // alert all group suppressed changes for groups that were not removed + for (Map.Entry<String, Boolean> entry : mOldSuppressedByGroup.entrySet()) { + NotificationGroup group = mGroupMapGetter.apply(entry.getKey()); + if (group == null) { + // The group can be null if this suppressed changed before the group was + // permanently removed, meaning that there's no guarantee that listeners will + // that field clear. + if (SPEW) { + Log.d(TAG, "flushBuffer: suppressed:" + + " cannot report for removed group: " + logKey(entry.getKey())); + } + continue; + } + boolean oldSuppressed = entry.getValue(); + if (group.suppressed == oldSuppressed) { + // If the final suppressed equals the initial, it means we coalesced two + // events which undid the change, so we can drop it entirely. + if (SPEW) { + Log.d(TAG, "flushBuffer: suppressed:" + + " did not change for group: " + logKey(entry.getKey())); + } + continue; + } + notifySuppressedChanged(group); + } + mOldSuppressedByGroup.clear(); // alert all group alert override changes for groups that were not removed for (Map.Entry<String, NotificationEntry> entry : mOldAlertOverrideByGroup.entrySet()) { - NotificationGroup group = mGroupMap.get(entry.getKey()); + NotificationGroup group = mGroupMapGetter.apply(entry.getKey()); if (group == null) { // The group can be null if this alertOverride changed before the group was // permanently removed, meaning that there's no guarantee that listeners will // that field clear. + if (SPEW) { + Log.d(TAG, "flushBuffer: alertOverride:" + + " cannot report for removed group: " + entry.getKey()); + } continue; } NotificationEntry oldAlertOverride = entry.getValue(); if (group.alertOverride == oldAlertOverride) { // If the final alertOverride equals the initial, it means we coalesced two // events which undid the change, so we can drop it entirely. + if (SPEW) { + Log.d(TAG, "flushBuffer: alertOverride:" + + " did not change for group: " + logKey(entry.getKey())); + } continue; } notifyAlertOverrideChanged(group, oldAlertOverride); @@ -1088,14 +1199,6 @@ public class NotificationGroupManagerLegacy implements @Nullable NotificationEntry newAlertOverride) {} /** - * A group of children just received a summary notification and should therefore become - * children of it. - * - * @param group the group created - */ - default void onGroupCreatedFromChildren(NotificationGroup group) {} - - /** * The groups have changed. This can happen if the isolation of a child has changes or if a * group became suppressed / unsuppressed */ 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/notifcollection/NotifLifetimeExtender.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifLifetimeExtender.java index 2fe3bd63c2e6..70ad8a552b9e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifLifetimeExtender.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifLifetimeExtender.java @@ -45,7 +45,7 @@ public interface NotifLifetimeExtender { * called on all lifetime extenders even if earlier ones return true (in other words, multiple * lifetime extenders can be extending a notification at the same time). */ - boolean shouldExtendLifetime(@NonNull NotificationEntry entry, @CancellationReason int reason); + boolean maybeExtendLifetime(@NonNull NotificationEntry entry, @CancellationReason int reason); /** * Called by the NotifCollection to inform a lifetime extender that its extension of a notif diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtender.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtender.kt index 145c1e54d732..51dab7246b28 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtender.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtender.kt @@ -73,7 +73,7 @@ abstract class SelfTrackingLifetimeExtender( final override fun getName(): String = name - final override fun shouldExtendLifetime(entry: NotificationEntry, reason: Int): Boolean { + final override fun maybeExtendLifetime(entry: NotificationEntry, reason: Int): Boolean { val shouldExtend = queryShouldExtendLifetime(entry) if (debug) { Log.d(tag, "$name.shouldExtendLifetime(key=${entry.key}, reason=$reason)" + 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/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/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/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 7c3399de21cb..5833ec286983 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -93,7 +93,6 @@ import com.android.systemui.statusbar.notification.collection.NotifCollection; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy; -import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy.NotificationGroup; import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy.OnGroupChangeListener; import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats; @@ -689,11 +688,6 @@ public class NotificationStackScrollLayoutController { (changedRow, expanded) -> mView.onGroupExpandChanged(changedRow, expanded)); legacyGroupManager.registerGroupChangeListener(new OnGroupChangeListener() { @Override - public void onGroupCreatedFromChildren(NotificationGroup group) { - mStatusBar.requestNotificationUpdate("onGroupCreatedFromChildren"); - } - - @Override public void onGroupsChanged() { mStatusBar.requestNotificationUpdate("onGroupsChanged"); } 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/NotificationGroupAlertTransferHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java index 6632c9cf69e3..8bababfb9d5e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java @@ -16,6 +16,9 @@ package com.android.systemui.statusbar.phone; +import static com.android.systemui.statusbar.notification.NotificationUtils.logKey; +import static com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy.logGroupKey; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Notification; @@ -148,7 +151,9 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis @Override public void onGroupSuppressionChanged(NotificationGroup group, boolean suppressed) { if (DEBUG) { - Log.d(TAG, "!! onGroupSuppressionChanged: group.summary=" + group.summary + Log.d(TAG, "!! onGroupSuppressionChanged:" + + " group=" + logGroupKey(group) + + " group.summary=" + logKey(group.summary) + " suppressed=" + suppressed); } NotificationEntry oldAlertOverride = group.alertOverride; @@ -160,9 +165,11 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis @Nullable NotificationEntry oldAlertOverride, @Nullable NotificationEntry newAlertOverride) { if (DEBUG) { - Log.d(TAG, "!! onGroupAlertOverrideChanged: group.summary=" + group.summary - + " oldAlertOverride=" + oldAlertOverride - + " newAlertOverride=" + newAlertOverride); + Log.d(TAG, "!! onGroupAlertOverrideChanged:" + + " group=" + logGroupKey(group) + + " group.summary=" + logKey(group.summary) + + " oldAlertOverride=" + logKey(oldAlertOverride) + + " newAlertOverride=" + logKey(newAlertOverride)); } onGroupChanged(group, oldAlertOverride); } @@ -208,7 +215,9 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis @Override public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) { if (DEBUG) { - Log.d(TAG, "!! onHeadsUpStateChanged: entry=" + entry + " isHeadsUp=" + isHeadsUp); + Log.d(TAG, "!! onHeadsUpStateChanged:" + + " entry=" + logKey(entry) + + " isHeadsUp=" + isHeadsUp); } if (isHeadsUp && entry.getSbn().getNotification().isGroupSummary()) { // a group summary is alerting; trigger the forward transfer checks @@ -240,6 +249,9 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis } else if (mGroupManager.isSummaryOfSuppressedGroup(summary.getSbn())) { handleSuppressedSummaryAlerted(summary, oldAlertOverride); } + if (DEBUG) { + Log.d(TAG, "checkForForwardAlertTransfer: done"); + } } private final NotificationEntryListener mNotificationEntryListener = @@ -249,7 +261,7 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis @Override public void onPendingEntryAdded(NotificationEntry entry) { if (DEBUG) { - Log.d(TAG, "!! onPendingEntryAdded: entry=" + entry); + Log.d(TAG, "!! onPendingEntryAdded: entry=" + logKey(entry)); } String groupKey = mGroupManager.getGroupKey(entry.getSbn()); GroupAlertEntry groupAlertEntry = mGroupAlertEntries.get(groupKey); @@ -345,7 +357,7 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis private void handleSuppressedSummaryAlerted(@NonNull NotificationEntry summary, NotificationEntry oldAlertOverride) { if (DEBUG) { - Log.d(TAG, "handleSuppressedSummaryAlerted: summary=" + summary); + Log.d(TAG, "handleSuppressedSummaryAlerted: summary=" + logKey(summary)); } GroupAlertEntry groupAlertEntry = mGroupAlertEntries.get(mGroupManager.getGroupKey(summary.getSbn())); @@ -407,7 +419,7 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis */ private void handleOverriddenSummaryAlerted(NotificationEntry summary) { if (DEBUG) { - Log.d(TAG, "handleOverriddenSummaryAlerted: summary=" + summary); + Log.d(TAG, "handleOverriddenSummaryAlerted: summary=" + logKey(summary)); } GroupAlertEntry groupAlertEntry = mGroupAlertEntries.get(mGroupManager.getGroupKey(summary.getSbn())); @@ -481,7 +493,9 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis groupAlertEntry.mLastAlertTransferTime = SystemClock.elapsedRealtime(); } if (DEBUG) { - Log.d(TAG, "transferAlertState: fromEntry=" + fromEntry + " toEntry=" + toEntry); + Log.d(TAG, "transferAlertState:" + + " fromEntry=" + logKey(fromEntry) + + " toEntry=" + logKey(toEntry)); } transferAlertState(fromEntry, toEntry); } @@ -583,7 +597,8 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis final RowContentBindParams params = mRowContentBindStage.getStageParams(entry); if ((params.getContentViews() & contentFlag) == 0) { if (DEBUG) { - Log.d(TAG, "alertNotificationWhenPossible: async requestRebind entry=" + entry); + Log.d(TAG, "alertNotificationWhenPossible:" + + " async requestRebind entry=" + logKey(entry)); } mPendingAlerts.put(entry.getKey(), new PendingAlertInfo(entry)); params.requireContentViews(contentFlag); @@ -593,6 +608,10 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis if (alertInfo.isStillValid()) { alertNotificationWhenPossible(entry); } else { + if (DEBUG) { + Log.d(TAG, "alertNotificationWhenPossible:" + + " markContentViewsFreeable entry=" + logKey(entry)); + } // The transfer is no longer valid. Free the content. mRowContentBindStage.getStageParams(entry).markContentViewsFreeable( contentFlag); @@ -604,12 +623,14 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis } if (mHeadsUpManager.isAlerting(entry.getKey())) { if (DEBUG) { - Log.d(TAG, "alertNotificationWhenPossible: continue alerting entry=" + entry); + Log.d(TAG, "alertNotificationWhenPossible:" + + " continue alerting entry=" + logKey(entry)); } mHeadsUpManager.updateNotification(entry.getKey(), true /* alert */); } else { if (DEBUG) { - Log.d(TAG, "alertNotificationWhenPossible: start alerting entry=" + entry); + Log.d(TAG, "alertNotificationWhenPossible:" + + " start alerting entry=" + logKey(entry)); } mHeadsUpManager.showNotification(entry); } @@ -657,7 +678,7 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis // Notification is aborted due to the transfer being explicitly cancelled return false; } - if (mEntry.getSbn().getGroupKey() != mOriginalNotification.getGroupKey()) { + if (!mEntry.getSbn().getGroupKey().equals(mOriginalNotification.getGroupKey())) { // Groups have changed return false; } 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 4d625cfbfbf5..016b953245b2 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; @@ -69,6 +70,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.PowerManager; import android.os.SystemClock; +import android.os.Trace; import android.os.UserManager; import android.os.VibrationEffect; import android.provider.Settings; @@ -210,8 +212,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; @@ -2438,7 +2442,7 @@ public class NotificationPanelViewController extends PanelViewController { mSplitShadeHeaderController.setShadeExpandedFraction(shadeExpandedFraction); mSplitShadeHeaderController.setQsExpandedFraction(qsExpansionFraction); mSplitShadeHeaderController.setShadeExpanded(mQsVisible); - + mKeyguardStatusBarViewController.updateViewState(); if (mCommunalViewController != null) { mCommunalViewController.updateQsExpansion(qsExpansionFraction); @@ -4590,11 +4594,20 @@ public class NotificationPanelViewController extends PanelViewController { @Override public void onSmallestScreenWidthChanged() { + Trace.beginSection("onSmallestScreenWidthChanged"); if (DEBUG) Log.d(TAG, "onSmallestScreenWidthChanged"); // 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(); } @Override @@ -4733,8 +4746,6 @@ public class NotificationPanelViewController extends PanelViewController { public interface NotificationPanelViewStateProvider { /** Returns the expanded height of the panel view. */ float getPanelViewExpandedHeight(); - /** Returns the fraction of QS that's expanded. */ - float getQsExpansionFraction(); /** * Returns true if heads up should be visible. * @@ -4755,18 +4766,15 @@ public class NotificationPanelViewController extends PanelViewController { } @Override - public float getQsExpansionFraction() { - return computeQsExpansionFraction(); - } - - @Override public boolean shouldHeadsUpBeVisible() { return mHeadsUpAppearanceController.shouldBeVisible(); } @Override public float getLockscreenShadeDragProgress() { - return mLockscreenShadeTransitionController.getQSDragProgress(); + return mTransitioningToFullShadeProgress > 0 + ? mLockscreenShadeTransitionController.getQSDragProgress() + : computeQsExpansionFraction(); } }; @@ -4915,33 +4923,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/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..5d83cc6259a0 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(); } } 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 137c51955053..07ae33c42c21 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 = @@ -1003,7 +1004,7 @@ public class StatusBar extends CoreStartable implements internalFilter.addAction(BANNER_ACTION_CANCEL); internalFilter.addAction(BANNER_ACTION_SETUP); mContext.registerReceiver(mBannerActionBroadcastReceiver, internalFilter, PERMISSION_SELF, - null); + null, Context.RECEIVER_EXPORTED_UNAUDITED); if (mWallpaperSupported) { IWallpaperManager wallpaperManager = IWallpaperManager.Stub.asInterface( @@ -1332,7 +1333,8 @@ public class StatusBar extends CoreStartable implements demoFilter.addAction(ACTION_FAKE_ARTWORK); } mContext.registerReceiverAsUser(mDemoReceiver, UserHandle.ALL, demoFilter, - android.Manifest.permission.DUMP, null); + android.Manifest.permission.DUMP, null, + Context.RECEIVER_EXPORTED_UNAUDITED); // listen for USER_SETUP_COMPLETE setting (per-user) mDeviceProvisionedController.addCallback(mUserSetupObserver); @@ -2634,8 +2636,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); } @@ -3167,16 +3178,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() { 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..6746b3e8883a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt @@ -290,6 +290,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 41cacf5142fd..33f2140b150e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java @@ -16,110 +16,53 @@ package com.android.systemui.statusbar.policy; - import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_IGNORED; import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_LOCKED; -import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_UNLOCKED; - -import static com.android.systemui.statusbar.policy.dagger.StatusBarPolicyModule.DEVICE_STATE_ROTATION_LOCK_DEFAULTS; import android.annotation.Nullable; import android.hardware.devicestate.DeviceStateManager; -import android.os.UserHandle; -import android.provider.Settings; -import android.text.TextUtils; import android.util.Log; -import android.util.SparseIntArray; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.util.settings.SecureSettings; import com.android.systemui.util.wrapper.RotationPolicyWrapper; import java.util.concurrent.Executor; import javax.inject.Inject; -import javax.inject.Named; /** - * Handles reading and writing of rotation lock settings per device state, as well as setting - * the rotation lock when device state changes. - **/ + * Handles reading and writing of rotation lock settings per device state, as well as setting the + * rotation lock when device state changes. + */ @SysUISingleton -public final class DeviceStateRotationLockSettingController implements Listenable, - RotationLockController.RotationLockControllerCallback { +public final class DeviceStateRotationLockSettingController + implements Listenable, RotationLockController.RotationLockControllerCallback { private static final String TAG = "DSRotateLockSettingCon"; - private static final String SEPARATOR_REGEX = ":"; - - private final SecureSettings mSecureSettings; private final RotationPolicyWrapper mRotationPolicyWrapper; private final DeviceStateManager mDeviceStateManager; private final Executor mMainExecutor; - private final String[] mDeviceStateRotationLockDefaults; + private final DeviceStateRotationLockSettingsManager mDeviceStateRotationLockSettingsManager; - private SparseIntArray mDeviceStateRotationLockSettings; - // TODO(b/183001527): Add API to query current device state and initialize this. + // On registration for DeviceStateCallback, we will receive a callback with the current state + // and this will be initialized. private int mDeviceState = -1; - @Nullable - private DeviceStateManager.DeviceStateCallback mDeviceStateCallback; - + @Nullable private DeviceStateManager.DeviceStateCallback mDeviceStateCallback; + private DeviceStateRotationLockSettingsManager.DeviceStateRotationLockSettingsListener + mDeviceStateRotationLockSettingsListener; @Inject public DeviceStateRotationLockSettingController( - SecureSettings secureSettings, RotationPolicyWrapper rotationPolicyWrapper, DeviceStateManager deviceStateManager, @Main Executor executor, - @Named(DEVICE_STATE_ROTATION_LOCK_DEFAULTS) String[] deviceStateRotationLockDefaults - ) { - mSecureSettings = secureSettings; + DeviceStateRotationLockSettingsManager deviceStateRotationLockSettingsManager) { mRotationPolicyWrapper = rotationPolicyWrapper; mDeviceStateManager = deviceStateManager; mMainExecutor = executor; - mDeviceStateRotationLockDefaults = deviceStateRotationLockDefaults; - } - - /** - * Loads the settings from storage. - */ - public void initialize() { - String serializedSetting = - mSecureSettings.getStringForUser(Settings.Secure.DEVICE_STATE_ROTATION_LOCK, - UserHandle.USER_CURRENT); - if (TextUtils.isEmpty(serializedSetting)) { - // No settings saved, we should load the defaults and persist them. - fallbackOnDefaults(); - return; - } - String[] values = serializedSetting.split(SEPARATOR_REGEX); - if (values.length % 2 != 0) { - // Each entry should be a key/value pair, so this is corrupt. - Log.wtf(TAG, "Can't deserialize saved settings, falling back on defaults"); - fallbackOnDefaults(); - return; - } - mDeviceStateRotationLockSettings = new SparseIntArray(values.length / 2); - int key; - int value; - - for (int i = 0; i < values.length - 1; ) { - try { - key = Integer.parseInt(values[i++]); - value = Integer.parseInt(values[i++]); - mDeviceStateRotationLockSettings.put(key, value); - } catch (NumberFormatException e) { - Log.wtf(TAG, "Error deserializing one of the saved settings", e); - fallbackOnDefaults(); - return; - } - } - } - - private void fallbackOnDefaults() { - loadDefaults(); - persistSettings(); + mDeviceStateRotationLockSettingsManager = deviceStateRotationLockSettingsManager; } @Override @@ -129,10 +72,17 @@ public final class DeviceStateRotationLockSettingController implements Listenabl // is no user action. mDeviceStateCallback = this::updateDeviceState; mDeviceStateManager.registerCallback(mMainExecutor, mDeviceStateCallback); + mDeviceStateRotationLockSettingsListener = () -> readPersistedSetting(mDeviceState); + mDeviceStateRotationLockSettingsManager.registerListener( + mDeviceStateRotationLockSettingsListener); } else { if (mDeviceStateCallback != null) { mDeviceStateManager.unregisterCallback(mDeviceStateCallback); } + if (mDeviceStateRotationLockSettingsListener != null) { + mDeviceStateRotationLockSettingsManager.unregisterListener( + mDeviceStateRotationLockSettingsListener); + } } } @@ -143,7 +93,8 @@ public final class DeviceStateRotationLockSettingController implements Listenabl return; } - if (rotationLocked == isRotationLockedForCurrentState()) { + if (rotationLocked + == mDeviceStateRotationLockSettingsManager.isRotationLocked(mDeviceState)) { Log.v(TAG, "Rotation lock same as the current setting, no need to update."); return; } @@ -152,19 +103,15 @@ public final class DeviceStateRotationLockSettingController implements Listenabl } private void saveNewRotationLockSetting(boolean isRotationLocked) { - Log.v(TAG, "saveNewRotationLockSetting [state=" + mDeviceState + "] [isRotationLocked=" - + isRotationLocked + "]"); - - mDeviceStateRotationLockSettings.put(mDeviceState, - isRotationLocked - ? DEVICE_STATE_ROTATION_LOCK_LOCKED - : DEVICE_STATE_ROTATION_LOCK_UNLOCKED); - persistSettings(); - } - - private boolean isRotationLockedForCurrentState() { - return mDeviceStateRotationLockSettings.get(mDeviceState, - DEVICE_STATE_ROTATION_LOCK_IGNORED) == DEVICE_STATE_ROTATION_LOCK_LOCKED; + Log.v( + TAG, + "saveNewRotationLockSetting [state=" + + mDeviceState + + "] [isRotationLocked=" + + isRotationLocked + + "]"); + + mDeviceStateRotationLockSettingsManager.updateSetting(mDeviceState, isRotationLocked); } private void updateDeviceState(int state) { @@ -173,67 +120,29 @@ public final class DeviceStateRotationLockSettingController implements Listenabl return; } + readPersistedSetting(state); + } + + private void readPersistedSetting(int state) { int rotationLockSetting = - mDeviceStateRotationLockSettings.get(state, DEVICE_STATE_ROTATION_LOCK_IGNORED); + 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; } // Accept the new state mDeviceState = state; - // Update the rotation lock setting if needed for this new device state + // Update the rotation policy, if needed, for this new device state boolean newRotationLockSetting = rotationLockSetting == DEVICE_STATE_ROTATION_LOCK_LOCKED; if (newRotationLockSetting != mRotationPolicyWrapper.isRotationLocked()) { mRotationPolicyWrapper.setRotationLock(newRotationLockSetting); } } - - private void persistSettings() { - if (mDeviceStateRotationLockSettings.size() == 0) { - mSecureSettings.putStringForUser(Settings.Secure.DEVICE_STATE_ROTATION_LOCK, - /* value= */"", UserHandle.USER_CURRENT); - return; - } - - StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append(mDeviceStateRotationLockSettings.keyAt(0)) - .append(SEPARATOR_REGEX) - .append(mDeviceStateRotationLockSettings.valueAt(0)); - - for (int i = 1; i < mDeviceStateRotationLockSettings.size(); i++) { - stringBuilder - .append(SEPARATOR_REGEX) - .append(mDeviceStateRotationLockSettings.keyAt(i)) - .append(SEPARATOR_REGEX) - .append(mDeviceStateRotationLockSettings.valueAt(i)); - } - mSecureSettings.putStringForUser(Settings.Secure.DEVICE_STATE_ROTATION_LOCK, - stringBuilder.toString(), UserHandle.USER_CURRENT); - } - - 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); - try { - int key = Integer.parseInt(entry[0]); - int value = Integer.parseInt(entry[1]); - mDeviceStateRotationLockSettings.put(key, value); - } catch (NumberFormatException e) { - Log.wtf(TAG, "Error deserializing default settings", e); - } - } - } - } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingsManager.java new file mode 100644 index 000000000000..bec5fc8e7b9f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingsManager.java @@ -0,0 +1,303 @@ +/* + * 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.policy; + +import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_IGNORED; +import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_LOCKED; +import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_UNLOCKED; + +import android.content.ContentResolver; +import android.content.Context; +import android.database.ContentObserver; +import android.os.Handler; +import android.os.UserHandle; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.Log; +import android.util.SparseIntArray; + +import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; + +import java.util.HashSet; +import java.util.Set; + +/** + * Manages device-state based rotation lock settings. Handles reading, writing, and listening for + * changes. + */ +public final class DeviceStateRotationLockSettingsManager { + + private static final String TAG = "DSRotLockSettingsMngr"; + private static final String SEPARATOR_REGEX = ":"; + + private static DeviceStateRotationLockSettingsManager sSingleton; + + private final ContentResolver mContentResolver; + private final String[] mDeviceStateRotationLockDefaults; + 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); + } + + /** Returns a singleton instance of this class */ + public static synchronized DeviceStateRotationLockSettingsManager getInstance(Context context) { + if (sSingleton == null) { + sSingleton = + new DeviceStateRotationLockSettingsManager(context.getApplicationContext()); + } + return sSingleton; + } + + /** Returns true if device-state based rotation lock settings are enabled. */ + public static boolean isDeviceStateRotationLockEnabled(Context context) { + return context.getResources() + .getStringArray(R.array.config_perDeviceStateRotationLockDefaults) + .length + > 0; + } + + private void listenForSettingsChange(Context context) { + context.getContentResolver() + .registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.DEVICE_STATE_ROTATION_LOCK), + /* notifyForDescendents= */ false, //NOTYPO + new ContentObserver(mMainHandler) { + @Override + public void onChange(boolean selfChange) { + onPersistedSettingsChanged(); + } + }, + UserHandle.USER_CURRENT); + } + + /** + * Registers a {@link DeviceStateRotationLockSettingsListener} to be notified when the settings + * change. Can be called multiple times with different listeners. + */ + public void registerListener(DeviceStateRotationLockSettingsListener runnable) { + mListeners.add(runnable); + } + + /** + * Unregisters a {@link DeviceStateRotationLockSettingsListener}. No-op if the given instance + * was never registered. + */ + public void unregisterListener( + DeviceStateRotationLockSettingsListener deviceStateRotationLockSettingsListener) { + if (!mListeners.remove(deviceStateRotationLockSettingsListener)) { + Log.w(TAG, "Attempting to unregister a listener hadn't been registered"); + } + } + + /** 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 + ? DEVICE_STATE_ROTATION_LOCK_LOCKED + : DEVICE_STATE_ROTATION_LOCK_UNLOCKED); + persistSettings(); + } + + /** + * 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) { + 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; + } + + /** + * Returns true if there is no device state for which the current setting is {@link + * DEVICE_STATE_ROTATION_LOCK_UNLOCKED}. + */ + public boolean isRotationLockedForAllStates() { + for (int i = 0; i < mDeviceStateRotationLockSettings.size(); i++) { + if (mDeviceStateRotationLockSettings.valueAt(i) + == DEVICE_STATE_ROTATION_LOCK_UNLOCKED) { + return false; + } + } + return true; + } + + private void initializeInMemoryMap() { + String serializedSetting = + Settings.Secure.getStringForUser( + mContentResolver, + Settings.Secure.DEVICE_STATE_ROTATION_LOCK, + UserHandle.USER_CURRENT); + if (TextUtils.isEmpty(serializedSetting)) { + // No settings saved, we should load the defaults and persist them. + fallbackOnDefaults(); + return; + } + String[] values = serializedSetting.split(SEPARATOR_REGEX); + if (values.length % 2 != 0) { + // Each entry should be a key/value pair, so this is corrupt. + Log.wtf(TAG, "Can't deserialize saved settings, falling back on defaults"); + fallbackOnDefaults(); + return; + } + mDeviceStateRotationLockSettings = new SparseIntArray(values.length / 2); + int key; + int value; + + for (int i = 0; i < values.length - 1; ) { + try { + key = Integer.parseInt(values[i++]); + value = Integer.parseInt(values[i++]); + mDeviceStateRotationLockSettings.put(key, value); + } catch (NumberFormatException e) { + Log.wtf(TAG, "Error deserializing one of the saved settings", e); + fallbackOnDefaults(); + return; + } + } + } + + private void fallbackOnDefaults() { + loadDefaults(); + persistSettings(); + } + + private void persistSettings() { + if (mDeviceStateRotationLockSettings.size() == 0) { + Settings.Secure.putStringForUser( + mContentResolver, + Settings.Secure.DEVICE_STATE_ROTATION_LOCK, + /* value= */ "", + UserHandle.USER_CURRENT); + return; + } + + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder + .append(mDeviceStateRotationLockSettings.keyAt(0)) + .append(SEPARATOR_REGEX) + .append(mDeviceStateRotationLockSettings.valueAt(0)); + + for (int i = 1; i < mDeviceStateRotationLockSettings.size(); i++) { + stringBuilder + .append(SEPARATOR_REGEX) + .append(mDeviceStateRotationLockSettings.keyAt(i)) + .append(SEPARATOR_REGEX) + .append(mDeviceStateRotationLockSettings.valueAt(i)); + } + Settings.Secure.putStringForUser( + mContentResolver, + Settings.Secure.DEVICE_STATE_ROTATION_LOCK, + stringBuilder.toString(), + UserHandle.USER_CURRENT); + } + + private void loadDefaults() { + mDeviceStateRotationLockSettings = new SparseIntArray( + mDeviceStateRotationLockDefaults.length); + mDeviceStateRotationLockFallbackSettings = new SparseIntArray(1); + for (String entry : mDeviceStateRotationLockDefaults) { + String[] values = entry.split(SEPARATOR_REGEX); + try { + 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 parsing settings entry. Entry was: " + entry, e); + return; + } + } + } + + /** + * Called when the persisted settings have changed, requiring a reinitialization of the + * in-memory map. + */ + @VisibleForTesting + public void onPersistedSettingsChanged() { + initializeInMemoryMap(); + notifyListeners(); + } + + private void notifyListeners() { + for (DeviceStateRotationLockSettingsListener r : mListeners) { + r.onSettingsChanged(); + } + } + + /** Listener for changes in device-state based rotation lock settings */ + public interface DeviceStateRotationLockSettingsListener { + /** Called whenever the settings have changed. */ + void onSettingsChanged(); + } +} 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 c1f63b80dbf8..05a586b1cdc2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java @@ -128,7 +128,7 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum update(true /* updateAlways */); } } - }, filter, null, null); + }, filter, null, null, Context.RECEIVER_EXPORTED_UNAUDITED); } } 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/statusbar/policy/RotationLockControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java index 3143a471649c..1eeb0ac8b3bb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java @@ -64,7 +64,6 @@ public final class RotationLockControllerImpl implements RotationLockController mDeviceStateRotationLockSettingController = deviceStateRotationLockSettingController; mIsPerDeviceStateRotationLockEnabled = deviceStateRotationLockDefaults.length > 0; if (mIsPerDeviceStateRotationLockEnabled) { - deviceStateRotationLockSettingController.initialize(); mCallbacks.add(mDeviceStateRotationLockSettingController); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java index f42e3885fe62..29285f886f4e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java @@ -74,7 +74,7 @@ public class UserInfoControllerImpl implements UserInfoController { profileFilter.addAction(ContactsContract.Intents.ACTION_PROFILE_CHANGED); profileFilter.addAction(Intent.ACTION_USER_INFO_CHANGED); mContext.registerReceiverAsUser(mProfileReceiver, UserHandle.ALL, profileFilter, - null, null); + null, null, Context.RECEIVER_EXPORTED_UNAUDITED); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java index 79ee7468b5fe..9f20bc55ebc9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java @@ -229,7 +229,8 @@ public class UserSwitcherController implements Dumpable { filter = new IntentFilter(); mContext.registerReceiverAsUser(mReceiver, UserHandle.SYSTEM, filter, - PERMISSION_SELF, null /* scheduler */); + PERMISSION_SELF, null /* scheduler */, + Context.RECEIVER_EXPORTED_UNAUDITED); mSettingsObserver = new ContentObserver(mHandler) { @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java index b6a96a7e49b9..60938fb2feb4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.policy.dagger; +import android.content.Context; import android.content.res.Resources; import android.os.UserManager; @@ -35,6 +36,7 @@ import com.android.systemui.statusbar.policy.DeviceControlsController; import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl; import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.statusbar.policy.DevicePostureControllerImpl; +import com.android.systemui.statusbar.policy.DeviceStateRotationLockSettingsManager; import com.android.systemui.statusbar.policy.ExtensionController; import com.android.systemui.statusbar.policy.ExtensionControllerImpl; import com.android.systemui.statusbar.policy.FlashlightController; @@ -163,6 +165,14 @@ public interface StatusBarPolicyModule { return controller; } + /** Returns a singleton instance of DeviceStateRotationLockSettingsManager */ + @SysUISingleton + @Provides + static DeviceStateRotationLockSettingsManager provideAutoRotateSettingsManager( + Context context) { + return DeviceStateRotationLockSettingsManager.getInstance(context); + } + /** * Default values for per-device state rotation lock settings. */ 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/usb/StorageNotification.java b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java index 90c7f1f7ac72..cf361ec304e5 100644 --- a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java +++ b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java @@ -167,9 +167,11 @@ public class StorageNotification extends CoreStartable { mStorageManager.registerListener(mListener); mContext.registerReceiver(mSnoozeReceiver, new IntentFilter(ACTION_SNOOZE_VOLUME), - android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null); + android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null, + Context.RECEIVER_EXPORTED_UNAUDITED); mContext.registerReceiver(mFinishReceiver, new IntentFilter(ACTION_FINISH_WIZARD), - android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null); + android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null, + Context.RECEIVER_EXPORTED_UNAUDITED); // Kick current state into place final List<DiskInfo> disks = mStorageManager.getDisks(); diff --git a/packages/SystemUI/src/com/android/systemui/volume/SafetyWarningDialog.java b/packages/SystemUI/src/com/android/systemui/volume/SafetyWarningDialog.java index a40cf4f37cc3..5b188b24d7dd 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/SafetyWarningDialog.java +++ b/packages/SystemUI/src/com/android/systemui/volume/SafetyWarningDialog.java @@ -63,7 +63,8 @@ abstract public class SafetyWarningDialog extends SystemUIDialog setOnDismissListener(this); final IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); - context.registerReceiver(mReceiver, filter); + context.registerReceiver(mReceiver, filter, + Context.RECEIVER_EXPORTED_UNAUDITED); } abstract protected void cleanUp(); 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/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/SliceBroadcastRelayHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/SliceBroadcastRelayHandlerTest.java index 7d556236b518..7c121e1754bf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/SliceBroadcastRelayHandlerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/SliceBroadcastRelayHandlerTest.java @@ -15,6 +15,7 @@ package com.android.systemui; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -79,7 +80,7 @@ public class SliceBroadcastRelayHandlerTest extends SysuiTestCase { intent.putExtra(SliceBroadcastRelay.EXTRA_URI, testUri); mRelayHandler.handleIntent(intent); - verify(mSpyContext).registerReceiver(any(), eq(value)); + verify(mSpyContext).registerReceiver(any(), eq(value), anyInt()); } @Test @@ -99,7 +100,7 @@ public class SliceBroadcastRelayHandlerTest extends SysuiTestCase { mRelayHandler.handleIntent(intent); ArgumentCaptor<BroadcastReceiver> relay = ArgumentCaptor.forClass(BroadcastReceiver.class); - verify(mSpyContext).registerReceiver(relay.capture(), eq(value)); + verify(mSpyContext).registerReceiver(relay.capture(), eq(value), anyInt()); intent = new Intent(SliceBroadcastRelay.ACTION_UNREGISTER); intent.putExtra(SliceBroadcastRelay.EXTRA_URI, ContentProvider.maybeAddUserId(testUri, 0)); @@ -138,7 +139,7 @@ public class SliceBroadcastRelayHandlerTest extends SysuiTestCase { mRelayHandler.handleIntent(intent); ArgumentCaptor<BroadcastReceiver> relay = ArgumentCaptor.forClass(BroadcastReceiver.class); - verify(mSpyContext).registerReceiver(relay.capture(), eq(value)); + verify(mSpyContext).registerReceiver(relay.capture(), eq(value), anyInt()); relay.getValue().onReceive(mSpyContext, new Intent(TEST_ACTION)); verify(Receiver.sReceiver, timeout(2000)).onReceive(any(), any()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestableContext.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestableContext.java index 3d679deaa426..0674ea855d7f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestableContext.java +++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestableContext.java @@ -83,6 +83,16 @@ public class SysuiTestableContext extends TestableContext { } @Override + public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, int flags) { + if (receiver != null) { + synchronized (mRegisteredReceivers) { + mRegisteredReceivers.add(receiver); + } + } + return super.registerReceiver(receiver, filter, flags); + } + + @Override public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler) { if (receiver != null) { @@ -94,6 +104,17 @@ public class SysuiTestableContext extends TestableContext { } @Override + public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, + String broadcastPermission, Handler scheduler, int flags) { + if (receiver != null) { + synchronized (mRegisteredReceivers) { + mRegisteredReceivers.add(receiver); + } + } + return super.registerReceiver(receiver, filter, broadcastPermission, scheduler, flags); + } + + @Override public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user, IntentFilter filter, String broadcastPermission, Handler scheduler) { if (receiver != null) { @@ -105,6 +126,18 @@ public class SysuiTestableContext extends TestableContext { } @Override + public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user, + IntentFilter filter, String broadcastPermission, Handler scheduler, int flags) { + if (receiver != null) { + synchronized (mRegisteredReceivers) { + mRegisteredReceivers.add(receiver); + } + } + return super.registerReceiverAsUser(receiver, user, filter, broadcastPermission, scheduler, + flags); + } + + @Override public void unregisterReceiver(BroadcastReceiver receiver) { if (receiver != null) { synchronized (mRegisteredReceivers) { 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..dcb7307250fe 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; @@ -45,6 +47,7 @@ import static org.mockito.Mockito.when; 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; @@ -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 @@ -322,11 +321,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 +530,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 +629,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/communal/CommunalManagerUpdaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalManagerUpdaterTest.java deleted file mode 100644 index 4a29ada8a998..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalManagerUpdaterTest.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.communal; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.clearInvocations; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -import android.app.communal.CommunalManager; -import android.testing.AndroidTestingRunner; - -import androidx.test.filters.SmallTest; - -import com.android.systemui.SysuiTestCase; -import com.android.systemui.util.concurrency.FakeExecutor; -import com.android.systemui.util.condition.Monitor; -import com.android.systemui.util.time.FakeSystemClock; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -public class CommunalManagerUpdaterTest extends SysuiTestCase { - private CommunalSourceMonitor mMonitor; - @Mock - private CommunalManager mCommunalManager; - @Mock - private Monitor mCommunalConditionsMonitor; - - private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock()); - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - mContext.addMockSystemService(CommunalManager.class, mCommunalManager); - - doAnswer(invocation -> { - final Monitor.Callback callback = invocation.getArgument(0); - callback.onConditionsChanged(true); - return null; - }).when(mCommunalConditionsMonitor).addCallback(any()); - - mMonitor = new CommunalSourceMonitor(mExecutor, mCommunalConditionsMonitor); - final CommunalManagerUpdater updater = new CommunalManagerUpdater(mContext, mMonitor); - updater.start(); - clearInvocations(mCommunalManager); - } - - @Test - public void testUpdateSystemService_false() { - mMonitor.setSource(null); - mExecutor.runAllReady(); - verify(mCommunalManager).setCommunalViewShowing(false); - } - - @Test - public void testUpdateSystemService_true() { - final CommunalSource source = mock(CommunalSource.class); - mMonitor.setSource(source); - mExecutor.runAllReady(); - verify(mCommunalManager).setCommunalViewShowing(true); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt index a328d9e06a74..efb3db700804 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt @@ -32,7 +32,6 @@ import androidx.test.filters.MediumTest import androidx.test.rule.ActivityTestRule import androidx.test.runner.intercepting.SingleActivityFactory import com.android.systemui.SysuiTestCase -import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.controls.controller.ControlInfo import com.android.systemui.controls.controller.ControlsController import com.android.systemui.util.mockito.capture @@ -70,8 +69,6 @@ class ControlsRequestDialogTest : SysuiTestCase() { @Mock private lateinit var listingController: ControlsListingController @Mock - private lateinit var broadcastDispatcher: BroadcastDispatcher - @Mock private lateinit var iIntentSender: IIntentSender @Captor private lateinit var captor: ArgumentCaptor<ControlInfo> @@ -85,7 +82,7 @@ class ControlsRequestDialogTest : SysuiTestCase() { override fun create(intent: Intent?): TestControlsRequestDialog { return TestControlsRequestDialog( controller, - broadcastDispatcher, + fakeBroadcastDispatcher, listingController ) } 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 deleted file mode 100644 index 736556871376..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/ComplicationProviderTest.java +++ /dev/null @@ -1,137 +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; - -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.appwidgets.AppWidgetProvider; -import com.android.systemui.dreams.appwidgets.ComplicationProvider; -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/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java index cf53ccffcdb0..7c5f57fe0b39 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java @@ -19,10 +19,13 @@ package com.android.systemui.dreams; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyFloat; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.res.Resources; +import android.os.Handler; import android.testing.AndroidTestingRunner; import android.view.View; import android.view.ViewGroup; @@ -45,6 +48,8 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidTestingRunner.class) public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { private static final int DREAM_OVERLAY_NOTIFICATIONS_DRAG_AREA_HEIGHT = 100; + private static final int MAX_BURN_IN_OFFSET = 20; + private static final long BURN_IN_PROTECTION_UPDATE_INTERVAL = 10; @Mock Resources mResources; @@ -61,6 +66,9 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { @Mock ViewGroup mDreamOverlayContentView; + @Mock + Handler mHandler; + DreamOverlayContainerViewController mController; @Before @@ -74,8 +82,12 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { when(mDreamOverlayContainerView.getViewTreeObserver()).thenReturn(mViewTreeObserver); mController = new DreamOverlayContainerViewController( - mDreamOverlayContainerView, mDreamOverlayContentView, - mDreamOverlayStatusBarViewController); + mDreamOverlayContainerView, + mDreamOverlayContentView, + mDreamOverlayStatusBarViewController, + mHandler, + MAX_BURN_IN_OFFSET, + BURN_IN_PROTECTION_UPDATE_INTERVAL); } @Test @@ -129,4 +141,37 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { computeInsetsListenerCapture.getValue().onComputeInternalInsets(info); assertNotNull(info.touchableRegion); } + + @Test + public void testBurnInProtectionStartsWhenContentViewAttached() { + mController.onViewAttached(); + verify(mHandler).postDelayed(any(Runnable.class), eq(BURN_IN_PROTECTION_UPDATE_INTERVAL)); + } + + @Test + public void testBurnInProtectionStopsWhenContentViewDetached() { + mController.onViewDetached(); + verify(mHandler).removeCallbacks(any(Runnable.class)); + } + + @Test + public void testBurnInProtectionUpdatesPeriodically() { + ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class); + mController.onViewAttached(); + verify(mHandler).postDelayed( + runnableCaptor.capture(), eq(BURN_IN_PROTECTION_UPDATE_INTERVAL)); + runnableCaptor.getValue().run(); + verify(mDreamOverlayContainerView).setTranslationX(anyFloat()); + verify(mDreamOverlayContainerView).setTranslationY(anyFloat()); + } + + @Test + public void testBurnInProtectionReschedulesUpdate() { + ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class); + mController.onViewAttached(); + verify(mHandler).postDelayed( + runnableCaptor.capture(), eq(BURN_IN_PROTECTION_UPDATE_INTERVAL)); + runnableCaptor.getValue().run(); + verify(mHandler).postDelayed(runnableCaptor.getValue(), BURN_IN_PROTECTION_UPDATE_INTERVAL); + } } 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/flags/FeatureFlagsDebugTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt index cb16becc48fd..87bc732d2f66 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt @@ -76,7 +76,8 @@ class FeatureFlagsDebugTest : SysuiTestCase() { ) verify(mFlagManager).restartAction = any() mBroadcastReceiver = withArgCaptor { - verify(mMockContext).registerReceiver(capture(), any(), nullable(), nullable()) + verify(mMockContext).registerReceiver(capture(), any(), nullable(), nullable(), + any()) } mClearCacheAction = withArgCaptor { verify(mFlagManager).clearCacheAction = capture() 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/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java index 7f357324bed0..cf1a36af24ba 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java @@ -24,6 +24,7 @@ import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewCont import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BATTERY; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BIOMETRIC_MESSAGE; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_DISCLOSURE; +import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_LOGOUT; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_OWNER_INFO; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_RESTING; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_TRANSIENT; @@ -86,7 +87,6 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.KeyguardIndicationTextView; -import com.android.systemui.statusbar.phone.LockIcon; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.concurrency.FakeExecutor; @@ -132,8 +132,6 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { @Mock private BroadcastDispatcher mBroadcastDispatcher; @Mock - private LockIcon mLockIcon; - @Mock private StatusBarStateController mStatusBarStateController; @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; @@ -738,6 +736,41 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { verifyHideIndication(INDICATION_TYPE_OWNER_INFO); } + @Test + public void testOnKeyguardShowingChanged_notShowing_resetsMessages() { + createController(); + + // GIVEN keyguard isn't showing + when(mKeyguardStateController.isShowing()).thenReturn(false); + + // WHEN keyguard showing changed called + mKeyguardStateControllerCallback.onKeyguardShowingChanged(); + + // THEN messages are reset + verify(mRotateTextViewController).clearMessages(); + assertThat(mTextView.getText()).isEqualTo(""); + } + + @Test + public void testOnKeyguardShowingChanged_showing_updatesPersistentMessages() { + createController(); + + // GIVEN keyguard is showing + when(mKeyguardStateController.isShowing()).thenReturn(true); + + // WHEN keyguard showing changed called + mKeyguardStateControllerCallback.onKeyguardShowingChanged(); + + // THEN persistent messages are updated (in this case, most messages are hidden since + // no info is provided) - verify that this happens + verify(mRotateTextViewController).hideIndication(INDICATION_TYPE_DISCLOSURE); + verify(mRotateTextViewController).hideIndication(INDICATION_TYPE_OWNER_INFO); + verify(mRotateTextViewController).hideIndication(INDICATION_TYPE_BATTERY); + verify(mRotateTextViewController).hideIndication(INDICATION_TYPE_TRUST); + verify(mRotateTextViewController).hideIndication(INDICATION_TYPE_ALIGNMENT); + verify(mRotateTextViewController).hideIndication(INDICATION_TYPE_LOGOUT); + } + private void sendUpdateDisclosureBroadcast() { mBroadcastReceiver.onReceive(mContext, new Intent()); } 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 a7f8b6e01949..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) @@ -459,7 +493,7 @@ public class NotifCollectionTest extends SysuiTestCase { mCollection.dismissNotification(entry1, defaultStats(entry1)); // THEN lifetime extenders are never queried - verify(mExtender1, never()).shouldExtendLifetime(eq(entry1), anyInt()); + verify(mExtender1, never()).maybeExtendLifetime(eq(entry1), anyInt()); } @Test @@ -912,9 +946,9 @@ public class NotifCollectionTest extends SysuiTestCase { mNoMan.retractNotif(notif2.sbn, REASON_APP_CANCEL); // THEN each extender is asked whether to extend, even if earlier ones return true - verify(mExtender1).shouldExtendLifetime(entry2, REASON_APP_CANCEL); - verify(mExtender2).shouldExtendLifetime(entry2, REASON_APP_CANCEL); - verify(mExtender3).shouldExtendLifetime(entry2, REASON_APP_CANCEL); + verify(mExtender1).maybeExtendLifetime(entry2, REASON_APP_CANCEL); + verify(mExtender2).maybeExtendLifetime(entry2, REASON_APP_CANCEL); + verify(mExtender3).maybeExtendLifetime(entry2, REASON_APP_CANCEL); // THEN the entry is not removed assertTrue(mCollection.getAllNotifs().contains(entry2)); @@ -948,9 +982,9 @@ public class NotifCollectionTest extends SysuiTestCase { mExtender2.callback.onEndLifetimeExtension(mExtender2, entry2); // THEN each extender is re-queried - verify(mExtender1).shouldExtendLifetime(entry2, REASON_APP_CANCEL); - verify(mExtender2).shouldExtendLifetime(entry2, REASON_APP_CANCEL); - verify(mExtender3).shouldExtendLifetime(entry2, REASON_APP_CANCEL); + verify(mExtender1).maybeExtendLifetime(entry2, REASON_APP_CANCEL); + verify(mExtender2).maybeExtendLifetime(entry2, REASON_APP_CANCEL); + verify(mExtender3).maybeExtendLifetime(entry2, REASON_APP_CANCEL); // THEN the entry is not removed assertTrue(mCollection.getAllNotifs().contains(entry2)); @@ -986,9 +1020,9 @@ public class NotifCollectionTest extends SysuiTestCase { assertTrue(mCollection.getAllNotifs().contains(entry2)); // THEN we don't re-query the extenders - verify(mExtender1, never()).shouldExtendLifetime(entry2, REASON_APP_CANCEL); - verify(mExtender2, never()).shouldExtendLifetime(entry2, REASON_APP_CANCEL); - verify(mExtender3, never()).shouldExtendLifetime(entry2, REASON_APP_CANCEL); + verify(mExtender1, never()).maybeExtendLifetime(entry2, REASON_APP_CANCEL); + verify(mExtender2, never()).maybeExtendLifetime(entry2, REASON_APP_CANCEL); + verify(mExtender3, never()).maybeExtendLifetime(entry2, REASON_APP_CANCEL); // THEN the entry properly records all extenders that returned true assertEquals(singletonList(mExtender1), entry2.mLifetimeExtenders); @@ -1469,6 +1503,18 @@ public class NotifCollectionTest extends SysuiTestCase { } @Test + public void testCannotDismissNoClearNotifications() { + // GIVEN an no-clear notification + final NotificationEntry container = new NotificationEntryBuilder() + .setFlag(mContext, FLAG_NO_CLEAR, true) + .build(); + + // THEN its children are not dismissible + assertFalse(mCollection.shouldAutoDismissChildren( + container, container.getSbn().getGroupKey())); + } + + @Test public void testCanDismissFgsNotificationChildren() { // GIVEN an FGS but not ongoing notification final NotificationEntry container = new NotificationEntryBuilder() @@ -1585,7 +1631,7 @@ public class NotifCollectionTest extends SysuiTestCase { } @Override - public boolean shouldExtendLifetime( + public boolean maybeExtendLifetime( @NonNull NotificationEntry entry, @CancellationReason int reason) { return shouldExtendLifetime; 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/GutsCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt index 0f6bd771d352..4143647592e4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt @@ -73,43 +73,43 @@ class GutsCoordinatorTest : SysuiTestCase() { @Test fun testSimpleLifetimeExtension() { - assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isFalse() + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isFalse() notifGutsViewListener.onGutsOpen(entry1, mock(NotificationGuts::class.java)) - assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isTrue() + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isTrue() notifGutsViewListener.onGutsClose(entry1) verify(lifetimeExtenderCallback).onEndLifetimeExtension(notifLifetimeExtender, entry1) - assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isFalse() + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isFalse() } @Test fun testDoubleOpenLifetimeExtension() { - assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isFalse() + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isFalse() notifGutsViewListener.onGutsOpen(entry1, mock(NotificationGuts::class.java)) - assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isTrue() + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isTrue() notifGutsViewListener.onGutsOpen(entry1, mock(NotificationGuts::class.java)) - assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isTrue() + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isTrue() notifGutsViewListener.onGutsClose(entry1) verify(lifetimeExtenderCallback).onEndLifetimeExtension(notifLifetimeExtender, entry1) - assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isFalse() + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isFalse() } @Test fun testTwoEntryLifetimeExtension() { - assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isFalse() - assertThat(notifLifetimeExtender.shouldExtendLifetime(entry2, 0)).isFalse() + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isFalse() + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry2, 0)).isFalse() notifGutsViewListener.onGutsOpen(entry1, mock(NotificationGuts::class.java)) - assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isTrue() - assertThat(notifLifetimeExtender.shouldExtendLifetime(entry2, 0)).isFalse() + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isTrue() + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry2, 0)).isFalse() notifGutsViewListener.onGutsOpen(entry2, mock(NotificationGuts::class.java)) - assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isTrue() - assertThat(notifLifetimeExtender.shouldExtendLifetime(entry2, 0)).isTrue() + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isTrue() + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry2, 0)).isTrue() notifGutsViewListener.onGutsClose(entry1) verify(lifetimeExtenderCallback).onEndLifetimeExtension(notifLifetimeExtender, entry1) - assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isFalse() - assertThat(notifLifetimeExtender.shouldExtendLifetime(entry2, 0)).isTrue() + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isFalse() + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry2, 0)).isTrue() notifGutsViewListener.onGutsClose(entry2) verify(lifetimeExtenderCallback).onEndLifetimeExtension(notifLifetimeExtender, entry2) - assertThat(notifLifetimeExtender.shouldExtendLifetime(entry1, 0)).isFalse() - assertThat(notifLifetimeExtender.shouldExtendLifetime(entry2, 0)).isFalse() + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry1, 0)).isFalse() + assertThat(notifLifetimeExtender.maybeExtendLifetime(entry2, 0)).isFalse() } } 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 b3ee5f8373e4..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.shouldExtendLifetime(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.shouldExtendLifetime(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.shouldExtendLifetime(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.shouldExtendLifetime(mEntry, /* cancellationReason */ 0)); - assertFalse(mNotifLifetimeExtender.shouldExtendLifetime( - 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/coordinator/RemoteInputCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt index 0ce6ada51f23..7073cc7c5707 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt @@ -101,27 +101,27 @@ class RemoteInputCoordinatorTest : SysuiTestCase() { @Test fun testRemoteInputActive() { `when`(remoteInputManager.isRemoteInputActive(entry1)).thenReturn(true) - assertThat(remoteInputActiveExtender.shouldExtendLifetime(entry1, 0)).isTrue() - assertThat(remoteInputHistoryExtender.shouldExtendLifetime(entry1, 0)).isFalse() - assertThat(smartReplyHistoryExtender.shouldExtendLifetime(entry1, 0)).isFalse() + assertThat(remoteInputActiveExtender.maybeExtendLifetime(entry1, 0)).isTrue() + assertThat(remoteInputHistoryExtender.maybeExtendLifetime(entry1, 0)).isFalse() + assertThat(smartReplyHistoryExtender.maybeExtendLifetime(entry1, 0)).isFalse() assertThat(listener.isNotificationKeptForRemoteInputHistory(entry1.key)).isFalse() } @Test fun testRemoteInputHistory() { `when`(remoteInputManager.shouldKeepForRemoteInputHistory(entry1)).thenReturn(true) - assertThat(remoteInputActiveExtender.shouldExtendLifetime(entry1, 0)).isFalse() - assertThat(remoteInputHistoryExtender.shouldExtendLifetime(entry1, 0)).isTrue() - assertThat(smartReplyHistoryExtender.shouldExtendLifetime(entry1, 0)).isFalse() + assertThat(remoteInputActiveExtender.maybeExtendLifetime(entry1, 0)).isFalse() + assertThat(remoteInputHistoryExtender.maybeExtendLifetime(entry1, 0)).isTrue() + assertThat(smartReplyHistoryExtender.maybeExtendLifetime(entry1, 0)).isFalse() assertThat(listener.isNotificationKeptForRemoteInputHistory(entry1.key)).isTrue() } @Test fun testSmartReplyHistory() { `when`(remoteInputManager.shouldKeepForSmartReplyHistory(entry1)).thenReturn(true) - assertThat(remoteInputActiveExtender.shouldExtendLifetime(entry1, 0)).isFalse() - assertThat(remoteInputHistoryExtender.shouldExtendLifetime(entry1, 0)).isFalse() - assertThat(smartReplyHistoryExtender.shouldExtendLifetime(entry1, 0)).isTrue() + assertThat(remoteInputActiveExtender.maybeExtendLifetime(entry1, 0)).isFalse() + assertThat(remoteInputHistoryExtender.maybeExtendLifetime(entry1, 0)).isFalse() + assertThat(smartReplyHistoryExtender.maybeExtendLifetime(entry1, 0)).isTrue() assertThat(listener.isNotificationKeptForRemoteInputHistory(entry1.key)).isTrue() } @@ -136,7 +136,7 @@ class RemoteInputCoordinatorTest : SysuiTestCase() { verify(lifetimeExtensionCallback, never()).onEndLifetimeExtension(any(), any()) // Start extending lifetime & validate that the extension is ended - assertThat(remoteInputActiveExtender.shouldExtendLifetime(entry1, 0)).isTrue() + assertThat(remoteInputActiveExtender.maybeExtendLifetime(entry1, 0)).isTrue() assertThat(remoteInputActiveExtender.isExtending(entry1.key)).isTrue() listener.onPanelCollapsed() verify(lifetimeExtensionCallback).onEndLifetimeExtension(remoteInputActiveExtender, entry1) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/legacy/GroupEventDispatcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/legacy/GroupEventDispatcherTest.kt new file mode 100644 index 000000000000..c17fe6f8b7e2 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/legacy/GroupEventDispatcherTest.kt @@ -0,0 +1,294 @@ +/* + * 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.legacy + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy.GroupEventDispatcher +import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy.NotificationGroup +import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy.OnGroupChangeListener +import com.android.systemui.statusbar.phone.NotificationGroupTestHelper +import com.android.systemui.util.mockito.mock +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyNoMoreInteractions + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper +class GroupEventDispatcherTest : SysuiTestCase() { + val groupMap = mutableMapOf<String, NotificationGroup>() + val groupTestHelper = NotificationGroupTestHelper(mContext) + + private val dispatcher = GroupEventDispatcher(groupMap::get) + private val listener: OnGroupChangeListener = mock() + + @Before + fun setup() { + dispatcher.registerGroupChangeListener(listener) + } + + @Test + fun testOnGroupsChangedUnbuffered() { + dispatcher.notifyGroupsChanged() + verify(listener).onGroupsChanged() + verifyNoMoreInteractions(listener) + } + + @Test + fun testOnGroupsChangedBuffered() { + dispatcher.openBufferScope() + dispatcher.notifyGroupsChanged() + verifyNoMoreInteractions(listener) + dispatcher.closeBufferScope() + verify(listener).onGroupsChanged() + verifyNoMoreInteractions(listener) + } + + @Test + fun testOnGroupsChangedDoubleBuffered() { + dispatcher.openBufferScope() + dispatcher.notifyGroupsChanged() + dispatcher.openBufferScope() // open a nested buffer scope + dispatcher.notifyGroupsChanged() + dispatcher.closeBufferScope() // should NOT flush events + dispatcher.notifyGroupsChanged() + verifyNoMoreInteractions(listener) + dispatcher.closeBufferScope() // this SHOULD flush events + verify(listener).onGroupsChanged() + verifyNoMoreInteractions(listener) + } + + @Test + fun testOnGroupsChangedBufferCoalesces() { + dispatcher.openBufferScope() + dispatcher.notifyGroupsChanged() + dispatcher.notifyGroupsChanged() + verifyNoMoreInteractions(listener) + dispatcher.closeBufferScope() + verify(listener).onGroupsChanged() + verifyNoMoreInteractions(listener) + } + + @Test + fun testOnGroupCreatedIsNeverBuffered() { + val group = addGroup(1) + + dispatcher.openBufferScope() + dispatcher.notifyGroupCreated(group) + verify(listener).onGroupCreated(group, group.groupKey) + verifyNoMoreInteractions(listener) + + dispatcher.closeBufferScope() + verifyNoMoreInteractions(listener) + } + + @Test + fun testOnGroupRemovedIsNeverBuffered() { + val group = addGroup(1) + + dispatcher.openBufferScope() + dispatcher.notifyGroupRemoved(group) + verify(listener).onGroupRemoved(group, group.groupKey) + verifyNoMoreInteractions(listener) + + dispatcher.closeBufferScope() + verifyNoMoreInteractions(listener) + } + + @Test + fun testAlertOverrideAddedUnbuffered() { + val group = addGroup(1) + val newAlertEntry = groupTestHelper.createChildNotification() + group.alertOverride = newAlertEntry + dispatcher.notifyAlertOverrideChanged(group, null) + verify(listener).onGroupAlertOverrideChanged(group, null, newAlertEntry) + verifyNoMoreInteractions(listener) + } + + @Test + fun testAlertOverrideRemovedUnbuffered() { + val group = addGroup(1) + val oldAlertEntry = groupTestHelper.createChildNotification() + dispatcher.notifyAlertOverrideChanged(group, oldAlertEntry) + verify(listener).onGroupAlertOverrideChanged(group, oldAlertEntry, null) + verifyNoMoreInteractions(listener) + } + + @Test + fun testAlertOverrideChangedUnbuffered() { + val group = addGroup(1) + val oldAlertEntry = groupTestHelper.createChildNotification() + val newAlertEntry = groupTestHelper.createChildNotification() + group.alertOverride = newAlertEntry + dispatcher.notifyAlertOverrideChanged(group, oldAlertEntry) + verify(listener).onGroupAlertOverrideChanged(group, oldAlertEntry, newAlertEntry) + verifyNoMoreInteractions(listener) + } + + @Test + fun testAlertOverrideChangedBuffered() { + dispatcher.openBufferScope() + val group = addGroup(1) + val oldAlertEntry = groupTestHelper.createChildNotification() + val newAlertEntry = groupTestHelper.createChildNotification() + group.alertOverride = newAlertEntry + dispatcher.notifyAlertOverrideChanged(group, oldAlertEntry) + verifyNoMoreInteractions(listener) + dispatcher.closeBufferScope() + verify(listener).onGroupAlertOverrideChanged(group, oldAlertEntry, newAlertEntry) + verifyNoMoreInteractions(listener) + } + + @Test + fun testAlertOverrideIgnoredIfRemoved() { + dispatcher.openBufferScope() + val group = addGroup(1) + val oldAlertEntry = groupTestHelper.createChildNotification() + val newAlertEntry = groupTestHelper.createChildNotification() + group.alertOverride = newAlertEntry + dispatcher.notifyAlertOverrideChanged(group, oldAlertEntry) + verifyNoMoreInteractions(listener) + groupMap.clear() + dispatcher.closeBufferScope() + verifyNoMoreInteractions(listener) + } + + @Test + fun testAlertOverrideMultipleChangesBuffered() { + dispatcher.openBufferScope() + val group = addGroup(1) + val oldAlertEntry = groupTestHelper.createChildNotification() + val newAlertEntry = groupTestHelper.createChildNotification() + group.alertOverride = null + dispatcher.notifyAlertOverrideChanged(group, oldAlertEntry) + group.alertOverride = newAlertEntry + dispatcher.notifyAlertOverrideChanged(group, null) + verifyNoMoreInteractions(listener) + dispatcher.closeBufferScope() + verify(listener).onGroupAlertOverrideChanged(group, oldAlertEntry, newAlertEntry) + verifyNoMoreInteractions(listener) + } + + @Test + fun testAlertOverrideTemporaryValueSwallowed() { + dispatcher.openBufferScope() + val group = addGroup(1) + val stableAlertEntry = groupTestHelper.createChildNotification() + group.alertOverride = null + dispatcher.notifyAlertOverrideChanged(group, stableAlertEntry) + group.alertOverride = stableAlertEntry + dispatcher.notifyAlertOverrideChanged(group, null) + verifyNoMoreInteractions(listener) + dispatcher.closeBufferScope() + verifyNoMoreInteractions(listener) + } + + @Test + fun testAlertOverrideTemporaryNullSwallowed() { + dispatcher.openBufferScope() + val group = addGroup(1) + val temporaryAlertEntry = groupTestHelper.createChildNotification() + group.alertOverride = temporaryAlertEntry + dispatcher.notifyAlertOverrideChanged(group, null) + group.alertOverride = null + dispatcher.notifyAlertOverrideChanged(group, temporaryAlertEntry) + verifyNoMoreInteractions(listener) + dispatcher.closeBufferScope() + verifyNoMoreInteractions(listener) + } + + @Test + fun testSuppressOnUnbuffered() { + val group = addGroup(1) + group.suppressed = true + dispatcher.notifySuppressedChanged(group) + verify(listener).onGroupSuppressionChanged(group, true) + verifyNoMoreInteractions(listener) + } + + @Test + fun testSuppressOffUnbuffered() { + val group = addGroup(1) + group.suppressed = false + dispatcher.notifySuppressedChanged(group) + verify(listener).onGroupSuppressionChanged(group, false) + verifyNoMoreInteractions(listener) + } + + @Test + fun testSuppressOnBuffered() { + dispatcher.openBufferScope() + val group = addGroup(1) + group.suppressed = false + dispatcher.notifySuppressedChanged(group) + verifyNoMoreInteractions(listener) + dispatcher.closeBufferScope() + verify(listener).onGroupSuppressionChanged(group, false) + verifyNoMoreInteractions(listener) + } + + @Test + fun testSuppressOnIgnoredIfRemoved() { + dispatcher.openBufferScope() + val group = addGroup(1) + group.suppressed = false + dispatcher.notifySuppressedChanged(group) + verifyNoMoreInteractions(listener) + groupMap.clear() + dispatcher.closeBufferScope() + verifyNoMoreInteractions(listener) + } + + @Test + fun testSuppressOnOffBuffered() { + dispatcher.openBufferScope() + val group = addGroup(1) + group.suppressed = true + dispatcher.notifySuppressedChanged(group) + group.suppressed = false + dispatcher.notifySuppressedChanged(group) + verifyNoMoreInteractions(listener) + dispatcher.closeBufferScope() + verifyNoMoreInteractions(listener) + } + + @Test + fun testSuppressOnOffOnBuffered() { + dispatcher.openBufferScope() + val group = addGroup(1) + group.suppressed = true + dispatcher.notifySuppressedChanged(group) + group.suppressed = false + dispatcher.notifySuppressedChanged(group) + group.suppressed = true + dispatcher.notifySuppressedChanged(group) + verifyNoMoreInteractions(listener) + dispatcher.closeBufferScope() + verify(listener).onGroupSuppressionChanged(group, true) + verifyNoMoreInteractions(listener) + } + + private fun addGroup(id: Int): NotificationGroup { + val group = NotificationGroup("group:$id") + groupMap[group.groupKey] = group + return group + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtenderTest.kt index 37ad8357aa95..a09f3a35c308 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtenderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtenderTest.kt @@ -77,7 +77,7 @@ class SelfTrackingLifetimeExtenderTest : SysuiTestCase() { @Test fun testNoExtend() { `when`(shouldExtend.test(entry1)).thenReturn(false) - assertThat(extender.shouldExtendLifetime(entry1, 0)).isFalse() + assertThat(extender.maybeExtendLifetime(entry1, 0)).isFalse() assertThat(extender.isExtending(entry1.key)).isFalse() verify(onStarted, never()).accept(entry1) verify(onCanceled, never()).accept(entry1) @@ -86,7 +86,7 @@ class SelfTrackingLifetimeExtenderTest : SysuiTestCase() { @Test fun testExtendThenCancelForRepost() { `when`(shouldExtend.test(entry1)).thenReturn(true) - assertThat(extender.shouldExtendLifetime(entry1, 0)).isTrue() + assertThat(extender.maybeExtendLifetime(entry1, 0)).isTrue() verify(onStarted).accept(entry1) verify(onCanceled, never()).accept(entry1) assertThat(extender.isExtending(entry1.key)).isTrue() @@ -108,7 +108,7 @@ class SelfTrackingLifetimeExtenderTest : SysuiTestCase() { @Test fun testExtendThenEnd() { `when`(shouldExtend.test(entry1)).thenReturn(true) - assertThat(extender.shouldExtendLifetime(entry1, 0)).isTrue() + assertThat(extender.maybeExtendLifetime(entry1, 0)).isTrue() verify(onStarted).accept(entry1) assertThat(extender.isExtending(entry1.key)).isTrue() extender.endLifetimeExtension(entry1.key) @@ -119,7 +119,7 @@ class SelfTrackingLifetimeExtenderTest : SysuiTestCase() { @Test fun testExtendThenEndAfterDelay() { `when`(shouldExtend.test(entry1)).thenReturn(true) - assertThat(extender.shouldExtendLifetime(entry1, 0)).isTrue() + assertThat(extender.maybeExtendLifetime(entry1, 0)).isTrue() verify(onStarted).accept(entry1) assertThat(extender.isExtending(entry1.key)).isTrue() @@ -142,11 +142,11 @@ class SelfTrackingLifetimeExtenderTest : SysuiTestCase() { fun testExtendThenEndAll() { `when`(shouldExtend.test(entry1)).thenReturn(true) `when`(shouldExtend.test(entry2)).thenReturn(true) - assertThat(extender.shouldExtendLifetime(entry1, 0)).isTrue() + assertThat(extender.maybeExtendLifetime(entry1, 0)).isTrue() verify(onStarted).accept(entry1) assertThat(extender.isExtending(entry1.key)).isTrue() assertThat(extender.isExtending(entry2.key)).isFalse() - assertThat(extender.shouldExtendLifetime(entry2, 0)).isTrue() + assertThat(extender.maybeExtendLifetime(entry2, 0)).isTrue() verify(onStarted).accept(entry2) assertThat(extender.isExtending(entry1.key)).isTrue() assertThat(extender.isExtending(entry2.key)).isTrue() @@ -160,11 +160,11 @@ class SelfTrackingLifetimeExtenderTest : SysuiTestCase() { @Test fun testExtendWithinEndCanReExtend() { `when`(shouldExtend.test(entry1)).thenReturn(true) - assertThat(extender.shouldExtendLifetime(entry1, 0)).isTrue() + assertThat(extender.maybeExtendLifetime(entry1, 0)).isTrue() verify(onStarted, times(1)).accept(entry1) `when`(callback.onEndLifetimeExtension(extender, entry1)).thenAnswer { - assertThat(extender.shouldExtendLifetime(entry1, 0)).isTrue() + assertThat(extender.maybeExtendLifetime(entry1, 0)).isTrue() } extender.endLifetimeExtension(entry1.key) verify(onStarted, times(2)).accept(entry1) @@ -174,11 +174,11 @@ class SelfTrackingLifetimeExtenderTest : SysuiTestCase() { @Test fun testExtendWithinEndCanNotReExtend() { `when`(shouldExtend.test(entry1)).thenReturn(true, false) - assertThat(extender.shouldExtendLifetime(entry1, 0)).isTrue() + assertThat(extender.maybeExtendLifetime(entry1, 0)).isTrue() verify(onStarted, times(1)).accept(entry1) `when`(callback.onEndLifetimeExtension(extender, entry1)).thenAnswer { - assertThat(extender.shouldExtendLifetime(entry1, 0)).isFalse() + assertThat(extender.maybeExtendLifetime(entry1, 0)).isFalse() } extender.endLifetimeExtension(entry1.key) verify(onStarted, times(1)).accept(entry1) @@ -188,11 +188,11 @@ class SelfTrackingLifetimeExtenderTest : SysuiTestCase() { @Test fun testExtendWithinEndAllCanReExtend() { `when`(shouldExtend.test(entry1)).thenReturn(true) - assertThat(extender.shouldExtendLifetime(entry1, 0)).isTrue() + assertThat(extender.maybeExtendLifetime(entry1, 0)).isTrue() verify(onStarted, times(1)).accept(entry1) `when`(callback.onEndLifetimeExtension(extender, entry1)).thenAnswer { - assertThat(extender.shouldExtendLifetime(entry1, 0)).isTrue() + assertThat(extender.maybeExtendLifetime(entry1, 0)).isTrue() } extender.endAllLifetimeExtensions() verify(onStarted, times(2)).accept(entry1) @@ -202,11 +202,11 @@ class SelfTrackingLifetimeExtenderTest : SysuiTestCase() { @Test fun testExtendWithinEndAllCanNotReExtend() { `when`(shouldExtend.test(entry1)).thenReturn(true, false) - assertThat(extender.shouldExtendLifetime(entry1, 0)).isTrue() + assertThat(extender.maybeExtendLifetime(entry1, 0)).isTrue() verify(onStarted, times(1)).accept(entry1) `when`(callback.onEndLifetimeExtension(extender, entry1)).thenAnswer { - assertThat(extender.shouldExtendLifetime(entry1, 0)).isFalse() + assertThat(extender.maybeExtendLifetime(entry1, 0)).isFalse() } extender.endAllLifetimeExtensions() verify(onStarted, times(1)).accept(entry1) 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/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java index 37cf7485b8ab..36a4c1e5ebfc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java @@ -354,7 +354,6 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { TestNotificationPanelViewStateProvider() {} private float mPanelViewExpandedHeight = 100f; - private float mQsExpansionFraction = 0f; private boolean mShouldHeadsUpBeVisible = false; private float mLockscreenShadeDragProgress = 0f; @@ -364,11 +363,6 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { } @Override - public float getQsExpansionFraction() { - return mQsExpansionFraction; - } - - @Override public boolean shouldHeadsUpBeVisible() { return mShouldHeadsUpBeVisible; } @@ -382,10 +376,6 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { this.mPanelViewExpandedHeight = panelViewExpandedHeight; } - public void setQsExpansionFraction(float qsExpansionFraction) { - this.mQsExpansionFraction = qsExpansionFraction; - } - public void setShouldHeadsUpBeVisible(boolean shouldHeadsUpBeVisible) { this.mShouldHeadsUpBeVisible = shouldHeadsUpBeVisible; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java index 9898b4b2fdbd..c13b3358a077 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java @@ -312,10 +312,11 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { @Test public void testUpdateChildToSummaryDoesNotTransfer() { + final String tag = "fooTag"; NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification(Notification.GROUP_ALERT_SUMMARY); NotificationEntry childEntry = - mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY, 47); + mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY, 47, tag); mockHasHeadsUpContentView(childEntry, false); mHeadsUpManager.showNotification(summaryEntry); @@ -327,7 +328,7 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { StatusBarNotification oldNotification = childEntry.getSbn(); childEntry.setSbn( mGroupTestHelper.createSummaryNotification( - Notification.GROUP_ALERT_SUMMARY, 47).getSbn()); + Notification.GROUP_ALERT_SUMMARY, 47, tag).getSbn()); mGroupManager.onEntryUpdated(childEntry, oldNotification); assertFalse(mGroupAlertTransferHelper.isAlertTransferPending(childEntry)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java index 6d170b673cc3..5d7b15424fec 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java @@ -21,14 +21,19 @@ import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; import static org.junit.Assert.assertNull; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +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 android.app.Notification; +import android.service.notification.StatusBarNotification; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import android.util.Log; import androidx.test.filters.SmallTest; @@ -64,8 +69,10 @@ public class NotificationGroupManagerLegacyTest extends SysuiTestCase { private final NotificationGroupTestHelper mGroupTestHelper = new NotificationGroupTestHelper(mContext); - @Mock PeopleNotificationIdentifier mPeopleNotificationIdentifier; - @Mock HeadsUpManager mHeadsUpManager; + @Mock + PeopleNotificationIdentifier mPeopleNotificationIdentifier; + @Mock + HeadsUpManager mHeadsUpManager; @Before public void setup() { @@ -177,15 +184,25 @@ public class NotificationGroupManagerLegacyTest extends SysuiTestCase { helpTestAlertOverrideWithSiblings(2); } + @Test + public void testAlertOverrideWithSiblings_3() { + helpTestAlertOverrideWithSiblings(3); + } + + @Test + public void testAlertOverrideWithSiblings_9() { + helpTestAlertOverrideWithSiblings(9); + } + /** * Helper for testing various sibling counts */ private void helpTestAlertOverrideWithSiblings(int numSiblings) { helpTestAlertOverride( /* numSiblings */ numSiblings, - /* summaryAlert */ Notification.GROUP_ALERT_SUMMARY, - /* childAlert */ Notification.GROUP_ALERT_SUMMARY, - /* siblingAlert */ Notification.GROUP_ALERT_SUMMARY, + /* summaryGroupAlert */ Notification.GROUP_ALERT_SUMMARY, + /* priorityGroupAlert */ Notification.GROUP_ALERT_SUMMARY, + /* siblingGroupAlert */ Notification.GROUP_ALERT_SUMMARY, /* expectAlertOverride */ true); } @@ -194,9 +211,9 @@ public class NotificationGroupManagerLegacyTest extends SysuiTestCase { // tests that summary can have GROUP_ALERT_ALL and this still works helpTestAlertOverride( /* numSiblings */ 1, - /* summaryAlert */ Notification.GROUP_ALERT_ALL, - /* childAlert */ Notification.GROUP_ALERT_SUMMARY, - /* siblingAlert */ Notification.GROUP_ALERT_SUMMARY, + /* summaryGroupAlert */ Notification.GROUP_ALERT_ALL, + /* priorityGroupAlert */ Notification.GROUP_ALERT_SUMMARY, + /* siblingGroupAlert */ Notification.GROUP_ALERT_SUMMARY, /* expectAlertOverride */ true); } @@ -205,9 +222,9 @@ public class NotificationGroupManagerLegacyTest extends SysuiTestCase { // Tests that if the summary alerts CHILDREN, there's no alertOverride helpTestAlertOverride( /* numSiblings */ 1, - /* summaryAlert */ Notification.GROUP_ALERT_CHILDREN, - /* childAlert */ Notification.GROUP_ALERT_SUMMARY, - /* siblingAlert */ Notification.GROUP_ALERT_SUMMARY, + /* summaryGroupAlert */ Notification.GROUP_ALERT_CHILDREN, + /* priorityGroupAlert */ Notification.GROUP_ALERT_SUMMARY, + /* siblingGroupAlert */ Notification.GROUP_ALERT_SUMMARY, /* expectAlertOverride */ false); } @@ -216,9 +233,9 @@ public class NotificationGroupManagerLegacyTest extends SysuiTestCase { // Tests that if the children alert ALL, there's no alertOverride helpTestAlertOverride( /* numSiblings */ 1, - /* summaryAlert */ Notification.GROUP_ALERT_SUMMARY, - /* childAlert */ Notification.GROUP_ALERT_ALL, - /* siblingAlert */ Notification.GROUP_ALERT_ALL, + /* summaryGroupAlert */ Notification.GROUP_ALERT_SUMMARY, + /* priorityGroupAlert */ Notification.GROUP_ALERT_ALL, + /* siblingGroupAlert */ Notification.GROUP_ALERT_ALL, /* expectAlertOverride */ false); } @@ -229,17 +246,19 @@ public class NotificationGroupManagerLegacyTest extends SysuiTestCase { * 3) when the priority entry is removed, these are reversed */ private void helpTestAlertOverride(int numSiblings, - @Notification.GroupAlertBehavior int summaryAlert, - @Notification.GroupAlertBehavior int childAlert, - @Notification.GroupAlertBehavior int siblingAlert, + @Notification.GroupAlertBehavior int summaryGroupAlert, + @Notification.GroupAlertBehavior int priorityGroupAlert, + @Notification.GroupAlertBehavior int siblingGroupAlert, boolean expectAlertOverride) { // Create entries in an order so that the priority entry can be deemed the newest child. NotificationEntry[] siblings = new NotificationEntry[numSiblings]; for (int i = 0; i < numSiblings; i++) { - siblings[i] = mGroupTestHelper.createChildNotification(siblingAlert); + siblings[i] = mGroupTestHelper.createChildNotification(siblingGroupAlert, i, "sibling"); } - NotificationEntry priorityEntry = mGroupTestHelper.createChildNotification(childAlert); - NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification(summaryAlert); + NotificationEntry priorityEntry = + mGroupTestHelper.createChildNotification(priorityGroupAlert, 0, "priority"); + NotificationEntry summaryEntry = + mGroupTestHelper.createSummaryNotification(summaryGroupAlert, 0, "summary"); // The priority entry is an important conversation. when(mPeopleNotificationIdentifier.getPeopleNotificationType(eq(priorityEntry))) @@ -263,11 +282,20 @@ public class NotificationGroupManagerLegacyTest extends SysuiTestCase { assertNull(summaryGroup.alertOverride); return; } + int max2Siblings = Math.min(2, numSiblings); // Verify that the summary group has the priority child as its alertOverride NotificationGroup summaryGroup = mGroupManager.getGroupForSummary(summaryEntry.getSbn()); assertEquals(priorityEntry, summaryGroup.alertOverride); verify(groupChangeListener).onGroupAlertOverrideChanged(summaryGroup, null, priorityEntry); + verify(groupChangeListener).onGroupSuppressionChanged(summaryGroup, true); + if (numSiblings > 1) { + verify(groupChangeListener).onGroupSuppressionChanged(summaryGroup, false); + } + verify(groupChangeListener).onGroupCreated(any(), eq(priorityEntry.getKey())); + verify(groupChangeListener).onGroupCreated(any(), eq(summaryEntry.getSbn().getGroupKey())); + verify(groupChangeListener, times(max2Siblings + 1)).onGroupsChanged(); + verifyNoMoreInteractions(groupChangeListener); // Verify that only the priority notification is isolated from the group assertEquals(priorityEntry, mGroupManager.getGroupSummary(priorityEntry)); @@ -283,6 +311,92 @@ public class NotificationGroupManagerLegacyTest extends SysuiTestCase { // verify that the alertOverride is removed when the priority notification is assertNull(summaryGroup.alertOverride); + verify(groupChangeListener).onGroupAlertOverrideChanged(summaryGroup, priorityEntry, null); + verify(groupChangeListener).onGroupRemoved(any(), eq(priorityEntry.getKey())); + verify(groupChangeListener, times(max2Siblings + 2)).onGroupsChanged(); + if (numSiblings == 0) { + verify(groupChangeListener).onGroupSuppressionChanged(summaryGroup, false); + } + verifyNoMoreInteractions(groupChangeListener); + } + + @Test + public void testAlertOverrideWhenUpdatingSummaryAtEnd() { + int numSiblings = 2; + int groupAlert = Notification.GROUP_ALERT_SUMMARY; + // Create entries in an order so that the priority entry can be deemed the newest child. + NotificationEntry[] siblings = new NotificationEntry[numSiblings]; + for (int i = 0; i < numSiblings; i++) { + siblings[i] = mGroupTestHelper.createChildNotification(groupAlert, i, "sibling"); + } + NotificationEntry priorityEntry = + mGroupTestHelper.createChildNotification(groupAlert, 0, "priority"); + NotificationEntry summaryEntry = + mGroupTestHelper.createSummaryNotification(groupAlert, 0, "summary"); + + // The priority entry is an important conversation. + when(mPeopleNotificationIdentifier.getPeopleNotificationType(eq(priorityEntry))) + .thenReturn(PeopleNotificationIdentifier.TYPE_IMPORTANT_PERSON); + + // Register a listener so we can verify that the event is sent. + OnGroupChangeListener groupChangeListener = mock(OnGroupChangeListener.class); + mGroupManager.registerGroupChangeListener(groupChangeListener); + + // Add all the entries. The order here shouldn't matter. + mGroupManager.onEntryAdded(summaryEntry); + for (int i = 0; i < numSiblings; i++) { + mGroupManager.onEntryAdded(siblings[i]); + } + mGroupManager.onEntryAdded(priorityEntry); + + int max2Siblings = Math.min(2, numSiblings); + + // Verify that the summary group has the priority child as its alertOverride + NotificationGroup summaryGroup = mGroupManager.getGroupForSummary(summaryEntry.getSbn()); + assertEquals(priorityEntry, summaryGroup.alertOverride); verify(groupChangeListener).onGroupAlertOverrideChanged(summaryGroup, null, priorityEntry); + verify(groupChangeListener).onGroupSuppressionChanged(summaryGroup, true); + if (numSiblings > 1) { + verify(groupChangeListener).onGroupSuppressionChanged(summaryGroup, false); + } + verify(groupChangeListener).onGroupCreated(any(), eq(priorityEntry.getKey())); + verify(groupChangeListener).onGroupCreated(any(), eq(summaryEntry.getSbn().getGroupKey())); + verify(groupChangeListener, times(max2Siblings + 1)).onGroupsChanged(); + verifyNoMoreInteractions(groupChangeListener); + + // Verify that only the priority notification is isolated from the group + assertEquals(priorityEntry, mGroupManager.getGroupSummary(priorityEntry)); + assertEquals(summaryEntry, mGroupManager.getLogicalGroupSummary(priorityEntry)); + // Verify that the siblings are NOT isolated from the group + for (int i = 0; i < numSiblings; i++) { + assertEquals(summaryEntry, mGroupManager.getGroupSummary(siblings[i])); + assertEquals(summaryEntry, mGroupManager.getLogicalGroupSummary(siblings[i])); + } + + Log.d("NotificationGroupManagerLegacyTest", + "testAlertOverrideWhenUpdatingSummaryAtEnd: About to update summary"); + + StatusBarNotification oldSummarySbn = mGroupTestHelper.incrementPost(summaryEntry, 10000); + mGroupManager.onEntryUpdated(summaryEntry, oldSummarySbn); + + verify(groupChangeListener, times(max2Siblings + 2)).onGroupsChanged(); + verify(groupChangeListener).onGroupAlertOverrideChanged(summaryGroup, priorityEntry, null); + verifyNoMoreInteractions(groupChangeListener); + + Log.d("NotificationGroupManagerLegacyTest", + "testAlertOverrideWhenUpdatingSummaryAtEnd: About to update priority child"); + + StatusBarNotification oldPrioritySbn = mGroupTestHelper.incrementPost(priorityEntry, 10000); + mGroupManager.onEntryUpdated(priorityEntry, oldPrioritySbn); + + verify(groupChangeListener).onGroupRemoved(any(), eq(priorityEntry.getKey())); + verify(groupChangeListener, times(2)).onGroupCreated(any(), eq(priorityEntry.getKey())); + verify(groupChangeListener, times(2)) + .onGroupAlertOverrideChanged(summaryGroup, null, priorityEntry); + verify(groupChangeListener, times(max2Siblings + 3)).onGroupsChanged(); + verifyNoMoreInteractions(groupChangeListener); + + Log.d("NotificationGroupManagerLegacyTest", + "testAlertOverrideWhenUpdatingSummaryAtEnd: Done"); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java index d405fea78170..ac32b9d6f01a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.phone; +import static com.google.common.truth.Truth.assertThat; + import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -23,8 +25,10 @@ import android.app.ActivityManager; import android.app.Notification; import android.content.Context; import android.os.UserHandle; +import android.service.notification.StatusBarNotification; import com.android.systemui.R; +import com.android.systemui.statusbar.SbnBuilder; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; @@ -44,15 +48,15 @@ public final class NotificationGroupTestHelper { } public NotificationEntry createSummaryNotification() { - return createSummaryNotification(Notification.GROUP_ALERT_ALL, mId++); + return createSummaryNotification(Notification.GROUP_ALERT_ALL, mId++, null); } public NotificationEntry createSummaryNotification(int groupAlertBehavior) { - return createSummaryNotification(groupAlertBehavior, mId++); + return createSummaryNotification(groupAlertBehavior, mId++, null); } - public NotificationEntry createSummaryNotification(int groupAlertBehavior, int id) { - return createEntry(id, true, groupAlertBehavior); + public NotificationEntry createSummaryNotification(int groupAlertBehavior, int id, String tag) { + return createEntry(id, tag, true, groupAlertBehavior); } public NotificationEntry createChildNotification() { @@ -60,14 +64,15 @@ public final class NotificationGroupTestHelper { } public NotificationEntry createChildNotification(int groupAlertBehavior) { - return createEntry(mId++, false, groupAlertBehavior); + return createEntry(mId++, null, false, groupAlertBehavior); } - public NotificationEntry createChildNotification(int groupAlertBehavior, int id) { - return createEntry(id, false, groupAlertBehavior); + public NotificationEntry createChildNotification(int groupAlertBehavior, int id, String tag) { + return createEntry(id, tag, false, groupAlertBehavior); } - public NotificationEntry createEntry(int id, boolean isSummary, int groupAlertBehavior) { + public NotificationEntry createEntry(int id, String tag, boolean isSummary, + int groupAlertBehavior) { Notification notif = new Notification.Builder(mContext, TEST_CHANNEL_ID) .setContentTitle("Title") .setSmallIcon(R.drawable.ic_person) @@ -80,6 +85,7 @@ public final class NotificationGroupTestHelper { .setOpPkg(TEST_PACKAGE_NAME) .setId(id) .setNotification(notif) + .setTag(tag) .setUser(new UserHandle(ActivityManager.getCurrentUser())) .build(); @@ -88,4 +94,16 @@ public final class NotificationGroupTestHelper { when(row.getEntry()).thenReturn(entry); return entry; } + + public StatusBarNotification incrementPost(NotificationEntry entry, int increment) { + StatusBarNotification oldSbn = entry.getSbn(); + final long oldPostTime = oldSbn.getPostTime(); + final long newPostTime = oldPostTime + increment; + entry.setSbn(new SbnBuilder(oldSbn) + .setPostTime(newPostTime) + .build()); + assertThat(oldSbn.getPostTime()).isEqualTo(oldPostTime); + assertThat(entry.getSbn().getPostTime()).isEqualTo(newPostTime); + return oldSbn; + } } 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/policy/DeviceStateRotationLockSettingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java index db7b2f20fa4c..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 @@ -16,6 +16,10 @@ package com.android.systemui.statusbar.policy; +import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_IGNORED; +import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_LOCKED; +import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_UNLOCKED; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -25,14 +29,15 @@ import android.hardware.devicestate.DeviceStateManager; import android.os.UserHandle; import android.provider.Settings; import android.testing.AndroidTestingRunner; +import android.testing.TestableContentResolver; import android.testing.TestableResources; import androidx.test.filters.SmallTest; +import com.android.internal.R; import com.android.internal.view.RotationPolicy; import com.android.systemui.SysuiTestCase; import com.android.systemui.util.concurrency.FakeExecutor; -import com.android.systemui.util.settings.FakeSettings; import com.android.systemui.util.time.FakeSystemClock; import com.android.systemui.util.wrapper.RotationPolicyWrapper; @@ -47,63 +52,56 @@ 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 FakeSettings mFakeSettings = new FakeSettings(); 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; @Before public void setUp() { MockitoAnnotations.initMocks(/* testClass= */ this); TestableResources resources = mContext.getOrCreateTestableResources(); + resources.addOverride(R.array.config_perDeviceStateRotationLockDefaults, DEFAULT_SETTINGS); ArgumentCaptor<DeviceStateManager.DeviceStateCallback> deviceStateCallbackArgumentCaptor = - ArgumentCaptor.forClass( - DeviceStateManager.DeviceStateCallback.class); - - mDeviceStateRotationLockSettingController = new DeviceStateRotationLockSettingController( - mFakeSettings, - mFakeRotationPolicy, - mDeviceStateManager, - mFakeExecutor, - DEFAULT_SETTINGS - ); + ArgumentCaptor.forClass(DeviceStateManager.DeviceStateCallback.class); + + mContentResolver = mContext.getContentResolver(); + mSettingsManager = DeviceStateRotationLockSettingsManager.getInstance(mContext); + mDeviceStateRotationLockSettingController = + new DeviceStateRotationLockSettingController( + mFakeRotationPolicy, mDeviceStateManager, mFakeExecutor, mSettingsManager); mDeviceStateRotationLockSettingController.setListening(true); - verify(mDeviceStateManager).registerCallback(any(), - deviceStateCallbackArgumentCaptor.capture()); + verify(mDeviceStateManager) + .registerCallback(any(), deviceStateCallbackArgumentCaptor.capture()); mDeviceStateCallback = deviceStateCallbackArgumentCaptor.getValue(); } @Test public void whenSavedSettingsEmpty_defaultsLoadedAndSaved() { - mFakeSettings.putStringForUser(Settings.Secure.DEVICE_STATE_ROTATION_LOCK, "", - UserHandle.USER_CURRENT); - - mDeviceStateRotationLockSettingController.initialize(); - - assertThat(mFakeSettings - .getStringForUser(Settings.Secure.DEVICE_STATE_ROTATION_LOCK, - UserHandle.USER_CURRENT)) - .isEqualTo("0:0:1:2"); + initializeSettingsWith(); + + assertThat( + Settings.Secure.getStringForUser( + mContentResolver, + Settings.Secure.DEVICE_STATE_ROTATION_LOCK, + UserHandle.USER_CURRENT)) + .isEqualTo("0:1:1:2:2:0"); } @Test public void whenNoSavedValueForDeviceState_assumeIgnored() { - mFakeSettings.putStringForUser( - Settings.Secure.DEVICE_STATE_ROTATION_LOCK, - /* value= */"0:2:1:2", - UserHandle.USER_CURRENT); + initializeSettingsWith( + 0, DEVICE_STATE_ROTATION_LOCK_UNLOCKED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED); mFakeRotationPolicy.setRotationLock(true); - mDeviceStateRotationLockSettingController.initialize(); mDeviceStateCallback.onStateChanged(1); assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse(); @@ -116,52 +114,68 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase @Test public void whenDeviceStateSwitched_loadCorrectSetting() { - mFakeSettings.putStringForUser( - Settings.Secure.DEVICE_STATE_ROTATION_LOCK, - /* value= */"0:2:1:1", - UserHandle.USER_CURRENT); + initializeSettingsWith( + 0, DEVICE_STATE_ROTATION_LOCK_UNLOCKED, 1, DEVICE_STATE_ROTATION_LOCK_LOCKED); mFakeRotationPolicy.setRotationLock(true); - mDeviceStateRotationLockSettingController.initialize(); mDeviceStateCallback.onStateChanged(0); assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse(); mDeviceStateCallback.onStateChanged(1); assertThat(mFakeRotationPolicy.isRotationLocked()).isTrue(); + } + + @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() { - mFakeSettings.putStringForUser( - Settings.Secure.DEVICE_STATE_ROTATION_LOCK, - /* value= */"0:1:1:2", - UserHandle.USER_CURRENT); + initializeSettingsWith( + 0, DEVICE_STATE_ROTATION_LOCK_LOCKED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED); + mSettingsManager.onPersistedSettingsChanged(); mFakeRotationPolicy.setRotationLock(true); - mDeviceStateRotationLockSettingController.initialize(); mDeviceStateCallback.onStateChanged(0); assertThat(mFakeRotationPolicy.isRotationLocked()).isTrue(); - mDeviceStateRotationLockSettingController - .onRotationLockStateChanged(/* rotationLocked= */false, - /* affordanceVisible= */ true); + mDeviceStateRotationLockSettingController.onRotationLockStateChanged( + /* rotationLocked= */ false, /* affordanceVisible= */ true); - assertThat(mFakeSettings - .getStringForUser(Settings.Secure.DEVICE_STATE_ROTATION_LOCK, - UserHandle.USER_CURRENT)) + assertThat( + Settings.Secure.getStringForUser( + mContentResolver, + Settings.Secure.DEVICE_STATE_ROTATION_LOCK, + UserHandle.USER_CURRENT)) .isEqualTo("0:2:1:2"); } - @Test public void whenDeviceStateSwitchedToIgnoredState_usePreviousSetting() { - mFakeSettings.putStringForUser( - Settings.Secure.DEVICE_STATE_ROTATION_LOCK, - /* value= */"0:0:1:2", - UserHandle.USER_CURRENT); + initializeSettingsWith( + 0, DEVICE_STATE_ROTATION_LOCK_IGNORED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED); mFakeRotationPolicy.setRotationLock(true); - mDeviceStateRotationLockSettingController.initialize(); mDeviceStateCallback.onStateChanged(1); assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse(); @@ -171,28 +185,127 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase } @Test - public void whenDeviceStateSwitchedToIgnoredState_newSettingsSaveForPreviousState() { - mFakeSettings.putStringForUser( - Settings.Secure.DEVICE_STATE_ROTATION_LOCK, - /* value= */"0:0:1:2", - UserHandle.USER_CURRENT); + public void whenDeviceStateSwitchedToIgnoredState_noFallback_newSettingsSaveForPreviousState() { + initializeSettingsWith( + 8, DEVICE_STATE_ROTATION_LOCK_IGNORED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED); mFakeRotationPolicy.setRotationLock(true); - mDeviceStateRotationLockSettingController.initialize(); mDeviceStateCallback.onStateChanged(1); assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse(); + mDeviceStateCallback.onStateChanged(8); + assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse(); + + mDeviceStateRotationLockSettingController.onRotationLockStateChanged( + /* rotationLocked= */ true, /* affordanceVisible= */ true); + + assertThat( + Settings.Secure.getStringForUser( + mContentResolver, + Settings.Secure.DEVICE_STATE_ROTATION_LOCK, + UserHandle.USER_CURRENT)) + .isEqualTo("1:1:8:0"); + } + + @Test + public void whenSettingsChangedExternally_updateRotationPolicy() throws InterruptedException { + initializeSettingsWith( + 0, DEVICE_STATE_ROTATION_LOCK_UNLOCKED, + 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED); + mFakeRotationPolicy.setRotationLock(false); mDeviceStateCallback.onStateChanged(0); + assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse(); - mDeviceStateRotationLockSettingController - .onRotationLockStateChanged(/* rotationLocked= */true, - /* affordanceVisible= */ true); + // Changing device state 0 to LOCKED + initializeSettingsWith( + 0, DEVICE_STATE_ROTATION_LOCK_LOCKED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED); + + 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(mFakeSettings - .getStringForUser(Settings.Secure.DEVICE_STATE_ROTATION_LOCK, + assertThat( + Settings.Secure.getStringForUser( + mContentResolver, + Settings.Secure.DEVICE_STATE_ROTATION_LOCK, UserHandle.USER_CURRENT)) - .isEqualTo("0:0:1:1"); + .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; ) { + if (i > 0) { + sb.append(":"); + } + sb.append(values[i++]).append(":").append(values[i++]); + } + + Settings.Secure.putStringForUser( + mContentResolver, + Settings.Secure.DEVICE_STATE_ROTATION_LOCK, + sb.toString(), + UserHandle.USER_CURRENT); + + mSettingsManager.onPersistedSettingsChanged(); } private static class FakeRotationPolicy implements RotationPolicyWrapper { @@ -230,8 +343,8 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase } @Override - public void registerRotationPolicyListener(RotationPolicy.RotationPolicyListener listener, - int userHandle) { + public void registerRotationPolicyListener( + RotationPolicy.RotationPolicyListener listener, int userHandle) { throw new AssertionError("Not implemented"); } 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/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java index ba7bbfe11bdc..20a3fdaa99a5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java @@ -102,7 +102,8 @@ public class RemoteInputViewTest extends SysuiTestCase { mReceiver = new BlockingQueueIntentReceiver(); mContext.registerReceiver(mReceiver, new IntentFilter(TEST_ACTION), null, - Handler.createAsync(Dependency.get(Dependency.BG_LOOPER))); + Handler.createAsync(Dependency.get(Dependency.BG_LOOPER)), + Context.RECEIVER_EXPORTED_UNAUDITED); // Avoid SecurityException RemoteInputView#sendRemoteInput(). mContext.addMockSystemService(ShortcutManager.class, mShortcutManager); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RotationLockControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RotationLockControllerImplTest.java index 0581264d18e2..ea620a6856f1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RotationLockControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RotationLockControllerImplTest.java @@ -23,7 +23,6 @@ import static org.mockito.Mockito.verifyZeroInteractions; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; -import android.testing.TestableResources; import androidx.test.filters.SmallTest; @@ -43,25 +42,19 @@ import org.mockito.MockitoAnnotations; @SmallTest public class RotationLockControllerImplTest extends SysuiTestCase { - private static final String[] DEFAULT_SETTINGS = new String[]{ - "0:0", - "1:2" - }; + private static final String[] DEFAULT_SETTINGS = new String[] {"0:0", "1:2"}; @Mock RotationPolicyWrapper mRotationPolicyWrapper; @Mock DeviceStateRotationLockSettingController mDeviceStateRotationLockSettingController; - private TestableResources mResources; - private ArgumentCaptor<RotationPolicy.RotationPolicyListener> - mRotationPolicyListenerCaptor; + private ArgumentCaptor<RotationPolicy.RotationPolicyListener> mRotationPolicyListenerCaptor; @Before public void setUp() { MockitoAnnotations.initMocks(/* testClass= */ this); - mResources = mContext.getOrCreateTestableResources(); - mRotationPolicyListenerCaptor = ArgumentCaptor.forClass( - RotationPolicy.RotationPolicyListener.class); + mRotationPolicyListenerCaptor = + ArgumentCaptor.forClass(RotationPolicy.RotationPolicyListener.class); } @Test @@ -79,14 +72,7 @@ public class RotationLockControllerImplTest extends SysuiTestCase { } @Test - public void whenFlagOn_initializesDeviceStateRotationController() { - createRotationLockController(); - - verify(mDeviceStateRotationLockSettingController).initialize(); - } - - @Test - public void whenFlagOn_dviceStateRotationControllerAddedToCallbacks() { + public void whenFlagOn_deviceStateRotationControllerAddedToCallbacks() { createRotationLockController(); captureRotationPolicyListener().onChange(); @@ -103,11 +89,11 @@ public class RotationLockControllerImplTest extends SysuiTestCase { private void createRotationLockController() { createRotationLockController(DEFAULT_SETTINGS); } + private void createRotationLockController(String[] deviceStateRotationLockDefaults) { new RotationLockControllerImpl( mRotationPolicyWrapper, mDeviceStateRotationLockSettingController, - deviceStateRotationLockDefaults - ); + deviceStateRotationLockDefaults); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java index 32aee2bd6554..0e25d24bea58 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java @@ -30,6 +30,7 @@ import static org.mockito.Mockito.when; import android.app.Notification; import android.app.PendingIntent; import android.app.RemoteInput; +import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.res.Resources; @@ -119,7 +120,8 @@ public class SmartReplyViewTest extends SysuiTestCase { public void setUp() { MockitoAnnotations.initMocks(this); mReceiver = new BlockingQueueIntentReceiver(); - mContext.registerReceiver(mReceiver, new IntentFilter(TEST_ACTION)); + mContext.registerReceiver(mReceiver, new IntentFilter(TEST_ACTION), + Context.RECEIVER_EXPORTED_UNAUDITED); mKeyguardDismissUtil.setDismissHandler((action, unused, afterKgGone) -> action.onDismiss()); mDependency.injectMockDependency(KeyguardUpdateMonitor.class); mDependency.injectMockDependency(ShadeController.class); 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/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 74d7f654df16..f33c8c0dae15 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -77,6 +77,7 @@ filegroup { ":services.appwidget-sources", ":services.autofill-sources", ":services.backup-sources", + ":services.bluetooth-sources", // TODO(b/214988855) : Remove once apex/service-bluetooth jar is ready ":backuplib-sources", ":services.companion-sources", ":services.contentcapture-sources", diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index aa69a09034fc..91e909357bc4 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -48,6 +48,7 @@ import android.accessibilityservice.MagnificationConfig; import android.accessibilityservice.TouchInteractionController; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.app.ActivityOptions; import android.app.AlertDialog; import android.app.PendingIntent; @@ -262,6 +263,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private final UiAutomationManager mUiAutomationManager = new UiAutomationManager(mLock); private final AccessibilityTraceManager mTraceManager; + private final CaptioningManagerImpl mCaptioningManagerImpl; private int mCurrentUserId = UserHandle.USER_SYSTEM; @@ -339,6 +341,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mA11yDisplayListener = a11yDisplayListener; mMagnificationController = magnificationController; mMagnificationProcessor = new MagnificationProcessor(mMagnificationController); + mCaptioningManagerImpl = new CaptioningManagerImpl(mContext); if (inputFilter != null) { mInputFilter = inputFilter; mHasInputFilter = true; @@ -373,6 +376,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mMagnificationController = new MagnificationController(this, mLock, mContext, new MagnificationScaleProvider(mContext)); mMagnificationProcessor = new MagnificationProcessor(mMagnificationController); + mCaptioningManagerImpl = new CaptioningManagerImpl(mContext); init(); } @@ -3458,6 +3462,31 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } @Override + @RequiresPermission(Manifest.permission.SET_SYSTEM_AUDIO_CAPTION) + public void setSystemAudioCaptioningRequested(boolean isEnabled, int userId) { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.SET_SYSTEM_AUDIO_CAPTION, + "setSystemAudioCaptioningRequested"); + + mCaptioningManagerImpl.setSystemAudioCaptioningRequested(isEnabled, userId); + } + + @Override + public boolean isSystemAudioCaptioningUiRequested(int userId) { + return mCaptioningManagerImpl.isSystemAudioCaptioningUiRequested(userId); + } + + @Override + @RequiresPermission(Manifest.permission.SET_SYSTEM_AUDIO_CAPTION) + public void setSystemAudioCaptioningUiRequested(boolean isEnabled, int userId) { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.SET_SYSTEM_AUDIO_CAPTION, + "setSystemAudioCaptioningUiRequested"); + + mCaptioningManagerImpl.setSystemAudioCaptioningUiRequested(isEnabled, userId); + } + + @Override public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return; synchronized (mLock) { diff --git a/services/accessibility/java/com/android/server/accessibility/CaptioningManagerImpl.java b/services/accessibility/java/com/android/server/accessibility/CaptioningManagerImpl.java new file mode 100644 index 000000000000..39780d21486d --- /dev/null +++ b/services/accessibility/java/com/android/server/accessibility/CaptioningManagerImpl.java @@ -0,0 +1,87 @@ +/* + * 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.accessibility; + +import android.content.Context; +import android.os.Binder; +import android.provider.Settings; +import android.view.accessibility.CaptioningManager; + +/** + * Implementation class for CaptioningManager's interface that need system server identity. + */ +public class CaptioningManagerImpl implements CaptioningManager.SystemAudioCaptioningAccessing { + private static final boolean SYSTEM_AUDIO_CAPTIONING_UI_DEFAULT_ENABLED = false; + + private final Context mContext; + + public CaptioningManagerImpl(Context context) { + mContext = context; + } + + /** + * Sets the system audio caption enabled state. + * + * @param isEnabled The system audio captioning enabled state. + * @param userId The user Id. + */ + @Override + public void setSystemAudioCaptioningRequested(boolean isEnabled, int userId) { + final long identity = Binder.clearCallingIdentity(); + try { + Settings.Secure.putIntForUser(mContext.getContentResolver(), + Settings.Secure.ODI_CAPTIONS_ENABLED, isEnabled ? 1 : 0, userId); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + /** + * Gets the system audio caption UI enabled state. + * + * @param userId The user Id. + * @return the system audio caption UI enabled state. + */ + @Override + public boolean isSystemAudioCaptioningUiRequested(int userId) { + final long identity = Binder.clearCallingIdentity(); + try { + return Settings.Secure.getIntForUser(mContext.getContentResolver(), + Settings.Secure.ODI_CAPTIONS_VOLUME_UI_ENABLED, + SYSTEM_AUDIO_CAPTIONING_UI_DEFAULT_ENABLED ? 1 : 0, userId) == 1; + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + /** + * Sets the system audio caption UI enabled state. + * + * @param isEnabled The system audio captioning UI enabled state. + * @param userId The user Id. + */ + @Override + public void setSystemAudioCaptioningUiRequested(boolean isEnabled, int userId) { + final long identity = Binder.clearCallingIdentity(); + try { + Settings.Secure.putIntForUser(mContext.getContentResolver(), + Settings.Secure.ODI_CAPTIONS_VOLUME_UI_ENABLED, isEnabled ? 1 : 0, userId); + } finally { + Binder.restoreCallingIdentity(identity); + } + } +} 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 5b318d319ebe..cda554e9d0cf 100644 --- a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java +++ b/services/companion/java/com/android/server/companion/AssociationStoreImpl.java @@ -37,6 +37,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.StringJoiner; /** * Implementation of the {@link AssociationStore}, with addition of the methods for modification. @@ -58,33 +59,15 @@ class AssociationStoreImpl implements AssociationStore { private final Object mLock = new Object(); @GuardedBy("mLock") - private final Map<Integer, AssociationInfo> mIdMap; + private final Map<Integer, AssociationInfo> mIdMap = new HashMap<>(); @GuardedBy("mLock") - private final Map<MacAddress, Set<Integer>> mAddressMap; + private final Map<MacAddress, Set<Integer>> mAddressMap = new HashMap<>(); @GuardedBy("mLock") private final SparseArray<List<AssociationInfo>> mCachedPerUser = new SparseArray<>(); @GuardedBy("mListeners") private final Set<OnChangeListener> mListeners = new LinkedHashSet<>(); - AssociationStoreImpl(Collection<AssociationInfo> associations) { - synchronized (mLock) { - final int size = associations.size(); - mIdMap = new HashMap<>(size); - mAddressMap = new HashMap<>(size); - - for (AssociationInfo association : associations) { - final int id = association.getId(); - mIdMap.put(id, association); - - final MacAddress address = association.getDeviceMacAddress(); - if (address != null) { - mAddressMap.computeIfAbsent(address, it -> new HashSet<>()).add(id); - } - } - } - } - void addAssociation(@NonNull AssociationInfo association) { final int id = association.getId(); @@ -142,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); @@ -230,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); @@ -280,25 +260,41 @@ class AssociationStoreImpl implements AssociationStore { synchronized (mListeners) { for (OnChangeListener listener : mListeners) { listener.onAssociationChanged(changeType, association); + } + } + } - switch (changeType) { - case CHANGE_TYPE_ADDED: - listener.onAssociationAdded(association); - break; + void setAssociations(Collection<AssociationInfo> allAssociations) { + if (DEBUG) { + Log.i(TAG, "setAssociations() n=" + allAssociations.size()); + final StringJoiner stringJoiner = new StringJoiner(", "); + allAssociations.forEach(assoc -> stringJoiner.add(assoc.toShortString())); + Log.v(TAG, " associations=" + stringJoiner); + } + synchronized (mLock) { + setAssociationsLocked(allAssociations); + } + } - case CHANGE_TYPE_REMOVED: - listener.onAssociationRemoved(association); - break; + @GuardedBy("mLock") + private void setAssociationsLocked(Collection<AssociationInfo> associations) { + clearLocked(); - case CHANGE_TYPE_UPDATED_ADDRESS_CHANGED: - listener.onAssociationUpdated(association, true); - break; + for (AssociationInfo association : associations) { + final int id = association.getId(); + mIdMap.put(id, association); - case CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED: - listener.onAssociationUpdated(association, false); - break; - } + final MacAddress address = association.getDeviceMacAddress(); + if (address != null) { + mAddressMap.computeIfAbsent(address, it -> new HashSet<>()).add(id); } } } + + @GuardedBy("mLock") + private void clearLocked() { + mIdMap.clear(); + mAddressMap.clear(); + mCachedPerUser.clear(); + } } 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 1c983477a2ab..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; @@ -159,7 +161,7 @@ public class CompanionDeviceManagerService extends SystemService // Persistent data store for all Associations. private PersistentDataStore mPersistentStore; - private AssociationStoreImpl mAssociationStore; + private final AssociationStoreImpl mAssociationStore = new AssociationStoreImpl(); private AssociationRequestsProcessor mAssociationRequestsProcessor; private PowerWhitelistManager mPowerWhitelistManager; @@ -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; /** @@ -219,16 +222,8 @@ public class CompanionDeviceManagerService extends SystemService @Override public void onStart() { mPersistentStore = new PersistentDataStore(); - final Set<AssociationInfo> allAssociations = new ArraySet<>(); - - synchronized (mPreviouslyUsedIds) { - // The data is stored in DE directories, so we can read the data for all users now - // (which would not be possible if the data was stored to CE directories). - mPersistentStore.readStateForUsers( - mUserManager.getAliveUsers(), allAssociations, mPreviouslyUsedIds); - } - mAssociationStore = new AssociationStoreImpl(allAssociations); + loadAssociationsFromDisk(); mAssociationStore.registerListener(this); mCompanionDevicePresenceController = new CompanionDevicePresenceController(this); @@ -239,6 +234,18 @@ public class CompanionDeviceManagerService extends SystemService publishBinderService(Context.COMPANION_DEVICE_SERVICE, impl); } + void loadAssociationsFromDisk() { + final Set<AssociationInfo> allAssociations = new ArraySet<>(); + synchronized (mPreviouslyUsedIds) { + // The data is stored in DE directories, so we can read the data for all users now + // (which would not be possible if the data was stored to CE directories). + mPersistentStore.readStateForUsers( + mUserManager.getAliveUsers(), allAssociations, mPreviouslyUsedIds); + } + + mAssociationStore.setAssociations(allAssociations); + } + @Override public void onBootPhase(int phase) { if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { @@ -362,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 @@ -377,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) -> { @@ -774,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; @@ -917,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(); } } @@ -970,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); } @@ -1345,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/CompanionDevicePresenceController.java b/services/companion/java/com/android/server/companion/CompanionDevicePresenceController.java index 444768491660..fc6681705cb6 100644 --- a/services/companion/java/com/android/server/companion/CompanionDevicePresenceController.java +++ b/services/companion/java/com/android/server/companion/CompanionDevicePresenceController.java @@ -48,7 +48,7 @@ import java.util.Objects; public class CompanionDevicePresenceController { private static final String LOG_TAG = "CompanionDevicePresenceController"; PerUser<ArrayMap<String, List<BoundService>>> mBoundServices; - private static final String META_DATA_KEY_PRIMARY = "primary"; + private static final String META_DATA_KEY_PRIMARY = "android.companion.primary"; private final CompanionDeviceManagerService mService; public CompanionDevicePresenceController(CompanionDeviceManagerService service) { diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java index 5c0571d801aa..f2e660779e9e 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java @@ -73,13 +73,9 @@ class CompanionDeviceShellCommand extends android.os.ShellCommand { } break; - case "simulate_connect": { - mService.onDeviceConnected(getNextArgRequired()); - } - break; - - case "simulate_disconnect": { - mService.onDeviceDisconnected(getNextArgRequired()); + case "clear-association-memory-cache": { + mService.persistState(); + mService.loadAssociationsFromDisk(); } break; @@ -110,5 +106,8 @@ class CompanionDeviceShellCommand extends android.os.ShellCommand { pw.println(" Create a new Association."); pw.println(" disassociate USER_ID PACKAGE MAC_ADDRESS"); pw.println(" Remove an existing Association."); + pw.println(" clear-association-memory-cache"); + pw.println(" Clear the in-memory association cache and reload all association " + + "information from persistent storage. USE FOR DEBUGGING PURPOSES ONLY."); } } 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/PackageUtils.java b/services/companion/java/com/android/server/companion/PackageUtils.java index 985daa356a53..fcb14a4f04d0 100644 --- a/services/companion/java/com/android/server/companion/PackageUtils.java +++ b/services/companion/java/com/android/server/companion/PackageUtils.java @@ -53,7 +53,7 @@ import java.util.Map; final class PackageUtils { private static final Intent COMPANION_SERVICE_INTENT = new Intent(CompanionDeviceService.SERVICE_INTERFACE); - private static final String META_DATA_KEY_PRIMARY = "primary"; + private static final String META_DATA_PRIMARY_TAG = "android.companion.primary"; static @Nullable PackageInfo getPackageInfo(@NonNull Context context, @UserIdInt int userId, @NonNull String packageName) { @@ -121,6 +121,6 @@ final class PackageUtils { } private static boolean isPrimaryCompanionDeviceService(ServiceInfo service) { - return service.metaData != null && service.metaData.getBoolean(META_DATA_KEY_PRIMARY); + return service.metaData != null && service.metaData.getBoolean(META_DATA_PRIMARY_TAG); } } diff --git a/services/companion/java/com/android/server/companion/PersistentDataStore.java b/services/companion/java/com/android/server/companion/PersistentDataStore.java index ef3aa7fea1b5..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; @@ -67,17 +66,20 @@ import java.util.concurrent.ConcurrentMap; * The class responsible for persisting Association records and other related information (such as * previously used IDs) to a disk, and reading the data back from the disk. * - * Before Android T the data was stored to `companion_device_manager_associations.xml` file in - * {@link Environment#getUserSystemDirectory(int)} - * (eg. `/data/system/users/0/companion_device_manager_associations.xml`) - * @see #getBaseLegacyStorageFileForUser(int) + * <p> + * Before Android T the data was stored in "companion_device_manager_associations.xml" file in + * {@link Environment#getUserSystemDirectory(int) /data/system/user/}. * - * Before Android T the data was stored using the v0 schema. + * See {@link #getBaseLegacyStorageFileForUser(int) getBaseLegacyStorageFileForUser()}. * - * @see #readAssociationsV0(TypedXmlPullParser, int, Collection) - * @see #readAssociationV0(TypedXmlPullParser, int, int, Collection) + * <p> + * Before Android T the data was stored using the v0 schema. See: + * <ul> + * <li>{@link #readAssociationsV0(TypedXmlPullParser, int, Collection) readAssociationsV0()}. + * <li>{@link #readAssociationV0(TypedXmlPullParser, int, int, Collection) readAssociationV0()}. + * </ul> * - * The following snippet is a sample of a the file that is using v0 schema. + * The following snippet is a sample of a file that is using v0 schema. * <pre>{@code * <associations> * <association @@ -93,24 +95,28 @@ import java.util.concurrent.ConcurrentMap; * </associations> * }</pre> * + * <p> + * Since Android T the data is stored to "companion_device_manager.xml" file in + * {@link Environment#getDataSystemDeDirectory(int) /data/system_de/}. * - * Since Android T the data is stored to `companion_device_manager.xml` file in - * {@link Environment#getDataSystemDeDirectory(int)}. - * (eg. `/data/system_de/0/companion_device_manager.xml`) - * @see #getBaseStorageFileForUser(int) - + * See {@link #getBaseStorageFileForUser(int) getBaseStorageFileForUser()} + * + * <p> * Since Android T the data is stored using the v1 schema. - * In the v1 schema, a list of the previously used IDs is storead along with the association + * + * In the v1 schema, a list of the previously used IDs is stored along with the association * records. - * V1 schema adds a new optional `display_name` attribute, and makes the `mac_address` attribute - * optional. * - * @see #CURRENT_PERSISTENCE_VERSION - * @see #readAssociationsV1(TypedXmlPullParser, int, Collection) - * @see #readAssociationV1(TypedXmlPullParser, int, Collection) - * @see #readPreviouslyUsedIdsV1(TypedXmlPullParser, Map) + * V1 schema adds a new optional "display_name" attribute, and makes the "mac_address" attribute + * optional. + * <ul> + * <li> {@link #CURRENT_PERSISTENCE_VERSION} + * <li> {@link #readAssociationsV1(TypedXmlPullParser, int, Collection) readAssociationsV1()} + * <li> {@link #readAssociationV1(TypedXmlPullParser, int, Collection) readAssociationV1()} + * <li> {@link #readPreviouslyUsedIdsV1(TypedXmlPullParser, Map) readPreviouslyUsedIdsV1()} + * </ul> * - * The following snippet is a sample of a the file that is using v0 schema. + * The following snippet is a sample of a file that is using v0 schema. * <pre>{@code * <state persistence-version="1"> * <associations> @@ -203,9 +209,12 @@ 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()); + // 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) { File legacyBaseFile = null; final AtomicFile readFrom; @@ -269,6 +278,8 @@ final class PersistentDataStore { final AtomicFile file = getStorageFileForUser(userId); if (DEBUG) Slog.d(LOG_TAG, " > 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) { persistStateToFileLocked(file, associations, previouslyUsedIdsPerPackage); } @@ -329,13 +340,16 @@ final class PersistentDataStore { }); } + /** + * 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 -> 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) { @@ -494,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; @@ -528,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..0fd29675d469 100644 --- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java +++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java @@ -21,6 +21,7 @@ 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.compat.annotation.ChangeId; import android.compat.annotation.EnabledSince; @@ -33,6 +34,7 @@ import android.util.Slog; import android.window.DisplayWindowPolicyController; import java.util.List; +import java.util.Set; /** @@ -49,13 +51,23 @@ 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 + private final ArraySet<UserHandle> mAllowedUsers; + @Nullable + private final ArraySet<ComponentName> mAllowedActivities; + @Nullable + private final ArraySet<ComponentName> mBlockedActivities; - @NonNull final ArraySet<Integer> mRunningUids = new ArraySet<>(); + @NonNull + final ArraySet<Integer> mRunningUids = new ArraySet<>(); GenericWindowPolicyController(int windowFlags, int systemWindowFlags, - @NonNull ArraySet<UserHandle> allowedUsers) { + @NonNull ArraySet<UserHandle> allowedUsers, + @Nullable Set<ComponentName> allowedActivities, + @Nullable Set<ComponentName> blockedActivities) { mAllowedUsers = allowedUsers; + mAllowedActivities = allowedActivities == null ? null : new ArraySet<>(allowedActivities); + mBlockedActivities = blockedActivities == null ? null : new ArraySet<>(blockedActivities); setInterestedWindowFlags(windowFlags, systemWindowFlags); } @@ -108,6 +120,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..6ab8e754e2b3 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -32,7 +32,9 @@ import android.companion.virtual.IVirtualDevice; import android.companion.virtual.VirtualDeviceParams; 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 +52,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,7 +71,7 @@ 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; @@ -202,7 +204,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 +230,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 +257,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 +327,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 +357,13 @@ 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()); mWindowPolicyControllers.put(displayId, dwpc); return dwpc; } @@ -374,6 +392,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/core/Android.bp b/services/core/Android.bp index 95ec9e9857a2..094ed375325e 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -103,6 +103,7 @@ java_library_static { ":android.hardware.biometrics.face-V2-java-source", ":statslog-art-java-gen", ":statslog-contexthub-java-gen", + ":services.bluetooth-sources", // TODO(b/214988855) : Remove once apex is ready ":services.core-sources", ":services.core.protologsrc", ":dumpstate_aidl", diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java index 9b2948f42ed8..f56bfab7055f 100644 --- a/services/core/java/android/content/pm/PackageManagerInternal.java +++ b/services/core/java/android/content/pm/PackageManagerInternal.java @@ -30,7 +30,6 @@ import android.content.Intent; import android.content.IntentSender; import android.content.pm.SigningDetails.CertCapabilities; import android.content.pm.overlay.OverlayPaths; -import android.content.pm.parsing.component.ParsedMainComponent; import android.os.Bundle; import android.os.Handler; import android.os.HandlerExecutor; @@ -50,6 +49,7 @@ import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.pkg.AndroidPackageApi; import com.android.server.pm.pkg.PackageState; import com.android.server.pm.pkg.PackageStateInternal; +import com.android.server.pm.pkg.component.ParsedMainComponent; import com.android.server.pm.pkg.mutate.PackageStateMutator; import java.io.IOException; @@ -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/BluetoothAirplaneModeListener.java b/services/core/java/com/android/server/BluetoothAirplaneModeListener.java deleted file mode 100644 index 380b1f37b981..000000000000 --- a/services/core/java/com/android/server/BluetoothAirplaneModeListener.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright 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 com.android.server; - -import android.annotation.RequiresPermission; -import android.content.Context; -import android.database.ContentObserver; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.provider.Settings; -import android.util.Log; - -import com.android.internal.annotations.VisibleForTesting; - -/** - * The BluetoothAirplaneModeListener handles system airplane mode change callback and checks - * whether we need to inform BluetoothManagerService on this change. - * - * The information of airplane mode turns on would not be passed to the BluetoothManagerService - * when Bluetooth is on and Bluetooth is in one of the following situations: - * 1. Bluetooth A2DP is connected. - * 2. Bluetooth Hearing Aid profile is connected. - * 3. Bluetooth LE Audio is connected - */ -class BluetoothAirplaneModeListener { - private static final String TAG = "BluetoothAirplaneModeListener"; - @VisibleForTesting static final String TOAST_COUNT = "bluetooth_airplane_toast_count"; - - private static final int MSG_AIRPLANE_MODE_CHANGED = 0; - - @VisibleForTesting static final int MAX_TOAST_COUNT = 10; // 10 times - - private final BluetoothManagerService mBluetoothManager; - private final BluetoothAirplaneModeHandler mHandler; - private BluetoothModeChangeHelper mAirplaneHelper; - - @VisibleForTesting int mToastCount = 0; - - BluetoothAirplaneModeListener(BluetoothManagerService service, Looper looper, Context context) { - mBluetoothManager = service; - - mHandler = new BluetoothAirplaneModeHandler(looper); - context.getContentResolver().registerContentObserver( - Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), true, - mAirplaneModeObserver); - } - - private final ContentObserver mAirplaneModeObserver = new ContentObserver(null) { - @Override - public void onChange(boolean unused) { - // Post from system main thread to android_io thread. - Message msg = mHandler.obtainMessage(MSG_AIRPLANE_MODE_CHANGED); - mHandler.sendMessage(msg); - } - }; - - private class BluetoothAirplaneModeHandler extends Handler { - BluetoothAirplaneModeHandler(Looper looper) { - super(looper); - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_AIRPLANE_MODE_CHANGED: - handleAirplaneModeChange(); - break; - default: - Log.e(TAG, "Invalid message: " + msg.what); - break; - } - } - } - - /** - * Call after boot complete - */ - @VisibleForTesting - void start(BluetoothModeChangeHelper helper) { - Log.i(TAG, "start"); - mAirplaneHelper = helper; - mToastCount = mAirplaneHelper.getSettingsInt(TOAST_COUNT); - } - - @VisibleForTesting - boolean shouldPopToast() { - if (mToastCount >= MAX_TOAST_COUNT) { - return false; - } - mToastCount++; - mAirplaneHelper.setSettingsInt(TOAST_COUNT, mToastCount); - return true; - } - - @VisibleForTesting - @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) - void handleAirplaneModeChange() { - if (shouldSkipAirplaneModeChange()) { - Log.i(TAG, "Ignore airplane mode change"); - // Airplane mode enabled when Bluetooth is being used for audio/headering aid. - // Bluetooth is not disabled in such case, only state is changed to - // BLUETOOTH_ON_AIRPLANE mode. - mAirplaneHelper.setSettingsInt(Settings.Global.BLUETOOTH_ON, - BluetoothManagerService.BLUETOOTH_ON_AIRPLANE); - if (shouldPopToast()) { - mAirplaneHelper.showToastMessage(); - } - return; - } - if (mAirplaneHelper != null) { - mAirplaneHelper.onAirplaneModeChanged(mBluetoothManager); - } - } - - @VisibleForTesting - boolean shouldSkipAirplaneModeChange() { - if (mAirplaneHelper == null) { - return false; - } - if (!mAirplaneHelper.isBluetoothOn() || !mAirplaneHelper.isAirplaneModeOn() - || !mAirplaneHelper.isMediaProfileConnected()) { - return false; - } - return true; - } -} diff --git a/services/core/java/com/android/server/BluetoothDeviceConfigListener.java b/services/core/java/com/android/server/BluetoothDeviceConfigListener.java deleted file mode 100644 index 611a37de70f4..000000000000 --- a/services/core/java/com/android/server/BluetoothDeviceConfigListener.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 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; - -import android.provider.DeviceConfig; -import android.util.Slog; - -import java.util.ArrayList; - -/** - * The BluetoothDeviceConfigListener handles system device config change callback and checks - * whether we need to inform BluetoothManagerService on this change. - * - * The information of device config change would not be passed to the BluetoothManagerService - * when Bluetooth is on and Bluetooth is in one of the following situations: - * 1. Bluetooth A2DP is connected. - * 2. Bluetooth Hearing Aid profile is connected. - */ -class BluetoothDeviceConfigListener { - private static final String TAG = "BluetoothDeviceConfigListener"; - - private final BluetoothManagerService mService; - private final boolean mLogDebug; - - BluetoothDeviceConfigListener(BluetoothManagerService service, boolean logDebug) { - mService = service; - mLogDebug = logDebug; - DeviceConfig.addOnPropertiesChangedListener( - DeviceConfig.NAMESPACE_BLUETOOTH, - (Runnable r) -> r.run(), - mDeviceConfigChangedListener); - } - - private final DeviceConfig.OnPropertiesChangedListener mDeviceConfigChangedListener = - new DeviceConfig.OnPropertiesChangedListener() { - @Override - public void onPropertiesChanged(DeviceConfig.Properties properties) { - if (!properties.getNamespace().equals(DeviceConfig.NAMESPACE_BLUETOOTH)) { - return; - } - if (mLogDebug) { - ArrayList<String> flags = new ArrayList<>(); - for (String name : properties.getKeyset()) { - flags.add(name + "='" + properties.getString(name, "") + "'"); - } - Slog.d(TAG, "onPropertiesChanged: " + String.join(",", flags)); - } - boolean foundInit = false; - for (String name : properties.getKeyset()) { - if (name.startsWith("INIT_")) { - foundInit = true; - break; - } - } - if (!foundInit) { - return; - } - mService.onInitFlagsChanged(); - } - }; - -} diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java deleted file mode 100644 index 262933dea27f..000000000000 --- a/services/core/java/com/android/server/BluetoothManagerService.java +++ /dev/null @@ -1,2962 +0,0 @@ -/* - * Copyright (C) 2012 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; - -import static android.Manifest.permission.BLUETOOTH_CONNECT; -import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY; -import static android.content.pm.PackageManager.PERMISSION_GRANTED; -import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED; -import static android.os.UserHandle.USER_SYSTEM; -import static android.permission.PermissionCheckerManager.PERMISSION_HARD_DENIED; - -import android.Manifest; -import android.annotation.NonNull; -import android.annotation.RequiresPermission; -import android.annotation.SuppressLint; -import android.app.ActivityManager; -import android.app.AppGlobals; -import android.app.AppOpsManager; -import android.app.BroadcastOptions; -import android.bluetooth.BluetoothA2dp; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothHearingAid; -import android.bluetooth.BluetoothLeAudio; -import android.bluetooth.BluetoothProfile; -import android.bluetooth.BluetoothProtoEnums; -import android.bluetooth.IBluetooth; -import android.bluetooth.IBluetoothCallback; -import android.bluetooth.IBluetoothGatt; -import android.bluetooth.IBluetoothHeadset; -import android.bluetooth.IBluetoothManager; -import android.bluetooth.IBluetoothManagerCallback; -import android.bluetooth.IBluetoothProfileServiceConnection; -import android.bluetooth.IBluetoothStateChangeCallback; -import android.bluetooth.IBluetoothLeCallControl; -import android.content.ActivityNotFoundException; -import android.content.AttributionSource; -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.ContentResolver; -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.IPackageManager; -import android.content.pm.PackageManager; -import android.content.pm.PackageManagerInternal; -import android.content.pm.UserInfo; -import android.database.ContentObserver; -import android.os.Binder; -import android.os.Bundle; -import android.os.Handler; -import android.os.IBinder; -import android.os.Looper; -import android.os.Message; -import android.os.PowerExemptionManager; -import android.os.Process; -import android.os.RemoteCallbackList; -import android.os.RemoteException; -import android.os.SystemClock; -import android.os.SystemProperties; -import android.os.UserHandle; -import android.os.UserManager; -import android.permission.PermissionManager; -import android.provider.Settings; -import android.provider.Settings.SettingNotFoundException; -import android.text.TextUtils; -import android.util.FeatureFlagUtils; -import android.util.Log; -import android.util.Slog; -import android.util.proto.ProtoOutputStream; - -import com.android.internal.R; -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.DumpUtils; -import com.android.internal.util.FrameworkStatsLog; -import com.android.server.pm.UserManagerInternal; -import com.android.server.pm.UserManagerInternal.UserRestrictionsListener; -import com.android.server.pm.UserRestrictionsUtils; - -import java.io.FileDescriptor; -import java.io.PrintWriter; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.Locale; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -class BluetoothManagerService extends IBluetoothManager.Stub { - private static final String TAG = "BluetoothManagerService"; - private static final boolean DBG = true; - - private static final String BLUETOOTH_PRIVILEGED = - android.Manifest.permission.BLUETOOTH_PRIVILEGED; - - private static final int ACTIVE_LOG_MAX_SIZE = 20; - private static final int CRASH_LOG_MAX_SIZE = 100; - - private static final int TIMEOUT_BIND_MS = 3000; //Maximum msec to wait for a bind - //Maximum msec to wait for service restart - private static final int SERVICE_RESTART_TIME_MS = 400; - //Maximum msec to wait for restart due to error - private static final int ERROR_RESTART_TIME_MS = 3000; - //Maximum msec to delay MESSAGE_USER_SWITCHED - private static final int USER_SWITCHED_TIME_MS = 200; - // Delay for the addProxy function in msec - private static final int ADD_PROXY_DELAY_MS = 100; - // Delay for retrying enable and disable in msec - private static final int ENABLE_DISABLE_DELAY_MS = 300; - private static final int DELAY_BEFORE_RESTART_DUE_TO_INIT_FLAGS_CHANGED_MS = 300; - private static final int DELAY_FOR_RETRY_INIT_FLAG_CHECK_MS = 86400; - - private static final int MESSAGE_ENABLE = 1; - private static final int MESSAGE_DISABLE = 2; - private static final int MESSAGE_HANDLE_ENABLE_DELAYED = 3; - private static final int MESSAGE_HANDLE_DISABLE_DELAYED = 4; - private static final int MESSAGE_REGISTER_STATE_CHANGE_CALLBACK = 30; - private static final int MESSAGE_UNREGISTER_STATE_CHANGE_CALLBACK = 31; - private static final int MESSAGE_BLUETOOTH_SERVICE_CONNECTED = 40; - private static final int MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED = 41; - private static final int MESSAGE_RESTART_BLUETOOTH_SERVICE = 42; - private static final int MESSAGE_BLUETOOTH_STATE_CHANGE = 60; - private static final int MESSAGE_TIMEOUT_BIND = 100; - private static final int MESSAGE_TIMEOUT_UNBIND = 101; - private static final int MESSAGE_GET_NAME_AND_ADDRESS = 200; - private static final int MESSAGE_USER_SWITCHED = 300; - private static final int MESSAGE_USER_UNLOCKED = 301; - private static final int MESSAGE_ADD_PROXY_DELAYED = 400; - private static final int MESSAGE_BIND_PROFILE_SERVICE = 401; - private static final int MESSAGE_RESTORE_USER_SETTING = 500; - private static final int MESSAGE_INIT_FLAGS_CHANGED = 600; - - private static final int RESTORE_SETTING_TO_ON = 1; - private static final int RESTORE_SETTING_TO_OFF = 0; - - private static final int MAX_ERROR_RESTART_RETRIES = 6; - private static final int MAX_WAIT_FOR_ENABLE_DISABLE_RETRIES = 10; - - // Bluetooth persisted setting is off - private static final int BLUETOOTH_OFF = 0; - // Bluetooth persisted setting is on - // and Airplane mode won't affect Bluetooth state at start up - private static final int BLUETOOTH_ON_BLUETOOTH = 1; - // Bluetooth persisted setting is on - // but Airplane mode will affect Bluetooth state at start up - // and Airplane mode will have higher priority. - @VisibleForTesting - static final int BLUETOOTH_ON_AIRPLANE = 2; - - private static final int SERVICE_IBLUETOOTH = 1; - private static final int SERVICE_IBLUETOOTHGATT = 2; - - private final Context mContext; - - // Locks are not provided for mName and mAddress. - // They are accessed in handler or broadcast receiver, same thread context. - private String mAddress; - private String mName; - private final ContentResolver mContentResolver; - private final int mUserId; - private final RemoteCallbackList<IBluetoothManagerCallback> mCallbacks; - private final RemoteCallbackList<IBluetoothStateChangeCallback> mStateChangeCallbacks; - private IBinder mBluetoothBinder; - private IBluetooth mBluetooth; - private IBluetoothGatt mBluetoothGatt; - private final ReentrantReadWriteLock mBluetoothLock = new ReentrantReadWriteLock(); - private boolean mBinding; - private boolean mUnbinding; - - private BluetoothModeChangeHelper mBluetoothModeChangeHelper; - - private BluetoothAirplaneModeListener mBluetoothAirplaneModeListener; - - private BluetoothDeviceConfigListener mBluetoothDeviceConfigListener; - - // used inside handler thread - private boolean mQuietEnable = false; - private boolean mEnable; - - private static CharSequence timeToLog(long timestamp) { - return android.text.format.DateFormat.format("MM-dd HH:mm:ss", timestamp); - } - - /** - * Used for tracking apps that enabled / disabled Bluetooth. - */ - private class ActiveLog { - private int mReason; - private String mPackageName; - private boolean mEnable; - private long mTimestamp; - - ActiveLog(int reason, String packageName, boolean enable, long timestamp) { - mReason = reason; - mPackageName = packageName; - mEnable = enable; - mTimestamp = timestamp; - } - - public String toString() { - return timeToLog(mTimestamp) + (mEnable ? " Enabled " : " Disabled ") - + " due to " + getEnableDisableReasonString(mReason) + " by " + mPackageName; - } - - void dump(ProtoOutputStream proto) { - proto.write(BluetoothManagerServiceDumpProto.ActiveLog.TIMESTAMP_MS, mTimestamp); - proto.write(BluetoothManagerServiceDumpProto.ActiveLog.ENABLE, mEnable); - proto.write(BluetoothManagerServiceDumpProto.ActiveLog.PACKAGE_NAME, mPackageName); - proto.write(BluetoothManagerServiceDumpProto.ActiveLog.REASON, mReason); - } - } - - private final LinkedList<ActiveLog> mActiveLogs = new LinkedList<>(); - private final LinkedList<Long> mCrashTimestamps = new LinkedList<>(); - private int mCrashes; - private long mLastEnabledTime; - - // configuration from external IBinder call which is used to - // synchronize with broadcast receiver. - private boolean mQuietEnableExternal; - private boolean mEnableExternal; - - // Map of apps registered to keep BLE scanning on. - private Map<IBinder, ClientDeathRecipient> mBleApps = - new ConcurrentHashMap<IBinder, ClientDeathRecipient>(); - - private int mState; - private final BluetoothHandler mHandler; - private int mErrorRecoveryRetryCounter; - private final int mSystemUiUid; - - private boolean mIsHearingAidProfileSupported; - - private AppOpsManager mAppOps; - - // Save a ProfileServiceConnections object for each of the bound - // bluetooth profile services - private final Map<Integer, ProfileServiceConnections> mProfileServices = new HashMap<>(); - - private final boolean mWirelessConsentRequired; - - private final IBluetoothCallback mBluetoothCallback = new IBluetoothCallback.Stub() { - @Override - public void onBluetoothStateChange(int prevState, int newState) throws RemoteException { - Message msg = - mHandler.obtainMessage(MESSAGE_BLUETOOTH_STATE_CHANGE, prevState, newState); - mHandler.sendMessage(msg); - } - }; - - private final UserRestrictionsListener mUserRestrictionsListener = - new UserRestrictionsListener() { - @Override - public void onUserRestrictionsChanged(int userId, Bundle newRestrictions, - Bundle prevRestrictions) { - - if (UserRestrictionsUtils.restrictionsChanged(prevRestrictions, newRestrictions, - UserManager.DISALLOW_BLUETOOTH_SHARING)) { - updateOppLauncherComponentState(userId, - newRestrictions.getBoolean(UserManager.DISALLOW_BLUETOOTH_SHARING)); - } - - // DISALLOW_BLUETOOTH can only be set by DO or PO on the system user. - if (userId == USER_SYSTEM - && UserRestrictionsUtils.restrictionsChanged(prevRestrictions, - newRestrictions, UserManager.DISALLOW_BLUETOOTH)) { - if (userId == USER_SYSTEM && newRestrictions.getBoolean( - UserManager.DISALLOW_BLUETOOTH)) { - updateOppLauncherComponentState(userId, true); // Sharing disallowed - sendDisableMsg(BluetoothProtoEnums.ENABLE_DISABLE_REASON_DISALLOWED, - mContext.getPackageName()); - } else { - updateOppLauncherComponentState(userId, newRestrictions.getBoolean( - UserManager.DISALLOW_BLUETOOTH_SHARING)); - } - } - } - }; - - @VisibleForTesting - public void onInitFlagsChanged() { - mHandler.removeMessages(MESSAGE_INIT_FLAGS_CHANGED); - mHandler.sendEmptyMessageDelayed( - MESSAGE_INIT_FLAGS_CHANGED, - DELAY_BEFORE_RESTART_DUE_TO_INIT_FLAGS_CHANGED_MS); - } - - public boolean onFactoryReset(AttributionSource attributionSource) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, - "Need BLUETOOTH_PRIVILEGED permission"); - - // Wait for stable state if bluetooth is temporary state. - int state = getState(); - if (state == BluetoothAdapter.STATE_BLE_TURNING_ON - || state == BluetoothAdapter.STATE_TURNING_ON - || state == BluetoothAdapter.STATE_TURNING_OFF) { - if (!waitForState(Set.of(BluetoothAdapter.STATE_BLE_ON, BluetoothAdapter.STATE_ON))) { - return false; - } - } - - // Clear registered LE apps to force shut-off Bluetooth - clearBleApps(); - state = getState(); - try { - mBluetoothLock.readLock().lock(); - if (mBluetooth == null) { - return false; - } - if (state == BluetoothAdapter.STATE_BLE_ON) { - addActiveLog( - BluetoothProtoEnums.ENABLE_DISABLE_REASON_FACTORY_RESET, - mContext.getPackageName(), false); - mBluetooth.onBrEdrDown(attributionSource); - return true; - } else if (state == BluetoothAdapter.STATE_ON) { - addActiveLog( - BluetoothProtoEnums.ENABLE_DISABLE_REASON_FACTORY_RESET, - mContext.getPackageName(), false); - mBluetooth.disable(attributionSource); - return true; - } - } catch (RemoteException e) { - Slog.e(TAG, "Unable to shutdown Bluetooth", e); - } finally { - mBluetoothLock.readLock().unlock(); - } - return false; - } - - @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) - public void onAirplaneModeChanged() { - synchronized (this) { - if (isBluetoothPersistedStateOn()) { - if (isAirplaneModeOn()) { - persistBluetoothSetting(BLUETOOTH_ON_AIRPLANE); - } else { - persistBluetoothSetting(BLUETOOTH_ON_BLUETOOTH); - } - } - - int st = BluetoothAdapter.STATE_OFF; - try { - mBluetoothLock.readLock().lock(); - if (mBluetooth != null) { - st = mBluetooth.getState(); - } - } catch (RemoteException e) { - Slog.e(TAG, "Unable to call getState", e); - return; - } finally { - mBluetoothLock.readLock().unlock(); - } - - Slog.d(TAG, - "Airplane Mode change - current state: " + BluetoothAdapter.nameForState( - st) + ", isAirplaneModeOn()=" + isAirplaneModeOn()); - - if (isAirplaneModeOn()) { - // Clear registered LE apps to force shut-off - clearBleApps(); - - // If state is BLE_ON make sure we trigger disableBLE - if (st == BluetoothAdapter.STATE_BLE_ON) { - try { - mBluetoothLock.readLock().lock(); - if (mBluetooth != null) { - addActiveLog( - BluetoothProtoEnums.ENABLE_DISABLE_REASON_AIRPLANE_MODE, - mContext.getPackageName(), false); - mBluetooth.onBrEdrDown(mContext.getAttributionSource()); - mEnable = false; - mEnableExternal = false; - } - } catch (RemoteException e) { - Slog.e(TAG, "Unable to call onBrEdrDown", e); - } finally { - mBluetoothLock.readLock().unlock(); - } - } else if (st == BluetoothAdapter.STATE_ON) { - sendDisableMsg(BluetoothProtoEnums.ENABLE_DISABLE_REASON_AIRPLANE_MODE, - mContext.getPackageName()); - } - } else if (mEnableExternal) { - sendEnableMsg(mQuietEnableExternal, - BluetoothProtoEnums.ENABLE_DISABLE_REASON_AIRPLANE_MODE, - mContext.getPackageName()); - } - } - } - - private final BroadcastReceiver mReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED.equals(action)) { - String newName = intent.getStringExtra(BluetoothAdapter.EXTRA_LOCAL_NAME); - if (DBG) { - Slog.d(TAG, "Bluetooth Adapter name changed to " + newName + " by " - + mContext.getPackageName()); - } - if (newName != null) { - storeNameAndAddress(newName, null); - } - } else if (BluetoothAdapter.ACTION_BLUETOOTH_ADDRESS_CHANGED.equals(action)) { - String newAddress = intent.getStringExtra(BluetoothAdapter.EXTRA_BLUETOOTH_ADDRESS); - if (newAddress != null) { - if (DBG) { - Slog.d(TAG, "Bluetooth Adapter address changed to " + newAddress); - } - storeNameAndAddress(null, newAddress); - } else { - if (DBG) { - Slog.e(TAG, "No Bluetooth Adapter address parameter found"); - } - } - } else if (Intent.ACTION_SETTING_RESTORED.equals(action)) { - final String name = intent.getStringExtra(Intent.EXTRA_SETTING_NAME); - if (Settings.Global.BLUETOOTH_ON.equals(name)) { - // The Bluetooth On state may be changed during system restore. - final String prevValue = - intent.getStringExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE); - final String newValue = intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE); - - if (DBG) { - Slog.d(TAG, - "ACTION_SETTING_RESTORED with BLUETOOTH_ON, prevValue=" + prevValue - + ", newValue=" + newValue); - } - - if ((newValue != null) && (prevValue != null) && !prevValue.equals(newValue)) { - Message msg = mHandler.obtainMessage(MESSAGE_RESTORE_USER_SETTING, - newValue.equals("0") ? RESTORE_SETTING_TO_OFF - : RESTORE_SETTING_TO_ON, 0); - mHandler.sendMessage(msg); - } - } - } else if (BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(action) - || BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED.equals(action) - || BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED.equals(action)) { - final int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, - BluetoothProfile.STATE_CONNECTED); - if (mHandler.hasMessages(MESSAGE_INIT_FLAGS_CHANGED) - && state == BluetoothProfile.STATE_DISCONNECTED - && !mBluetoothModeChangeHelper.isMediaProfileConnected()) { - Slog.i(TAG, "Device disconnected, reactivating pending flag changes"); - onInitFlagsChanged(); - } - } - } - }; - - BluetoothManagerService(Context context) { - mHandler = new BluetoothHandler(IoThread.get().getLooper()); - - mContext = context; - - mWirelessConsentRequired = context.getResources() - .getBoolean(com.android.internal.R.bool.config_wirelessConsentRequired); - - mCrashes = 0; - mBluetooth = null; - mBluetoothBinder = null; - mBluetoothGatt = null; - mBinding = false; - mUnbinding = false; - mEnable = false; - mState = BluetoothAdapter.STATE_OFF; - mQuietEnableExternal = false; - mEnableExternal = false; - mAddress = null; - mName = null; - mErrorRecoveryRetryCounter = 0; - mContentResolver = context.getContentResolver(); - mUserId = mContentResolver.getUserId(); - // Observe BLE scan only mode settings change. - registerForBleScanModeChange(); - mCallbacks = new RemoteCallbackList<IBluetoothManagerCallback>(); - mStateChangeCallbacks = new RemoteCallbackList<IBluetoothStateChangeCallback>(); - - mIsHearingAidProfileSupported = context.getResources() - .getBoolean(com.android.internal.R.bool.config_hearing_aid_profile_supported); - - // TODO: We need a more generic way to initialize the persist keys of FeatureFlagUtils - String value = SystemProperties.get(FeatureFlagUtils.PERSIST_PREFIX + FeatureFlagUtils.HEARING_AID_SETTINGS); - if (!TextUtils.isEmpty(value)) { - boolean isHearingAidEnabled = Boolean.parseBoolean(value); - Log.v(TAG, "set feature flag HEARING_AID_SETTINGS to " + isHearingAidEnabled); - FeatureFlagUtils.setEnabled(context, FeatureFlagUtils.HEARING_AID_SETTINGS, isHearingAidEnabled); - if (isHearingAidEnabled && !mIsHearingAidProfileSupported) { - // Overwrite to enable support by FeatureFlag - mIsHearingAidProfileSupported = true; - } - } - - IntentFilter filter = new IntentFilter(); - filter.addAction(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED); - filter.addAction(BluetoothAdapter.ACTION_BLUETOOTH_ADDRESS_CHANGED); - filter.addAction(Intent.ACTION_SETTING_RESTORED); - filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); - filter.addAction(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED); - filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); - mContext.registerReceiver(mReceiver, filter); - - loadStoredNameAndAddress(); - if (isBluetoothPersistedStateOn()) { - if (DBG) { - Slog.d(TAG, "Startup: Bluetooth persisted state is ON."); - } - mEnableExternal = true; - } - - String airplaneModeRadios = - Settings.Global.getString(mContentResolver, Settings.Global.AIRPLANE_MODE_RADIOS); - if (airplaneModeRadios == null || airplaneModeRadios.contains( - Settings.Global.RADIO_BLUETOOTH)) { - mBluetoothAirplaneModeListener = new BluetoothAirplaneModeListener( - this, IoThread.get().getLooper(), context); - } - - int systemUiUid = -1; - // Check if device is configured with no home screen, which implies no SystemUI. - boolean noHome = mContext.getResources().getBoolean(R.bool.config_noHomeScreen); - if (!noHome) { - PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class); - systemUiUid = pm.getPackageUid(pm.getSystemUiServiceComponent().getPackageName(), - MATCH_SYSTEM_ONLY, USER_SYSTEM); - } - if (systemUiUid >= 0) { - Slog.d(TAG, "Detected SystemUiUid: " + Integer.toString(systemUiUid)); - } else { - // Some platforms, such as wearables do not have a system ui. - Slog.w(TAG, "Unable to resolve SystemUI's UID."); - } - mSystemUiUid = systemUiUid; - } - - /** - * Returns true if airplane mode is currently on - */ - private boolean isAirplaneModeOn() { - return Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.AIRPLANE_MODE_ON, 0) == 1; - } - - private boolean supportBluetoothPersistedState() { - return mContext.getResources().getBoolean(R.bool.config_supportBluetoothPersistedState); - } - - /** - * Returns true if the Bluetooth saved state is "on" - */ - private boolean isBluetoothPersistedStateOn() { - if (!supportBluetoothPersistedState()) { - return false; - } - int state = Settings.Global.getInt(mContentResolver, Settings.Global.BLUETOOTH_ON, -1); - if (DBG) { - Slog.d(TAG, "Bluetooth persisted state: " + state); - } - return state != BLUETOOTH_OFF; - } - - private boolean isBluetoothPersistedStateOnAirplane() { - if (!supportBluetoothPersistedState()) { - return false; - } - int state = Settings.Global.getInt(mContentResolver, Settings.Global.BLUETOOTH_ON, -1); - if (DBG) { - Slog.d(TAG, "Bluetooth persisted state: " + state); - } - return state == BLUETOOTH_ON_AIRPLANE; - } - - /** - * Returns true if the Bluetooth saved state is BLUETOOTH_ON_BLUETOOTH - */ - private boolean isBluetoothPersistedStateOnBluetooth() { - if (!supportBluetoothPersistedState()) { - return false; - } - return Settings.Global.getInt(mContentResolver, Settings.Global.BLUETOOTH_ON, - BLUETOOTH_ON_BLUETOOTH) == BLUETOOTH_ON_BLUETOOTH; - } - - /** - * Save the Bluetooth on/off state - */ - private void persistBluetoothSetting(int value) { - if (DBG) { - Slog.d(TAG, "Persisting Bluetooth Setting: " + value); - } - // waive WRITE_SECURE_SETTINGS permission check - final long callingIdentity = Binder.clearCallingIdentity(); - try { - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.BLUETOOTH_ON, value); - } finally { - Binder.restoreCallingIdentity(callingIdentity); - } - } - - /** - * Returns true if the Bluetooth Adapter's name and address is - * locally cached - * @return - */ - private boolean isNameAndAddressSet() { - return mName != null && mAddress != null && mName.length() > 0 && mAddress.length() > 0; - } - - /** - * Retrieve the Bluetooth Adapter's name and address and save it in - * in the local cache - */ - private void loadStoredNameAndAddress() { - if (DBG) { - Slog.d(TAG, "Loading stored name and address"); - } - if (mContext.getResources() - .getBoolean(com.android.internal.R.bool.config_bluetooth_address_validation) - && Settings.Secure.getIntForUser(mContentResolver, - Settings.Secure.BLUETOOTH_NAME, 0, mUserId) - == 0) { - // if the valid flag is not set, don't load the address and name - if (DBG) { - Slog.d(TAG, "invalid bluetooth name and address stored"); - } - return; - } - mName = Settings.Secure.getStringForUser( - mContentResolver, Settings.Secure.BLUETOOTH_NAME, mUserId); - mAddress = Settings.Secure.getStringForUser( - mContentResolver, Settings.Secure.BLUETOOTH_ADDRESS, mUserId); - if (DBG) { - Slog.d(TAG, "Stored bluetooth Name=" + mName + ",Address=" + mAddress); - } - } - - /** - * Save the Bluetooth name and address in the persistent store. - * Only non-null values will be saved. - * @param name - * @param address - */ - private void storeNameAndAddress(String name, String address) { - if (name != null) { - Settings.Secure.putStringForUser(mContentResolver, Settings.Secure.BLUETOOTH_NAME, name, - mUserId); - mName = name; - if (DBG) { - Slog.d(TAG, "Stored Bluetooth name: " + Settings.Secure.getStringForUser( - mContentResolver, Settings.Secure.BLUETOOTH_NAME, - mUserId)); - } - } - - if (address != null) { - Settings.Secure.putStringForUser(mContentResolver, Settings.Secure.BLUETOOTH_ADDRESS, - address, mUserId); - mAddress = address; - if (DBG) { - Slog.d(TAG, - "Stored Bluetoothaddress: " + Settings.Secure.getStringForUser( - mContentResolver, Settings.Secure.BLUETOOTH_ADDRESS, - mUserId)); - } - } - - if ((name != null) && (address != null)) { - Settings.Secure.putIntForUser(mContentResolver, Settings.Secure.BLUETOOTH_ADDR_VALID, 1, - mUserId); - } - } - - public IBluetooth registerAdapter(IBluetoothManagerCallback callback) { - if (callback == null) { - Slog.w(TAG, "Callback is null in registerAdapter"); - return null; - } - synchronized (mCallbacks) { - mCallbacks.register(callback); - } - return mBluetooth; - } - - public void unregisterAdapter(IBluetoothManagerCallback callback) { - if (callback == null) { - Slog.w(TAG, "Callback is null in unregisterAdapter"); - return; - } - synchronized (mCallbacks) { - mCallbacks.unregister(callback); - } - } - - public void registerStateChangeCallback(IBluetoothStateChangeCallback callback) { - if (callback == null) { - Slog.w(TAG, "registerStateChangeCallback: Callback is null!"); - return; - } - Message msg = mHandler.obtainMessage(MESSAGE_REGISTER_STATE_CHANGE_CALLBACK); - msg.obj = callback; - mHandler.sendMessage(msg); - } - - public void unregisterStateChangeCallback(IBluetoothStateChangeCallback callback) { - if (callback == null) { - Slog.w(TAG, "unregisterStateChangeCallback: Callback is null!"); - return; - } - Message msg = mHandler.obtainMessage(MESSAGE_UNREGISTER_STATE_CHANGE_CALLBACK); - msg.obj = callback; - mHandler.sendMessage(msg); - } - - public boolean isEnabled() { - return getState() == BluetoothAdapter.STATE_ON; - } - - public int getState() { - if ((Binder.getCallingUid() != Process.SYSTEM_UID) && (!checkIfCallerIsForegroundUser())) { - Slog.w(TAG, "getState(): report OFF for non-active and non system user"); - return BluetoothAdapter.STATE_OFF; - } - - try { - mBluetoothLock.readLock().lock(); - if (mBluetooth != null) { - return mBluetooth.getState(); - } - } catch (RemoteException e) { - Slog.e(TAG, "getState()", e); - } finally { - mBluetoothLock.readLock().unlock(); - } - return BluetoothAdapter.STATE_OFF; - } - - class ClientDeathRecipient implements IBinder.DeathRecipient { - private String mPackageName; - - ClientDeathRecipient(String packageName) { - mPackageName = packageName; - } - - public void binderDied() { - if (DBG) { - Slog.d(TAG, "Binder is dead - unregister " + mPackageName); - } - - for (Map.Entry<IBinder, ClientDeathRecipient> entry : mBleApps.entrySet()) { - IBinder token = entry.getKey(); - ClientDeathRecipient deathRec = entry.getValue(); - if (deathRec.equals(this)) { - updateBleAppCount(token, false, mPackageName); - break; - } - } - } - - public String getPackageName() { - return mPackageName; - } - } - - @Override - public boolean isBleScanAlwaysAvailable() { - if (isAirplaneModeOn() && !mEnable) { - return false; - } - try { - return Settings.Global.getInt(mContentResolver, - Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE) != 0; - } catch (SettingNotFoundException e) { - } - return false; - } - - @Override - public boolean isHearingAidProfileSupported() { - return mIsHearingAidProfileSupported; - } - - private boolean isDeviceProvisioned() { - return Settings.Global.getInt(mContentResolver, Settings.Global.DEVICE_PROVISIONED, - 0) != 0; - } - - // Monitor change of BLE scan only mode settings. - private void registerForProvisioningStateChange() { - ContentObserver contentObserver = new ContentObserver(null) { - @Override - public void onChange(boolean selfChange) { - if (!isDeviceProvisioned()) { - if (DBG) { - Slog.d(TAG, "DEVICE_PROVISIONED setting changed, but device is not " - + "provisioned"); - } - return; - } - if (mHandler.hasMessages(MESSAGE_INIT_FLAGS_CHANGED)) { - Slog.i(TAG, "Device provisioned, reactivating pending flag changes"); - onInitFlagsChanged(); - } - } - }; - - mContentResolver.registerContentObserver( - Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), false, - contentObserver); - } - - // Monitor change of BLE scan only mode settings. - private void registerForBleScanModeChange() { - ContentObserver contentObserver = new ContentObserver(null) { - @Override - public void onChange(boolean selfChange) { - if (isBleScanAlwaysAvailable()) { - // Nothing to do - return; - } - // BLE scan is not available. - disableBleScanMode(); - clearBleApps(); - try { - mBluetoothLock.readLock().lock(); - if (mBluetooth != null) { - addActiveLog(BluetoothProtoEnums.ENABLE_DISABLE_REASON_APPLICATION_REQUEST, - mContext.getPackageName(), false); - mBluetooth.onBrEdrDown(mContext.getAttributionSource()); - } - } catch (RemoteException e) { - Slog.e(TAG, "error when disabling bluetooth", e); - } finally { - mBluetoothLock.readLock().unlock(); - } - } - }; - - mContentResolver.registerContentObserver( - Settings.Global.getUriFor(Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE), false, - contentObserver); - } - - // Disable ble scan only mode. - private void disableBleScanMode() { - try { - mBluetoothLock.writeLock().lock(); - if (mBluetooth != null && (mBluetooth.getState() != BluetoothAdapter.STATE_ON)) { - if (DBG) { - Slog.d(TAG, "Reseting the mEnable flag for clean disable"); - } - mEnable = false; - } - } catch (RemoteException e) { - Slog.e(TAG, "getState()", e); - } finally { - mBluetoothLock.writeLock().unlock(); - } - } - - private int updateBleAppCount(IBinder token, boolean enable, String packageName) { - ClientDeathRecipient r = mBleApps.get(token); - if (r == null && enable) { - ClientDeathRecipient deathRec = new ClientDeathRecipient(packageName); - try { - token.linkToDeath(deathRec, 0); - } catch (RemoteException ex) { - throw new IllegalArgumentException("BLE app (" + packageName + ") already dead!"); - } - mBleApps.put(token, deathRec); - if (DBG) { - Slog.d(TAG, "Registered for death of " + packageName); - } - } else if (!enable && r != null) { - // Unregister death recipient as the app goes away. - token.unlinkToDeath(r, 0); - mBleApps.remove(token); - if (DBG) { - Slog.d(TAG, "Unregistered for death of " + packageName); - } - } - int appCount = mBleApps.size(); - if (DBG) { - Slog.d(TAG, appCount + " registered Ble Apps"); - } - return appCount; - } - - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - private boolean checkBluetoothPermissions(AttributionSource attributionSource, String message, - boolean requireForeground) { - if (isBluetoothDisallowed()) { - if (DBG) { - Slog.d(TAG, "checkBluetoothPermissions: bluetooth disallowed"); - } - return false; - } - // Check if packageName belongs to callingUid - final int callingUid = Binder.getCallingUid(); - final boolean isCallerSystem = UserHandle.getAppId(callingUid) == Process.SYSTEM_UID; - if (!isCallerSystem) { - checkPackage(callingUid, attributionSource.getPackageName()); - - if (requireForeground && !checkIfCallerIsForegroundUser()) { - Slog.w(TAG, "Not allowed for non-active and non system user"); - return false; - } - - if (!checkConnectPermissionForDataDelivery(mContext, attributionSource, message)) { - return false; - } - } - return true; - } - - public boolean enableBle(AttributionSource attributionSource, IBinder token) - throws RemoteException { - final String packageName = attributionSource.getPackageName(); - if (!checkBluetoothPermissions(attributionSource, "enableBle", false)) { - if (DBG) { - Slog.d(TAG, "enableBle(): bluetooth disallowed"); - } - return false; - } - - if (DBG) { - Slog.d(TAG, "enableBle(" + packageName + "): mBluetooth =" + mBluetooth - + " mBinding = " + mBinding + " mState = " - + BluetoothAdapter.nameForState(mState)); - } - updateBleAppCount(token, true, packageName); - - if (mState == BluetoothAdapter.STATE_ON - || mState == BluetoothAdapter.STATE_BLE_ON - || mState == BluetoothAdapter.STATE_TURNING_ON - || mState == BluetoothAdapter.STATE_TURNING_OFF - || mState == BluetoothAdapter.STATE_BLE_TURNING_ON) { - Log.d(TAG, "enableBLE(): Bluetooth is already enabled or is turning on"); - return true; - } - synchronized (mReceiver) { - // waive WRITE_SECURE_SETTINGS permission check - sendEnableMsg(false, BluetoothProtoEnums.ENABLE_DISABLE_REASON_APPLICATION_REQUEST, - packageName, true); - } - return true; - } - - @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) - public boolean disableBle(AttributionSource attributionSource, IBinder token) - throws RemoteException { - final String packageName = attributionSource.getPackageName(); - if (!checkBluetoothPermissions(attributionSource, "disableBle", false)) { - if (DBG) { - Slog.d(TAG, "disableBLE(): bluetooth disallowed"); - } - return false; - } - - if (DBG) { - Slog.d(TAG, "disableBle(" + packageName + "): mBluetooth =" + mBluetooth - + " mBinding = " + mBinding + " mState = " - + BluetoothAdapter.nameForState(mState)); - } - - if (mState == BluetoothAdapter.STATE_OFF) { - Slog.d(TAG, "disableBLE(): Already disabled"); - return false; - } - updateBleAppCount(token, false, packageName); - - if (mState == BluetoothAdapter.STATE_BLE_ON && !isBleAppPresent()) { - if (mEnable) { - disableBleScanMode(); - } - if (!mEnableExternal) { - addActiveLog(BluetoothProtoEnums.ENABLE_DISABLE_REASON_APPLICATION_REQUEST, - packageName, false); - sendBrEdrDownCallback(attributionSource); - } - } - return true; - } - - // Clear all apps using BLE scan only mode. - private void clearBleApps() { - mBleApps.clear(); - } - - /** @hide */ - public boolean isBleAppPresent() { - if (DBG) { - Slog.d(TAG, "isBleAppPresent() count: " + mBleApps.size()); - } - return mBleApps.size() > 0; - } - - /** - * Call IBluetooth.onLeServiceUp() to continue if Bluetooth should be on, - * call IBluetooth.onBrEdrDown() to disable if Bluetooth should be off. - */ - @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) - private void continueFromBleOnState() { - if (DBG) { - Slog.d(TAG, "continueFromBleOnState()"); - } - try { - mBluetoothLock.readLock().lock(); - if (mBluetooth == null) { - Slog.e(TAG, "onBluetoothServiceUp: mBluetooth is null!"); - return; - } - if (!mEnableExternal && !isBleAppPresent()) { - Slog.i(TAG, "Bluetooth was disabled while enabling BLE, disable BLE now"); - mEnable = false; - mBluetooth.onBrEdrDown(mContext.getAttributionSource()); - return; - } - if (isBluetoothPersistedStateOnBluetooth() || !isBleAppPresent()) { - // This triggers transition to STATE_ON - mBluetooth.onLeServiceUp(mContext.getAttributionSource()); - persistBluetoothSetting(BLUETOOTH_ON_BLUETOOTH); - } - } catch (RemoteException e) { - Slog.e(TAG, "Unable to call onServiceUp", e); - } finally { - mBluetoothLock.readLock().unlock(); - } - } - - /** - * Inform BluetoothAdapter instances that BREDR part is down - * and turn off all service and stack if no LE app needs it - */ - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - private void sendBrEdrDownCallback(AttributionSource attributionSource) { - if (DBG) { - Slog.d(TAG, "Calling sendBrEdrDownCallback callbacks"); - } - - if (mBluetooth == null) { - Slog.w(TAG, "Bluetooth handle is null"); - return; - } - - if (isBleAppPresent()) { - // Need to stay at BLE ON. Disconnect all Gatt connections - try { - mBluetoothGatt.unregAll(attributionSource); - } catch (RemoteException e) { - Slog.e(TAG, "Unable to disconnect all apps.", e); - } - } else { - try { - mBluetoothLock.readLock().lock(); - if (mBluetooth != null) { - mBluetooth.onBrEdrDown(attributionSource); - } - } catch (RemoteException e) { - Slog.e(TAG, "Call to onBrEdrDown() failed.", e); - } finally { - mBluetoothLock.readLock().unlock(); - } - } - - } - - public boolean enableNoAutoConnect(AttributionSource attributionSource) { - final String packageName = attributionSource.getPackageName(); - if (!checkBluetoothPermissions(attributionSource, "enableNoAutoConnect", false)) { - if (DBG) { - Slog.d(TAG, "enableNoAutoConnect(): not enabling - bluetooth disallowed"); - } - return false; - } - - if (DBG) { - Slog.d(TAG, "enableNoAutoConnect(): mBluetooth =" + mBluetooth + " mBinding = " - + mBinding); - } - - int callingAppId = UserHandle.getAppId(Binder.getCallingUid()); - if (callingAppId != Process.NFC_UID) { - throw new SecurityException("no permission to enable Bluetooth quietly"); - } - - synchronized (mReceiver) { - mQuietEnableExternal = true; - mEnableExternal = true; - sendEnableMsg(true, - BluetoothProtoEnums.ENABLE_DISABLE_REASON_APPLICATION_REQUEST, packageName); - } - return true; - } - - public boolean enable(AttributionSource attributionSource) throws RemoteException { - final String packageName = attributionSource.getPackageName(); - if (!checkBluetoothPermissions(attributionSource, "enable", true)) { - if (DBG) { - Slog.d(TAG, "enable(): not enabling - bluetooth disallowed"); - } - return false; - } - - final int callingUid = Binder.getCallingUid(); - final boolean callerSystem = UserHandle.getAppId(callingUid) == Process.SYSTEM_UID; - if (!callerSystem && !isEnabled() && mWirelessConsentRequired - && startConsentUiIfNeeded(packageName, - callingUid, BluetoothAdapter.ACTION_REQUEST_ENABLE)) { - return false; - } - - if (DBG) { - Slog.d(TAG, "enable(" + packageName + "): mBluetooth =" + mBluetooth + " mBinding = " - + mBinding + " mState = " + BluetoothAdapter.nameForState(mState)); - } - - synchronized (mReceiver) { - mQuietEnableExternal = false; - mEnableExternal = true; - // waive WRITE_SECURE_SETTINGS permission check - sendEnableMsg(false, - BluetoothProtoEnums.ENABLE_DISABLE_REASON_APPLICATION_REQUEST, packageName); - } - if (DBG) { - Slog.d(TAG, "enable returning"); - } - return true; - } - - public boolean disable(AttributionSource attributionSource, boolean persist) - throws RemoteException { - if (!persist) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, - "Need BLUETOOTH_PRIVILEGED permission"); - } - - final String packageName = attributionSource.getPackageName(); - if (!checkBluetoothPermissions(attributionSource, "disable", true)) { - if (DBG) { - Slog.d(TAG, "disable(): not disabling - bluetooth disallowed"); - } - return false; - } - - final int callingUid = Binder.getCallingUid(); - final boolean callerSystem = UserHandle.getAppId(callingUid) == Process.SYSTEM_UID; - if (!callerSystem && isEnabled() && mWirelessConsentRequired - && startConsentUiIfNeeded(packageName, - callingUid, BluetoothAdapter.ACTION_REQUEST_DISABLE)) { - return false; - } - - if (DBG) { - Slog.d(TAG, "disable(): mBluetooth = " + mBluetooth + " mBinding = " + mBinding); - } - - synchronized (mReceiver) { - if (!isBluetoothPersistedStateOnAirplane()) { - if (persist) { - persistBluetoothSetting(BLUETOOTH_OFF); - } - mEnableExternal = false; - } - sendDisableMsg(BluetoothProtoEnums.ENABLE_DISABLE_REASON_APPLICATION_REQUEST, - packageName); - } - return true; - } - - private boolean startConsentUiIfNeeded(String packageName, - int callingUid, String intentAction) throws RemoteException { - if (checkBluetoothPermissionWhenWirelessConsentRequired()) { - return false; - } - try { - // Validate the package only if we are going to use it - ApplicationInfo applicationInfo = mContext.getPackageManager() - .getApplicationInfoAsUser(packageName, - PackageManager.MATCH_DEBUG_TRIAGED_MISSING, - UserHandle.getUserId(callingUid)); - if (applicationInfo.uid != callingUid) { - throw new SecurityException("Package " + packageName - + " not in uid " + callingUid); - } - - Intent intent = new Intent(intentAction); - intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName); - intent.setFlags( - Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); - try { - mContext.startActivity(intent); - } catch (ActivityNotFoundException e) { - // Shouldn't happen - Slog.e(TAG, "Intent to handle action " + intentAction + " missing"); - return false; - } - return true; - } catch (PackageManager.NameNotFoundException e) { - throw new RemoteException(e.getMessage()); - } - } - - /** - * Check if AppOpsManager is available and the packageName belongs to uid - * - * A null package belongs to any uid - */ - private void checkPackage(int uid, String packageName) { - if (mAppOps == null) { - Slog.w(TAG, "checkPackage(): called before system boot up, uid " - + uid + ", packageName " + packageName); - throw new IllegalStateException("System has not boot yet"); - } - if (packageName == null) { - Slog.w(TAG, "checkPackage(): called with null packageName from " + uid); - return; - } - try { - mAppOps.checkPackage(uid, packageName); - } catch (SecurityException e) { - Slog.w(TAG, "checkPackage(): " + packageName + " does not belong to uid " + uid); - throw new SecurityException(e.getMessage()); - } - } - - /** - * Check if the caller must still pass permission check or if the caller is exempted - * from the consent UI via the MANAGE_BLUETOOTH_WHEN_WIRELESS_CONSENT_REQUIRED check. - * - * Commands from some callers may be exempted from triggering the consent UI when - * enabling bluetooth. This exemption is checked via the - * MANAGE_BLUETOOTH_WHEN_WIRELESS_CONSENT_REQUIRED and allows calls to skip - * the consent UI where it may otherwise be required. - * - * @hide - */ - @SuppressLint("AndroidFrameworkRequiresPermission") - private boolean checkBluetoothPermissionWhenWirelessConsentRequired() { - int result = mContext.checkCallingPermission( - android.Manifest.permission.MANAGE_BLUETOOTH_WHEN_WIRELESS_CONSENT_REQUIRED); - return result == PackageManager.PERMISSION_GRANTED; - } - - @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) - public void unbindAndFinish() { - if (DBG) { - Slog.d(TAG, "unbindAndFinish(): " + mBluetooth + " mBinding = " + mBinding - + " mUnbinding = " + mUnbinding); - } - - try { - mBluetoothLock.writeLock().lock(); - if (mUnbinding) { - return; - } - mUnbinding = true; - mHandler.removeMessages(MESSAGE_BLUETOOTH_STATE_CHANGE); - mHandler.removeMessages(MESSAGE_BIND_PROFILE_SERVICE); - if (mBluetooth != null) { - //Unregister callback object - try { - mBluetooth.unregisterCallback(mBluetoothCallback, - mContext.getAttributionSource()); - } catch (RemoteException re) { - Slog.e(TAG, "Unable to unregister BluetoothCallback", re); - } - mBluetoothBinder = null; - mBluetooth = null; - mContext.unbindService(mConnection); - mUnbinding = false; - mBinding = false; - } else { - mUnbinding = false; - } - mBluetoothGatt = null; - } finally { - mBluetoothLock.writeLock().unlock(); - } - } - - public IBluetoothGatt getBluetoothGatt() { - // sync protection - return mBluetoothGatt; - } - - @Override - public boolean bindBluetoothProfileService(int bluetoothProfile, - IBluetoothProfileServiceConnection proxy) { - if (mState != BluetoothAdapter.STATE_ON) { - if (DBG) { - Slog.d(TAG, "Trying to bind to profile: " + bluetoothProfile - + ", while Bluetooth was disabled"); - } - return false; - } - synchronized (mProfileServices) { - ProfileServiceConnections psc = mProfileServices.get(new Integer(bluetoothProfile)); - if (psc == null) { - if (DBG) { - Slog.d(TAG, "Creating new ProfileServiceConnections object for" + " profile: " - + bluetoothProfile); - } - - Intent intent; - if (bluetoothProfile == BluetoothProfile.HEADSET) { - intent = new Intent(IBluetoothHeadset.class.getName()); - } else if (bluetoothProfile== BluetoothProfile.LE_CALL_CONTROL) { - intent = new Intent(IBluetoothLeCallControl.class.getName()); - } else { - return false; - } - - psc = new ProfileServiceConnections(intent); - if (!psc.bindService()) { - return false; - } - - mProfileServices.put(new Integer(bluetoothProfile), psc); - } - } - - // Introducing a delay to give the client app time to prepare - Message addProxyMsg = mHandler.obtainMessage(MESSAGE_ADD_PROXY_DELAYED); - addProxyMsg.arg1 = bluetoothProfile; - addProxyMsg.obj = proxy; - mHandler.sendMessageDelayed(addProxyMsg, ADD_PROXY_DELAY_MS); - return true; - } - - @Override - public void unbindBluetoothProfileService(int bluetoothProfile, - IBluetoothProfileServiceConnection proxy) { - synchronized (mProfileServices) { - Integer profile = new Integer(bluetoothProfile); - ProfileServiceConnections psc = mProfileServices.get(profile); - if (psc == null) { - return; - } - psc.removeProxy(proxy); - if (psc.isEmpty()) { - // All prxoies are disconnected, unbind with the service. - try { - mContext.unbindService(psc); - } catch (IllegalArgumentException e) { - Slog.e(TAG, "Unable to unbind service with intent: " + psc.mIntent, e); - } - mProfileServices.remove(profile); - } - } - } - - private void unbindAllBluetoothProfileServices() { - synchronized (mProfileServices) { - for (Integer i : mProfileServices.keySet()) { - ProfileServiceConnections psc = mProfileServices.get(i); - try { - mContext.unbindService(psc); - } catch (IllegalArgumentException e) { - Slog.e(TAG, "Unable to unbind service with intent: " + psc.mIntent, e); - } - psc.removeAllProxies(); - } - mProfileServices.clear(); - } - } - - /** - * Send enable message and set adapter name and address. Called when the boot phase becomes - * PHASE_SYSTEM_SERVICES_READY. - */ - public void handleOnBootPhase() { - if (DBG) { - Slog.d(TAG, "Bluetooth boot completed"); - } - mAppOps = mContext.getSystemService(AppOpsManager.class); - UserManagerInternal userManagerInternal = - LocalServices.getService(UserManagerInternal.class); - userManagerInternal.addUserRestrictionsListener(mUserRestrictionsListener); - final boolean isBluetoothDisallowed = isBluetoothDisallowed(); - if (isBluetoothDisallowed) { - return; - } - final boolean isSafeMode = mContext.getPackageManager().isSafeMode(); - if (mEnableExternal && isBluetoothPersistedStateOnBluetooth() && !isSafeMode) { - if (DBG) { - Slog.d(TAG, "Auto-enabling Bluetooth."); - } - sendEnableMsg(mQuietEnableExternal, - BluetoothProtoEnums.ENABLE_DISABLE_REASON_SYSTEM_BOOT, - mContext.getPackageName()); - } else if (!isNameAndAddressSet()) { - if (DBG) { - Slog.d(TAG, "Getting adapter name and address"); - } - Message getMsg = mHandler.obtainMessage(MESSAGE_GET_NAME_AND_ADDRESS); - mHandler.sendMessage(getMsg); - } - - mBluetoothModeChangeHelper = new BluetoothModeChangeHelper(mContext); - if (mBluetoothAirplaneModeListener != null) { - mBluetoothAirplaneModeListener.start(mBluetoothModeChangeHelper); - } - registerForProvisioningStateChange(); - mBluetoothDeviceConfigListener = new BluetoothDeviceConfigListener(this, DBG); - } - - /** - * Called when switching to a different foreground user. - */ - public void handleOnSwitchUser(int userHandle) { - if (DBG) { - Slog.d(TAG, "User " + userHandle + " switched"); - } - mHandler.obtainMessage(MESSAGE_USER_SWITCHED, userHandle, 0).sendToTarget(); - } - - /** - * Called when user is unlocked. - */ - public void handleOnUnlockUser(int userHandle) { - if (DBG) { - Slog.d(TAG, "User " + userHandle + " unlocked"); - } - mHandler.obtainMessage(MESSAGE_USER_UNLOCKED, userHandle, 0).sendToTarget(); - } - - /** - * This class manages the clients connected to a given ProfileService - * and maintains the connection with that service. - */ - private final class ProfileServiceConnections - implements ServiceConnection, IBinder.DeathRecipient { - final RemoteCallbackList<IBluetoothProfileServiceConnection> mProxies = - new RemoteCallbackList<IBluetoothProfileServiceConnection>(); - IBinder mService; - ComponentName mClassName; - Intent mIntent; - boolean mInvokingProxyCallbacks = false; - - ProfileServiceConnections(Intent intent) { - mService = null; - mClassName = null; - mIntent = intent; - } - - private boolean bindService() { - int state = BluetoothAdapter.STATE_OFF; - try { - mBluetoothLock.readLock().lock(); - if (mBluetooth != null) { - state = mBluetooth.getState(); - } - } catch (RemoteException e) { - Slog.e(TAG, "Unable to call getState", e); - return false; - } finally { - mBluetoothLock.readLock().unlock(); - } - - if (state != BluetoothAdapter.STATE_ON) { - if (DBG) { - Slog.d(TAG, "Unable to bindService while Bluetooth is disabled"); - } - return false; - } - - if (mIntent != null && mService == null && doBind(mIntent, this, 0, - UserHandle.CURRENT_OR_SELF)) { - Message msg = mHandler.obtainMessage(MESSAGE_BIND_PROFILE_SERVICE); - msg.obj = this; - mHandler.sendMessageDelayed(msg, TIMEOUT_BIND_MS); - return true; - } - Slog.w(TAG, "Unable to bind with intent: " + mIntent); - return false; - } - - private void addProxy(IBluetoothProfileServiceConnection proxy) { - mProxies.register(proxy); - if (mService != null) { - try { - proxy.onServiceConnected(mClassName, mService); - } catch (RemoteException e) { - Slog.e(TAG, "Unable to connect to proxy", e); - } - } else { - if (!mHandler.hasMessages(MESSAGE_BIND_PROFILE_SERVICE, this)) { - Message msg = mHandler.obtainMessage(MESSAGE_BIND_PROFILE_SERVICE); - msg.obj = this; - mHandler.sendMessage(msg); - } - } - } - - private void removeProxy(IBluetoothProfileServiceConnection proxy) { - if (proxy != null) { - if (mProxies.unregister(proxy)) { - try { - proxy.onServiceDisconnected(mClassName); - } catch (RemoteException e) { - Slog.e(TAG, "Unable to disconnect proxy", e); - } - } - } else { - Slog.w(TAG, "Trying to remove a null proxy"); - } - } - - private void removeAllProxies() { - onServiceDisconnected(mClassName); - mProxies.kill(); - } - - private boolean isEmpty() { - return mProxies.getRegisteredCallbackCount() == 0; - } - - @Override - public void onServiceConnected(ComponentName className, IBinder service) { - // remove timeout message - mHandler.removeMessages(MESSAGE_BIND_PROFILE_SERVICE, this); - mService = service; - mClassName = className; - try { - mService.linkToDeath(this, 0); - } catch (RemoteException e) { - Slog.e(TAG, "Unable to linkToDeath", e); - } - - if (mInvokingProxyCallbacks) { - Slog.e(TAG, "Proxy callbacks already in progress."); - return; - } - mInvokingProxyCallbacks = true; - - final int n = mProxies.beginBroadcast(); - try { - for (int i = 0; i < n; i++) { - try { - mProxies.getBroadcastItem(i).onServiceConnected(className, service); - } catch (RemoteException e) { - Slog.e(TAG, "Unable to connect to proxy", e); - } - } - } finally { - mProxies.finishBroadcast(); - mInvokingProxyCallbacks = false; - } - } - - @Override - public void onServiceDisconnected(ComponentName className) { - if (mService == null) { - return; - } - try { - mService.unlinkToDeath(this, 0); - } catch (NoSuchElementException e) { - Log.e(TAG, "error unlinking to death", e); - } - mService = null; - mClassName = null; - - if (mInvokingProxyCallbacks) { - Slog.e(TAG, "Proxy callbacks already in progress."); - return; - } - mInvokingProxyCallbacks = true; - - final int n = mProxies.beginBroadcast(); - try { - for (int i = 0; i < n; i++) { - try { - mProxies.getBroadcastItem(i).onServiceDisconnected(className); - } catch (RemoteException e) { - Slog.e(TAG, "Unable to disconnect from proxy", e); - } - } - } finally { - mProxies.finishBroadcast(); - mInvokingProxyCallbacks = false; - } - } - - @Override - public void binderDied() { - if (DBG) { - Slog.w(TAG, "Profile service for profile: " + mClassName + " died."); - } - onServiceDisconnected(mClassName); - // Trigger rebind - Message msg = mHandler.obtainMessage(MESSAGE_BIND_PROFILE_SERVICE); - msg.obj = this; - mHandler.sendMessageDelayed(msg, TIMEOUT_BIND_MS); - } - } - - private void sendBluetoothStateCallback(boolean isUp) { - try { - int n = mStateChangeCallbacks.beginBroadcast(); - if (DBG) { - Slog.d(TAG, "Broadcasting onBluetoothStateChange(" + isUp + ") to " + n - + " receivers."); - } - for (int i = 0; i < n; i++) { - try { - mStateChangeCallbacks.getBroadcastItem(i).onBluetoothStateChange(isUp); - } catch (RemoteException e) { - Slog.e(TAG, "Unable to call onBluetoothStateChange() on callback #" + i, e); - } - } - } finally { - mStateChangeCallbacks.finishBroadcast(); - } - } - - /** - * Inform BluetoothAdapter instances that Adapter service is up - */ - private void sendBluetoothServiceUpCallback() { - synchronized (mCallbacks) { - try { - int n = mCallbacks.beginBroadcast(); - Slog.d(TAG, "Broadcasting onBluetoothServiceUp() to " + n + " receivers."); - for (int i = 0; i < n; i++) { - try { - mCallbacks.getBroadcastItem(i).onBluetoothServiceUp(mBluetooth); - } catch (RemoteException e) { - Slog.e(TAG, "Unable to call onBluetoothServiceUp() on callback #" + i, e); - } - } - } finally { - mCallbacks.finishBroadcast(); - } - } - } - - /** - * Inform BluetoothAdapter instances that Adapter service is down - */ - private void sendBluetoothServiceDownCallback() { - synchronized (mCallbacks) { - try { - int n = mCallbacks.beginBroadcast(); - Slog.d(TAG, "Broadcasting onBluetoothServiceDown() to " + n + " receivers."); - for (int i = 0; i < n; i++) { - try { - mCallbacks.getBroadcastItem(i).onBluetoothServiceDown(); - } catch (RemoteException e) { - Slog.e(TAG, "Unable to call onBluetoothServiceDown() on callback #" + i, e); - } - } - } finally { - mCallbacks.finishBroadcast(); - } - } - } - - public String getAddress(AttributionSource attributionSource) { - if (!checkConnectPermissionForDataDelivery(mContext, attributionSource, "getAddress")) { - return null; - } - - if ((Binder.getCallingUid() != Process.SYSTEM_UID) && (!checkIfCallerIsForegroundUser())) { - Slog.w(TAG, "getAddress(): not allowed for non-active and non system user"); - return null; - } - - if (mContext.checkCallingOrSelfPermission(Manifest.permission.LOCAL_MAC_ADDRESS) - != PackageManager.PERMISSION_GRANTED) { - return BluetoothAdapter.DEFAULT_MAC_ADDRESS; - } - - try { - mBluetoothLock.readLock().lock(); - if (mBluetooth != null) { - return mBluetooth.getAddressWithAttribution(attributionSource); - } - } catch (RemoteException e) { - Slog.e(TAG, - "getAddress(): Unable to retrieve address remotely. Returning cached address", - e); - } finally { - mBluetoothLock.readLock().unlock(); - } - - // mAddress is accessed from outside. - // It is alright without a lock. Here, bluetooth is off, no other thread is - // changing mAddress - return mAddress; - } - - public String getName(AttributionSource attributionSource) { - if (!checkConnectPermissionForDataDelivery(mContext, attributionSource, "getName")) { - return null; - } - - if ((Binder.getCallingUid() != Process.SYSTEM_UID) && (!checkIfCallerIsForegroundUser())) { - Slog.w(TAG, "getName(): not allowed for non-active and non system user"); - return null; - } - - try { - mBluetoothLock.readLock().lock(); - if (mBluetooth != null) { - return mBluetooth.getName(attributionSource); - } - } catch (RemoteException e) { - Slog.e(TAG, "getName(): Unable to retrieve name remotely. Returning cached name", e); - } finally { - mBluetoothLock.readLock().unlock(); - } - - // mName is accessed from outside. - // It alright without a lock. Here, bluetooth is off, no other thread is - // changing mName - return mName; - } - - private class BluetoothServiceConnection implements ServiceConnection { - public void onServiceConnected(ComponentName componentName, IBinder service) { - String name = componentName.getClassName(); - if (DBG) { - Slog.d(TAG, "BluetoothServiceConnection: " + name); - } - Message msg = mHandler.obtainMessage(MESSAGE_BLUETOOTH_SERVICE_CONNECTED); - if (name.equals("com.android.bluetooth.btservice.AdapterService")) { - msg.arg1 = SERVICE_IBLUETOOTH; - } else if (name.equals("com.android.bluetooth.gatt.GattService")) { - msg.arg1 = SERVICE_IBLUETOOTHGATT; - } else { - Slog.e(TAG, "Unknown service connected: " + name); - return; - } - msg.obj = service; - mHandler.sendMessage(msg); - } - - public void onServiceDisconnected(ComponentName componentName) { - // Called if we unexpectedly disconnect. - String name = componentName.getClassName(); - if (DBG) { - Slog.d(TAG, "BluetoothServiceConnection, disconnected: " + name); - } - Message msg = mHandler.obtainMessage(MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED); - if (name.equals("com.android.bluetooth.btservice.AdapterService")) { - msg.arg1 = SERVICE_IBLUETOOTH; - } else if (name.equals("com.android.bluetooth.gatt.GattService")) { - msg.arg1 = SERVICE_IBLUETOOTHGATT; - } else { - Slog.e(TAG, "Unknown service disconnected: " + name); - return; - } - mHandler.sendMessage(msg); - } - } - - private BluetoothServiceConnection mConnection = new BluetoothServiceConnection(); - - private class BluetoothHandler extends Handler { - boolean mGetNameAddressOnly = false; - private int mWaitForEnableRetry; - private int mWaitForDisableRetry; - - BluetoothHandler(Looper looper) { - super(looper); - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MESSAGE_GET_NAME_AND_ADDRESS: - if (DBG) { - Slog.d(TAG, "MESSAGE_GET_NAME_AND_ADDRESS"); - } - try { - mBluetoothLock.writeLock().lock(); - if ((mBluetooth == null) && (!mBinding)) { - if (DBG) { - Slog.d(TAG, "Binding to service to get name and address"); - } - mGetNameAddressOnly = true; - Message timeoutMsg = mHandler.obtainMessage(MESSAGE_TIMEOUT_BIND); - mHandler.sendMessageDelayed(timeoutMsg, TIMEOUT_BIND_MS); - Intent i = new Intent(IBluetooth.class.getName()); - if (!doBind(i, mConnection, - Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT, - UserHandle.CURRENT)) { - mHandler.removeMessages(MESSAGE_TIMEOUT_BIND); - } else { - mBinding = true; - } - } else if (mBluetooth != null) { - try { - storeNameAndAddress( - mBluetooth.getName(mContext.getAttributionSource()), - mBluetooth.getAddressWithAttribution( - mContext.getAttributionSource())); - } catch (RemoteException re) { - Slog.e(TAG, "Unable to grab names", re); - } - if (mGetNameAddressOnly && !mEnable) { - unbindAndFinish(); - } - mGetNameAddressOnly = false; - } - } finally { - mBluetoothLock.writeLock().unlock(); - } - break; - - case MESSAGE_ENABLE: - int quietEnable = msg.arg1; - int isBle = msg.arg2; - if (mHandler.hasMessages(MESSAGE_HANDLE_DISABLE_DELAYED) - || mHandler.hasMessages(MESSAGE_HANDLE_ENABLE_DELAYED)) { - // We are handling enable or disable right now, wait for it. - mHandler.sendMessageDelayed(mHandler.obtainMessage(MESSAGE_ENABLE, - quietEnable, isBle), ENABLE_DISABLE_DELAY_MS); - break; - } - - if (DBG) { - Slog.d(TAG, "MESSAGE_ENABLE(" + quietEnable + "): mBluetooth = " - + mBluetooth); - } - mHandler.removeMessages(MESSAGE_RESTART_BLUETOOTH_SERVICE); - mEnable = true; - - if (isBle == 0) { - persistBluetoothSetting(BLUETOOTH_ON_BLUETOOTH); - } - - // Use service interface to get the exact state - try { - mBluetoothLock.readLock().lock(); - if (mBluetooth != null) { - boolean isHandled = true; - int state = mBluetooth.getState(); - switch (state) { - case BluetoothAdapter.STATE_BLE_ON: - if (isBle == 1) { - Slog.i(TAG, "Already at BLE_ON State"); - } else { - Slog.w(TAG, "BT Enable in BLE_ON State, going to ON"); - mBluetooth.onLeServiceUp(mContext.getAttributionSource()); - } - break; - case BluetoothAdapter.STATE_BLE_TURNING_ON: - case BluetoothAdapter.STATE_TURNING_ON: - case BluetoothAdapter.STATE_ON: - Slog.i(TAG, "MESSAGE_ENABLE: already enabled"); - break; - default: - isHandled = false; - break; - } - if (isHandled) break; - } - } catch (RemoteException e) { - Slog.e(TAG, "", e); - } finally { - mBluetoothLock.readLock().unlock(); - } - - mQuietEnable = (quietEnable == 1); - if (mBluetooth == null) { - handleEnable(mQuietEnable); - } else { - // - // We need to wait until transitioned to STATE_OFF and - // the previous Bluetooth process has exited. The - // waiting period has three components: - // (a) Wait until the local state is STATE_OFF. This - // is accomplished by sending delay a message - // MESSAGE_HANDLE_ENABLE_DELAYED - // (b) Wait until the STATE_OFF state is updated to - // all components. - // (c) Wait until the Bluetooth process exits, and - // ActivityManager detects it. - // The waiting for (b) and (c) is accomplished by - // delaying the MESSAGE_RESTART_BLUETOOTH_SERVICE - // message. The delay time is backed off if Bluetooth - // continuously failed to turn on itself. - // - mWaitForEnableRetry = 0; - Message enableDelayedMsg = - mHandler.obtainMessage(MESSAGE_HANDLE_ENABLE_DELAYED); - mHandler.sendMessageDelayed(enableDelayedMsg, ENABLE_DISABLE_DELAY_MS); - } - break; - - case MESSAGE_DISABLE: - if (mHandler.hasMessages(MESSAGE_HANDLE_DISABLE_DELAYED) || mBinding - || mHandler.hasMessages(MESSAGE_HANDLE_ENABLE_DELAYED)) { - // We are handling enable or disable right now, wait for it. - mHandler.sendMessageDelayed(mHandler.obtainMessage(MESSAGE_DISABLE), - ENABLE_DISABLE_DELAY_MS); - break; - } - - if (DBG) { - Slog.d(TAG, "MESSAGE_DISABLE: mBluetooth = " + mBluetooth - + ", mBinding = " + mBinding); - } - mHandler.removeMessages(MESSAGE_RESTART_BLUETOOTH_SERVICE); - - if (mEnable && mBluetooth != null) { - mWaitForDisableRetry = 0; - Message disableDelayedMsg = - mHandler.obtainMessage(MESSAGE_HANDLE_DISABLE_DELAYED, 0, 0); - mHandler.sendMessageDelayed(disableDelayedMsg, ENABLE_DISABLE_DELAY_MS); - } else { - mEnable = false; - handleDisable(); - } - break; - - case MESSAGE_HANDLE_ENABLE_DELAYED: { - // The Bluetooth is turning off, wait for STATE_OFF - if (mState != BluetoothAdapter.STATE_OFF) { - if (mWaitForEnableRetry < MAX_WAIT_FOR_ENABLE_DISABLE_RETRIES) { - mWaitForEnableRetry++; - Message enableDelayedMsg = - mHandler.obtainMessage(MESSAGE_HANDLE_ENABLE_DELAYED); - mHandler.sendMessageDelayed(enableDelayedMsg, ENABLE_DISABLE_DELAY_MS); - break; - } else { - Slog.e(TAG, "Wait for STATE_OFF timeout"); - } - } - // Either state is changed to STATE_OFF or reaches the maximum retry, we - // should move forward to the next step. - mWaitForEnableRetry = 0; - Message restartMsg = - mHandler.obtainMessage(MESSAGE_RESTART_BLUETOOTH_SERVICE); - mHandler.sendMessageDelayed(restartMsg, getServiceRestartMs()); - Slog.d(TAG, "Handle enable is finished"); - break; - } - - case MESSAGE_HANDLE_DISABLE_DELAYED: { - boolean disabling = (msg.arg1 == 1); - Slog.d(TAG, "MESSAGE_HANDLE_DISABLE_DELAYED: disabling:" + disabling); - if (!disabling) { - // The Bluetooth is turning on, wait for STATE_ON - if (mState != BluetoothAdapter.STATE_ON) { - if (mWaitForDisableRetry < MAX_WAIT_FOR_ENABLE_DISABLE_RETRIES) { - mWaitForDisableRetry++; - Message disableDelayedMsg = mHandler.obtainMessage( - MESSAGE_HANDLE_DISABLE_DELAYED, 0, 0); - mHandler.sendMessageDelayed(disableDelayedMsg, - ENABLE_DISABLE_DELAY_MS); - break; - } else { - Slog.e(TAG, "Wait for STATE_ON timeout"); - } - } - // Either state is changed to STATE_ON or reaches the maximum retry, we - // should move forward to the next step. - mWaitForDisableRetry = 0; - mEnable = false; - handleDisable(); - // Wait for state exiting STATE_ON - Message disableDelayedMsg = - mHandler.obtainMessage(MESSAGE_HANDLE_DISABLE_DELAYED, 1, 0); - mHandler.sendMessageDelayed(disableDelayedMsg, ENABLE_DISABLE_DELAY_MS); - } else { - // The Bluetooth is turning off, wait for exiting STATE_ON - if (mState == BluetoothAdapter.STATE_ON) { - if (mWaitForDisableRetry < MAX_WAIT_FOR_ENABLE_DISABLE_RETRIES) { - mWaitForDisableRetry++; - Message disableDelayedMsg = mHandler.obtainMessage( - MESSAGE_HANDLE_DISABLE_DELAYED, 1, 0); - mHandler.sendMessageDelayed(disableDelayedMsg, - ENABLE_DISABLE_DELAY_MS); - break; - } else { - Slog.e(TAG, "Wait for exiting STATE_ON timeout"); - } - } - // Either state is exited from STATE_ON or reaches the maximum retry, we - // should move forward to the next step. - Slog.d(TAG, "Handle disable is finished"); - } - break; - } - - case MESSAGE_RESTORE_USER_SETTING: - if ((msg.arg1 == RESTORE_SETTING_TO_OFF) && mEnable) { - if (DBG) { - Slog.d(TAG, "Restore Bluetooth state to disabled"); - } - persistBluetoothSetting(BLUETOOTH_OFF); - mEnableExternal = false; - sendDisableMsg( - BluetoothProtoEnums.ENABLE_DISABLE_REASON_RESTORE_USER_SETTING, - mContext.getPackageName()); - } else if ((msg.arg1 == RESTORE_SETTING_TO_ON) && !mEnable) { - if (DBG) { - Slog.d(TAG, "Restore Bluetooth state to enabled"); - } - mQuietEnableExternal = false; - mEnableExternal = true; - // waive WRITE_SECURE_SETTINGS permission check - sendEnableMsg(false, - BluetoothProtoEnums.ENABLE_DISABLE_REASON_RESTORE_USER_SETTING, - mContext.getPackageName()); - } - break; - case MESSAGE_REGISTER_STATE_CHANGE_CALLBACK: { - IBluetoothStateChangeCallback callback = - (IBluetoothStateChangeCallback) msg.obj; - mStateChangeCallbacks.register(callback); - break; - } - case MESSAGE_UNREGISTER_STATE_CHANGE_CALLBACK: { - IBluetoothStateChangeCallback callback = - (IBluetoothStateChangeCallback) msg.obj; - mStateChangeCallbacks.unregister(callback); - break; - } - case MESSAGE_ADD_PROXY_DELAYED: { - ProfileServiceConnections psc = mProfileServices.get(msg.arg1); - if (psc == null) { - break; - } - IBluetoothProfileServiceConnection proxy = - (IBluetoothProfileServiceConnection) msg.obj; - psc.addProxy(proxy); - break; - } - case MESSAGE_BIND_PROFILE_SERVICE: { - ProfileServiceConnections psc = (ProfileServiceConnections) msg.obj; - removeMessages(MESSAGE_BIND_PROFILE_SERVICE, msg.obj); - if (psc == null) { - break; - } - psc.bindService(); - break; - } - case MESSAGE_BLUETOOTH_SERVICE_CONNECTED: { - if (DBG) { - Slog.d(TAG, "MESSAGE_BLUETOOTH_SERVICE_CONNECTED: " + msg.arg1); - } - - IBinder service = (IBinder) msg.obj; - try { - mBluetoothLock.writeLock().lock(); - if (msg.arg1 == SERVICE_IBLUETOOTHGATT) { - mBluetoothGatt = - IBluetoothGatt.Stub.asInterface(Binder.allowBlocking(service)); - continueFromBleOnState(); - break; - } // else must be SERVICE_IBLUETOOTH - - //Remove timeout - mHandler.removeMessages(MESSAGE_TIMEOUT_BIND); - - mBinding = false; - mBluetoothBinder = service; - mBluetooth = IBluetooth.Stub.asInterface(Binder.allowBlocking(service)); - - if (!isNameAndAddressSet()) { - Message getMsg = mHandler.obtainMessage(MESSAGE_GET_NAME_AND_ADDRESS); - mHandler.sendMessage(getMsg); - if (mGetNameAddressOnly) { - return; - } - } - - //Register callback object - try { - mBluetooth.registerCallback(mBluetoothCallback, - mContext.getAttributionSource()); - } catch (RemoteException re) { - Slog.e(TAG, "Unable to register BluetoothCallback", re); - } - //Inform BluetoothAdapter instances that service is up - sendBluetoothServiceUpCallback(); - - //Do enable request - try { - if (!mBluetooth.enable(mQuietEnable, mContext.getAttributionSource())) { - Slog.e(TAG, "IBluetooth.enable() returned false"); - } - } catch (RemoteException e) { - Slog.e(TAG, "Unable to call enable()", e); - } - } finally { - mBluetoothLock.writeLock().unlock(); - } - - if (!mEnable) { - waitForState(Set.of(BluetoothAdapter.STATE_ON)); - handleDisable(); - waitForState(Set.of(BluetoothAdapter.STATE_OFF, - BluetoothAdapter.STATE_TURNING_ON, - BluetoothAdapter.STATE_TURNING_OFF, - BluetoothAdapter.STATE_BLE_TURNING_ON, - BluetoothAdapter.STATE_BLE_ON, - BluetoothAdapter.STATE_BLE_TURNING_OFF)); - } - break; - } - case MESSAGE_BLUETOOTH_STATE_CHANGE: { - int prevState = msg.arg1; - int newState = msg.arg2; - if (DBG) { - Slog.d(TAG, - "MESSAGE_BLUETOOTH_STATE_CHANGE: " + BluetoothAdapter.nameForState( - prevState) + " > " + BluetoothAdapter.nameForState( - newState)); - } - mState = newState; - bluetoothStateChangeHandler(prevState, newState); - // handle error state transition case from TURNING_ON to OFF - // unbind and rebind bluetooth service and enable bluetooth - if ((prevState == BluetoothAdapter.STATE_BLE_TURNING_ON) && (newState - == BluetoothAdapter.STATE_OFF) && (mBluetooth != null) && mEnable) { - recoverBluetoothServiceFromError(false); - } - if ((prevState == BluetoothAdapter.STATE_TURNING_ON) && (newState - == BluetoothAdapter.STATE_BLE_ON) && (mBluetooth != null) && mEnable) { - recoverBluetoothServiceFromError(true); - } - // If we tried to enable BT while BT was in the process of shutting down, - // wait for the BT process to fully tear down and then force a restart - // here. This is a bit of a hack (b/29363429). - if ((prevState == BluetoothAdapter.STATE_BLE_TURNING_OFF) && (newState - == BluetoothAdapter.STATE_OFF)) { - if (mEnable) { - Slog.d(TAG, "Entering STATE_OFF but mEnabled is true; restarting."); - waitForState(Set.of(BluetoothAdapter.STATE_OFF)); - Message restartMsg = - mHandler.obtainMessage(MESSAGE_RESTART_BLUETOOTH_SERVICE); - mHandler.sendMessageDelayed(restartMsg, getServiceRestartMs()); - } - } - if (newState == BluetoothAdapter.STATE_ON - || newState == BluetoothAdapter.STATE_BLE_ON) { - // bluetooth is working, reset the counter - if (mErrorRecoveryRetryCounter != 0) { - Slog.w(TAG, "bluetooth is recovered from error"); - mErrorRecoveryRetryCounter = 0; - } - } - break; - } - case MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED: { - Slog.e(TAG, "MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED(" + msg.arg1 + ")"); - try { - mBluetoothLock.writeLock().lock(); - if (msg.arg1 == SERVICE_IBLUETOOTH) { - // if service is unbinded already, do nothing and return - if (mBluetooth == null) { - break; - } - mBluetooth = null; - } else if (msg.arg1 == SERVICE_IBLUETOOTHGATT) { - mBluetoothGatt = null; - break; - } else { - Slog.e(TAG, "Unknown argument for service disconnect!"); - break; - } - } finally { - mBluetoothLock.writeLock().unlock(); - } - - // log the unexpected crash - addCrashLog(); - addActiveLog(BluetoothProtoEnums.ENABLE_DISABLE_REASON_CRASH, - mContext.getPackageName(), false); - if (mEnable) { - mEnable = false; - // Send a Bluetooth Restart message - Message restartMsg = - mHandler.obtainMessage(MESSAGE_RESTART_BLUETOOTH_SERVICE); - mHandler.sendMessageDelayed(restartMsg, getServiceRestartMs()); - } - - sendBluetoothServiceDownCallback(); - - // Send BT state broadcast to update - // the BT icon correctly - if ((mState == BluetoothAdapter.STATE_TURNING_ON) || (mState - == BluetoothAdapter.STATE_ON)) { - bluetoothStateChangeHandler(BluetoothAdapter.STATE_ON, - BluetoothAdapter.STATE_TURNING_OFF); - mState = BluetoothAdapter.STATE_TURNING_OFF; - } - if (mState == BluetoothAdapter.STATE_TURNING_OFF) { - bluetoothStateChangeHandler(BluetoothAdapter.STATE_TURNING_OFF, - BluetoothAdapter.STATE_OFF); - } - - mHandler.removeMessages(MESSAGE_BLUETOOTH_STATE_CHANGE); - mState = BluetoothAdapter.STATE_OFF; - break; - } - case MESSAGE_RESTART_BLUETOOTH_SERVICE: { - mErrorRecoveryRetryCounter++; - Slog.d(TAG, "MESSAGE_RESTART_BLUETOOTH_SERVICE: retry count=" - + mErrorRecoveryRetryCounter); - if (mErrorRecoveryRetryCounter < MAX_ERROR_RESTART_RETRIES) { - /* Enable without persisting the setting as - it doesnt change when IBluetooth - service restarts */ - mEnable = true; - addActiveLog(BluetoothProtoEnums.ENABLE_DISABLE_REASON_RESTARTED, - mContext.getPackageName(), true); - handleEnable(mQuietEnable); - } else { - Slog.e(TAG, "Reach maximum retry to restart Bluetooth!"); - } - break; - } - case MESSAGE_TIMEOUT_BIND: { - Slog.e(TAG, "MESSAGE_TIMEOUT_BIND"); - mBluetoothLock.writeLock().lock(); - mBinding = false; - mBluetoothLock.writeLock().unlock(); - break; - } - case MESSAGE_TIMEOUT_UNBIND: { - Slog.e(TAG, "MESSAGE_TIMEOUT_UNBIND"); - mBluetoothLock.writeLock().lock(); - mUnbinding = false; - mBluetoothLock.writeLock().unlock(); - break; - } - - case MESSAGE_USER_SWITCHED: { - if (DBG) { - Slog.d(TAG, "MESSAGE_USER_SWITCHED"); - } - mHandler.removeMessages(MESSAGE_USER_SWITCHED); - - /* disable and enable BT when detect a user switch */ - if (mBluetooth != null && isEnabled()) { - restartForReason(BluetoothProtoEnums.ENABLE_DISABLE_REASON_USER_SWITCH); - } else if (mBinding || mBluetooth != null) { - Message userMsg = mHandler.obtainMessage(MESSAGE_USER_SWITCHED); - userMsg.arg2 = 1 + msg.arg2; - // if user is switched when service is binding retry after a delay - mHandler.sendMessageDelayed(userMsg, USER_SWITCHED_TIME_MS); - if (DBG) { - Slog.d(TAG, "Retry MESSAGE_USER_SWITCHED " + userMsg.arg2); - } - } - break; - } - case MESSAGE_USER_UNLOCKED: { - if (DBG) { - Slog.d(TAG, "MESSAGE_USER_UNLOCKED"); - } - mHandler.removeMessages(MESSAGE_USER_SWITCHED); - - if (mEnable && !mBinding && (mBluetooth == null)) { - // We should be connected, but we gave up for some - // reason; maybe the Bluetooth service wasn't encryption - // aware, so try binding again. - if (DBG) { - Slog.d(TAG, "Enabled but not bound; retrying after unlock"); - } - handleEnable(mQuietEnable); - } - break; - } - case MESSAGE_INIT_FLAGS_CHANGED: { - if (DBG) { - Slog.d(TAG, "MESSAGE_INIT_FLAGS_CHANGED"); - } - mHandler.removeMessages(MESSAGE_INIT_FLAGS_CHANGED); - if (mBluetoothModeChangeHelper.isMediaProfileConnected()) { - Slog.i(TAG, "Delaying MESSAGE_INIT_FLAGS_CHANGED by " - + DELAY_FOR_RETRY_INIT_FLAG_CHECK_MS - + " ms due to existing connections"); - mHandler.sendEmptyMessageDelayed( - MESSAGE_INIT_FLAGS_CHANGED, - DELAY_FOR_RETRY_INIT_FLAG_CHECK_MS); - break; - } - if (!isDeviceProvisioned()) { - Slog.i(TAG, "Delaying MESSAGE_INIT_FLAGS_CHANGED by " - + DELAY_FOR_RETRY_INIT_FLAG_CHECK_MS - + "ms because device is not provisioned"); - mHandler.sendEmptyMessageDelayed( - MESSAGE_INIT_FLAGS_CHANGED, - DELAY_FOR_RETRY_INIT_FLAG_CHECK_MS); - break; - } - if (mBluetooth != null && isEnabled()) { - Slog.i(TAG, "Restarting Bluetooth due to init flag change"); - restartForReason( - BluetoothProtoEnums.ENABLE_DISABLE_REASON_INIT_FLAGS_CHANGED); - } - break; - } - } - } - - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED - }) - private void restartForReason(int reason) { - try { - mBluetoothLock.readLock().lock(); - if (mBluetooth != null) { - mBluetooth.unregisterCallback(mBluetoothCallback, - mContext.getAttributionSource()); - } - } catch (RemoteException re) { - Slog.e(TAG, "Unable to unregister", re); - } finally { - mBluetoothLock.readLock().unlock(); - } - - if (mState == BluetoothAdapter.STATE_TURNING_OFF) { - // MESSAGE_USER_SWITCHED happened right after MESSAGE_ENABLE - bluetoothStateChangeHandler(mState, BluetoothAdapter.STATE_OFF); - mState = BluetoothAdapter.STATE_OFF; - } - if (mState == BluetoothAdapter.STATE_OFF) { - bluetoothStateChangeHandler(mState, BluetoothAdapter.STATE_TURNING_ON); - mState = BluetoothAdapter.STATE_TURNING_ON; - } - - waitForState(Set.of(BluetoothAdapter.STATE_ON)); - - if (mState == BluetoothAdapter.STATE_TURNING_ON) { - bluetoothStateChangeHandler(mState, BluetoothAdapter.STATE_ON); - } - - unbindAllBluetoothProfileServices(); - // disable - addActiveLog(reason, mContext.getPackageName(), false); - handleDisable(); - // Pbap service need receive STATE_TURNING_OFF intent to close - bluetoothStateChangeHandler(BluetoothAdapter.STATE_ON, - BluetoothAdapter.STATE_TURNING_OFF); - - boolean didDisableTimeout = - !waitForState(Set.of(BluetoothAdapter.STATE_OFF)); - - bluetoothStateChangeHandler(BluetoothAdapter.STATE_TURNING_OFF, - BluetoothAdapter.STATE_OFF); - sendBluetoothServiceDownCallback(); - - try { - mBluetoothLock.writeLock().lock(); - if (mBluetooth != null) { - mBluetooth = null; - // Unbind - mContext.unbindService(mConnection); - } - mBluetoothGatt = null; - } finally { - mBluetoothLock.writeLock().unlock(); - } - - // - // If disabling Bluetooth times out, wait for an - // additional amount of time to ensure the process is - // shut down completely before attempting to restart. - // - if (didDisableTimeout) { - SystemClock.sleep(3000); - } else { - SystemClock.sleep(100); - } - - mHandler.removeMessages(MESSAGE_BLUETOOTH_STATE_CHANGE); - mState = BluetoothAdapter.STATE_OFF; - // enable - addActiveLog(reason, mContext.getPackageName(), true); - // mEnable flag could have been reset on disableBLE. Reenable it. - mEnable = true; - handleEnable(mQuietEnable); - } - } - - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - private void handleEnable(boolean quietMode) { - mQuietEnable = quietMode; - - try { - mBluetoothLock.writeLock().lock(); - if ((mBluetooth == null) && (!mBinding)) { - Slog.d(TAG, "binding Bluetooth service"); - //Start bind timeout and bind - Message timeoutMsg = mHandler.obtainMessage(MESSAGE_TIMEOUT_BIND); - mHandler.sendMessageDelayed(timeoutMsg, TIMEOUT_BIND_MS); - Intent i = new Intent(IBluetooth.class.getName()); - if (!doBind(i, mConnection, Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT, - UserHandle.CURRENT)) { - mHandler.removeMessages(MESSAGE_TIMEOUT_BIND); - } else { - mBinding = true; - } - } else if (mBluetooth != null) { - //Enable bluetooth - try { - if (!mBluetooth.enable(mQuietEnable, mContext.getAttributionSource())) { - Slog.e(TAG, "IBluetooth.enable() returned false"); - } - } catch (RemoteException e) { - Slog.e(TAG, "Unable to call enable()", e); - } - } - } finally { - mBluetoothLock.writeLock().unlock(); - } - } - - boolean doBind(Intent intent, ServiceConnection conn, int flags, UserHandle user) { - ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0); - intent.setComponent(comp); - if (comp == null || !mContext.bindServiceAsUser(intent, conn, flags, user)) { - Slog.e(TAG, "Fail to bind to: " + intent); - return false; - } - return true; - } - - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - private void handleDisable() { - try { - mBluetoothLock.readLock().lock(); - if (mBluetooth != null) { - if (DBG) { - Slog.d(TAG, "Sending off request."); - } - if (!mBluetooth.disable(mContext.getAttributionSource())) { - Slog.e(TAG, "IBluetooth.disable() returned false"); - } - } - } catch (RemoteException e) { - Slog.e(TAG, "Unable to call disable()", e); - } finally { - mBluetoothLock.readLock().unlock(); - } - } - - private boolean checkIfCallerIsForegroundUser() { - int foregroundUser; - int callingUser = UserHandle.getCallingUserId(); - int callingUid = Binder.getCallingUid(); - final long callingIdentity = Binder.clearCallingIdentity(); - UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); - UserInfo ui = um.getProfileParent(callingUser); - int parentUser = (ui != null) ? ui.id : UserHandle.USER_NULL; - int callingAppId = UserHandle.getAppId(callingUid); - boolean valid = false; - try { - foregroundUser = ActivityManager.getCurrentUser(); - valid = (callingUser == foregroundUser) || parentUser == foregroundUser - || callingAppId == Process.NFC_UID || callingAppId == mSystemUiUid - || callingAppId == Process.SHELL_UID; - if (DBG && !valid) { - Slog.d(TAG, "checkIfCallerIsForegroundUser: valid=" + valid + " callingUser=" - + callingUser + " parentUser=" + parentUser + " foregroundUser=" - + foregroundUser); - } - } finally { - Binder.restoreCallingIdentity(callingIdentity); - } - return valid; - } - - private void sendBleStateChanged(int prevState, int newState) { - if (DBG) { - Slog.d(TAG, - "Sending BLE State Change: " + BluetoothAdapter.nameForState(prevState) + " > " - + BluetoothAdapter.nameForState(newState)); - } - // Send broadcast message to everyone else - Intent intent = new Intent(BluetoothAdapter.ACTION_BLE_STATE_CHANGED); - intent.putExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, prevState); - intent.putExtra(BluetoothAdapter.EXTRA_STATE, newState); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - mContext.sendBroadcastAsUser(intent, UserHandle.ALL, null, getTempAllowlistBroadcastOptions()); - } - - private boolean isBleState(int state) { - switch (state) { - case BluetoothAdapter.STATE_BLE_ON: - case BluetoothAdapter.STATE_BLE_TURNING_ON: - case BluetoothAdapter.STATE_BLE_TURNING_OFF: - return true; - } - return false; - } - - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - private void bluetoothStateChangeHandler(int prevState, int newState) { - boolean isStandardBroadcast = true; - if (prevState == newState) { // No change. Nothing to do. - return; - } - // Notify all proxy objects first of adapter state change - if (newState == BluetoothAdapter.STATE_BLE_ON || newState == BluetoothAdapter.STATE_OFF) { - boolean intermediate_off = (prevState == BluetoothAdapter.STATE_TURNING_OFF - && newState == BluetoothAdapter.STATE_BLE_ON); - - if (newState == BluetoothAdapter.STATE_OFF) { - // If Bluetooth is off, send service down event to proxy objects, and unbind - if (DBG) { - Slog.d(TAG, "Bluetooth is complete send Service Down"); - } - sendBluetoothServiceDownCallback(); - unbindAndFinish(); - sendBleStateChanged(prevState, newState); - - /* Currently, the OFF intent is broadcasted externally only when we transition - * from TURNING_OFF to BLE_ON state. So if the previous state is a BLE state, - * we are guaranteed that the OFF intent has been broadcasted earlier and we - * can safely skip it. - * Conversely, if the previous state is not a BLE state, it indicates that some - * sort of crash has occurred, moving us directly to STATE_OFF without ever - * passing through BLE_ON. We should broadcast the OFF intent in this case. */ - isStandardBroadcast = !isBleState(prevState); - - } else if (!intermediate_off) { - // connect to GattService - if (DBG) { - Slog.d(TAG, "Bluetooth is in LE only mode"); - } - if (mBluetoothGatt != null || !mContext.getPackageManager() - .hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) { - continueFromBleOnState(); - } else { - if (DBG) { - Slog.d(TAG, "Binding Bluetooth GATT service"); - } - Intent i = new Intent(IBluetoothGatt.class.getName()); - doBind(i, mConnection, Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT, - UserHandle.CURRENT); - } - sendBleStateChanged(prevState, newState); - //Don't broadcase this as std intent - isStandardBroadcast = false; - - } else if (intermediate_off) { - if (DBG) { - Slog.d(TAG, "Intermediate off, back to LE only mode"); - } - // For LE only mode, broadcast as is - sendBleStateChanged(prevState, newState); - sendBluetoothStateCallback(false); // BT is OFF for general users - // Broadcast as STATE_OFF - newState = BluetoothAdapter.STATE_OFF; - sendBrEdrDownCallback(mContext.getAttributionSource()); - } - } else if (newState == BluetoothAdapter.STATE_ON) { - boolean isUp = (newState == BluetoothAdapter.STATE_ON); - sendBluetoothStateCallback(isUp); - sendBleStateChanged(prevState, newState); - - } else if (newState == BluetoothAdapter.STATE_BLE_TURNING_ON - || newState == BluetoothAdapter.STATE_BLE_TURNING_OFF) { - sendBleStateChanged(prevState, newState); - isStandardBroadcast = false; - - } else if (newState == BluetoothAdapter.STATE_TURNING_ON - || newState == BluetoothAdapter.STATE_TURNING_OFF) { - sendBleStateChanged(prevState, newState); - } - - if (isStandardBroadcast) { - if (prevState == BluetoothAdapter.STATE_BLE_ON) { - // Show prevState of BLE_ON as OFF to standard users - prevState = BluetoothAdapter.STATE_OFF; - } - if (DBG) { - Slog.d(TAG, - "Sending State Change: " + BluetoothAdapter.nameForState(prevState) + " > " - + BluetoothAdapter.nameForState(newState)); - } - Intent intent = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED); - intent.putExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, prevState); - intent.putExtra(BluetoothAdapter.EXTRA_STATE, newState); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - mContext.sendBroadcastAsUser(intent, UserHandle.ALL, null, - getTempAllowlistBroadcastOptions()); - } - } - - private boolean waitForState(Set<Integer> states) { - int i = 0; - while (i < 10) { - try { - mBluetoothLock.readLock().lock(); - if (mBluetooth == null) { - break; - } - if (states.contains(mBluetooth.getState())) { - return true; - } - } catch (RemoteException e) { - Slog.e(TAG, "getState()", e); - break; - } finally { - mBluetoothLock.readLock().unlock(); - } - SystemClock.sleep(300); - i++; - } - Slog.e(TAG, "waitForState " + states + " time out"); - return false; - } - - private void sendDisableMsg(int reason, String packageName) { - mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_DISABLE)); - addActiveLog(reason, packageName, false); - } - - private void sendEnableMsg(boolean quietMode, int reason, String packageName) { - sendEnableMsg(quietMode, reason, packageName, false); - } - - private void sendEnableMsg(boolean quietMode, int reason, String packageName, boolean isBle) { - mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_ENABLE, quietMode ? 1 : 0, - isBle ? 1 : 0)); - addActiveLog(reason, packageName, true); - mLastEnabledTime = SystemClock.elapsedRealtime(); - } - - private void addActiveLog(int reason, String packageName, boolean enable) { - synchronized (mActiveLogs) { - if (mActiveLogs.size() > ACTIVE_LOG_MAX_SIZE) { - mActiveLogs.remove(); - } - mActiveLogs.add( - new ActiveLog(reason, packageName, enable, System.currentTimeMillis())); - } - - int state = enable ? FrameworkStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED__STATE__ENABLED : - FrameworkStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED__STATE__DISABLED; - FrameworkStatsLog.write_non_chained(FrameworkStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED, - Binder.getCallingUid(), null, state, reason, packageName); - } - - private void addCrashLog() { - synchronized (mCrashTimestamps) { - if (mCrashTimestamps.size() == CRASH_LOG_MAX_SIZE) { - mCrashTimestamps.removeFirst(); - } - mCrashTimestamps.add(System.currentTimeMillis()); - mCrashes++; - } - } - - @RequiresPermission(allOf = { - android.Manifest.permission.BLUETOOTH_CONNECT, - android.Manifest.permission.BLUETOOTH_PRIVILEGED, - }) - private void recoverBluetoothServiceFromError(boolean clearBle) { - Slog.e(TAG, "recoverBluetoothServiceFromError"); - try { - mBluetoothLock.readLock().lock(); - if (mBluetooth != null) { - //Unregister callback object - mBluetooth.unregisterCallback(mBluetoothCallback, mContext.getAttributionSource()); - } - } catch (RemoteException re) { - Slog.e(TAG, "Unable to unregister", re); - } finally { - mBluetoothLock.readLock().unlock(); - } - - SystemClock.sleep(500); - - // disable - addActiveLog(BluetoothProtoEnums.ENABLE_DISABLE_REASON_START_ERROR, - mContext.getPackageName(), false); - handleDisable(); - - waitForState(Set.of(BluetoothAdapter.STATE_OFF)); - - sendBluetoothServiceDownCallback(); - - try { - mBluetoothLock.writeLock().lock(); - if (mBluetooth != null) { - mBluetooth = null; - // Unbind - mContext.unbindService(mConnection); - } - mBluetoothGatt = null; - } finally { - mBluetoothLock.writeLock().unlock(); - } - - mHandler.removeMessages(MESSAGE_BLUETOOTH_STATE_CHANGE); - mState = BluetoothAdapter.STATE_OFF; - - if (clearBle) { - clearBleApps(); - } - - mEnable = false; - - // Send a Bluetooth Restart message to reenable bluetooth - Message restartMsg = mHandler.obtainMessage(MESSAGE_RESTART_BLUETOOTH_SERVICE); - mHandler.sendMessageDelayed(restartMsg, ERROR_RESTART_TIME_MS); - } - - private boolean isBluetoothDisallowed() { - final long callingIdentity = Binder.clearCallingIdentity(); - try { - return mContext.getSystemService(UserManager.class) - .hasUserRestriction(UserManager.DISALLOW_BLUETOOTH, UserHandle.SYSTEM); - } finally { - Binder.restoreCallingIdentity(callingIdentity); - } - } - - /** - * Disables BluetoothOppLauncherActivity component, so the Bluetooth sharing option is not - * offered to the user if Bluetooth or sharing is disallowed. Puts the component to its default - * state if Bluetooth is not disallowed. - * - * @param userId user to disable bluetooth sharing for. - * @param bluetoothSharingDisallowed whether bluetooth sharing is disallowed. - */ - private void updateOppLauncherComponentState(int userId, boolean bluetoothSharingDisallowed) { - final ComponentName oppLauncherComponent = new ComponentName("com.android.bluetooth", - "com.android.bluetooth.opp.BluetoothOppLauncherActivity"); - final int newState = - bluetoothSharingDisallowed ? PackageManager.COMPONENT_ENABLED_STATE_DISABLED - : PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; - try { - final IPackageManager imp = AppGlobals.getPackageManager(); - imp.setComponentEnabledSetting(oppLauncherComponent, newState, - PackageManager.DONT_KILL_APP, userId); - } catch (Exception e) { - // The component was not found, do nothing. - } - } - - private int getServiceRestartMs() { - return (mErrorRecoveryRetryCounter + 1) * SERVICE_RESTART_TIME_MS; - } - - @Override - public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { - if (!DumpUtils.checkDumpPermission(mContext, TAG, writer)) { - return; - } - if ((args.length > 0) && args[0].startsWith("--proto")) { - dumpProto(fd); - return; - } - String errorMsg = null; - - writer.println("Bluetooth Status"); - writer.println(" enabled: " + isEnabled()); - writer.println(" state: " + BluetoothAdapter.nameForState(mState)); - writer.println(" address: " + mAddress); - writer.println(" name: " + mName); - if (mEnable) { - long onDuration = SystemClock.elapsedRealtime() - mLastEnabledTime; - String onDurationString = String.format(Locale.US, "%02d:%02d:%02d.%03d", - (int) (onDuration / (1000 * 60 * 60)), - (int) ((onDuration / (1000 * 60)) % 60), (int) ((onDuration / 1000) % 60), - (int) (onDuration % 1000)); - writer.println(" time since enabled: " + onDurationString); - } - - if (mActiveLogs.size() == 0) { - writer.println("\nBluetooth never enabled!"); - } else { - writer.println("\nEnable log:"); - for (ActiveLog log : mActiveLogs) { - writer.println(" " + log); - } - } - - writer.println( - "\nBluetooth crashed " + mCrashes + " time" + (mCrashes == 1 ? "" : "s")); - if (mCrashes == CRASH_LOG_MAX_SIZE) { - writer.println("(last " + CRASH_LOG_MAX_SIZE + ")"); - } - for (Long time : mCrashTimestamps) { - writer.println(" " + timeToLog(time)); - } - - writer.println("\n" + mBleApps.size() + " BLE app" + (mBleApps.size() == 1 ? "" : "s") - + " registered"); - for (ClientDeathRecipient app : mBleApps.values()) { - writer.println(" " + app.getPackageName()); - } - - writer.println("\nBluetoothManagerService:"); - writer.println(" mEnable:" + mEnable); - writer.println(" mQuietEnable:" + mQuietEnable); - writer.println(" mEnableExternal:" + mEnableExternal); - writer.println(" mQuietEnableExternal:" + mQuietEnableExternal); - - writer.println(""); - writer.flush(); - if (args.length == 0) { - // Add arg to produce output - args = new String[1]; - args[0] = "--print"; - } - - if (mBluetoothBinder == null) { - errorMsg = "Bluetooth Service not connected"; - } else { - try { - mBluetoothBinder.dump(fd, args); - } catch (RemoteException re) { - errorMsg = "RemoteException while dumping Bluetooth Service"; - } - } - if (errorMsg != null) { - writer.println(errorMsg); - } - } - - private void dumpProto(FileDescriptor fd) { - final ProtoOutputStream proto = new ProtoOutputStream(fd); - proto.write(BluetoothManagerServiceDumpProto.ENABLED, isEnabled()); - proto.write(BluetoothManagerServiceDumpProto.STATE, mState); - proto.write(BluetoothManagerServiceDumpProto.STATE_NAME, - BluetoothAdapter.nameForState(mState)); - proto.write(BluetoothManagerServiceDumpProto.ADDRESS, mAddress); - proto.write(BluetoothManagerServiceDumpProto.NAME, mName); - if (mEnable) { - proto.write(BluetoothManagerServiceDumpProto.LAST_ENABLED_TIME_MS, mLastEnabledTime); - } - proto.write(BluetoothManagerServiceDumpProto.CURR_TIMESTAMP_MS, - SystemClock.elapsedRealtime()); - for (ActiveLog log : mActiveLogs) { - long token = proto.start(BluetoothManagerServiceDumpProto.ACTIVE_LOGS); - log.dump(proto); - proto.end(token); - } - proto.write(BluetoothManagerServiceDumpProto.NUM_CRASHES, mCrashes); - proto.write(BluetoothManagerServiceDumpProto.CRASH_LOG_MAXED, - mCrashes == CRASH_LOG_MAX_SIZE); - for (Long time : mCrashTimestamps) { - proto.write(BluetoothManagerServiceDumpProto.CRASH_TIMESTAMPS_MS, time); - } - proto.write(BluetoothManagerServiceDumpProto.NUM_BLE_APPS, mBleApps.size()); - for (ClientDeathRecipient app : mBleApps.values()) { - proto.write(BluetoothManagerServiceDumpProto.BLE_APP_PACKAGE_NAMES, - app.getPackageName()); - } - proto.flush(); - } - - private static String getEnableDisableReasonString(int reason) { - switch (reason) { - case BluetoothProtoEnums.ENABLE_DISABLE_REASON_APPLICATION_REQUEST: - return "APPLICATION_REQUEST"; - case BluetoothProtoEnums.ENABLE_DISABLE_REASON_AIRPLANE_MODE: - return "AIRPLANE_MODE"; - case BluetoothProtoEnums.ENABLE_DISABLE_REASON_DISALLOWED: - return "DISALLOWED"; - case BluetoothProtoEnums.ENABLE_DISABLE_REASON_RESTARTED: - return "RESTARTED"; - case BluetoothProtoEnums.ENABLE_DISABLE_REASON_START_ERROR: - return "START_ERROR"; - case BluetoothProtoEnums.ENABLE_DISABLE_REASON_SYSTEM_BOOT: - return "SYSTEM_BOOT"; - case BluetoothProtoEnums.ENABLE_DISABLE_REASON_CRASH: - return "CRASH"; - case BluetoothProtoEnums.ENABLE_DISABLE_REASON_USER_SWITCH: - return "USER_SWITCH"; - case BluetoothProtoEnums.ENABLE_DISABLE_REASON_RESTORE_USER_SETTING: - return "RESTORE_USER_SETTING"; - case BluetoothProtoEnums.ENABLE_DISABLE_REASON_FACTORY_RESET: - return "FACTORY_RESET"; - case BluetoothProtoEnums.ENABLE_DISABLE_REASON_INIT_FLAGS_CHANGED: - return "INIT_FLAGS_CHANGED"; - case BluetoothProtoEnums.ENABLE_DISABLE_REASON_UNSPECIFIED: - default: return "UNKNOWN[" + reason + "]"; - } - } - - @SuppressLint("AndroidFrameworkRequiresPermission") - private static boolean checkPermissionForDataDelivery(Context context, String permission, - AttributionSource attributionSource, String message) { - PermissionManager pm = context.getSystemService(PermissionManager.class); - if (pm == null) { - return false; - } - AttributionSource currentAttribution = new AttributionSource - .Builder(context.getAttributionSource()) - .setNext(attributionSource) - .build(); - final int result = pm.checkPermissionForDataDeliveryFromDataSource(permission, - currentAttribution, message); - if (result == PERMISSION_GRANTED) { - return true; - } - - final String msg = "Need " + permission + " permission for " + attributionSource + ": " - + message; - if (result == PERMISSION_HARD_DENIED) { - throw new SecurityException(msg); - } else { - Log.w(TAG, msg); - return false; - } - } - - /** - * Returns true if the BLUETOOTH_CONNECT permission is granted for the calling app. Returns - * false if the result is a soft denial. Throws SecurityException if the result is a hard - * denial. - * - * <p>Should be used in situations where the app op should not be noted. - */ - @SuppressLint("AndroidFrameworkRequiresPermission") - @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) - public static boolean checkConnectPermissionForDataDelivery( - Context context, AttributionSource attributionSource, String message) { - return checkPermissionForDataDelivery(context, BLUETOOTH_CONNECT, - attributionSource, message); - } - - static @NonNull Bundle getTempAllowlistBroadcastOptions() { - final long duration = 10_000; - final BroadcastOptions bOptions = BroadcastOptions.makeBasic(); - bOptions.setTemporaryAppAllowlist(duration, - TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED, - PowerExemptionManager.REASON_BLUETOOTH_BROADCAST, ""); - return bOptions.toBundle(); - } -} diff --git a/services/core/java/com/android/server/BluetoothModeChangeHelper.java b/services/core/java/com/android/server/BluetoothModeChangeHelper.java deleted file mode 100644 index e5854c968207..000000000000 --- a/services/core/java/com/android/server/BluetoothModeChangeHelper.java +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright 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; - -import android.annotation.RequiresPermission; -import android.bluetooth.BluetoothA2dp; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothHearingAid; -import android.bluetooth.BluetoothLeAudio; -import android.bluetooth.BluetoothProfile; -import android.bluetooth.BluetoothProfile.ServiceListener; -import android.content.Context; -import android.content.res.Resources; -import android.provider.Settings; -import android.widget.Toast; - -import com.android.internal.R; -import com.android.internal.annotations.VisibleForTesting; - -/** - * Helper class that handles callout and callback methods without - * complex logic. - */ -public class BluetoothModeChangeHelper { - private volatile BluetoothA2dp mA2dp; - private volatile BluetoothHearingAid mHearingAid; - private volatile BluetoothLeAudio mLeAudio; - private final BluetoothAdapter mAdapter; - private final Context mContext; - - BluetoothModeChangeHelper(Context context) { - mAdapter = BluetoothAdapter.getDefaultAdapter(); - mContext = context; - - mAdapter.getProfileProxy(mContext, mProfileServiceListener, BluetoothProfile.A2DP); - mAdapter.getProfileProxy(mContext, mProfileServiceListener, - BluetoothProfile.HEARING_AID); - mAdapter.getProfileProxy(mContext, mProfileServiceListener, BluetoothProfile.LE_AUDIO); - } - - private final ServiceListener mProfileServiceListener = new ServiceListener() { - @Override - public void onServiceConnected(int profile, BluetoothProfile proxy) { - // Setup Bluetooth profile proxies - switch (profile) { - case BluetoothProfile.A2DP: - mA2dp = (BluetoothA2dp) proxy; - break; - case BluetoothProfile.HEARING_AID: - mHearingAid = (BluetoothHearingAid) proxy; - break; - case BluetoothProfile.LE_AUDIO: - mLeAudio = (BluetoothLeAudio) proxy; - break; - default: - break; - } - } - - @Override - public void onServiceDisconnected(int profile) { - // Clear Bluetooth profile proxies - switch (profile) { - case BluetoothProfile.A2DP: - mA2dp = null; - break; - case BluetoothProfile.HEARING_AID: - mHearingAid = null; - break; - case BluetoothProfile.LE_AUDIO: - mLeAudio = null; - break; - default: - break; - } - } - }; - - @VisibleForTesting - public boolean isMediaProfileConnected() { - return isA2dpConnected() || isHearingAidConnected() || isLeAudioConnected(); - } - - @VisibleForTesting - public boolean isBluetoothOn() { - final BluetoothAdapter adapter = mAdapter; - if (adapter == null) { - return false; - } - return adapter.getLeState() == BluetoothAdapter.STATE_ON; - } - - @VisibleForTesting - public boolean isAirplaneModeOn() { - return Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.AIRPLANE_MODE_ON, 0) == 1; - } - - @VisibleForTesting - @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) - public void onAirplaneModeChanged(BluetoothManagerService managerService) { - managerService.onAirplaneModeChanged(); - } - - @VisibleForTesting - public int getSettingsInt(String name) { - return Settings.Global.getInt(mContext.getContentResolver(), - name, 0); - } - - @VisibleForTesting - public void setSettingsInt(String name, int value) { - Settings.Global.putInt(mContext.getContentResolver(), - name, value); - } - - @VisibleForTesting - public void showToastMessage() { - Resources r = mContext.getResources(); - final CharSequence text = r.getString( - R.string.bluetooth_airplane_mode_toast, 0); - Toast.makeText(mContext, text, Toast.LENGTH_LONG).show(); - } - - private boolean isA2dpConnected() { - final BluetoothA2dp a2dp = mA2dp; - if (a2dp == null) { - return false; - } - return a2dp.getConnectedDevices().size() > 0; - } - - private boolean isHearingAidConnected() { - final BluetoothHearingAid hearingAid = mHearingAid; - if (hearingAid == null) { - return false; - } - return hearingAid.getConnectedDevices().size() > 0; - } - - private boolean isLeAudioConnected() { - final BluetoothLeAudio leAudio = mLeAudio; - if (leAudio == null) { - return false; - } - return leAudio.getConnectedDevices().size() > 0; - } -} diff --git a/services/core/java/com/android/server/BluetoothService.java b/services/core/java/com/android/server/BluetoothService.java deleted file mode 100644 index 1a1eecd0f439..000000000000 --- a/services/core/java/com/android/server/BluetoothService.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2015 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; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.bluetooth.BluetoothAdapter; -import android.content.Context; -import android.os.UserManager; - -import com.android.server.SystemService.TargetUser; - -class BluetoothService extends SystemService { - private BluetoothManagerService mBluetoothManagerService; - private boolean mInitialized = false; - - public BluetoothService(Context context) { - super(context); - mBluetoothManagerService = new BluetoothManagerService(context); - } - - private void initialize() { - if (!mInitialized) { - mBluetoothManagerService.handleOnBootPhase(); - mInitialized = true; - } - } - - @Override - public void onStart() { - } - - @Override - public void onBootPhase(int phase) { - if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { - publishBinderService(BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE, - mBluetoothManagerService); - } else if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY && - !UserManager.isHeadlessSystemUserMode()) { - initialize(); - } - } - - @Override - public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) { - if (!mInitialized) { - initialize(); - } else { - mBluetoothManagerService.handleOnSwitchUser(to.getUserIdentifier()); - } - } - - @Override - public void onUserUnlocking(@NonNull TargetUser user) { - mBluetoothManagerService.handleOnUnlockUser(user.getUserIdentifier()); - } -} diff --git a/services/core/java/com/android/server/SerialService.java b/services/core/java/com/android/server/SerialService.java index 1abe4588261a..e915fa1522a1 100644 --- a/services/core/java/com/android/server/SerialService.java +++ b/services/core/java/com/android/server/SerialService.java @@ -16,6 +16,7 @@ package com.android.server; +import android.annotation.EnforcePermission; import android.content.Context; import android.hardware.ISerialManager; import android.os.ParcelFileDescriptor; @@ -34,9 +35,8 @@ public class SerialService extends ISerialManager.Stub { com.android.internal.R.array.config_serialPorts); } + @EnforcePermission(android.Manifest.permission.SERIAL_PORT) public String[] getSerialPorts() { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.SERIAL_PORT, null); - ArrayList<String> ports = new ArrayList<String>(); for (int i = 0; i < mSerialPorts.length; i++) { String path = mSerialPorts[i]; @@ -49,8 +49,8 @@ public class SerialService extends ISerialManager.Stub { return result; } + @EnforcePermission(android.Manifest.permission.SERIAL_PORT) public ParcelFileDescriptor openSerialPort(String path) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.SERIAL_PORT, null); for (int i = 0; i < mSerialPorts.length; i++) { if (mSerialPorts[i].equals(path)) { return native_open(path); 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 7993936cd568..888710857706 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -3472,11 +3472,9 @@ public final class ActiveServices { } private int getAllowMode(Intent service, @Nullable String callingPackage) { - if (callingPackage == null || service.getComponent() == null) { - return ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE; - } - if (callingPackage.equals(service.getComponent().getPackageName())) { - return ActivityManagerInternal.ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE; + if (callingPackage != null && service.getComponent() != null + && callingPackage.equals(service.getComponent().getPackageName())) { + return ActivityManagerInternal.ALLOW_PROFILES_OR_NON_FULL; } else { return ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE; } @@ -4140,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); @@ -4167,7 +4165,7 @@ public final class ActiveServices { return msg; } if (isolated) { - r.isolatedProc = app; + r.isolationHostProc = app; } } @@ -4978,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; } @@ -5018,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; } @@ -5050,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--; @@ -5085,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<>(); } @@ -5323,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(); @@ -6794,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 96594d5ea4e1..b1b4c4447ec8 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -233,11 +233,11 @@ import android.content.pm.ProcessInfo; import android.content.pm.ProviderInfo; import android.content.pm.ProviderInfoList; import android.content.pm.ResolveInfo; -import android.content.pm.SELinuxUtil; +import com.android.server.pm.pkg.SELinuxUtil; import android.content.pm.ServiceInfo; import android.content.pm.TestUtilityService; import android.content.pm.UserInfo; -import android.content.pm.parsing.ParsingPackageUtils; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.content.res.Resources; @@ -393,6 +393,7 @@ import com.android.server.graphics.fonts.FontManagerInternal; import com.android.server.job.JobSchedulerInternal; import com.android.server.os.NativeTombstoneManager; import com.android.server.pm.Installer; +import com.android.server.pm.UserManagerInternal; import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.android.server.uri.GrantUri; @@ -10383,10 +10384,6 @@ public class ActivityManagerService extends IActivityManager.Stub if (thread != null) { pw.println("\n\n** Cache info for pid " + pid + " [" + r.processName + "] **"); pw.flush(); - if (pid == MY_PID) { - PropertyInvalidatedCache.dumpCacheInfo(fd, args); - continue; - } try { TransferPipe tp = new TransferPipe(); try { @@ -13336,6 +13333,14 @@ public class ActivityManagerService extends IActivityManager.Stub } switch (action) { + case Intent.ACTION_MEDIA_SCANNER_SCAN_FILE: + UserManagerInternal umInternal = LocalServices.getService( + UserManagerInternal.class); + UserInfo userInfo = umInternal.getUserInfo(userId); + if (userInfo != null && userInfo.isCloneProfile()) { + userId = umInternal.getProfileParentId(userId); + } + break; case Intent.ACTION_UID_REMOVED: case Intent.ACTION_PACKAGE_REMOVED: case Intent.ACTION_PACKAGE_CHANGED: 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 a7864b9607c8..252584c76696 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -26,10 +26,10 @@ import static android.app.ActivityManager.USER_OP_ERROR_IS_SYSTEM; import static android.app.ActivityManager.USER_OP_ERROR_RELATED_USERS_CANNOT_STOP; import static android.app.ActivityManager.USER_OP_IS_CURRENT; import static android.app.ActivityManager.USER_OP_SUCCESS; -import static android.app.ActivityManagerInternal.ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE; import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY; import static android.app.ActivityManagerInternal.ALLOW_NON_FULL; import static android.app.ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE; +import static android.app.ActivityManagerInternal.ALLOW_PROFILES_OR_NON_FULL; import static android.os.PowerWhitelistManager.REASON_BOOT_COMPLETED; import static android.os.PowerWhitelistManager.REASON_LOCKED_BOOT_COMPLETED; import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED; @@ -2153,11 +2153,10 @@ class UserController implements Handler.Callback { callingUid, -1, true) != PackageManager.PERMISSION_GRANTED) { // If the caller does not have either permission, they are always doomed. allow = false; - } else if (allowMode == ALLOW_NON_FULL) { + } else if (allowMode == ALLOW_NON_FULL || allowMode == ALLOW_PROFILES_OR_NON_FULL) { // We are blanket allowing non-full access, you lucky caller! allow = true; - } else if (allowMode == ALLOW_NON_FULL_IN_PROFILE - || allowMode == ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE) { + } else if (allowMode == ALLOW_NON_FULL_IN_PROFILE) { // We may or may not allow this depending on whether the two users are // in the same profile. allow = isSameProfileGroup; @@ -2184,12 +2183,13 @@ class UserController implements Handler.Callback { builder.append("; this requires "); builder.append(INTERACT_ACROSS_USERS_FULL); if (allowMode != ALLOW_FULL_ONLY) { - if (allowMode == ALLOW_NON_FULL || isSameProfileGroup) { + if (allowMode == ALLOW_NON_FULL + || allowMode == ALLOW_PROFILES_OR_NON_FULL + || (allowMode == ALLOW_NON_FULL_IN_PROFILE && isSameProfileGroup)) { builder.append(" or "); builder.append(INTERACT_ACROSS_USERS); } - if (isSameProfileGroup - && allowMode == ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE) { + if (isSameProfileGroup && allowMode == ALLOW_PROFILES_OR_NON_FULL) { builder.append(" or "); builder.append(INTERACT_ACROSS_PROFILES); } @@ -2216,19 +2216,14 @@ class UserController implements Handler.Callback { private boolean canInteractWithAcrossProfilesPermission( int allowMode, boolean isSameProfileGroup, int callingPid, int callingUid, String callingPackage) { - if (allowMode != ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE) { + if (allowMode != ALLOW_PROFILES_OR_NON_FULL) { return false; } if (!isSameProfileGroup) { return false; } - return PermissionChecker.PERMISSION_GRANTED - == PermissionChecker.checkPermissionForPreflight( - mInjector.getContext(), - INTERACT_ACROSS_PROFILES, - callingPid, - callingUid, - callingPackage); + return mInjector.checkPermissionForPreflight(INTERACT_ACROSS_PROFILES, callingPid, + callingUid, callingPackage); } int unsafeConvertIncomingUser(@UserIdInt int userId) { @@ -2908,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, @@ -3157,6 +3152,12 @@ class UserController implements Handler.Callback { return mService.checkComponentPermission(permission, pid, uid, owningUid, exported); } + boolean checkPermissionForPreflight(String permission, int pid, int uid, String pkg) { + return PermissionChecker.PERMISSION_GRANTED + == PermissionChecker.checkPermissionForPreflight( + getContext(), permission, pid, uid, pkg); + } + protected void startHomeActivity(@UserIdInt int userId, String reason) { mService.mAtmInternal.startHomeActivity(userId, reason); } @@ -3234,7 +3235,7 @@ class UserController implements Handler.Callback { mService.mAtmInternal.clearLockedTasks(reason); } - protected boolean isCallerRecents(int callingUid) { + boolean isCallerRecents(int callingUid) { return mService.mAtmInternal.isCallerRecents(callingUid); } 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 3f3f257aedc5..43c9839a04d8 100644 --- a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java +++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java @@ -17,22 +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; @@ -60,12 +75,50 @@ 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 = + new IGameServiceController.Stub() { + @Override + public void createGameSession(int taskId) { + mBackgroundExecutor.execute(() -> { + GameServiceProviderInstanceImpl.this.createGameSession(taskId); + }); + } + }; + + 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; @@ -76,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; } @@ -114,7 +171,7 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan // TODO(b/204503192): In cases where the connection to the game service fails retry with // back off mechanism. AndroidFuture<Void> unusedPostConnectedFuture = mGameServiceConnector.post(gameService -> { - gameService.connected(); + gameService.connected(mGameServiceController); }); try { @@ -138,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(); @@ -168,10 +216,62 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan } synchronized (mLock) { - createGameSessionLocked(taskId, componentName); + gameTaskStartedLocked(taskId, componentName); + } + } + + 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 taskId, @NonNull ComponentName componentName) { + if (DEBUG) { + Slog.i(TAG, "gameStartedLocked() id: " + taskId + " component: " + componentName); + } + + if (!mIsRunning) { + return; + } + + GameSessionRecord existingGameSessionRecord = mGameSessions.get(taskId); + if (existingGameSessionRecord != null) { + Slog.w(TAG, "Existing game session found for task (id: " + taskId + + ") creation. Ignoring."); + return; + } + + GameSessionRecord gameSessionRecord = GameSessionRecord.awaitingGameSessionRequest( + taskId, componentName); + mGameSessions.put(taskId, gameSessionRecord); + + AndroidFuture<Void> unusedPostGameStartedFuture = mGameServiceConnector.post( + gameService -> { + gameService.gameStarted( + new GameStartedEvent(taskId, componentName.getPackageName())); + }); + } + private void onTaskRemoved(int taskId) { synchronized (mLock) { boolean isTaskAssociatedWithGameSession = mGameSessions.containsKey(taskId); @@ -179,103 +279,186 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan return; } - destroyGameSessionLocked(taskId); + removeAndDestroyGameSessionIfNecessaryLocked(taskId); + } + } + + private void createGameSession(int taskId) { + synchronized (mLock) { + createGameSessionLocked(taskId); } } @GuardedBy("mLock") - private void createGameSessionLocked(int sessionId, @NonNull ComponentName componentName) { + private void createGameSessionLocked(int taskId) { if (DEBUG) { - Slog.i(TAG, "createGameSession() id: " + sessionId + " component: " + componentName); + Slog.i(TAG, "createGameSessionLocked() id: " + taskId); } if (!mIsRunning) { return; } - GameSessionRecord existingGameSessionRecord = mGameSessions.get(sessionId); - if (existingGameSessionRecord != null) { - Slog.w(TAG, "Existing game session found for task (id: " + sessionId + GameSessionRecord existingGameSessionRecord = mGameSessions.get(taskId); + if (existingGameSessionRecord == null) { + 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: " + taskId + + ") is not awaiting game session request. Ignoring."); + return; + } + + GameSessionViewHostConfiguration gameSessionViewHostConfiguration = + createViewHostConfigurationForTask(taskId); + if (gameSessionViewHostConfiguration == null) { + Slog.w(TAG, "Failed to create view host configuration for task (id" + taskId + ") creation. Ignoring."); return; } - GameSessionRecord gameSessionRecord = GameSessionRecord.pendingGameSession(sessionId, - componentName); - mGameSessions.put(sessionId, gameSessionRecord); - - // 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: " + gameSessionRecord, - exception); - synchronized (mLock) { - destroyGameSessionLocked(sessionId); - } - return; - } - - synchronized (mLock) { - attachGameSessionLocked(sessionId, gameSession); - } - }, mBackgroundExecutor); + 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); + GameSessionRecord gameSessionRecord = mGameSessions.get(taskId); + if (gameSessionRecord == null) { - Slog.w(TAG, "No associated game session record. Destroying id: " + sessionId); + Slog.w(TAG, "No associated game session record. Destroying id: " + taskId); + destroyGameSessionDuringAttach(taskId, createGameSessionResult); + return; + } - try { - gameSession.destroy(); - } catch (RemoteException ex) { - Slog.w(TAG, "Failed to destroy session: " + gameSessionRecord, ex); - } + if (!gameSessionRecord.isGameSessionRequested()) { + destroyGameSessionDuringAttach(taskId, createGameSessionResult); + return; + } + + 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 destroyGameSessionLocked(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); } @@ -283,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) { @@ -291,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 329e9e8144e0..a241812f7868 100644 --- a/services/core/java/com/android/server/app/GameSessionRecord.java +++ b/services/core/java/com/android/server/app/GameSessionRecord.java @@ -20,33 +20,92 @@ 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; final class GameSessionRecord { + private enum State { + // Game task is running, but GameSession not created. + NO_GAME_SESSION_REQUESTED, + // Game Service provider requested a Game Session and we are in the + // process of creating it. GameSessionRecord.getGameSession() == null; + GAME_SESSION_REQUESTED, + // A Game Session is created and attached. + // GameSessionRecord.getGameSession() != null. + GAME_SESSION_ATTACHED, + } + private final int mTaskId; + private final State mState; private final ComponentName mRootComponentName; @Nullable private final IGameSession mIGameSession; + @Nullable + private final SurfacePackage mSurfacePackage; - static GameSessionRecord pendingGameSession(int taskId, ComponentName rootComponentName) { - return new GameSessionRecord(taskId, rootComponentName, /* gameSession= */ null); + static GameSessionRecord awaitingGameSessionRequest(int taskId, + ComponentName rootComponentName) { + 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) { + @Nullable IGameSession gameSession, + @Nullable SurfacePackage surfacePackage) { this.mTaskId = taskId; + this.mState = state; this.mRootComponentName = rootComponentName; this.mIGameSession = gameSession; + this.mSurfacePackage = surfacePackage; + } + + public boolean isAwaitingGameSessionRequest() { + return mState == State.NO_GAME_SESSION_REQUESTED; + } + + @NonNull + public GameSessionRecord withGameSessionRequested() { + return new GameSessionRecord( + mTaskId, + State.GAME_SESSION_REQUESTED, + mRootComponentName, + /* gameSession=*/ null, + /* surfacePackage=*/ null); + } + + public boolean isGameSessionRequested() { + return mState == State.GAME_SESSION_REQUESTED; } @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); + return new GameSessionRecord(mTaskId, + State.GAME_SESSION_ATTACHED, + mRootComponentName, + gameSession, + surfacePackage); + } + + @NonNull + public int getTaskId() { + return mTaskId; + } + + @NonNull + public ComponentName getComponentName() { + return mRootComponentName; } @Nullable @@ -54,15 +113,24 @@ final class GameSessionRecord { return mIGameSession; } + @Nullable + public SurfacePackage getSurfacePackage() { + return mSurfacePackage; + } + @Override public String toString() { return "GameSessionRecord{" + "mTaskId=" + mTaskId + + ", mState=" + + mState + ", mRootComponentName=" + mRootComponentName + ", mIGameSession=" + mIGameSession + + ", mSurfacePackage=" + + mSurfacePackage + '}'; } @@ -77,12 +145,16 @@ final class GameSessionRecord { } GameSessionRecord that = (GameSessionRecord) o; - return mTaskId == that.mTaskId && mRootComponentName.equals(that.mRootComponentName) - && Objects.equals(mIGameSession, that.mIGameSession); + 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); + return Objects.hash( + mTaskId, mState, mRootComponentName, mIGameSession, mState, mSurfacePackage); } } diff --git a/services/core/java/com/android/server/app/OWNERS b/services/core/java/com/android/server/app/OWNERS index aaebbfa8e253..221e06ce5f67 100644 --- a/services/core/java/com/android/server/app/OWNERS +++ b/services/core/java/com/android/server/app/OWNERS @@ -1 +1 @@ -per-file GameManager* = file:/GAME_MANAGER_OWNERS +per-file Game* = file:/GAME_MANAGER_OWNERS diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 3c557d0cc35c..40fda4cbec5e 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -113,7 +113,7 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.PermissionInfo; import android.content.pm.UserInfo; -import android.content.pm.parsing.component.ParsedAttribution; +import com.android.server.pm.pkg.component.ParsedAttribution; import android.database.ContentObserver; import android.hardware.camera2.CameraDevice.CAMERA_AUDIO_RESTRICTION; import android.net.Uri; diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 2fcdd6145a64..565e29561d19 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -1219,6 +1219,7 @@ import java.util.concurrent.atomic.AtomicBoolean; mDeviceInventory.onSetBtActiveDevice((BtDeviceInfo) msg.obj, mAudioService.getBluetoothContextualVolumeStream()); } + break; case MSG_BT_HEADSET_CNCT_FAILED: synchronized (mSetModeLock) { synchronized (mDeviceStateLock) { diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 805e45db7010..0ea936e35a37 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -2611,6 +2611,16 @@ public class AudioService extends IAudioService.Stub return getDevicesForAttributesInt(attributes); } + /** @see AudioManager#getAudioDevicesForAttributes(AudioAttributes) + * This method is similar with AudioService#getDevicesForAttributes, + * only it doesn't enforce permissions because it is used by an unprivileged public API + * instead of the system API. + */ + public @NonNull ArrayList<AudioDeviceAttributes> getDevicesForAttributesUnprotected( + @NonNull AudioAttributes attributes) { + return getDevicesForAttributesInt(attributes); + } + /** * @see AudioManager#isMusicActive() * @param remotely true if query is for remote playback (cast), false for local playback. @@ -4374,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; } @@ -9398,6 +9402,7 @@ public class AudioService extends IAudioService.Stub pw.println("mHasSpatializerEffect:" + mHasSpatializerEffect); pw.println("isSpatializerEnabled:" + isSpatializerEnabled()); pw.println("isSpatialAudioEnabled:" + isSpatialAudioEnabled()); + mSpatializerHelper.dump(pw); mAudioSystem.dump(pw); } diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java index b47ea4f7a4b8..e6789d5e1d49 100644 --- a/services/core/java/com/android/server/audio/SpatializerHelper.java +++ b/services/core/java/com/android/server/audio/SpatializerHelper.java @@ -39,6 +39,7 @@ import android.os.RemoteCallbackList; import android.os.RemoteException; import android.util.Log; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import java.util.Locale; @@ -84,7 +85,7 @@ public class SpatializerHelper { private int mSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; private int mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; private int mActualHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED; - private int mDesiredHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED; + private int mDesiredHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD; private int mSpatOutput = 0; private @Nullable ISpatializer mSpat; private @Nullable SpatializerCallback mSpatCallback; @@ -681,12 +682,13 @@ public class SpatializerHelper { return; } try { - if (mode != mDesiredHeadTrackingMode) { - mSpat.setDesiredHeadTrackingMode(spatializerIntToHeadTrackingModeType(mode)); + if (mDesiredHeadTrackingMode != mode) { mDesiredHeadTrackingMode = mode; dispatchDesiredHeadTrackingMode(mode); } - + if (mode != headTrackingModeTypeToSpatializerInt(mSpat.getActualHeadTrackingMode())) { + mSpat.setDesiredHeadTrackingMode(spatializerIntToHeadTrackingModeType(mode)); + } } catch (RemoteException e) { Log.e(TAG, "Error calling setDesiredHeadTrackingMode", e); } @@ -937,6 +939,7 @@ public class SpatializerHelper { } catch (Exception e) { Log.e(TAG, "Error calling setHeadSensor:" + headHandle, e); } + setDesiredHeadTrackingMode(mDesiredHeadTrackingMode); } //------------------------------------------------------ @@ -983,4 +986,22 @@ public class SpatializerHelper { throw(new IllegalArgumentException("Unexpected spatializer level:" + level)); } } + + void dump(PrintWriter pw) { + pw.println("SpatializerHelper:"); + pw.println("\tmState:" + mState); + pw.println("\tmSpatLevel:" + mSpatLevel); + pw.println("\tmCapableSpatLevel:" + mCapableSpatLevel); + pw.println("\tmActualHeadTrackingMode:" + + Spatializer.headtrackingModeToString(mActualHeadTrackingMode)); + pw.println("\tmDesiredHeadTrackingMode:" + + Spatializer.headtrackingModeToString(mDesiredHeadTrackingMode)); + String modesString = ""; + int[] modes = getSupportedHeadTrackingModes(); + for (int mode : modes) { + modesString += Spatializer.headtrackingModeToString(mode) + " "; + } + pw.println("\tsupported head tracking modes:" + modesString); + pw.println("\tmSpatOutput:" + mSpatOutput); + } } 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/broadcastradio/hal2/ProgramInfoCache.java b/services/core/java/com/android/server/broadcastradio/hal2/ProgramInfoCache.java index 8c9389101141..6654c0c2304d 100644 --- a/services/core/java/com/android/server/broadcastradio/hal2/ProgramInfoCache.java +++ b/services/core/java/com/android/server/broadcastradio/hal2/ProgramInfoCache.java @@ -189,7 +189,8 @@ class ProgramInfoCache { removed.add(id); } } - if (modified.isEmpty() && removed.isEmpty() && mComplete == chunk.isComplete()) { + if (modified.isEmpty() && removed.isEmpty() && mComplete == chunk.isComplete() + && !chunk.isPurge()) { return null; } mComplete = chunk.isComplete(); @@ -239,9 +240,10 @@ class ProgramInfoCache { } // Determine number of chunks we need to send. - int numChunks = 0; + int numChunks = purge ? 1 : 0; if (modified != null) { - numChunks = roundUpFraction(modified.size(), maxNumModifiedPerChunk); + numChunks = Math.max(numChunks, + roundUpFraction(modified.size(), maxNumModifiedPerChunk)); } if (removed != null) { numChunks = Math.max(numChunks, roundUpFraction(removed.size(), maxNumRemovedPerChunk)); 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/communal/CommunalManagerService.java b/services/core/java/com/android/server/communal/CommunalManagerService.java deleted file mode 100644 index 600313bbad64..000000000000 --- a/services/core/java/com/android/server/communal/CommunalManagerService.java +++ /dev/null @@ -1,69 +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.communal; - -import android.Manifest; -import android.annotation.RequiresPermission; -import android.app.communal.ICommunalManager; -import android.content.Context; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.server.SystemService; - -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * System service for handling Communal Mode state. - */ -public final class CommunalManagerService extends SystemService { - private final Context mContext; - private final AtomicBoolean mCommunalViewIsShowing = new AtomicBoolean(false); - private final BinderService mBinderService; - - public CommunalManagerService(Context context) { - super(context); - mContext = context; - mBinderService = new BinderService(); - } - - @VisibleForTesting - BinderService getBinderServiceInstance() { - return mBinderService; - } - - @Override - public void onStart() { - publishBinderService(Context.COMMUNAL_SERVICE, mBinderService); - } - - private final class BinderService extends ICommunalManager.Stub { - /** - * Sets whether or not we are in communal mode. - */ - @RequiresPermission(Manifest.permission.WRITE_COMMUNAL_STATE) - @Override - public void setCommunalViewShowing(boolean isShowing) { - mContext.enforceCallingPermission(Manifest.permission.WRITE_COMMUNAL_STATE, - Manifest.permission.WRITE_COMMUNAL_STATE - + "permission required to modify communal state."); - if (mCommunalViewIsShowing.get() == isShowing) { - return; - } - mCommunalViewIsShowing.set(isShowing); - } - } -} diff --git a/services/core/java/com/android/server/communal/OWNERS b/services/core/java/com/android/server/communal/OWNERS deleted file mode 100644 index b02883da854a..000000000000 --- a/services/core/java/com/android/server/communal/OWNERS +++ /dev/null @@ -1,4 +0,0 @@ -brycelee@google.com -justinkoh@google.com -lusilva@google.com -xilei@google.com
\ No newline at end of file diff --git a/services/core/java/com/android/server/communal/TEST_MAPPING b/services/core/java/com/android/server/communal/TEST_MAPPING deleted file mode 100644 index 026e9bb91aee..000000000000 --- a/services/core/java/com/android/server/communal/TEST_MAPPING +++ /dev/null @@ -1,12 +0,0 @@ -{ - "presubmit": [ - { - "name": "FrameworksMockingServicesTests", - "options": [ - { - "include-filter": "com.android.server.communal" - } - ] - } - ] -}
\ No newline at end of file diff --git a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java index cc9efbc64c02..fce673765020 100644 --- a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java +++ b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java @@ -199,6 +199,7 @@ public class MultipathPolicyTracker { private final NetworkTemplate mNetworkTemplate; private final UsageCallback mUsageCallback; private NetworkCapabilities mNetworkCapabilities; + private final NetworkStatsManager mStatsManager; public MultipathTracker(Network network, NetworkCapabilities nc) { this.network = network; @@ -238,6 +239,13 @@ public class MultipathPolicyTracker { updateMultipathBudget(); } }; + mStatsManager = mContext.getSystemService(NetworkStatsManager.class); + // Query stats from NetworkStatsService will trigger a poll by default. + // But since MultipathPolicyTracker listens NPMS events that triggered by + // stats updated event, and will query stats + // after the event. A polling -> updated -> query -> polling loop will be introduced + // if polls on open. Hence, set flag to false to prevent a polling loop. + mStatsManager.setPollOnOpen(false); updateMultipathBudget(); } @@ -262,8 +270,7 @@ public class MultipathPolicyTracker { private long getNetworkTotalBytes(long start, long end) { try { final android.app.usage.NetworkStats.Bucket ret = - mContext.getSystemService(NetworkStatsManager.class) - .querySummaryForDevice(mNetworkTemplate, start, end); + mStatsManager.querySummaryForDevice(mNetworkTemplate, start, end); return ret.getRxBytes() + ret.getTxBytes(); } catch (RuntimeException e) { Log.w(TAG, "Failed to get data usage: " + e); diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java index ffed68ed7d38..f4c36c6b6ec2 100644 --- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java +++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java @@ -74,13 +74,6 @@ class AutomaticBrightnessController { private static final int MSG_UPDATE_FOREGROUND_APP_SYNC = 5; private static final int MSG_RUN_UPDATE = 6; - // Length of the ambient light horizon used to calculate the long term estimate of ambient - // light. - private static final int AMBIENT_LIGHT_LONG_HORIZON_MILLIS = 10000; - - // Length of the ambient light horizon used to calculate short-term estimate of ambient light. - private static final int AMBIENT_LIGHT_SHORT_HORIZON_MILLIS = 2000; - // Callbacks for requesting updates to the display's power state private final Callbacks mCallbacks; @@ -125,8 +118,10 @@ class AutomaticBrightnessController { // and only then decide whether to change brightness. private final boolean mResetAmbientLuxAfterWarmUpConfig; - // Period of time in which to consider light samples in milliseconds. - private final int mAmbientLightHorizon; + // Period of time in which to consider light samples for a short/long-term estimate of ambient + // light in milliseconds. + private final int mAmbientLightHorizonLong; + private final int mAmbientLightHorizonShort; // The intercept used for the weighting calculation. This is used in order to keep all possible // weighting values positive. @@ -220,6 +215,7 @@ class AutomaticBrightnessController { private Context mContext; private int mState = AUTO_BRIGHTNESS_DISABLED; + private Clock mClock; private final Injector mInjector; AutomaticBrightnessController(Callbacks callbacks, Looper looper, @@ -231,14 +227,16 @@ class AutomaticBrightnessController { boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds, HysteresisLevels screenBrightnessThresholds, Context context, HighBrightnessModeController hbmController, - BrightnessMappingStrategy idleModeBrightnessMapper) { + BrightnessMappingStrategy idleModeBrightnessMapper, int ambientLightHorizonShort, + int ambientLightHorizonLong) { this(new Injector(), callbacks, looper, sensorManager, lightSensor, interactiveModeBrightnessMapper, lightSensorWarmUpTime, brightnessMin, brightnessMax, dozeScaleFactor, lightSensorRate, initialLightSensorRate, brighteningLightDebounceConfig, darkeningLightDebounceConfig, resetAmbientLuxAfterWarmUpConfig, ambientBrightnessThresholds, screenBrightnessThresholds, context, - hbmController, idleModeBrightnessMapper + hbmController, idleModeBrightnessMapper, ambientLightHorizonShort, + ambientLightHorizonLong ); } @@ -252,8 +250,10 @@ class AutomaticBrightnessController { boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds, HysteresisLevels screenBrightnessThresholds, Context context, HighBrightnessModeController hbmController, - BrightnessMappingStrategy idleModeBrightnessMapper) { + BrightnessMappingStrategy idleModeBrightnessMapper, int ambientLightHorizonShort, + int ambientLightHorizonLong) { mInjector = injector; + mClock = injector.createClock(); mContext = context; mCallbacks = callbacks; mSensorManager = sensorManager; @@ -268,15 +268,16 @@ class AutomaticBrightnessController { mBrighteningLightDebounceConfig = brighteningLightDebounceConfig; mDarkeningLightDebounceConfig = darkeningLightDebounceConfig; mResetAmbientLuxAfterWarmUpConfig = resetAmbientLuxAfterWarmUpConfig; - mAmbientLightHorizon = AMBIENT_LIGHT_LONG_HORIZON_MILLIS; - mWeightingIntercept = AMBIENT_LIGHT_LONG_HORIZON_MILLIS; + mAmbientLightHorizonLong = ambientLightHorizonLong; + mAmbientLightHorizonShort = ambientLightHorizonShort; + mWeightingIntercept = ambientLightHorizonLong; mAmbientBrightnessThresholds = ambientBrightnessThresholds; mScreenBrightnessThresholds = screenBrightnessThresholds; mShortTermModelValid = true; mShortTermModelAnchor = -1; mHandler = new AutomaticBrightnessHandler(looper); mAmbientLightRingBuffer = - new AmbientLightRingBuffer(mNormalLightSensorRate, mAmbientLightHorizon); + new AmbientLightRingBuffer(mNormalLightSensorRate, mAmbientLightHorizonLong, mClock); if (!DEBUG_PRETEND_LIGHT_SENSOR_ABSENT) { mLightSensor = lightSensor; @@ -397,6 +398,11 @@ class AutomaticBrightnessController { mHandler.sendEmptyMessage(MSG_RUN_UPDATE); } + @VisibleForTesting + float getAmbientLux() { + return mAmbientLux; + } + private boolean setDisplayPolicy(int policy) { if (mDisplayPolicy == policy) { return false; @@ -476,7 +482,8 @@ class AutomaticBrightnessController { pw.println(" mBrighteningLightDebounceConfig=" + mBrighteningLightDebounceConfig); pw.println(" mDarkeningLightDebounceConfig=" + mDarkeningLightDebounceConfig); pw.println(" mResetAmbientLuxAfterWarmUpConfig=" + mResetAmbientLuxAfterWarmUpConfig); - pw.println(" mAmbientLightHorizon=" + mAmbientLightHorizon); + pw.println(" mAmbientLightHorizonLong=" + mAmbientLightHorizonLong); + pw.println(" mAmbientLightHorizonShort=" + mAmbientLightHorizonShort); pw.println(" mWeightingIntercept=" + mWeightingIntercept); pw.println(); @@ -545,7 +552,7 @@ class AutomaticBrightnessController { if (enable) { if (!mLightSensorEnabled) { mLightSensorEnabled = true; - mLightSensorEnableTime = SystemClock.uptimeMillis(); + mLightSensorEnableTime = mClock.uptimeMillis(); mCurrentLightSensorRate = mInitialLightSensorRate; registerForegroundAppUpdater(); mSensorManager.registerListener(mLightSensorListener, mLightSensor, @@ -580,7 +587,7 @@ class AutomaticBrightnessController { private void applyLightSensorMeasurement(long time, float lux) { mRecentLightSamples++; - mAmbientLightRingBuffer.prune(time - mAmbientLightHorizon); + mAmbientLightRingBuffer.prune(time - mAmbientLightHorizonLong); mAmbientLightRingBuffer.push(time, lux); // Remember this sample value. @@ -721,8 +728,8 @@ class AutomaticBrightnessController { } private void updateAmbientLux() { - long time = SystemClock.uptimeMillis(); - mAmbientLightRingBuffer.prune(time - mAmbientLightHorizon); + long time = mClock.uptimeMillis(); + mAmbientLightRingBuffer.prune(time - mAmbientLightHorizonLong); updateAmbientLux(time); } @@ -742,7 +749,7 @@ class AutomaticBrightnessController { timeWhenSensorWarmedUp); return; } - setAmbientLux(calculateAmbientLux(time, AMBIENT_LIGHT_SHORT_HORIZON_MILLIS)); + setAmbientLux(calculateAmbientLux(time, mAmbientLightHorizonShort)); mAmbientLuxValid = true; if (mLoggingEnabled) { Slog.d(TAG, "updateAmbientLux: Initializing: " + @@ -762,8 +769,8 @@ class AutomaticBrightnessController { // proposed ambient light value since the slow value might be sufficiently far enough away // from the fast value to cause a recalculation while its actually just converging on // the fast value still. - float slowAmbientLux = calculateAmbientLux(time, AMBIENT_LIGHT_LONG_HORIZON_MILLIS); - float fastAmbientLux = calculateAmbientLux(time, AMBIENT_LIGHT_SHORT_HORIZON_MILLIS); + float slowAmbientLux = calculateAmbientLux(time, mAmbientLightHorizonLong); + float fastAmbientLux = calculateAmbientLux(time, mAmbientLightHorizonShort); if ((slowAmbientLux >= mAmbientBrighteningThreshold && fastAmbientLux >= mAmbientBrighteningThreshold @@ -1044,7 +1051,7 @@ class AutomaticBrightnessController { @Override public void onSensorChanged(SensorEvent event) { if (mLightSensorEnabled) { - final long time = SystemClock.uptimeMillis(); + final long time = mClock.uptimeMillis(); final float lux = event.values[0]; handleLightSensorEvent(time, lux); } @@ -1070,6 +1077,15 @@ class AutomaticBrightnessController { void updateBrightness(); } + /** Functional interface for providing time. */ + @VisibleForTesting + interface Clock { + /** + * Returns current time in milliseconds since boot, not counting time spent in deep sleep. + */ + long uptimeMillis(); + } + /** * A ring buffer of ambient light measurements sorted by time. * @@ -1089,14 +1105,16 @@ class AutomaticBrightnessController { private int mStart; private int mEnd; private int mCount; + Clock mClock; - public AmbientLightRingBuffer(long lightSensorRate, int ambientLightHorizon) { + public AmbientLightRingBuffer(long lightSensorRate, int ambientLightHorizon, Clock clock) { if (lightSensorRate <= 0) { throw new IllegalArgumentException("lightSensorRate must be above 0"); } mCapacity = (int) Math.ceil(ambientLightHorizon * BUFFER_SLACK / lightSensorRate); mRingLux = new float[mCapacity]; mRingTime = new long[mCapacity]; + mClock = clock; } public float getLux(int index) { @@ -1181,7 +1199,7 @@ class AutomaticBrightnessController { StringBuilder buf = new StringBuilder(); buf.append('['); for (int i = 0; i < mCount; i++) { - final long next = i + 1 < mCount ? getTime(i + 1) : SystemClock.uptimeMillis(); + final long next = i + 1 < mCount ? getTime(i + 1) : mClock.uptimeMillis(); if (i != 0) { buf.append(", "); } @@ -1210,5 +1228,9 @@ class AutomaticBrightnessController { public Handler getBackgroundThreadHandler() { return BackgroundThread.getHandler(); } + + Clock createClock() { + return SystemClock::uptimeMillis; + } } } diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index a9e1647446cb..3df2422071b8 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -52,6 +52,7 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.math.BigDecimal; +import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -86,6 +87,13 @@ public class DisplayDeviceConfig { private static final float NITS_INVALID = -1; + // Length of the ambient light horizon used to calculate the long term estimate of ambient + // light. + private static final int AMBIENT_LIGHT_LONG_HORIZON_MILLIS = 10000; + + // Length of the ambient light horizon used to calculate short-term estimate of ambient light. + private static final int AMBIENT_LIGHT_SHORT_HORIZON_MILLIS = 2000; + private final Context mContext; // The details of the ambient light sensor associated with this display. @@ -120,6 +128,8 @@ public class DisplayDeviceConfig { private float mBrightnessRampFastIncrease = Float.NaN; private float mBrightnessRampSlowDecrease = Float.NaN; private float mBrightnessRampSlowIncrease = Float.NaN; + private int mAmbientHorizonLong = AMBIENT_LIGHT_LONG_HORIZON_MILLIS; + private int mAmbientHorizonShort = AMBIENT_LIGHT_SHORT_HORIZON_MILLIS; private Spline mBrightnessToBacklightSpline; private Spline mBacklightToBrightnessSpline; private Spline mBacklightToNitsSpline; @@ -346,6 +356,14 @@ public class DisplayDeviceConfig { return mBrightnessRampSlowIncrease; } + public int getAmbientHorizonLong() { + return mAmbientHorizonLong; + } + + public int getAmbientHorizonShort() { + return mAmbientHorizonShort; + } + SensorData getAmbientLightSensor() { return mAmbientLightSensor; } @@ -405,6 +423,8 @@ public class DisplayDeviceConfig { + ", mBrightnessRampFastIncrease=" + mBrightnessRampFastIncrease + ", mBrightnessRampSlowDecrease=" + mBrightnessRampSlowDecrease + ", mBrightnessRampSlowIncrease=" + mBrightnessRampSlowIncrease + + ", mAmbientHorizonLong=" + mAmbientHorizonLong + + ", mAmbientHorizonShort=" + mAmbientHorizonShort + ", mAmbientLightSensor=" + mAmbientLightSensor + ", mProximitySensor=" + mProximitySensor + ", mRefreshRateLimitations= " + Arrays.toString(mRefreshRateLimitations.toArray()) @@ -461,6 +481,7 @@ public class DisplayDeviceConfig { loadBrightnessRamps(config); loadAmbientLightSensorFromDdc(config); loadProxSensorFromDdc(config); + loadAmbientHorizonFromDdc(config); } else { Slog.w(TAG, "DisplayDeviceConfig file is null"); } @@ -869,6 +890,17 @@ public class DisplayDeviceConfig { } } + private void loadAmbientHorizonFromDdc(DisplayConfiguration config) { + final BigInteger configLongHorizon = config.getAmbientLightHorizonLong(); + if (configLongHorizon != null) { + mAmbientHorizonLong = configLongHorizon.intValue(); + } + final BigInteger configShortHorizon = config.getAmbientLightHorizonShort(); + if (configShortHorizon != null) { + mAmbientHorizonShort = configShortHorizon.intValue(); + } + } + static class SensorData { public String type; public String name; 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 faf0038b29c8..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; @@ -957,7 +955,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call lightSensorRate, initialLightSensorRate, brighteningLightDebounce, darkeningLightDebounce, autoBrightnessResetAmbientLuxAfterWarmUp, ambientBrightnessThresholds, screenBrightnessThresholds, mContext, - mHbmController, mIdleModeBrightnessMapper); + mHbmController, mIdleModeBrightnessMapper, + mDisplayDeviceConfig.getAmbientHorizonShort(), + mDisplayDeviceConfig.getAmbientHorizonLong()); } else { mUseSoftwareAutoBrightnessConfig = false; } @@ -1380,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) { @@ -1473,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. @@ -1601,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(); @@ -1659,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(); @@ -2161,7 +2137,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private void setCurrentScreenBrightness(float brightnessValue) { if (brightnessValue != mCurrentScreenBrightnessSetting) { mCurrentScreenBrightnessSetting = brightnessValue; - postBrightnessChangeRunnable(); + mHandler.post(mOnBrightnessChangeRunnable); } } @@ -2213,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 @@ -2323,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); @@ -2491,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; @@ -2791,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/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java index 16273cef37b8..b3be894b9510 100644 --- a/services/core/java/com/android/server/display/HighBrightnessModeController.java +++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java @@ -91,6 +91,7 @@ class HighBrightnessModeController { private int mHeight; private float mAmbientLux; private int mDisplayStatsId; + private int mHbmStatsState = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF; /** * If HBM is currently running, this is the start time for the current HBM session. @@ -278,6 +279,7 @@ class HighBrightnessModeController { pw.println(" mHbmMode=" + BrightnessInfo.hbmToString(mHbmMode) + (mHbmMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR ? "(" + getHdrBrightnessValue() + ")" : "")); + pw.println(" mHbmStatsState=" + hbmStatsStateToString(mHbmStatsState)); pw.println(" mHbmData=" + mHbmData); pw.println(" mAmbientLux=" + mAmbientLux + (mIsAutoBrightnessEnabled ? "" : " (old/invalid)")); @@ -444,8 +446,8 @@ class HighBrightnessModeController { private void updateHbmMode() { int newHbmMode = calculateHighBrightnessMode(); + updateHbmStats(mHbmMode, newHbmMode); if (mHbmMode != newHbmMode) { - updateHbmStats(mHbmMode, newHbmMode); mHbmMode = newHbmMode; mHbmChangeCallback.run(); } @@ -453,11 +455,16 @@ class HighBrightnessModeController { private void updateHbmStats(int mode, int newMode) { int state = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF; - if (newMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR) { + if (newMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR + && getHdrBrightnessValue() > mHbmData.transitionPoint) { state = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR; } else if (newMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT) { state = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT; } + if (state == mHbmStatsState) { + return; + } + mHbmStatsState = state; int reason = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN; @@ -491,6 +498,19 @@ class HighBrightnessModeController { mInjector.reportHbmStateChange(mDisplayStatsId, state, reason); } + private String hbmStatsStateToString(int hbmStatsState) { + switch (hbmStatsState) { + case FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF: + return "HBM_OFF"; + case FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR: + return "HBM_ON_HDR"; + case FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT: + return "HBM_ON_SUNLIGHT"; + default: + return String.valueOf(hbmStatsState); + } + } + private int calculateHighBrightnessMode() { if (!deviceSupportsHbm()) { return BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF; 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/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java index 58308d8f1343..751f2db99528 100644 --- a/services/core/java/com/android/server/hdmi/Constants.java +++ b/services/core/java/com/android/server/hdmi/Constants.java @@ -81,7 +81,7 @@ final class Constants { public static final int ADDR_BROADCAST = 15; /** Logical address used to indicate it is not initialized or invalid. */ - public static final int ADDR_INVALID = -1; + public static final int ADDR_INVALID = HdmiDeviceInfo.ADDR_INVALID; /** Logical address used to indicate the source comes from internal device. */ public static final int ADDR_INTERNAL = HdmiDeviceInfo.ADDR_INTERNAL; @@ -199,6 +199,7 @@ final class Constants { static final int MESSAGE_SET_SYSTEM_AUDIO_MODE = 0x72; static final int MESSAGE_REPORT_AUDIO_STATUS = 0x7A; static final int MESSAGE_GIVE_SYSTEM_AUDIO_MODE_STATUS = 0x7D; + static final int MESSAGE_SET_AUDIO_VOLUME_LEVEL = 0x73; static final int MESSAGE_SYSTEM_AUDIO_MODE_STATUS = 0x7E; static final int MESSAGE_ROUTING_CHANGE = 0x80; static final int MESSAGE_ROUTING_INFORMATION = 0x81; @@ -243,7 +244,7 @@ final class Constants { static final int MESSAGE_CDC_MESSAGE = 0xF8; static final int MESSAGE_ABORT = 0xFF; - static final int UNKNOWN_VENDOR_ID = 0xFFFFFF; + static final int VENDOR_ID_UNKNOWN = HdmiDeviceInfo.VENDOR_ID_UNKNOWN; static final int TRUE = 1; static final int FALSE = 0; @@ -389,6 +390,17 @@ final class Constants { static final int UNKNOWN_VOLUME = -1; + // This constant is used in two operands in the CEC spec. + // + // CEC 1.4: [Audio Volume Status] (part of [Audio Status]) - operand for <Report Audio Status> + // Indicates that the current audio volume status is unknown. + // + // CEC 2.1a: [Audio Volume Level] - operand for <Set Audio Volume Level> + // Part of the Absolute Volume Control feature. Indicates that no change shall be made to the + // volume level of the recipient. This allows <Set Audio Volume Level> to be sent to determine + // whether the recipient supports Absolute Volume Control. + static final int AUDIO_VOLUME_STATUS_UNKNOWN = 0x7F; + // States of property PROPERTY_SYSTEM_AUDIO_CONTROL_ON_POWER_ON // to decide if turn on the system audio control when power on the device @IntDef({ diff --git a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java index 8980de12a31f..e827866368fe 100755 --- a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java +++ b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java @@ -77,7 +77,7 @@ final class DeviceDiscoveryAction extends HdmiCecFeatureAction { private int mPhysicalAddress = Constants.INVALID_PHYSICAL_ADDRESS; private int mPortId = Constants.INVALID_PORT_ID; - private int mVendorId = Constants.UNKNOWN_VENDOR_ID; + private int mVendorId = Constants.VENDOR_ID_UNKNOWN; private int mPowerStatus = HdmiControlManager.POWER_STATUS_UNKNOWN; private String mDisplayName = ""; private int mDeviceType = HdmiDeviceInfo.DEVICE_INACTIVE; @@ -87,8 +87,15 @@ final class DeviceDiscoveryAction extends HdmiCecFeatureAction { } private HdmiDeviceInfo toHdmiDeviceInfo() { - return new HdmiDeviceInfo(mLogicalAddress, mPhysicalAddress, mPortId, mDeviceType, - mVendorId, mDisplayName, mPowerStatus); + return HdmiDeviceInfo.cecDeviceBuilder() + .setLogicalAddress(mLogicalAddress) + .setPhysicalAddress(mPhysicalAddress) + .setPortId(mPortId) + .setVendorId(mVendorId) + .setDeviceType(mDeviceType) + .setDisplayName(mDisplayName) + .setDevicePowerStatus(mPowerStatus) + .build(); } } diff --git a/services/core/java/com/android/server/hdmi/GiveFeaturesAction.java b/services/core/java/com/android/server/hdmi/GiveFeaturesAction.java new file mode 100644 index 000000000000..45425657c0f6 --- /dev/null +++ b/services/core/java/com/android/server/hdmi/GiveFeaturesAction.java @@ -0,0 +1,79 @@ +/* + * 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.hdmi; + +import android.hardware.hdmi.HdmiControlManager; +import android.hardware.hdmi.IHdmiControlCallback; +import android.hardware.tv.cec.V1_0.SendMessageResult; + +/** + * Sends <Give Features> to a target device. This action succeeds if the device responds with + * <Report Features> within {@link HdmiConfig.TIMEOUT_MS}. + * + * This action does not update the CEC network directly; an incoming <Report Features> message + * should be handled separately by {@link HdmiCecNetwork}. + */ +public class GiveFeaturesAction extends HdmiCecFeatureAction { + private static final String TAG = "GiveFeaturesAction"; + + private static final int STATE_WAITING_FOR_REPORT_FEATURES = 1; + + private final int mTargetAddress; + + public GiveFeaturesAction(HdmiCecLocalDevice source, int targetAddress, + IHdmiControlCallback callback) { + super(source, callback); + + mTargetAddress = targetAddress; + } + + boolean start() { + sendCommand(HdmiCecMessageBuilder.buildGiveFeatures(getSourceAddress(), mTargetAddress), + result -> { + if (result == SendMessageResult.SUCCESS) { + mState = STATE_WAITING_FOR_REPORT_FEATURES; + addTimer(STATE_WAITING_FOR_REPORT_FEATURES, HdmiConfig.TIMEOUT_MS); + } else { + finishWithCallback(HdmiControlManager.RESULT_COMMUNICATION_FAILED); + } + }); + return true; + } + + boolean processCommand(HdmiCecMessage cmd) { + if (mState != STATE_WAITING_FOR_REPORT_FEATURES) { + return false; + } + if (cmd instanceof ReportFeaturesMessage) { + return handleReportFeatures((ReportFeaturesMessage) cmd); + } + return false; + } + + private boolean handleReportFeatures(ReportFeaturesMessage cmd) { + if (cmd.getSource() == mTargetAddress) { + // No need to update the network, since it should already have processed this message. + finishWithCallback(HdmiControlManager.RESULT_SUCCESS); + return true; + } + return false; + } + + void handleTimerEvent(int state) { + finishWithCallback(HdmiControlManager.RESULT_COMMUNICATION_FAILED); + } +} diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java index cc864307daf1..1a568c30c899 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecController.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java @@ -47,6 +47,7 @@ import libcore.util.EmptyArray; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; import java.util.LinkedList; import java.util.List; @@ -694,7 +695,19 @@ final class HdmiCecController { @ServiceThreadOnly private void handleIncomingCecCommand(int srcAddress, int dstAddress, byte[] body) { assertRunOnServiceThread(); - HdmiCecMessage command = HdmiCecMessageBuilder.of(srcAddress, dstAddress, body); + + if (body.length == 0) { + Slog.e(TAG, "Message with empty body received."); + return; + } + + HdmiCecMessage command = HdmiCecMessage.build(srcAddress, dstAddress, body[0], + Arrays.copyOfRange(body, 1, body.length)); + + if (command.getValidationResult() != HdmiCecMessageValidator.OK) { + Slog.e(TAG, "Invalid message received: " + command); + } + HdmiLogger.debug("[R]:" + command); addCecMessageToHistory(true /* isReceived */, command); diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java index 3aa2f420d11f..c674ffebfe92 100755 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java @@ -17,6 +17,7 @@ package com.android.server.hdmi; import android.annotation.CallSuper; +import android.hardware.hdmi.DeviceFeatures; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.IHdmiControlCallback; @@ -634,7 +635,34 @@ abstract class HdmiCecLocalDevice { protected abstract List<Integer> getRcFeatures(); - protected abstract List<Integer> getDeviceFeatures(); + /** + * Computes the set of supported device features. To update local state with changes in + * the set of supported device features, use {@link #getDeviceFeatures} instead. + */ + protected DeviceFeatures computeDeviceFeatures() { + return DeviceFeatures.NO_FEATURES_SUPPORTED; + } + + /** + * Computes the set of supported device features, and updates local state to match. + */ + private void updateDeviceFeatures() { + synchronized (mLock) { + setDeviceInfo(getDeviceInfo().toBuilder() + .setDeviceFeatures(computeDeviceFeatures()) + .build()); + } + } + + /** + * Computes and returns the set of supported device features. Updates local state to match. + */ + protected final DeviceFeatures getDeviceFeatures() { + updateDeviceFeatures(); + synchronized (mLock) { + return getDeviceInfo().getDeviceFeatures(); + } + } @Constants.HandleMessageResult protected int handleGiveFeatures(HdmiCecMessage message) { @@ -655,11 +683,17 @@ abstract class HdmiCecLocalDevice { int rcProfile = getRcProfile(); List<Integer> rcFeatures = getRcFeatures(); - List<Integer> deviceFeatures = getDeviceFeatures(); + DeviceFeatures deviceFeatures = getDeviceFeatures(); + + + int logicalAddress; + synchronized (mLock) { + logicalAddress = mDeviceInfo.getLogicalAddress(); + } mService.sendCecCommand( - HdmiCecMessageBuilder.buildReportFeatures( - mDeviceInfo.getLogicalAddress(), + ReportFeaturesMessage.build( + logicalAddress, mService.getCecVersion(), localDeviceTypes, rcProfile, @@ -922,6 +956,7 @@ abstract class HdmiCecLocalDevice { final void handleAddressAllocated(int logicalAddress, int reason) { assertRunOnServiceThread(); mPreferredAddress = logicalAddress; + updateDeviceFeatures(); if (mService.getCecVersion() >= HdmiControlManager.HDMI_CEC_VERSION_2_0) { reportFeatures(); } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java index 7e71589302f1..2ef3ebf5d07e 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java @@ -15,6 +15,9 @@ */ package com.android.server.hdmi; +import static android.hardware.hdmi.DeviceFeatures.FEATURE_NOT_SUPPORTED; +import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORTED; + import static com.android.server.hdmi.Constants.ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON; import static com.android.server.hdmi.Constants.PROPERTY_SYSTEM_AUDIO_CONTROL_ON_POWER_ON; import static com.android.server.hdmi.Constants.USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON; @@ -22,6 +25,7 @@ import static com.android.server.hdmi.Constants.USE_LAST_STATE_SYSTEM_AUDIO_CONT import android.annotation.Nullable; import android.content.ActivityNotFoundException; import android.content.Intent; +import android.hardware.hdmi.DeviceFeatures; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.HdmiPortInfo; @@ -177,14 +181,12 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { } @Override - protected List<Integer> getDeviceFeatures() { - List<Integer> deviceFeatures = new ArrayList<>(); - - if (SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true)) { - deviceFeatures.add(Constants.DEVICE_FEATURE_SOURCE_SUPPORTS_ARC_RX); - } + protected DeviceFeatures computeDeviceFeatures() { + boolean arcSupport = SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true); - return deviceFeatures; + return DeviceFeatures.NO_FEATURES_SUPPORTED.toBuilder() + .setArcRxSupport(arcSupport ? FEATURE_SUPPORTED : FEATURE_NOT_SUPPORTED) + .build(); } @Override diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java index 4f5524948aa1..90b4f76fec10 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java @@ -28,8 +28,6 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.server.hdmi.Constants.LocalActivePort; import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; -import com.google.android.collect.Lists; - import java.util.ArrayList; import java.util.List; @@ -358,11 +356,6 @@ abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice { return features; } - @Override - protected List<Integer> getDeviceFeatures() { - return Lists.newArrayList(); - } - // Active source claiming needs to be handled in Service // since service can decide who will be the active source when the device supports // multiple device types in this method. diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index c2ed24a1136d..afcd3dde0633 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -16,6 +16,8 @@ package com.android.server.hdmi; +import static android.hardware.hdmi.DeviceFeatures.FEATURE_NOT_SUPPORTED; +import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORTED; import static android.hardware.hdmi.HdmiControlManager.CLEAR_TIMER_STATUS_CEC_DISABLE; import static android.hardware.hdmi.HdmiControlManager.CLEAR_TIMER_STATUS_CHECK_RECORDER_CONNECTION; import static android.hardware.hdmi.HdmiControlManager.CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE; @@ -31,6 +33,7 @@ import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_DIGI import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_EXTERNAL; import android.annotation.Nullable; +import android.hardware.hdmi.DeviceFeatures; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.HdmiPortInfo; @@ -346,7 +349,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { if (info == null) { // No CEC/MHL device is present at the port. Attempt to switch to // the hardware port itself for non-CEC devices that may be connected. - info = new HdmiDeviceInfo(path, getActivePortId()); + info = HdmiDeviceInfo.hardwarePort(path, getActivePortId()); } } mService.invokeInputChangeListener(info); @@ -1548,9 +1551,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } @Override - protected List<Integer> getDeviceFeatures() { - List<Integer> deviceFeatures = new ArrayList<>(); - + protected DeviceFeatures computeDeviceFeatures() { boolean hasArcPort = false; List<HdmiPortInfo> ports = mService.getPortInfo(); for (HdmiPortInfo port : ports) { @@ -1559,11 +1560,11 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { break; } } - if (hasArcPort) { - deviceFeatures.add(Constants.DEVICE_FEATURE_SINK_SUPPORTS_ARC_TX); - } - deviceFeatures.add(Constants.DEVICE_FEATURE_TV_SUPPORTS_RECORD_TV_SCREEN); - return deviceFeatures; + + return DeviceFeatures.NO_FEATURES_SUPPORTED.toBuilder() + .setRecordTvScreenSupport(FEATURE_SUPPORTED) + .setArcTxSupport(hasArcPort ? FEATURE_SUPPORTED : FEATURE_NOT_SUPPORTED) + .build(); } @Override diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessage.java b/services/core/java/com/android/server/hdmi/HdmiCecMessage.java index e3292a35bf6b..290cae50f2cb 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecMessage.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecMessage.java @@ -16,6 +16,8 @@ package com.android.server.hdmi; +import static com.android.server.hdmi.HdmiCecMessageValidator.ValidationResult; + import android.annotation.Nullable; import com.android.server.hdmi.Constants.FeatureOpcode; @@ -26,11 +28,13 @@ import java.util.Arrays; import java.util.Objects; /** - * A class to encapsulate HDMI-CEC message used for the devices connected via - * HDMI cable to communicate with one another. A message is defined by its - * source and destination address, command (or opcode), and optional parameters. + * Encapsulates the data that defines an HDMI-CEC message: source and destination address, + * command (or opcode), and optional parameters. Also stores the result of validating the message. + * + * Subclasses of this class represent specific messages that have been validated, and expose their + * parsed parameters. */ -public final class HdmiCecMessage { +public class HdmiCecMessage { public static final byte[] EMPTY_PARAM = EmptyArray.BYTE; private final int mSource; @@ -39,24 +43,60 @@ public final class HdmiCecMessage { private final int mOpcode; private final byte[] mParams; + private final int mValidationResult; + /** - * Constructor. + * Constructor that allows the caller to provide the validation result. + * Must only be called by subclasses; other callers should use {@link #build}. */ - public HdmiCecMessage(int source, int destination, int opcode, byte[] params) { + protected HdmiCecMessage(int source, int destination, int opcode, byte[] params, + @ValidationResult int validationResult) { mSource = source; mDestination = destination; mOpcode = opcode & 0xFF; mParams = Arrays.copyOf(params, params.length); + mValidationResult = validationResult; + } + + private HdmiCecMessage(int source, int destination, int opcode, byte[] params) { + this(source, destination, opcode, params, + HdmiCecMessageValidator.validate(source, destination, opcode & 0xFF, params)); + } + + /** + * Constructs and validates a message. The result of validation will be accessible via + * {@link #getValidationResult}. + * + * Intended for parsing incoming messages, as it takes raw bytes as message parameters. + * + * If the opcode has its own subclass of this one, this method will instead validate and build + * the message using the logic in that class. If successful, it will return a validated + * instance of that class that exposes parsed parameters. + */ + static HdmiCecMessage build(int source, int destination, int opcode, byte[] params) { + switch (opcode & 0xFF) { + case Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL: + return SetAudioVolumeLevelMessage.build(source, destination, params); + case Constants.MESSAGE_REPORT_FEATURES: + return ReportFeaturesMessage.build(source, destination, params); + default: + return new HdmiCecMessage(source, destination, opcode & 0xFF, params); + } + } + + static HdmiCecMessage build(int source, int destination, int opcode) { + return new HdmiCecMessage(source, destination, opcode, EMPTY_PARAM); } @Override public boolean equals(@Nullable Object message) { if (message instanceof HdmiCecMessage) { HdmiCecMessage that = (HdmiCecMessage) message; - return this.mSource == that.getSource() && - this.mDestination == that.getDestination() && - this.mOpcode == that.getOpcode() && - Arrays.equals(this.mParams, that.getParams()); + return this.mSource == that.getSource() + && this.mDestination == that.getDestination() + && this.mOpcode == that.getOpcode() + && Arrays.equals(this.mParams, that.getParams()) + && this.mValidationResult == that.getValidationResult(); } return false; } @@ -111,6 +151,13 @@ public final class HdmiCecMessage { return mParams; } + /** + * Returns the validation result of the message. + */ + public int getValidationResult() { + return mValidationResult; + } + @Override public String toString() { StringBuilder s = new StringBuilder(); @@ -129,9 +176,30 @@ public final class HdmiCecMessage { } } } + if (mValidationResult != HdmiCecMessageValidator.OK) { + s.append(String.format(" <Validation error: %s>", + validationResultToString(mValidationResult))); + } return s.toString(); } + private static String validationResultToString(@ValidationResult int validationResult) { + switch (validationResult) { + case HdmiCecMessageValidator.OK: + return "ok"; + case HdmiCecMessageValidator.ERROR_SOURCE: + return "invalid source"; + case HdmiCecMessageValidator.ERROR_DESTINATION: + return "invalid destination"; + case HdmiCecMessageValidator.ERROR_PARAMETER: + return "invalid parameters"; + case HdmiCecMessageValidator.ERROR_PARAMETER_SHORT: + return "short parameters"; + default: + return "unknown error"; + } + } + private static String opcodeToString(@FeatureOpcode int opcode) { switch (opcode) { case Constants.MESSAGE_FEATURE_ABORT: diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java index 1c8f21f6526e..adcff4c1111f 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java @@ -16,17 +16,15 @@ package com.android.server.hdmi; -import android.hardware.hdmi.HdmiControlManager; -import android.hardware.hdmi.HdmiDeviceInfo; - import com.android.server.hdmi.Constants.AudioCodec; import java.io.UnsupportedEncodingException; -import java.util.Arrays; -import java.util.List; /** * A helper class to build {@link HdmiCecMessage} from various cec commands. + * + * If a message type has its own specific subclass of {@link HdmiCecMessage}, + * its static factory method is instead declared in that subclass. */ public class HdmiCecMessageBuilder { private static final int OSD_NAME_MAX_LENGTH = 14; @@ -34,20 +32,6 @@ public class HdmiCecMessageBuilder { private HdmiCecMessageBuilder() {} /** - * Build {@link HdmiCecMessage} from raw data. - * - * @param src source address of command - * @param dest destination address of command - * @param body body of message. It includes opcode. - * @return newly created {@link HdmiCecMessage} - */ - static HdmiCecMessage of(int src, int dest, byte[] body) { - byte opcode = body[0]; - byte params[] = Arrays.copyOfRange(body, 1, body.length); - return new HdmiCecMessage(src, dest, opcode, params); - } - - /** * Build <Feature Abort> command. <Feature Abort> consists of * 1 byte original opcode and 1 byte reason fields with basic fields. * @@ -63,7 +47,7 @@ public class HdmiCecMessageBuilder { (byte) (originalOpcode & 0xFF), (byte) (reason & 0xFF), }; - return buildCommand(src, dest, Constants.MESSAGE_FEATURE_ABORT, params); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_FEATURE_ABORT, params); } /** @@ -74,7 +58,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildGivePhysicalAddress(int src, int dest) { - return buildCommand(src, dest, Constants.MESSAGE_GIVE_PHYSICAL_ADDRESS); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_GIVE_PHYSICAL_ADDRESS); } /** @@ -85,7 +69,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildGiveOsdNameCommand(int src, int dest) { - return buildCommand(src, dest, Constants.MESSAGE_GIVE_OSD_NAME); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_GIVE_OSD_NAME); } /** @@ -96,7 +80,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildGiveDeviceVendorIdCommand(int src, int dest) { - return buildCommand(src, dest, Constants.MESSAGE_GIVE_DEVICE_VENDOR_ID); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_GIVE_DEVICE_VENDOR_ID); } /** @@ -121,7 +105,7 @@ public class HdmiCecMessageBuilder { (byte) (normalized.charAt(2) & 0xFF), }; // <Set Menu Language> is broadcast message. - return buildCommand(src, Constants.ADDR_BROADCAST, + return HdmiCecMessage.build(src, Constants.ADDR_BROADCAST, Constants.MESSAGE_SET_MENU_LANGUAGE, params); } @@ -141,7 +125,7 @@ public class HdmiCecMessageBuilder { } catch (UnsupportedEncodingException e) { return null; } - return buildCommand(src, dest, Constants.MESSAGE_SET_OSD_NAME, params); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_SET_OSD_NAME, params); } /** @@ -164,7 +148,7 @@ public class HdmiCecMessageBuilder { (byte) (deviceType & 0xFF) }; // <Report Physical Address> is broadcast message. - return buildCommand(src, Constants.ADDR_BROADCAST, + return HdmiCecMessage.build(src, Constants.ADDR_BROADCAST, Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS, params); } @@ -185,7 +169,7 @@ public class HdmiCecMessageBuilder { (byte) (vendorId & 0xFF) }; // <Device Vendor Id> is broadcast message. - return buildCommand(src, Constants.ADDR_BROADCAST, + return HdmiCecMessage.build(src, Constants.ADDR_BROADCAST, Constants.MESSAGE_DEVICE_VENDOR_ID, params); } @@ -202,7 +186,7 @@ public class HdmiCecMessageBuilder { byte[] params = new byte[] { (byte) (version & 0xFF) }; - return buildCommand(src, dest, Constants.MESSAGE_CEC_VERSION, params); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_CEC_VERSION, params); } /** @@ -213,7 +197,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildRequestArcInitiation(int src, int dest) { - return buildCommand(src, dest, Constants.MESSAGE_REQUEST_ARC_INITIATION); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_REQUEST_ARC_INITIATION); } /** @@ -224,7 +208,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildInitiateArc(int src, int dest) { - return buildCommand(src, dest, Constants.MESSAGE_INITIATE_ARC); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_INITIATE_ARC); } /** @@ -235,7 +219,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildTerminateArc(int src, int dest) { - return buildCommand(src, dest, Constants.MESSAGE_TERMINATE_ARC); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_TERMINATE_ARC); } /** @@ -246,7 +230,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildRequestArcTermination(int src, int dest) { - return buildCommand(src, dest, Constants.MESSAGE_REQUEST_ARC_TERMINATION); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_REQUEST_ARC_TERMINATION); } /** @@ -257,7 +241,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildReportArcInitiated(int src, int dest) { - return buildCommand(src, dest, Constants.MESSAGE_REPORT_ARC_INITIATED); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_REPORT_ARC_INITIATED); } /** @@ -268,7 +252,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildReportArcTerminated(int src, int dest) { - return buildCommand(src, dest, Constants.MESSAGE_REPORT_ARC_TERMINATED); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_REPORT_ARC_TERMINATED); } @@ -286,7 +270,8 @@ public class HdmiCecMessageBuilder { for (int i = 0; i < params.length ; i++){ params[i] = (byte) (audioFormats[i] & 0xff); } - return buildCommand(src, dest, Constants.MESSAGE_REQUEST_SHORT_AUDIO_DESCRIPTOR, params); + return HdmiCecMessage.build( + src, dest, Constants.MESSAGE_REQUEST_SHORT_AUDIO_DESCRIPTOR, params); } @@ -298,7 +283,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildTextViewOn(int src, int dest) { - return buildCommand(src, dest, Constants.MESSAGE_TEXT_VIEW_ON); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_TEXT_VIEW_ON); } /** @@ -308,7 +293,8 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildRequestActiveSource(int src) { - return buildCommand(src, Constants.ADDR_BROADCAST, Constants.MESSAGE_REQUEST_ACTIVE_SOURCE); + return HdmiCecMessage.build( + src, Constants.ADDR_BROADCAST, Constants.MESSAGE_REQUEST_ACTIVE_SOURCE); } /** @@ -319,7 +305,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildActiveSource(int src, int physicalAddress) { - return buildCommand(src, Constants.ADDR_BROADCAST, Constants.MESSAGE_ACTIVE_SOURCE, + return HdmiCecMessage.build(src, Constants.ADDR_BROADCAST, Constants.MESSAGE_ACTIVE_SOURCE, physicalAddressToParam(physicalAddress)); } @@ -331,7 +317,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildInactiveSource(int src, int physicalAddress) { - return buildCommand(src, Constants.ADDR_TV, + return HdmiCecMessage.build(src, Constants.ADDR_TV, Constants.MESSAGE_INACTIVE_SOURCE, physicalAddressToParam(physicalAddress)); } @@ -345,7 +331,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildSetStreamPath(int src, int streamPath) { - return buildCommand(src, Constants.ADDR_BROADCAST, + return HdmiCecMessage.build(src, Constants.ADDR_BROADCAST, Constants.MESSAGE_SET_STREAM_PATH, physicalAddressToParam(streamPath)); } @@ -364,7 +350,7 @@ public class HdmiCecMessageBuilder { (byte) ((oldPath >> 8) & 0xFF), (byte) (oldPath & 0xFF), (byte) ((newPath >> 8) & 0xFF), (byte) (newPath & 0xFF) }; - return buildCommand(src, Constants.ADDR_BROADCAST, Constants.MESSAGE_ROUTING_CHANGE, + return HdmiCecMessage.build(src, Constants.ADDR_BROADCAST, Constants.MESSAGE_ROUTING_CHANGE, param); } @@ -378,7 +364,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildRoutingInformation(int src, int physicalAddress) { - return buildCommand(src, Constants.ADDR_BROADCAST, + return HdmiCecMessage.build(src, Constants.ADDR_BROADCAST, Constants.MESSAGE_ROUTING_INFORMATION, physicalAddressToParam(physicalAddress)); } @@ -390,7 +376,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildGiveDevicePowerStatus(int src, int dest) { - return buildCommand(src, dest, Constants.MESSAGE_GIVE_DEVICE_POWER_STATUS); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_GIVE_DEVICE_POWER_STATUS); } /** @@ -405,7 +391,7 @@ public class HdmiCecMessageBuilder { byte[] param = new byte[] { (byte) (powerStatus & 0xFF) }; - return buildCommand(src, dest, Constants.MESSAGE_REPORT_POWER_STATUS, param); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_REPORT_POWER_STATUS, param); } /** @@ -420,7 +406,7 @@ public class HdmiCecMessageBuilder { byte[] param = new byte[] { (byte) (menuStatus & 0xFF) }; - return buildCommand(src, dest, Constants.MESSAGE_MENU_STATUS, param); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_MENU_STATUS, param); } /** @@ -435,10 +421,10 @@ public class HdmiCecMessageBuilder { static HdmiCecMessage buildSystemAudioModeRequest(int src, int avr, int avrPhysicalAddress, boolean enableSystemAudio) { if (enableSystemAudio) { - return buildCommand(src, avr, Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST, + return HdmiCecMessage.build(src, avr, Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST, physicalAddressToParam(avrPhysicalAddress)); } else { - return buildCommand(src, avr, Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST); + return HdmiCecMessage.build(src, avr, Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST); } } @@ -479,7 +465,8 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildReportShortAudioDescriptor(int src, int des, byte[] sadBytes) { - return buildCommand(src, des, Constants.MESSAGE_REPORT_SHORT_AUDIO_DESCRIPTOR, sadBytes); + return HdmiCecMessage.build( + src, des, Constants.MESSAGE_REPORT_SHORT_AUDIO_DESCRIPTOR, sadBytes); } /** @@ -490,7 +477,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildGiveAudioStatus(int src, int dest) { - return buildCommand(src, dest, Constants.MESSAGE_GIVE_AUDIO_STATUS); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_GIVE_AUDIO_STATUS); } /** @@ -505,7 +492,7 @@ public class HdmiCecMessageBuilder { static HdmiCecMessage buildReportAudioStatus(int src, int dest, int volume, boolean mute) { byte status = (byte) ((byte) (mute ? 1 << 7 : 0) | ((byte) volume & 0x7F)); byte[] params = new byte[] { status }; - return buildCommand(src, dest, Constants.MESSAGE_REPORT_AUDIO_STATUS, params); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_REPORT_AUDIO_STATUS, params); } /** @@ -529,7 +516,8 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildUserControlPressed(int src, int dest, byte[] commandParam) { - return buildCommand(src, dest, Constants.MESSAGE_USER_CONTROL_PRESSED, commandParam); + return HdmiCecMessage.build( + src, dest, Constants.MESSAGE_USER_CONTROL_PRESSED, commandParam); } /** @@ -540,7 +528,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildUserControlReleased(int src, int dest) { - return buildCommand(src, dest, Constants.MESSAGE_USER_CONTROL_RELEASED); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_USER_CONTROL_RELEASED); } /** @@ -551,7 +539,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildGiveSystemAudioModeStatus(int src, int dest) { - return buildCommand(src, dest, Constants.MESSAGE_GIVE_SYSTEM_AUDIO_MODE_STATUS); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_GIVE_SYSTEM_AUDIO_MODE_STATUS); } /** @@ -562,7 +550,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ public static HdmiCecMessage buildStandby(int src, int dest) { - return buildCommand(src, dest, Constants.MESSAGE_STANDBY); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_STANDBY); } /** @@ -574,7 +562,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildVendorCommand(int src, int dest, byte[] params) { - return buildCommand(src, dest, Constants.MESSAGE_VENDOR_COMMAND, params); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_VENDOR_COMMAND, params); } /** @@ -593,7 +581,7 @@ public class HdmiCecMessageBuilder { params[1] = (byte) ((vendorId >> 8) & 0xFF); params[2] = (byte) (vendorId & 0xFF); System.arraycopy(operands, 0, params, 3, operands.length); - return buildCommand(src, dest, Constants.MESSAGE_VENDOR_COMMAND_WITH_ID, params); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_VENDOR_COMMAND_WITH_ID, params); } /** @@ -605,7 +593,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildRecordOn(int src, int dest, byte[] params) { - return buildCommand(src, dest, Constants.MESSAGE_RECORD_ON, params); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_RECORD_ON, params); } /** @@ -616,7 +604,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildRecordOff(int src, int dest) { - return buildCommand(src, dest, Constants.MESSAGE_RECORD_OFF); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_RECORD_OFF); } /** @@ -628,7 +616,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildSetDigitalTimer(int src, int dest, byte[] params) { - return buildCommand(src, dest, Constants.MESSAGE_SET_DIGITAL_TIMER, params); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_SET_DIGITAL_TIMER, params); } /** @@ -640,7 +628,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildSetAnalogueTimer(int src, int dest, byte[] params) { - return buildCommand(src, dest, Constants.MESSAGE_SET_ANALOG_TIMER, params); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_SET_ANALOG_TIMER, params); } /** @@ -652,7 +640,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildSetExternalTimer(int src, int dest, byte[] params) { - return buildCommand(src, dest, Constants.MESSAGE_SET_EXTERNAL_TIMER, params); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_SET_EXTERNAL_TIMER, params); } /** @@ -664,7 +652,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildClearDigitalTimer(int src, int dest, byte[] params) { - return buildCommand(src, dest, Constants.MESSAGE_CLEAR_DIGITAL_TIMER, params); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_CLEAR_DIGITAL_TIMER, params); } /** @@ -676,7 +664,7 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildClearAnalogueTimer(int src, int dest, byte[] params) { - return buildCommand(src, dest, Constants.MESSAGE_CLEAR_ANALOG_TIMER, params); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_CLEAR_ANALOG_TIMER, params); } /** @@ -688,73 +676,16 @@ public class HdmiCecMessageBuilder { * @return newly created {@link HdmiCecMessage} */ static HdmiCecMessage buildClearExternalTimer(int src, int dest, byte[] params) { - return buildCommand(src, dest, Constants.MESSAGE_CLEAR_EXTERNAL_TIMER, params); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_CLEAR_EXTERNAL_TIMER, params); } static HdmiCecMessage buildGiveFeatures(int src, int dest) { - return buildCommand(src, dest, Constants.MESSAGE_GIVE_FEATURES); - } - - static HdmiCecMessage buildReportFeatures(int src, - @HdmiControlManager.HdmiCecVersion int cecVersion, - List<Integer> allDeviceTypes, @Constants.RcProfile int rcProfile, - List<Integer> rcFeatures, - List<Integer> deviceFeatures) { - byte cecVersionByte = (byte) (cecVersion & 0xFF); - byte deviceTypes = 0; - for (Integer deviceType : allDeviceTypes) { - deviceTypes |= 1 << hdmiDeviceInfoDeviceTypeToShiftValue(deviceType); - } - - byte rcProfileByte = 0; - rcProfileByte |= rcProfile << 6; - if (rcProfile == Constants.RC_PROFILE_SOURCE) { - for (@Constants.RcProfileSource Integer rcFeature : rcFeatures) { - rcProfileByte |= 1 << rcFeature; - } - } else { - @Constants.RcProfileTv byte rcProfileTv = (byte) (rcFeatures.get(0) & 0xFFFF); - rcProfileByte |= rcProfileTv; - } - - byte deviceFeaturesByte = 0; - for (@Constants.DeviceFeature Integer deviceFeature : deviceFeatures) { - deviceFeaturesByte |= 1 << deviceFeature; - } - - byte[] params = {cecVersionByte, deviceTypes, rcProfileByte, deviceFeaturesByte}; - return buildCommand(src, Constants.ADDR_BROADCAST, Constants.MESSAGE_REPORT_FEATURES, - params); + return HdmiCecMessage.build(src, dest, Constants.MESSAGE_GIVE_FEATURES); } /***** Please ADD new buildXXX() methods above. ******/ /** - * Build a {@link HdmiCecMessage} without extra parameter. - * - * @param src source address of command - * @param dest destination address of command - * @param opcode opcode for a message - * @return newly created {@link HdmiCecMessage} - */ - private static HdmiCecMessage buildCommand(int src, int dest, int opcode) { - return new HdmiCecMessage(src, dest, opcode, HdmiCecMessage.EMPTY_PARAM); - } - - /** - * Build a {@link HdmiCecMessage} with given values. - * - * @param src source address of command - * @param dest destination address of command - * @param opcode opcode for a message - * @param params extra parameters for command - * @return newly created {@link HdmiCecMessage} - */ - private static HdmiCecMessage buildCommand(int src, int dest, int opcode, byte[] params) { - return new HdmiCecMessage(src, dest, opcode, params); - } - - /** * Build a {@link HdmiCecMessage} with a boolean param and other given values. * * @param src source address of command @@ -768,7 +699,7 @@ public class HdmiCecMessageBuilder { byte[] params = new byte[]{ param ? (byte) 0b1 : 0b0 }; - return buildCommand(src, des, opcode, params); + return HdmiCecMessage.build(src, des, opcode, params); } private static byte[] physicalAddressToParam(int physicalAddress) { @@ -777,24 +708,4 @@ public class HdmiCecMessageBuilder { (byte) (physicalAddress & 0xFF) }; } - - @Constants.DeviceType - private static int hdmiDeviceInfoDeviceTypeToShiftValue(int deviceType) { - switch (deviceType) { - case HdmiDeviceInfo.DEVICE_TV: - return Constants.ALL_DEVICE_TYPES_TV; - case HdmiDeviceInfo.DEVICE_RECORDER: - return Constants.ALL_DEVICE_TYPES_RECORDER; - case HdmiDeviceInfo.DEVICE_TUNER: - return Constants.ALL_DEVICE_TYPES_TUNER; - case HdmiDeviceInfo.DEVICE_PLAYBACK: - return Constants.ALL_DEVICE_TYPES_PLAYBACK; - case HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM: - return Constants.ALL_DEVICE_TYPES_AUDIO_SYSTEM; - case HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH: - return Constants.ALL_DEVICE_TYPES_SWITCH; - default: - throw new IllegalArgumentException("Unhandled device type: " + deviceType); - } - } } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java index 8a727c6ffb24..220a438d55ee 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java @@ -16,23 +16,34 @@ package com.android.server.hdmi; +import android.annotation.IntDef; import android.hardware.hdmi.HdmiDeviceInfo; import android.util.SparseArray; /** - * A helper class to validates {@link HdmiCecMessage}. + * A helper class to validate {@link HdmiCecMessage}. + * + * If a message type has its own specific subclass of {@link HdmiCecMessage}, + * validation is performed in that subclass instead. */ public class HdmiCecMessageValidator { private static final String TAG = "HdmiCecMessageValidator"; + @IntDef({ + OK, + ERROR_SOURCE, + ERROR_DESTINATION, + ERROR_PARAMETER, + ERROR_PARAMETER_SHORT, + }) + public @interface ValidationResult {}; + static final int OK = 0; static final int ERROR_SOURCE = 1; static final int ERROR_DESTINATION = 2; static final int ERROR_PARAMETER = 3; static final int ERROR_PARAMETER_SHORT = 4; - private final HdmiControlService mService; - interface ParameterValidator { /** * @return errorCode errorCode can be {@link #OK}, {@link #ERROR_PARAMETER} or @@ -42,13 +53,13 @@ public class HdmiCecMessageValidator { } // Only the direct addressing is allowed. - private static final int DEST_DIRECT = 1 << 0; + public static final int DEST_DIRECT = 1 << 0; // Only the broadcast addressing is allowed. - private static final int DEST_BROADCAST = 1 << 1; + public static final int DEST_BROADCAST = 1 << 1; // Both the direct and the broadcast addressing are allowed. - private static final int DEST_ALL = DEST_DIRECT | DEST_BROADCAST; + public static final int DEST_ALL = DEST_DIRECT | DEST_BROADCAST; // True if the messages from address 15 (unregistered) are allowed. - private static final int SRC_UNREGISTERED = 1 << 2; + public static final int SRC_UNREGISTERED = 1 << 2; private static class ValidationInfo { public final ParameterValidator parameterValidator; @@ -60,11 +71,11 @@ public class HdmiCecMessageValidator { } } - final SparseArray<ValidationInfo> mValidationInfo = new SparseArray<>(); + private HdmiCecMessageValidator() {} - public HdmiCecMessageValidator(HdmiControlService service) { - mService = service; + private static final SparseArray<ValidationInfo> sValidationInfo = new SparseArray<>(); + static { // Messages related to the physical address. PhysicalAddressValidator physicalAddressValidator = new PhysicalAddressValidator(); addValidationInfo(Constants.MESSAGE_ACTIVE_SOURCE, @@ -234,8 +245,6 @@ public class HdmiCecMessageValidator { // Messages for Feature Discovery. addValidationInfo(Constants.MESSAGE_GIVE_FEATURES, noneValidator, DEST_DIRECT | SRC_UNREGISTERED); - addValidationInfo(Constants.MESSAGE_REPORT_FEATURES, new VariableLengthValidator(4, 14), - DEST_BROADCAST); // Messages for Dynamic Auto Lipsync addValidationInfo(Constants.MESSAGE_REQUEST_CURRENT_LATENCY, physicalAddressValidator, @@ -250,59 +259,62 @@ public class HdmiCecMessageValidator { DEST_BROADCAST | SRC_UNREGISTERED); } - private void addValidationInfo(int opcode, ParameterValidator validator, int addrType) { - mValidationInfo.append(opcode, new ValidationInfo(validator, addrType)); + private static void addValidationInfo(int opcode, ParameterValidator validator, int addrType) { + sValidationInfo.append(opcode, new ValidationInfo(validator, addrType)); } - int isValid(HdmiCecMessage message, boolean isMessageReceived) { - int opcode = message.getOpcode(); - ValidationInfo info = mValidationInfo.get(opcode); + /** + * Validates all parameters of a HDMI-CEC message using static information stored in this class. + */ + @ValidationResult + static int validate(int source, int destination, int opcode, byte[] params) { + ValidationInfo info = sValidationInfo.get(opcode); + if (info == null) { - HdmiLogger.warning("No validation information for the message: " + message); + HdmiLogger.warning("No validation information for the opcode: " + opcode); return OK; } - // Check the source field. - if (message.getSource() == Constants.ADDR_UNREGISTERED && - (info.addressType & SRC_UNREGISTERED) == 0) { - HdmiLogger.warning("Unexpected source: " + message); - return ERROR_SOURCE; + int addressValidationResult = validateAddress(source, destination, info.addressType); + if (addressValidationResult != OK) { + return addressValidationResult; } - if (isMessageReceived) { - // Check if the source's logical address and local device's logical - // address are the same. - for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) { - synchronized (device.mLock) { - if (message.getSource() == device.getDeviceInfo().getLogicalAddress() - && message.getSource() != Constants.ADDR_UNREGISTERED) { - HdmiLogger.warning( - "Unexpected source: message sent from device itself, " + message); - return ERROR_SOURCE; - } - } - } + // Validate parameters + int errorCode = info.parameterValidator.isValid(params); + if (errorCode != OK) { + return errorCode; + } + + return OK; + } + + /** + * Validates the source and destination addresses of a HDMI-CEC message according to input + * address type. Allows address validation logic to be expressed concisely without depending + * on static information in this class. + * @param source Source address to validate + * @param destination Destination address to validate + * @param addressType Rules for validating the addresses - e.g. {@link #DEST_BROADCAST} + */ + @ValidationResult + static int validateAddress(int source, int destination, int addressType) { + // Check the source field. + if (source == Constants.ADDR_UNREGISTERED + && (addressType & SRC_UNREGISTERED) == 0) { + return ERROR_SOURCE; } // Check the destination field. - if (message.getDestination() == Constants.ADDR_BROADCAST) { - if ((info.addressType & DEST_BROADCAST) == 0) { - HdmiLogger.warning("Unexpected broadcast message: " + message); + if (destination == Constants.ADDR_BROADCAST) { + if ((addressType & DEST_BROADCAST) == 0) { return ERROR_DESTINATION; } } else { // Direct addressing. - if ((info.addressType & DEST_DIRECT) == 0) { - HdmiLogger.warning("Unexpected direct message: " + message); + if ((addressType & DEST_DIRECT) == 0) { return ERROR_DESTINATION; } } - - // Check the parameter type. - int errorCode = info.parameterValidator.isValid(message.getParams()); - if (errorCode != OK) { - HdmiLogger.warning("Unexpected parameters: " + message); - return errorCode; - } return OK; } @@ -336,7 +348,7 @@ public class HdmiCecMessageValidator { } } - private boolean isValidPhysicalAddress(byte[] params, int offset) { + private static boolean isValidPhysicalAddress(byte[] params, int offset) { int physicalAddress = HdmiUtils.twoBytesToInt(params, offset); while (physicalAddress != 0) { int maskedAddress = physicalAddress & 0xF000; @@ -345,19 +357,6 @@ public class HdmiCecMessageValidator { return false; } } - - if (!mService.isTvDevice()) { - // If the device is not TV, we can't convert path to port-id, so stop here. - return true; - } - int path = HdmiUtils.twoBytesToInt(params, offset); - if (path != Constants.INVALID_PHYSICAL_ADDRESS && path == mService.getPhysicalAddress()) { - return true; - } - int portId = mService.pathToPortId(path); - if (portId == Constants.INVALID_PORT_ID) { - return false; - } return true; } @@ -380,7 +379,7 @@ public class HdmiCecMessageValidator { return success ? OK : ERROR_PARAMETER; } - private boolean isWithinRange(int value, int min, int max) { + private static boolean isWithinRange(int value, int min, int max) { value = value & 0xFF; return (value >= min && value <= max); } @@ -392,7 +391,7 @@ public class HdmiCecMessageValidator { * @param value Display Control * @return true if the Display Control is valid */ - private boolean isValidDisplayControl(int value) { + private static boolean isValidDisplayControl(int value) { value = value & 0xFF; return (value == 0x00 || value == 0x40 || value == 0x80 || value == 0xC0); } @@ -407,7 +406,7 @@ public class HdmiCecMessageValidator { * @param maxLength Maximum length of string to be evaluated * @return true if the given type is valid */ - private boolean isValidAsciiString(byte[] params, int offset, int maxLength) { + private static boolean isValidAsciiString(byte[] params, int offset, int maxLength) { for (int i = offset; i < params.length && i < maxLength; i++) { if (!isWithinRange(params[i], 0x20, 0x7E)) { return false; @@ -423,7 +422,7 @@ public class HdmiCecMessageValidator { * @param value day of month * @return true if the day of month is valid */ - private boolean isValidDayOfMonth(int value) { + private static boolean isValidDayOfMonth(int value) { return isWithinRange(value, 1, 31); } @@ -434,7 +433,7 @@ public class HdmiCecMessageValidator { * @param value month of year * @return true if the month of year is valid */ - private boolean isValidMonthOfYear(int value) { + private static boolean isValidMonthOfYear(int value) { return isWithinRange(value, 1, 12); } @@ -445,7 +444,7 @@ public class HdmiCecMessageValidator { * @param value hour * @return true if the hour is valid */ - private boolean isValidHour(int value) { + private static boolean isValidHour(int value) { return isWithinRange(value, 0, 23); } @@ -456,7 +455,7 @@ public class HdmiCecMessageValidator { * @param value minute * @return true if the minute is valid */ - private boolean isValidMinute(int value) { + private static boolean isValidMinute(int value) { return isWithinRange(value, 0, 59); } @@ -467,7 +466,7 @@ public class HdmiCecMessageValidator { * @param value duration hours * @return true if the duration hours is valid */ - private boolean isValidDurationHours(int value) { + private static boolean isValidDurationHours(int value) { return isWithinRange(value, 0, 99); } @@ -478,7 +477,7 @@ public class HdmiCecMessageValidator { * @param value recording sequence * @return true if the given recording sequence is valid */ - private boolean isValidRecordingSequence(int value) { + private static boolean isValidRecordingSequence(int value) { value = value & 0xFF; // Validate bit 7 is set to zero if ((value & 0x80) != 0x00) { @@ -496,7 +495,7 @@ public class HdmiCecMessageValidator { * @param value analogue broadcast type * @return true if the analogue broadcast type is valid */ - private boolean isValidAnalogueBroadcastType(int value) { + private static boolean isValidAnalogueBroadcastType(int value) { return isWithinRange(value, 0x00, 0x02); } @@ -508,7 +507,7 @@ public class HdmiCecMessageValidator { * @param value analogue frequency * @return true if the analogue frequency is valid */ - private boolean isValidAnalogueFrequency(int value) { + private static boolean isValidAnalogueFrequency(int value) { value = value & 0xFFFF; return (value != 0x000 && value != 0xFFFF); } @@ -520,7 +519,7 @@ public class HdmiCecMessageValidator { * @param value broadcast system * @return true if the broadcast system is valid */ - private boolean isValidBroadcastSystem(int value) { + private static boolean isValidBroadcastSystem(int value) { return isWithinRange(value, 0, 31); } @@ -531,7 +530,7 @@ public class HdmiCecMessageValidator { * @param value Digital Broadcast System * @return true if the Digital Broadcast System is ARIB type */ - private boolean isAribDbs(int value) { + private static boolean isAribDbs(int value) { return (value == 0x00 || isWithinRange(value, 0x08, 0x0A)); } @@ -542,7 +541,7 @@ public class HdmiCecMessageValidator { * @param value Digital Broadcast System * @return true if the Digital Broadcast System is ATSC type */ - private boolean isAtscDbs(int value) { + private static boolean isAtscDbs(int value) { return (value == 0x01 || isWithinRange(value, 0x10, 0x12)); } @@ -553,7 +552,7 @@ public class HdmiCecMessageValidator { * @param value Digital Broadcast System * @return true if the Digital Broadcast System is DVB type */ - private boolean isDvbDbs(int value) { + private static boolean isDvbDbs(int value) { return (value == 0x02 || isWithinRange(value, 0x18, 0x1B)); } @@ -565,7 +564,7 @@ public class HdmiCecMessageValidator { * @param value Digital Broadcast System * @return true if the Digital Broadcast System is valid */ - private boolean isValidDigitalBroadcastSystem(int value) { + private static boolean isValidDigitalBroadcastSystem(int value) { return (isAribDbs(value) || isAtscDbs(value) || isDvbDbs(value)); } @@ -578,7 +577,7 @@ public class HdmiCecMessageValidator { * @param offset start offset of Channel Identifier * @return true if the Channel Identifier is valid */ - private boolean isValidChannelIdentifier(byte[] params, int offset) { + private static boolean isValidChannelIdentifier(byte[] params, int offset) { // First 6 bits contain Channel Number Format int channelNumberFormat = params[offset] & 0xFC; if (channelNumberFormat == 0x04) { @@ -600,7 +599,7 @@ public class HdmiCecMessageValidator { * @param offset start offset of Digital Service Identification * @return true if the Digital Service Identification is valid */ - private boolean isValidDigitalServiceIdentification(byte[] params, int offset) { + private static boolean isValidDigitalServiceIdentification(byte[] params, int offset) { // MSB contains Service Identification Method int serviceIdentificationMethod = params[offset] & 0x80; // Last 7 bits contains Digital Broadcast System @@ -634,7 +633,7 @@ public class HdmiCecMessageValidator { * @param value External Plug * @return true if the External Plug is valid */ - private boolean isValidExternalPlug(int value) { + private static boolean isValidExternalPlug(int value) { return isWithinRange(value, 1, 255); } @@ -645,7 +644,7 @@ public class HdmiCecMessageValidator { * @param value External Source Specifier * @return true if the External Source is valid */ - private boolean isValidExternalSource(byte[] params, int offset) { + private static boolean isValidExternalSource(byte[] params, int offset) { int externalSourceSpecifier = params[offset]; offset = offset + 1; if (externalSourceSpecifier == 0x04) { @@ -661,15 +660,15 @@ public class HdmiCecMessageValidator { return false; } - private boolean isValidProgrammedInfo(int programedInfo) { + private static boolean isValidProgrammedInfo(int programedInfo) { return (isWithinRange(programedInfo, 0x00, 0x0B)); } - private boolean isValidNotProgrammedErrorInfo(int nonProgramedErrorInfo) { + private static boolean isValidNotProgrammedErrorInfo(int nonProgramedErrorInfo) { return (isWithinRange(nonProgramedErrorInfo, 0x00, 0x0E)); } - private boolean isValidTimerStatusData(byte[] params, int offset) { + private static boolean isValidTimerStatusData(byte[] params, int offset) { int programedIndicator = params[offset] & 0x10; boolean durationAvailable = false; if (programedIndicator == 0x10) { @@ -708,7 +707,7 @@ public class HdmiCecMessageValidator { * @param value Play mode * @return true if the Play mode is valid */ - private boolean isValidPlayMode(int value) { + private static boolean isValidPlayMode(int value) { return (isWithinRange(value, 0x05, 0x07) || isWithinRange(value, 0x09, 0x0B) || isWithinRange(value, 0x15, 0x17) @@ -725,7 +724,7 @@ public class HdmiCecMessageValidator { * @param value UI Broadcast type * @return true if the UI Broadcast type is valid */ - private boolean isValidUiBroadcastType(int value) { + private static boolean isValidUiBroadcastType(int value) { return ((value == 0x00) || (value == 0x01) || (value == 0x10) @@ -749,7 +748,7 @@ public class HdmiCecMessageValidator { * @param value UI Sound Presenation Control * @return true if the UI Sound Presenation Control is valid */ - private boolean isValidUiSoundPresenationControl(int value) { + private static boolean isValidUiSoundPresenationControl(int value) { value = value & 0xFF; return ((value == 0x20) || (value == 0x30) @@ -768,7 +767,7 @@ public class HdmiCecMessageValidator { * @param params Tuner device info * @return true if the Tuner device info is valid */ - private boolean isValidTunerDeviceInfo(byte[] params) { + private static boolean isValidTunerDeviceInfo(byte[] params) { int tunerDisplayInfo = params[0] & 0x7F; if (tunerDisplayInfo == 0x00) { // Displaying digital tuner @@ -789,7 +788,7 @@ public class HdmiCecMessageValidator { return false; } - private class PhysicalAddressValidator implements ParameterValidator { + private static class PhysicalAddressValidator implements ParameterValidator { @Override public int isValid(byte[] params) { if (params.length < 2) { @@ -799,7 +798,7 @@ public class HdmiCecMessageValidator { } } - private class SystemAudioModeRequestValidator extends PhysicalAddressValidator { + private static class SystemAudioModeRequestValidator extends PhysicalAddressValidator { @Override public int isValid(byte[] params) { // TV can send <System Audio Mode Request> with no parameters to terminate system audio. @@ -810,7 +809,7 @@ public class HdmiCecMessageValidator { } } - private class ReportPhysicalAddressValidator implements ParameterValidator { + private static class ReportPhysicalAddressValidator implements ParameterValidator { @Override public int isValid(byte[] params) { if (params.length < 3) { @@ -820,7 +819,7 @@ public class HdmiCecMessageValidator { } } - private class RoutingChangeValidator implements ParameterValidator { + private static class RoutingChangeValidator implements ParameterValidator { @Override public int isValid(byte[] params) { if (params.length < 4) { @@ -836,7 +835,7 @@ public class HdmiCecMessageValidator { * A valid parameter should lie within the range description of Record Status Info defined in * CEC 1.4 Specification : Operand Descriptions (Section 17) */ - private class RecordStatusInfoValidator implements ParameterValidator { + private static class RecordStatusInfoValidator implements ParameterValidator { @Override public int isValid(byte[] params) { if (params.length < 1) { @@ -855,7 +854,7 @@ public class HdmiCecMessageValidator { * A valid parameter should lie within the range description of ASCII defined in CEC 1.4 * Specification : Operand Descriptions (Section 17) */ - private class AsciiValidator implements ParameterValidator { + private static class AsciiValidator implements ParameterValidator { private final int mMinLength; private final int mMaxLength; @@ -885,7 +884,7 @@ public class HdmiCecMessageValidator { * A valid parameter should lie within the range description of ASCII defined in CEC 1.4 * Specification : Operand Descriptions (Section 17) */ - private class OsdStringValidator implements ParameterValidator { + private static class OsdStringValidator implements ParameterValidator { @Override public int isValid(byte[] params) { // If the length is longer than expected, we assume it's OK since the parameter can be @@ -902,7 +901,7 @@ public class HdmiCecMessageValidator { } /** Check if the given parameters are one byte parameters and within range. */ - private class OneByteRangeValidator implements ParameterValidator { + private static class OneByteRangeValidator implements ParameterValidator { private final int mMinValue, mMaxValue; OneByteRangeValidator(int minValue, int maxValue) { @@ -924,7 +923,7 @@ public class HdmiCecMessageValidator { * adhere to message description of Analogue Timer defined in CEC 1.4 Specification : Message * Descriptions for Timer Programming Feature (CEC Table 12) */ - private class AnalogueTimerValidator implements ParameterValidator { + private static class AnalogueTimerValidator implements ParameterValidator { @Override public int isValid(byte[] params) { if (params.length < 11) { @@ -950,7 +949,7 @@ public class HdmiCecMessageValidator { * to message description of Digital Timer defined in CEC 1.4 Specification : Message * Descriptions for Timer Programming Feature (CEC Table 12) */ - private class DigitalTimerValidator implements ParameterValidator { + private static class DigitalTimerValidator implements ParameterValidator { @Override public int isValid(byte[] params) { if (params.length < 11) { @@ -974,7 +973,7 @@ public class HdmiCecMessageValidator { * adhere to message description of External Timer defined in CEC 1.4 Specification : Message * Descriptions for Timer Programming Feature (CEC Table 12) */ - private class ExternalTimerValidator implements ParameterValidator { + private static class ExternalTimerValidator implements ParameterValidator { @Override public int isValid(byte[] params) { if (params.length < 9) { @@ -997,7 +996,7 @@ public class HdmiCecMessageValidator { * within the range description defined in CEC 1.4 Specification : Operand Descriptions * (Section 17) */ - private class TimerClearedStatusValidator implements ParameterValidator { + private static class TimerClearedStatusValidator implements ParameterValidator { @Override public int isValid(byte[] params) { if (params.length < 1) { @@ -1011,7 +1010,7 @@ public class HdmiCecMessageValidator { * Check if the given timer status data parameter is valid. A valid parameter should lie within * the range description defined in CEC 1.4 Specification : Operand Descriptions (Section 17) */ - private class TimerStatusValidator implements ParameterValidator { + private static class TimerStatusValidator implements ParameterValidator { @Override public int isValid(byte[] params) { if (params.length < 1) { @@ -1025,7 +1024,7 @@ public class HdmiCecMessageValidator { * Check if the given play mode parameter is valid. A valid parameter should lie within the * range description defined in CEC 1.4 Specification : Operand Descriptions (Section 17) */ - private class PlayModeValidator implements ParameterValidator { + private static class PlayModeValidator implements ParameterValidator { @Override public int isValid(byte[] params) { if (params.length < 1) { @@ -1040,7 +1039,7 @@ public class HdmiCecMessageValidator { * within the range description defined in CEC 1.4 Specification : Operand Descriptions * (Section 17) */ - private class SelectAnalogueServiceValidator implements ParameterValidator { + private static class SelectAnalogueServiceValidator implements ParameterValidator { @Override public int isValid(byte[] params) { if (params.length < 4) { @@ -1057,7 +1056,7 @@ public class HdmiCecMessageValidator { * within the range description defined in CEC 1.4 Specification : Operand Descriptions * (Section 17) */ - private class SelectDigitalServiceValidator implements ParameterValidator { + private static class SelectDigitalServiceValidator implements ParameterValidator { @Override public int isValid(byte[] params) { if (params.length < 4) { @@ -1072,7 +1071,7 @@ public class HdmiCecMessageValidator { * within the range description defined in CEC 1.4 Specification : Operand Descriptions (Section * 17) */ - private class TunerDeviceStatusValidator implements ParameterValidator { + private static class TunerDeviceStatusValidator implements ParameterValidator { @Override public int isValid(byte[] params) { if (params.length < 1) { @@ -1083,7 +1082,7 @@ public class HdmiCecMessageValidator { } /** Check if the given user control press parameter is valid. */ - private class UserControlPressedValidator implements ParameterValidator { + private static class UserControlPressedValidator implements ParameterValidator { @Override public int isValid(byte[] params) { if (params.length < 1) { diff --git a/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java b/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java index 225785a4401d..64971746dce9 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java @@ -57,7 +57,8 @@ import java.util.concurrent.ArrayBlockingQueue; * This class should not take any active action in sending CEC messages. * * Note that the information cached in this class is not guaranteed to be up-to-date, especially OSD - * names, power states can be outdated. + * names, power states can be outdated. For local devices, more up-to-date information can be + * accessed through {@link HdmiCecLocalDevice#getDeviceInfo()}. */ @VisibleForTesting public class HdmiCecNetwork { @@ -390,7 +391,7 @@ public class HdmiCecNetwork { return; } - updateCecDevice(HdmiUtils.cloneHdmiDeviceInfo(info, newPowerStatus)); + updateCecDevice(info.toBuilder().setDevicePowerStatus(newPowerStatus).build()); } /** @@ -427,7 +428,8 @@ public class HdmiCecNetwork { for (HdmiPortInfo info : cecPortInfo) { portIdMap.put(info.getAddress(), info.getId()); portInfoMap.put(info.getId(), info); - portDeviceMap.put(info.getId(), new HdmiDeviceInfo(info.getAddress(), info.getId())); + portDeviceMap.put(info.getId(), + HdmiDeviceInfo.hardwarePort(info.getAddress(), info.getId())); } mPortIdMap = new UnmodifiableSparseIntArray(portIdMap); mPortInfoMap = new UnmodifiableSparseArray<>(portInfoMap); @@ -496,13 +498,19 @@ public class HdmiCecNetwork { // Add device by logical address if it's not already known int sourceAddress = message.getSource(); if (getCecDeviceInfo(sourceAddress) == null) { - HdmiDeviceInfo newDevice = new HdmiDeviceInfo(sourceAddress, - HdmiDeviceInfo.PATH_INVALID, HdmiDeviceInfo.PORT_INVALID, - HdmiDeviceInfo.DEVICE_RESERVED, Constants.UNKNOWN_VENDOR_ID, - HdmiUtils.getDefaultDeviceName(sourceAddress)); + HdmiDeviceInfo newDevice = HdmiDeviceInfo.cecDeviceBuilder() + .setLogicalAddress(sourceAddress) + .setDisplayName(HdmiUtils.getDefaultDeviceName(sourceAddress)) + .build(); addCecDevice(newDevice); } + // If a message type has its own class, all valid messages of that type + // will be represented by an instance of that class. + if (message instanceof ReportFeaturesMessage) { + handleReportFeatures((ReportFeaturesMessage) message); + } + switch (message.getOpcode()) { case Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS: handleReportPhysicalAddress(message); @@ -519,18 +527,20 @@ public class HdmiCecNetwork { case Constants.MESSAGE_CEC_VERSION: handleCecVersion(message); break; - case Constants.MESSAGE_REPORT_FEATURES: - handleReportFeatures(message); - break; } } @ServiceThreadOnly - private void handleReportFeatures(HdmiCecMessage message) { + private void handleReportFeatures(ReportFeaturesMessage message) { assertRunOnServiceThread(); - int version = Byte.toUnsignedInt(message.getParams()[0]); - updateDeviceCecVersion(message.getSource(), version); + HdmiDeviceInfo currentDeviceInfo = getCecDeviceInfo(message.getSource()); + HdmiDeviceInfo newDeviceInfo = currentDeviceInfo.toBuilder() + .setCecVersion(message.getCecVersion()) + .updateDeviceFeatures(message.getDeviceFeatures()) + .build(); + + updateCecDevice(newDeviceInfo); } @ServiceThreadOnly @@ -554,11 +564,11 @@ public class HdmiCecNetwork { if (deviceInfo == null) { Slog.i(TAG, "Unknown source device info for <Report Physical Address> " + message); } else { - HdmiDeviceInfo updatedDeviceInfo = new HdmiDeviceInfo(deviceInfo.getLogicalAddress(), - physicalAddress, - physicalAddressToPortId(physicalAddress), type, deviceInfo.getVendorId(), - deviceInfo.getDisplayName(), deviceInfo.getDevicePowerStatus(), - deviceInfo.getCecVersion()); + HdmiDeviceInfo updatedDeviceInfo = deviceInfo.toBuilder() + .setPhysicalAddress(physicalAddress) + .setPortId(physicalAddressToPortId(physicalAddress)) + .setDeviceType(type) + .build(); updateCecDevice(updatedDeviceInfo); } } @@ -588,11 +598,9 @@ public class HdmiCecNetwork { return; } - HdmiDeviceInfo updatedDeviceInfo = new HdmiDeviceInfo(deviceInfo.getLogicalAddress(), - deviceInfo.getPhysicalAddress(), deviceInfo.getPortId(), deviceInfo.getDeviceType(), - deviceInfo.getVendorId(), - deviceInfo.getDisplayName(), deviceInfo.getDevicePowerStatus(), - hdmiCecVersion); + HdmiDeviceInfo updatedDeviceInfo = deviceInfo.toBuilder() + .setCecVersion(hdmiCecVersion) + .build(); updateCecDevice(updatedDeviceInfo); } @@ -623,10 +631,11 @@ public class HdmiCecNetwork { Slog.d(TAG, "Updating device OSD name from " + deviceInfo.getDisplayName() + " to " + osdName); - updateCecDevice(new HdmiDeviceInfo(deviceInfo.getLogicalAddress(), - deviceInfo.getPhysicalAddress(), deviceInfo.getPortId(), - deviceInfo.getDeviceType(), deviceInfo.getVendorId(), osdName, - deviceInfo.getDevicePowerStatus(), deviceInfo.getCecVersion())); + + HdmiDeviceInfo updatedDeviceInfo = deviceInfo.toBuilder() + .setDisplayName(osdName) + .build(); + updateCecDevice(updatedDeviceInfo); } @ServiceThreadOnly @@ -639,11 +648,9 @@ public class HdmiCecNetwork { if (deviceInfo == null) { Slog.i(TAG, "Unknown source device info for <Device Vendor ID> " + message); } else { - HdmiDeviceInfo updatedDeviceInfo = new HdmiDeviceInfo(deviceInfo.getLogicalAddress(), - deviceInfo.getPhysicalAddress(), - deviceInfo.getPortId(), deviceInfo.getDeviceType(), vendorId, - deviceInfo.getDisplayName(), deviceInfo.getDevicePowerStatus(), - deviceInfo.getCecVersion()); + HdmiDeviceInfo updatedDeviceInfo = deviceInfo.toBuilder() + .setVendorId(vendorId) + .build(); updateCecDevice(updatedDeviceInfo); } } @@ -723,10 +730,7 @@ public class HdmiCecNetwork { /** * Returns the {@link HdmiDeviceInfo} instance whose physical address matches - * - * - * - * qq * the given routing path. CEC devices use routing path for its physical address to + * the given routing path. CEC devices use routing path for its physical address to * describe the hierarchy of the devices in the network. * * @param path routing path or physical address diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 6dd9aa029ded..8391e0b4e19a 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -372,8 +372,6 @@ public class HdmiControlService extends SystemService { @Nullable private HdmiCecController mCecController; - private HdmiCecMessageValidator mMessageValidator; - private HdmiCecPowerStatusController mPowerStatusController; @ServiceThreadOnly @@ -606,9 +604,6 @@ public class HdmiControlService extends SystemService { mMhlDevices = Collections.emptyList(); mHdmiCecNetwork.initPortInfo(); - if (mMessageValidator == null) { - mMessageValidator = new HdmiCecMessageValidator(this); - } mHdmiCecConfig.registerChangeListener(HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED, new HdmiCecConfig.SettingChangeListener() { @Override @@ -1086,11 +1081,6 @@ public class HdmiControlService extends SystemService { } @VisibleForTesting - void setMessageValidator(HdmiCecMessageValidator messageValidator) { - mMessageValidator = messageValidator; - } - - @VisibleForTesting void setCecMessageBuffer(CecMessageBuffer cecMessageBuffer) { this.mCecMessageBuffer = cecMessageBuffer; } @@ -1182,7 +1172,8 @@ public class HdmiControlService extends SystemService { @ServiceThreadOnly void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) { assertRunOnServiceThread(); - if (mMessageValidator.isValid(command, false) == HdmiCecMessageValidator.OK) { + if (command.getValidationResult() == HdmiCecMessageValidator.OK + && verifyPhysicalAddresses(command)) { mCecController.sendCommand(command, callback); } else { HdmiLogger.error("Invalid message type:" + command); @@ -1210,20 +1201,99 @@ public class HdmiControlService extends SystemService { mCecController.maySendFeatureAbortCommand(command, reason); } + /** + * Returns whether all of the physical addresses in a message could exist in this CEC network. + */ + boolean verifyPhysicalAddresses(HdmiCecMessage message) { + byte[] params = message.getParams(); + switch (message.getOpcode()) { + case Constants.MESSAGE_ROUTING_CHANGE: + return verifyPhysicalAddress(params, 0) + && verifyPhysicalAddress(params, 2); + case Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST: + return params.length == 0 || verifyPhysicalAddress(params, 0); + case Constants.MESSAGE_ACTIVE_SOURCE: + case Constants.MESSAGE_INACTIVE_SOURCE: + case Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS: + case Constants.MESSAGE_ROUTING_INFORMATION: + case Constants.MESSAGE_SET_STREAM_PATH: + return verifyPhysicalAddress(params, 0); + case Constants.MESSAGE_CLEAR_EXTERNAL_TIMER: + case Constants.MESSAGE_SET_EXTERNAL_TIMER: + return verifyExternalSourcePhysicalAddress(params, 7); + default: + return true; + } + } + + /** + * Returns whether a given physical address could exist in this CEC network. + * For a TV, the physical address must either be the address of the TV itself, + * or the address of a device connected to one of its ports (possibly indirectly). + */ + private boolean verifyPhysicalAddress(byte[] params, int offset) { + if (!isTvDevice()) { + // If the device is not TV, we can't convert path to port-id, so stop here. + return true; + } + int path = HdmiUtils.twoBytesToInt(params, offset); + if (path != Constants.INVALID_PHYSICAL_ADDRESS && path == getPhysicalAddress()) { + return true; + } + int portId = pathToPortId(path); + if (portId == Constants.INVALID_PORT_ID) { + return false; + } + return true; + } + + /** + * Returns whether the physical address of an external source could exist in this network. + */ + private boolean verifyExternalSourcePhysicalAddress(byte[] params, int offset) { + int externalSourceSpecifier = params[offset]; + offset = offset + 1; + if (externalSourceSpecifier == 0x05) { + if (params.length - offset >= 2) { + return verifyPhysicalAddress(params, offset); + } + } + return true; + } + + /** + * Returns whether the source address of a message is a local logical address. + */ + private boolean sourceAddressIsLocal(HdmiCecMessage message) { + for (HdmiCecLocalDevice device : getAllLocalDevices()) { + synchronized (device.mLock) { + if (message.getSource() == device.getDeviceInfo().getLogicalAddress() + && message.getSource() != Constants.ADDR_UNREGISTERED) { + HdmiLogger.warning( + "Unexpected source: message sent from device itself, " + message); + return true; + } + } + } + return false; + } + @ServiceThreadOnly @VisibleForTesting @Constants.HandleMessageResult protected int handleCecCommand(HdmiCecMessage message) { assertRunOnServiceThread(); - int errorCode = mMessageValidator.isValid(message, true); - if (errorCode != HdmiCecMessageValidator.OK) { - // We'll not response on the messages with the invalid source or destination - // or with parameter length shorter than specified in the standard. - if (errorCode == HdmiCecMessageValidator.ERROR_PARAMETER) { - return Constants.ABORT_INVALID_OPERAND; - } + + @HdmiCecMessageValidator.ValidationResult + int validationResult = message.getValidationResult(); + if (validationResult == HdmiCecMessageValidator.ERROR_PARAMETER + || !verifyPhysicalAddresses(message)) { + return Constants.ABORT_INVALID_OPERAND; + } else if (validationResult != HdmiCecMessageValidator.OK + || sourceAddressIsLocal(message)) { return Constants.HANDLED; } + getHdmiCecNetwork().handleCecMessage(message); @Constants.HandleMessageResult int handleMessageResult = @@ -1409,9 +1479,16 @@ public class HdmiControlService extends SystemService { private HdmiDeviceInfo createDeviceInfo(int logicalAddress, int deviceType, int powerStatus, int cecVersion) { String displayName = readStringSetting(Global.DEVICE_NAME, Build.MODEL); - return new HdmiDeviceInfo(logicalAddress, - getPhysicalAddress(), pathToPortId(getPhysicalAddress()), deviceType, - getVendorId(), displayName, powerStatus, cecVersion); + return HdmiDeviceInfo.cecDeviceBuilder() + .setLogicalAddress(logicalAddress) + .setPhysicalAddress(getPhysicalAddress()) + .setPortId(pathToPortId(getPhysicalAddress())) + .setDeviceType(deviceType) + .setVendorId(getVendorId()) + .setDisplayName(displayName) + .setDevicePowerStatus(powerStatus) + .setCecVersion(cecVersion) + .build(); } // Set the display name in HdmiDeviceInfo of the current devices to content provided by @@ -1422,10 +1499,9 @@ public class HdmiControlService extends SystemService { if (deviceInfo.getDisplayName().equals(newDisplayName)) { continue; } - device.setDeviceInfo(new HdmiDeviceInfo( - deviceInfo.getLogicalAddress(), deviceInfo.getPhysicalAddress(), - deviceInfo.getPortId(), deviceInfo.getDeviceType(), deviceInfo.getVendorId(), - newDisplayName, deviceInfo.getDevicePowerStatus(), deviceInfo.getCecVersion())); + synchronized (device.mLock) { + device.setDeviceInfo(deviceInfo.toBuilder().setDisplayName(newDisplayName).build()); + } sendCecCommand( HdmiCecMessageBuilder.buildSetOsdNameCommand( deviceInfo.getLogicalAddress(), Constants.ADDR_TV, newDisplayName)); @@ -2629,7 +2705,7 @@ public class HdmiControlService extends SystemService { return activeSourceInfo; } - return new HdmiDeviceInfo(activeSource.physicalAddress, + return HdmiDeviceInfo.hardwarePort(activeSource.physicalAddress, pathToPortId(activeSource.physicalAddress)); } @@ -2637,7 +2713,7 @@ public class HdmiControlService extends SystemService { int activePath = tv().getActivePath(); if (activePath != HdmiDeviceInfo.PATH_INVALID) { HdmiDeviceInfo info = mHdmiCecNetwork.getSafeDeviceInfoByPath(activePath); - return (info != null) ? info : new HdmiDeviceInfo(activePath, + return (info != null) ? info : HdmiDeviceInfo.hardwarePort(activePath, tv().getActivePortId()); } } diff --git a/services/core/java/com/android/server/hdmi/HdmiMhlLocalDeviceStub.java b/services/core/java/com/android/server/hdmi/HdmiMhlLocalDeviceStub.java index 06ecb5a2c8a0..43469b529108 100644 --- a/services/core/java/com/android/server/hdmi/HdmiMhlLocalDeviceStub.java +++ b/services/core/java/com/android/server/hdmi/HdmiMhlLocalDeviceStub.java @@ -8,7 +8,7 @@ import android.hardware.hdmi.IHdmiControlCallback; */ final class HdmiMhlLocalDeviceStub { - private static final HdmiDeviceInfo INFO = new HdmiDeviceInfo( + private static final HdmiDeviceInfo INFO = HdmiDeviceInfo.mhlDevice( Constants.INVALID_PHYSICAL_ADDRESS, Constants.INVALID_PORT_ID, -1, -1); private final HdmiControlService mService; private final int mPortId; diff --git a/services/core/java/com/android/server/hdmi/HdmiUtils.java b/services/core/java/com/android/server/hdmi/HdmiUtils.java index 03e5de8ac8bd..ba19cf062cdf 100644 --- a/services/core/java/com/android/server/hdmi/HdmiUtils.java +++ b/services/core/java/com/android/server/hdmi/HdmiUtils.java @@ -391,15 +391,6 @@ final class HdmiUtils { } /** - * Clone {@link HdmiDeviceInfo} with new power status. - */ - static HdmiDeviceInfo cloneHdmiDeviceInfo(HdmiDeviceInfo info, int newPowerStatus) { - return new HdmiDeviceInfo(info.getLogicalAddress(), - info.getPhysicalAddress(), info.getPortId(), info.getDeviceType(), - info.getVendorId(), info.getDisplayName(), newPowerStatus, info.getCecVersion()); - } - - /** * Dump a {@link SparseArray} to the print writer. * * <p>The dump is formatted: @@ -470,7 +461,7 @@ final class HdmiUtils { } /** - * Method to parse target physical address to the port number on the current device. + * Method to build target physical address to the port number on the current device. * * <p>This check assumes target address is valid. * @@ -562,7 +553,28 @@ final class HdmiUtils { for (int i = 0; i < params.length; i++) { params[i] = (byte) Integer.parseInt(parts[i + 2], 16); } - return new HdmiCecMessage(src, dest, opcode, params); + return HdmiCecMessage.build(src, dest, opcode, params); + } + + /** + * Some operands in the CEC spec consist of a variable number of bytes, where each byte except + * the last one has bit 7 set to 1. + * Given the index of a byte in such an operand, this method returns the index of the last byte + * in the operand, or -1 if the input is invalid (e.g. operand not terminated properly). + * @param params Byte array representing a CEC message's parameters + * @param offset Index of a byte in the operand to find the end of + */ + public static int getEndOfSequence(byte[] params, int offset) { + if (offset < 0) { + return -1; + } + while (offset < params.length && ((params[offset] >> 7) & 1) == 1) { + offset++; + } + if (offset >= params.length) { + return -1; + } + return offset; } public static class ShortAudioDescriptorXmlParser { diff --git a/services/core/java/com/android/server/hdmi/NewDeviceAction.java b/services/core/java/com/android/server/hdmi/NewDeviceAction.java index a307ea33abf7..c4b98c21b608 100644 --- a/services/core/java/com/android/server/hdmi/NewDeviceAction.java +++ b/services/core/java/com/android/server/hdmi/NewDeviceAction.java @@ -68,7 +68,7 @@ final class NewDeviceAction extends HdmiCecFeatureAction { mDeviceLogicalAddress = deviceLogicalAddress; mDevicePhysicalAddress = devicePhysicalAddress; mDeviceType = deviceType; - mVendorId = Constants.UNKNOWN_VENDOR_ID; + mVendorId = Constants.VENDOR_ID_UNKNOWN; } @Override @@ -174,10 +174,14 @@ final class NewDeviceAction extends HdmiCecFeatureAction { if (mDisplayName == null) { mDisplayName = HdmiUtils.getDefaultDeviceName(mDeviceLogicalAddress); } - HdmiDeviceInfo deviceInfo = new HdmiDeviceInfo( - mDeviceLogicalAddress, mDevicePhysicalAddress, - tv().getPortId(mDevicePhysicalAddress), - mDeviceType, mVendorId, mDisplayName); + HdmiDeviceInfo deviceInfo = HdmiDeviceInfo.cecDeviceBuilder() + .setLogicalAddress(mDeviceLogicalAddress) + .setPhysicalAddress(mDevicePhysicalAddress) + .setPortId(tv().getPortId(mDevicePhysicalAddress)) + .setDeviceType(mDeviceType) + .setVendorId(mVendorId) + .setDisplayName(mDisplayName) + .build(); localDevice().mService.getHdmiCecNetwork().addCecDevice(deviceInfo); // Consume CEC messages we already got for this newly found device. diff --git a/services/core/java/com/android/server/hdmi/ReportFeaturesMessage.java b/services/core/java/com/android/server/hdmi/ReportFeaturesMessage.java new file mode 100644 index 000000000000..80bfb96d15d4 --- /dev/null +++ b/services/core/java/com/android/server/hdmi/ReportFeaturesMessage.java @@ -0,0 +1,189 @@ +/* + * 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.hdmi; + +import static com.android.server.hdmi.HdmiCecMessageValidator.ERROR_PARAMETER_SHORT; +import static com.android.server.hdmi.HdmiCecMessageValidator.OK; +import static com.android.server.hdmi.HdmiCecMessageValidator.ValidationResult; + +import android.annotation.NonNull; +import android.hardware.hdmi.DeviceFeatures; +import android.hardware.hdmi.HdmiControlManager; +import android.hardware.hdmi.HdmiDeviceInfo; + +import java.util.Arrays; +import java.util.List; +import java.util.function.Function; + +/** + * Represents a validated <Report Features> message with parsed parameters. + * + * Only parses the [CEC Version] and [Device Features] operands. + * [All Device Types] and [RC Profile] are not parsed, but can be specified in construction. + */ +public class ReportFeaturesMessage extends HdmiCecMessage { + + @HdmiControlManager.HdmiCecVersion + private final int mCecVersion; + + @NonNull + private final DeviceFeatures mDeviceFeatures; + + private ReportFeaturesMessage(int source, int destination, byte[] params, int cecVersion, + DeviceFeatures deviceFeatures) { + super(source, destination, Constants.MESSAGE_REPORT_FEATURES, params, OK); + mCecVersion = cecVersion; + mDeviceFeatures = deviceFeatures; + } + + /** + * Static factory method. Intended for constructing outgoing or test messages, as it uses + * structured types instead of raw bytes to construct the parameters. + */ + public static HdmiCecMessage build( + int source, + @HdmiControlManager.HdmiCecVersion int cecVersion, + List<Integer> allDeviceTypes, + @Constants.RcProfile int rcProfile, + List<Integer> rcFeatures, + DeviceFeatures deviceFeatures) { + + byte cecVersionByte = (byte) (cecVersion & 0xFF); + byte deviceTypes = 0; + for (Integer deviceType : allDeviceTypes) { + deviceTypes |= (byte) (1 << hdmiDeviceInfoDeviceTypeToShiftValue(deviceType)); + } + + byte rcProfileByte = 0; + rcProfileByte |= (byte) (rcProfile << 6); + if (rcProfile == Constants.RC_PROFILE_SOURCE) { + for (@Constants.RcProfileSource Integer rcFeature : rcFeatures) { + rcProfileByte |= (byte) (1 << rcFeature); + } + } else { + @Constants.RcProfileTv byte rcProfileTv = (byte) (rcFeatures.get(0) & 0xFFFF); + rcProfileByte |= rcProfileTv; + } + + byte[] fixedOperands = {cecVersionByte, deviceTypes, rcProfileByte}; + byte[] deviceFeaturesBytes = deviceFeatures.toOperand(); + + // Concatenate fixed length operands and [Device Features] + byte[] params = Arrays.copyOf(fixedOperands, + fixedOperands.length + deviceFeaturesBytes.length); + System.arraycopy(deviceFeaturesBytes, 0, params, + fixedOperands.length, deviceFeaturesBytes.length); + + @ValidationResult + int addressValidationResult = validateAddress(source, Constants.ADDR_BROADCAST); + if (addressValidationResult != OK) { + return new HdmiCecMessage(source, Constants.ADDR_BROADCAST, + Constants.MESSAGE_REPORT_FEATURES, params, addressValidationResult); + } else { + return new ReportFeaturesMessage(source, Constants.ADDR_BROADCAST, params, + cecVersion, deviceFeatures); + } + } + + @Constants.DeviceType + private static int hdmiDeviceInfoDeviceTypeToShiftValue(int deviceType) { + switch (deviceType) { + case HdmiDeviceInfo.DEVICE_TV: + return Constants.ALL_DEVICE_TYPES_TV; + case HdmiDeviceInfo.DEVICE_RECORDER: + return Constants.ALL_DEVICE_TYPES_RECORDER; + case HdmiDeviceInfo.DEVICE_TUNER: + return Constants.ALL_DEVICE_TYPES_TUNER; + case HdmiDeviceInfo.DEVICE_PLAYBACK: + return Constants.ALL_DEVICE_TYPES_PLAYBACK; + case HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM: + return Constants.ALL_DEVICE_TYPES_AUDIO_SYSTEM; + case HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH: + return Constants.ALL_DEVICE_TYPES_SWITCH; + default: + throw new IllegalArgumentException("Unhandled device type: " + deviceType); + } + } + + /** + * Must only be called from {@link HdmiCecMessage#build}. + * + * Parses and validates CEC message data as a <Report Features> message. Intended for + * constructing a representation of an incoming message, as it takes raw bytes for parameters. + * + * If successful, returns an instance of {@link ReportFeaturesMessage}. + * If unsuccessful, returns an {@link HdmiCecMessage} with the reason for validation failure + * accessible through {@link HdmiCecMessage#getValidationResult}. + */ + static HdmiCecMessage build(int source, int destination, byte[] params) { + // Helper function for building a message that failed validation + Function<Integer, HdmiCecMessage> invalidMessage = + validationResult -> new HdmiCecMessage(source, destination, + Constants.MESSAGE_REPORT_FEATURES, params, validationResult); + + @ValidationResult int addressValidationResult = validateAddress(source, destination); + if (addressValidationResult != OK) { + return invalidMessage.apply(addressValidationResult); + } + + if (params.length < 4) { + return invalidMessage.apply(ERROR_PARAMETER_SHORT); + } + + int cecVersion = Byte.toUnsignedInt(params[0]); + + int rcProfileEnd = HdmiUtils.getEndOfSequence(params, 2); + if (rcProfileEnd == -1) { + return invalidMessage.apply(ERROR_PARAMETER_SHORT); + } + int deviceFeaturesEnd = HdmiUtils.getEndOfSequence( + params, rcProfileEnd + 1); + if (deviceFeaturesEnd == -1) { + return invalidMessage.apply(ERROR_PARAMETER_SHORT); + } + int deviceFeaturesStart = HdmiUtils.getEndOfSequence(params, 2) + 1; + byte[] deviceFeaturesBytes = Arrays.copyOfRange(params, deviceFeaturesStart, params.length); + DeviceFeatures deviceFeatures = DeviceFeatures.fromOperand(deviceFeaturesBytes); + + return new ReportFeaturesMessage(source, destination, params, cecVersion, deviceFeatures); + } + + /** + * Validates the source and destination addresses for a <Report Features> message. + */ + public static int validateAddress(int source, int destination) { + return HdmiCecMessageValidator.validateAddress(source, destination, + HdmiCecMessageValidator.DEST_BROADCAST); + } + + /** + * Returns the contents of the [CEC Version] operand: the version number of the CEC + * specification which was used to design the device. + */ + @HdmiControlManager.HdmiCecVersion + public int getCecVersion() { + return mCecVersion; + } + + /** + * Returns the contents of the [Device Features] operand: the set of features supported by + * the device. + */ + public DeviceFeatures getDeviceFeatures() { + return mDeviceFeatures; + } +} diff --git a/services/core/java/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryAction.java b/services/core/java/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryAction.java new file mode 100644 index 000000000000..5154669a90ab --- /dev/null +++ b/services/core/java/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryAction.java @@ -0,0 +1,126 @@ +/* + * 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.hdmi; + +import static android.hardware.hdmi.DeviceFeatures.FEATURE_NOT_SUPPORTED; +import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORTED; + +import static com.android.server.hdmi.Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL; + +import android.hardware.hdmi.DeviceFeatures; +import android.hardware.hdmi.HdmiControlManager; +import android.hardware.hdmi.HdmiDeviceInfo; +import android.hardware.hdmi.IHdmiControlCallback; +import android.hardware.tv.cec.V1_0.SendMessageResult; + +/** + * Determines whether a target device supports the <Set Audio Volume Level> message. + * + * Sends the device <Set Audio Volume Level>[0x7F]. The value 0x7F is defined by the spec such that + * setting the volume to this level results in no change to the current volume level. + * + * The target device supports <Set Audio Volume Level> only if it does not respond with + * <Feature Abort> within {@link HdmiConfig.TIMEOUT_MS} milliseconds. + */ +public class SetAudioVolumeLevelDiscoveryAction extends HdmiCecFeatureAction { + private static final String TAG = "SetAudioVolumeLevelDiscoveryAction"; + + private static final int STATE_WAITING_FOR_FEATURE_ABORT = 1; + + private final int mTargetAddress; + + public SetAudioVolumeLevelDiscoveryAction(HdmiCecLocalDevice source, + int targetAddress, IHdmiControlCallback callback) { + super(source, callback); + + mTargetAddress = targetAddress; + } + + boolean start() { + sendCommand(SetAudioVolumeLevelMessage.build( + getSourceAddress(), mTargetAddress, Constants.AUDIO_VOLUME_STATUS_UNKNOWN), + result -> { + if (result == SendMessageResult.SUCCESS) { + // Message sent successfully; wait for <Feature Abort> in response + mState = STATE_WAITING_FOR_FEATURE_ABORT; + addTimer(mState, HdmiConfig.TIMEOUT_MS); + } else { + finishWithCallback(HdmiControlManager.RESULT_COMMUNICATION_FAILED); + } + }); + return true; + } + + boolean processCommand(HdmiCecMessage cmd) { + if (mState != STATE_WAITING_FOR_FEATURE_ABORT) { + return false; + } + switch (cmd.getOpcode()) { + case Constants.MESSAGE_FEATURE_ABORT: + return handleFeatureAbort(cmd); + default: + return false; + } + } + + private boolean handleFeatureAbort(HdmiCecMessage cmd) { + int originalOpcode = cmd.getParams()[0] & 0xFF; + if (originalOpcode == MESSAGE_SET_AUDIO_VOLUME_LEVEL && cmd.getSource() == mTargetAddress) { + if (updateAvcSupport(FEATURE_NOT_SUPPORTED)) { + finishWithCallback(HdmiControlManager.RESULT_SUCCESS); + } else { + finishWithCallback(HdmiControlManager.RESULT_EXCEPTION); + } + return true; + } + return false; + } + + void handleTimerEvent(int state) { + if (updateAvcSupport(FEATURE_SUPPORTED)) { + finishWithCallback(HdmiControlManager.RESULT_SUCCESS); + } else { + finishWithCallback(HdmiControlManager.RESULT_EXCEPTION); + } + } + + /** + * Updates the System Audio device's support for <Set Audio Volume Level> in the + * {@link HdmiCecNetwork}. Can fail if the System Audio device is not in our + * {@link HdmiCecNetwork}. + * + * @return Whether support was successfully updated in the network. + */ + private boolean updateAvcSupport( + @DeviceFeatures.FeatureSupportStatus int setAudioVolumeLevelSupport) { + HdmiCecNetwork network = source().mService.getHdmiCecNetwork(); + HdmiDeviceInfo currentDeviceInfo = network.getCecDeviceInfo(mTargetAddress); + + if (currentDeviceInfo == null) { + return false; + } else { + network.updateCecDevice( + currentDeviceInfo.toBuilder() + .setDeviceFeatures(currentDeviceInfo.getDeviceFeatures().toBuilder() + .setSetAudioVolumeLevelSupport(setAudioVolumeLevelSupport) + .build()) + .build() + ); + return true; + } + } +} diff --git a/services/core/java/com/android/server/hdmi/SetAudioVolumeLevelMessage.java b/services/core/java/com/android/server/hdmi/SetAudioVolumeLevelMessage.java new file mode 100644 index 000000000000..2ec0e7feec32 --- /dev/null +++ b/services/core/java/com/android/server/hdmi/SetAudioVolumeLevelMessage.java @@ -0,0 +1,102 @@ +/* + * 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.hdmi; + +import static com.android.server.hdmi.HdmiCecMessageValidator.ERROR_PARAMETER_SHORT; +import static com.android.server.hdmi.HdmiCecMessageValidator.OK; +import static com.android.server.hdmi.HdmiCecMessageValidator.ValidationResult; + +/** + * Represents a validated <Set Audio Volume Level> message with parsed parameters. + */ +public class SetAudioVolumeLevelMessage extends HdmiCecMessage { + private final int mAudioVolumeLevel; + + private SetAudioVolumeLevelMessage(int source, int destination, byte[] params, + int audioVolumeLevel) { + super(source, destination, Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL, params, OK); + mAudioVolumeLevel = audioVolumeLevel; + } + + /** + * Static factory method. Intended for constructing outgoing or test messages, as it uses + * structured types instead of raw bytes to construct the parameters. + * + * @param source Initiator address. Cannot be {@link Constants#ADDR_UNREGISTERED} + * @param destination Destination address. Cannot be {@link Constants#ADDR_BROADCAST} + * @param audioVolumeLevel [Audio Volume Level]. Either 0x7F (representing no volume change) + * or between 0 and 100 inclusive (representing volume percentage). + */ + public static HdmiCecMessage build(int source, int destination, int audioVolumeLevel) { + byte[] params = { (byte) (audioVolumeLevel & 0xFF) }; + + @ValidationResult + int addressValidationResult = validateAddress(source, destination); + if (addressValidationResult == OK) { + return new SetAudioVolumeLevelMessage(source, destination, params, audioVolumeLevel); + } else { + return new HdmiCecMessage(source, destination, Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL, + params, addressValidationResult); + } + } + + /** + * Must only be called from {@link HdmiCecMessage#build}. + * + * Parses and validates CEC message data as a <SetAudioVolumeLevel> message. Intended for + * constructing a representation of an incoming message, as it takes raw bytes for + * parameters. + * + * If successful, returns an instance of {@link SetAudioVolumeLevelMessage}. + * If unsuccessful, returns an {@link HdmiCecMessage} with the reason for validation failure + * accessible through {@link HdmiCecMessage#getValidationResult}. + */ + public static HdmiCecMessage build(int source, int destination, byte[] params) { + if (params.length == 0) { + return new HdmiCecMessage(source, destination, Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL, + params, ERROR_PARAMETER_SHORT); + } + + int audioVolumeLevel = params[0]; + + @ValidationResult + int addressValidationResult = validateAddress(source, destination); + if (addressValidationResult == OK) { + return new SetAudioVolumeLevelMessage(source, destination, params, audioVolumeLevel); + } else { + return new HdmiCecMessage(source, destination, Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL, + params, addressValidationResult); + } + } + + /** + * Validates the source and destination addresses for a <Set Audio Volume Level> message. + */ + public static int validateAddress(int source, int destination) { + return HdmiCecMessageValidator.validateAddress(source, destination, + HdmiCecMessageValidator.DEST_DIRECT); + } + + /** + * Returns the contents of the [Audio Volume Level] operand: + * either 0x7F, indicating no change to the current volume level, + * or a percentage between 0 and 100 (inclusive). + */ + public int getAudioVolumeLevel() { + return mAudioVolumeLevel; + } +} diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 261aa32f093e..4de39bcd24e0 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -38,6 +38,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 +270,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); @@ -341,6 +346,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); @@ -1902,6 +1910,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 +2595,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 +2986,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 +3136,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 +3457,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/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java index 3af51f4ba55f..784d058c4a4b 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java @@ -18,8 +18,6 @@ package com.android.server.inputmethod; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; -import static com.android.server.inputmethod.InputMethodManagerService.MSG_INITIALIZE_IME; - import android.annotation.NonNull; import android.annotation.Nullable; import android.app.PendingIntent; @@ -311,11 +309,8 @@ final class InputMethodBindingController { if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken); final InputMethodInfo info = mMethodMap.get(mSelectedMethodId); mSupportsStylusHw = info.supportsStylusHandwriting(); - // Dispatch display id for InputMethodService to update context display. - mService.executeOrSendMessage(mCurMethod, - mService.mCaller.obtainMessageIOOO(MSG_INITIALIZE_IME, - info.getConfigChanges(), mCurMethod, mCurToken, - 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 f1e8d0d59957..2a24489956a7 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; @@ -219,38 +218,25 @@ public class InputMethodManagerService extends IInputMethodManager.Stub int FAILURE = -1; } - static final int MSG_SHOW_IM_SUBTYPE_PICKER = 1; - static final int MSG_SHOW_IM_SUBTYPE_ENABLER = 2; - static final int MSG_SHOW_IM_CONFIG = 3; + private static final int MSG_SHOW_IM_SUBTYPE_PICKER = 1; - static final int MSG_UNBIND_INPUT = 1000; - static final int MSG_BIND_INPUT = 1010; - static final int MSG_SHOW_SOFT_INPUT = 1020; - static final int MSG_HIDE_SOFT_INPUT = 1030; - static final int MSG_HIDE_CURRENT_INPUT_METHOD = 1035; - static final int MSG_INITIALIZE_IME = 1040; - static final int MSG_CREATE_SESSION = 1050; - static final int MSG_REMOVE_IME_SURFACE = 1060; - static final int MSG_REMOVE_IME_SURFACE_FROM_WINDOW = 1061; - static final int MSG_UPDATE_IME_WINDOW_STATUS = 1070; - static final int MSG_START_HANDWRITING = 1100; + private static final int MSG_HIDE_CURRENT_INPUT_METHOD = 1035; + 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; - static final int MSG_START_INPUT = 2000; + private static final int MSG_UNBIND_CLIENT = 3000; + private static final int MSG_BIND_CLIENT = 3010; + private static final int MSG_SET_ACTIVE = 3020; + private static final int MSG_SET_INTERACTIVE = 3030; + private static final int MSG_REPORT_FULLSCREEN_MODE = 3045; - static final int MSG_UNBIND_CLIENT = 3000; - static final int MSG_BIND_CLIENT = 3010; - static final int MSG_SET_ACTIVE = 3020; - static final int MSG_SET_INTERACTIVE = 3030; - static final int MSG_REPORT_FULLSCREEN_MODE = 3045; + private static final int MSG_HARD_KEYBOARD_SWITCH_CHANGED = 4000; - static final int MSG_HARD_KEYBOARD_SWITCH_CHANGED = 4000; + private static final int MSG_SYSTEM_UNLOCK_USER = 5000; + private static final int MSG_DISPATCH_ON_INPUT_METHOD_LIST_UPDATED = 5010; - static final int MSG_SYSTEM_UNLOCK_USER = 5000; - static final int MSG_DISPATCH_ON_INPUT_METHOD_LIST_UPDATED = 5010; - - static final int MSG_INLINE_SUGGESTIONS_REQUEST = 6000; - - static final int MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE = 7000; + 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; private static final String TAG_TRY_SUPPRESSING_IME_SWITCHER = "TrySuppressingImeSwitcher"; @@ -278,14 +264,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub final Context mContext; final Resources mRes; - final Handler mHandler; + private final Handler mHandler; final InputMethodSettings mSettings; final SettingsObserver mSettingsObserver; final IWindowManager mIWindowManager; final WindowManagerInternal mWindowManagerInternal; final PackageManagerInternal mPackageManagerInternal; final InputManagerInternal mInputManagerInternal; - final HandlerCaller mCaller; + private final HandlerCaller mCaller; final boolean mHasFeature; private final ArrayMap<String, List<InputMethodSubtype>> mAdditionalSubtypeMap = new ArrayMap<>(); @@ -654,9 +640,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 +691,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 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 +771,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; @@ -1464,31 +1450,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - private static final class MethodCallback extends IInputSessionCallback.Stub { - private final InputMethodManagerService mParentIMMS; - private final IInputMethod mMethod; - private final InputChannel mChannel; - - MethodCallback(InputMethodManagerService imms, IInputMethod method, - InputChannel channel) { - mParentIMMS = imms; - mMethod = method; - mChannel = channel; - } - - @Override - public void sessionCreated(IInputMethodSession session) { - Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.sessionCreated"); - final long ident = Binder.clearCallingIdentity(); - try { - mParentIMMS.onSessionCreated(mMethod, session, mChannel); - } finally { - Binder.restoreCallingIdentity(ident); - } - Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); - } - } - private static final class UserSwitchHandlerTask implements Runnable { final InputMethodManagerService mService; @@ -1600,7 +1561,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, @@ -1806,10 +1767,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mShowOngoingImeSwitcherForPhones = mRes.getBoolean( com.android.internal.R.bool.show_ongoing_ime_switcher); if (mShowOngoingImeSwitcherForPhones) { - final InputMethodMenuController.HardKeyboardListener hardKeyboardListener = - mMenuController.getHardKeyboardListener(); - mWindowManagerInternal.setOnHardKeyboardStatusChangeListener( - hardKeyboardListener); + mWindowManagerInternal.setOnHardKeyboardStatusChangeListener(available -> { + mHandler.obtainMessage(MSG_HARD_KEYBOARD_SWITCH_CHANGED, + available ? 1 : 0, 0 /* unused */).sendToTarget(); + }); } mMyPackageMonitor.register(mContext, null, UserHandle.ALL, true); @@ -1993,12 +1954,15 @@ public class InputMethodManagerService extends IInputMethodManager.Stub IInputMethod 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); + try { + curMethod.onCreateInlineSuggestionsRequest(requestInfo, callbackImpl); + } catch (RemoteException e) { + Slog.w(TAG, "RemoteException calling onCreateInlineSuggestionsRequest()", e); + } } else { callback.onInlineSuggestionsUnsupported(); } @@ -2228,8 +2192,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mBoundToMethod = false; IInputMethod curMethod = getCurMethodLocked(); if (curMethod != null) { - executeOrSendMessage(curMethod, mCaller.obtainMessageO( - MSG_UNBIND_INPUT, curMethod)); + try { + curMethod.unbindInput(); + } catch (RemoteException e) { + // There is nothing interesting about the method dying. + } } } mCurClient = null; @@ -2241,8 +2208,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - 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); @@ -2259,8 +2232,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mBoundToMethod = false; IInputMethod curMethod = getCurMethodLocked(); if (curMethod != null) { - executeOrSendMessage(curMethod, mCaller.obtainMessageO( - MSG_UNBIND_INPUT, curMethod)); + try { + curMethod.unbindInput(); + } catch (RemoteException e) { + // There is nothing interesting about the method dying. + } } } @@ -2310,15 +2286,18 @@ public class InputMethodManagerService extends IInputMethodManager.Stub InputBindResult attachNewInputLocked(@StartInputReason int startInputReason, boolean initial) { if (!mBoundToMethod) { IInputMethod curMethod = getCurMethodLocked(); - executeOrSendMessage(curMethod, mCaller.obtainMessageOO( - MSG_BIND_INPUT, curMethod, mCurClient.binding)); + try { + curMethod.bindInput(mCurClient.binding); + } catch (RemoteException e) { + } 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()); @@ -2337,9 +2316,12 @@ 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)); + try { + setEnabledSessionLocked(session); + session.method.startInput(startInputToken, mCurInputContext, mCurAttribute, restarting); + } catch (RemoteException e) { + } + if (mShowRequested) { if (DEBUG) Slog.v(TAG, "Attach new input asks to show input"); showCurrentInputLocked(mCurFocusedWindow, getAppShowFlagsLocked(), null, @@ -2355,11 +2337,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) { @@ -2381,10 +2372,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, @@ -2526,40 +2513,61 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } + @GuardedBy("ImfLock.class") + void initializeImeLocked(@NonNull IInputMethod inputMethod, @NonNull IBinder token, + @android.content.pm.ActivityInfo.Config int configChanges, boolean supportStylusHw) { + if (DEBUG) { + Slog.v(TAG, "Sending attach of token: " + token + " for display: " + + mCurTokenDisplayId); + } + try { + inputMethod.initializeInternal(token, + new InputMethodPrivilegedOperationsImpl(this, token), configChanges, + supportStylusHw); + } catch (RemoteException e) { + } + } + @AnyThread void scheduleNotifyImeUidToAudioService(int uid) { mCaller.removeMessages(MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE); mCaller.obtainMessageI(MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE, uid).sendToTarget(); } - void onSessionCreated(IInputMethod method, IInputMethodSession session, - InputChannel channel) { - synchronized (ImfLock.class) { - if (mUserSwitchHandlerTask != null) { - // We have a pending user-switching task so it's better to just ignore this session. - channel.dispose(); - return; - } - IInputMethod curMethod = getCurMethodLocked(); - if (curMethod != null && method != null - && curMethod.asBinder() == method.asBinder()) { - if (mCurClient != null) { - clearClientSessionLocked(mCurClient); - mCurClient.curSession = new SessionState(mCurClient, - method, session, channel); - InputBindResult res = attachNewInputLocked( - StartInputReason.SESSION_CREATED_BY_IME, true); - if (res.method != null) { - executeOrSendMessage(mCurClient.client, mCaller.obtainMessageOO( - MSG_BIND_CLIENT, mCurClient.client, res)); - } + @BinderThread + void onSessionCreated(IInputMethod method, IInputMethodSession session, InputChannel channel) { + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.onSessionCreated"); + try { + synchronized (ImfLock.class) { + if (mUserSwitchHandlerTask != null) { + // We have a pending user-switching task so it's better to just ignore this + // session. + channel.dispose(); return; } + IInputMethod curMethod = getCurMethodLocked(); + if (curMethod != null && method != null + && curMethod.asBinder() == method.asBinder()) { + if (mCurClient != null) { + clearClientSessionLocked(mCurClient); + mCurClient.curSession = new SessionState(mCurClient, + method, session, channel); + InputBindResult res = attachNewInputLocked( + StartInputReason.SESSION_CREATED_BY_IME, true); + if (res.method != null) { + executeOrSendMessage(mCurClient.client, mCaller.obtainMessageOO( + MSG_BIND_CLIENT, mCurClient.client, res)); + } + return; + } + } } - } - // Session abandoned. Close its associated input channel. - channel.dispose(); + // Session abandoned. Close its associated input channel. + channel.dispose(); + } finally { + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); + } } @GuardedBy("ImfLock.class") @@ -2591,12 +2599,39 @@ 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 MethodCallback(this, curMethod, channels[0]))); + + final IInputMethod 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); + } catch (RemoteException e) { + } finally { + // Dispose the channel because the remote proxy will get its own copy when + // unparceled. + if (clientChannel != null) { + clientChannel.dispose(); + } + } } } @@ -3061,9 +3096,13 @@ 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 IInputMethod curMethod = getCurMethodLocked(); + if (curMethod != null) { + try { + curMethod.canStartStylusHandwriting(++mHwRequestId); + } catch (RemoteException e) { + Slog.w(TAG, "RemoteException calling canStartStylusHandwriting(): ", e); + } } } finally { Binder.restoreCallingIdentity(ident); @@ -3114,18 +3153,25 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } mBindingController.setCurrentMethodVisible(); - if (getCurMethodLocked() != null) { + final IInputMethod 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(); + try { + if (DEBUG) { + Slog.v(TAG, "Calling " + curMethod + ".showSoftInput(" + showInputToken + + ", " + showFlags + ", " + resultReceiver + ") for reason: " + + InputMethodDebug.softInputDisplayReasonToString(reason)); + } + curMethod.showSoftInput(showInputToken, showFlags, resultReceiver); + onShowHideSoftInputRequested(true /* show */, windowToken, reason); + } catch (RemoteException e) { + } mInputShown = true; return true; } - return false; } @@ -3149,14 +3195,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; } @@ -3204,8 +3247,16 @@ 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)); + } + try { + curMethod.hideSoftInput(hideInputToken, 0 /* flags */, resultReceiver); + onShowHideSoftInputRequested(false /* show */, windowToken, reason); + } catch (RemoteException e) { + } res = true; } else { res = false; @@ -3218,6 +3269,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( @@ -3311,31 +3368,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) { @@ -3563,8 +3619,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; } @@ -3681,8 +3736,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (!calledFromValidUserLocked()) { return; } - executeOrSendMessage(getCurMethodLocked(), mCaller.obtainMessageO( - MSG_SHOW_IM_SUBTYPE_ENABLER, inputMethodId)); + showInputMethodAndSubtypeEnabler(inputMethodId); } } @@ -4157,7 +4211,8 @@ 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 { @@ -4205,69 +4260,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; @@ -4275,40 +4269,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 { @@ -4340,25 +4300,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); @@ -4416,9 +4357,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // -------------------------------------------------------------- case MSG_HARD_KEYBOARD_SWITCH_CHANGED: - final InputMethodMenuController.HardKeyboardListener hardKeyboardListener = - mMenuController.getHardKeyboardListener(); - hardKeyboardListener.handleHardKeyboardStatusChange(msg.arg1 == 1); + mMenuController.handleHardKeyboardStatusChange(msg.arg1 == 1); return true; case MSG_SYSTEM_UNLOCK_USER: { final int userId = msg.arg1; @@ -4434,23 +4373,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); @@ -4460,13 +4382,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; } @@ -4745,14 +4660,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); - } - // ---------------------------------------------------------------------- /** diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java index 132be7d11ab1..fcb1be0fc88c 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java @@ -18,7 +18,6 @@ package com.android.server.inputmethod; import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; import static com.android.server.inputmethod.InputMethodManagerService.DEBUG; -import static com.android.server.inputmethod.InputMethodManagerService.MSG_HARD_KEYBOARD_SWITCH_CHANGED; import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_ID; import android.app.ActivityThread; @@ -28,7 +27,6 @@ import android.content.Context; import android.content.DialogInterface; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; -import android.os.Handler; import android.os.IBinder; import android.text.TextUtils; import android.util.ArrayMap; @@ -62,10 +60,8 @@ public class InputMethodMenuController { private final InputMethodManagerService mService; private final InputMethodUtils.InputMethodSettings mSettings; private final InputMethodSubtypeSwitchingController mSwitchingController; - private final Handler mHandler; private final ArrayMap<String, InputMethodInfo> mMethodMap; private final KeyguardManager mKeyguardManager; - private final HardKeyboardListener mHardKeyboardListener; private final WindowManagerInternal mWindowManagerInternal; private Context mSettingsContext; @@ -83,10 +79,8 @@ public class InputMethodMenuController { mService = service; mSettings = mService.mSettings; mSwitchingController = mService.mSwitchingController; - mHandler = mService.mHandler; mMethodMap = mService.mMethodMap; mKeyguardManager = mService.mKeyguardManager; - mHardKeyboardListener = new HardKeyboardListener(); mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class); } @@ -271,10 +265,6 @@ public class InputMethodMenuController { } } - HardKeyboardListener getHardKeyboardListener() { - return mHardKeyboardListener; - } - AlertDialog getSwitchingDialogLocked() { return mSwitchingDialog; } @@ -290,24 +280,16 @@ public class InputMethodMenuController { return mSwitchingDialog.isShowing(); } - class HardKeyboardListener implements WindowManagerInternal.OnHardKeyboardStatusChangeListener { - @Override - public void onHardKeyboardStatusChange(boolean available) { - mHandler.sendMessage(mHandler.obtainMessage(MSG_HARD_KEYBOARD_SWITCH_CHANGED, - available ? 1 : 0)); + void handleHardKeyboardStatusChange(boolean available) { + if (DEBUG) { + Slog.w(TAG, "HardKeyboardStatusChanged: available=" + available); } - - public void handleHardKeyboardStatusChange(boolean available) { - if (DEBUG) { - Slog.w(TAG, "HardKeyboardStatusChanged: available=" + available); - } - synchronized (ImfLock.class) { - if (mSwitchingDialog != null && mSwitchingDialogTitleView != null - && mSwitchingDialog.isShowing()) { - mSwitchingDialogTitleView.findViewById( - com.android.internal.R.id.hard_keyboard_section).setVisibility( - available ? View.VISIBLE : View.GONE); - } + synchronized (ImfLock.class) { + if (mSwitchingDialog != null && mSwitchingDialogTitleView != null + && mSwitchingDialog.isShowing()) { + mSwitchingDialogTitleView.findViewById( + com.android.internal.R.id.hard_keyboard_section).setVisibility( + available ? View.VISIBLE : View.GONE); } } } diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java index 09780f3f6f40..667698767818 100644 --- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java +++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java @@ -45,7 +45,7 @@ import android.content.pm.ParceledListSlice; import android.content.pm.Signature; import android.content.pm.SigningDetails; import android.content.pm.SigningInfo; -import android.content.pm.parsing.ParsingPackageUtils; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import android.content.pm.parsing.result.ParseResult; import android.content.pm.parsing.result.ParseTypeImpl; import android.net.Uri; diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java index 1657b220d32c..d459f8df99b6 100644 --- a/services/core/java/com/android/server/locales/LocaleManagerService.java +++ b/services/core/java/com/android/server/locales/LocaleManagerService.java @@ -239,18 +239,13 @@ public class LocaleManagerService extends SystemService { */ private void notifyInstallerOfAppWhoseLocaleChanged(String appPackageName, int userId, LocaleList locales) { - try { - String installingPackageName = mContext.getPackageManager() - .getInstallSourceInfo(appPackageName).getInstallingPackageName(); - if (installingPackageName != null) { - Intent intent = createBaseIntent(Intent.ACTION_APPLICATION_LOCALE_CHANGED, - appPackageName, locales); - //Set package name to ensure that only installer of the app receives this intent. - intent.setPackage(installingPackageName); - mContext.sendBroadcastAsUser(intent, UserHandle.of(userId)); - } - } catch (PackageManager.NameNotFoundException e) { - Slog.w(TAG, "Package not found " + appPackageName); + String installingPackageName = getInstallingPackageName(appPackageName); + if (installingPackageName != null) { + Intent intent = createBaseIntent(Intent.ACTION_APPLICATION_LOCALE_CHANGED, + appPackageName, locales); + //Set package name to ensure that only installer of the app receives this intent. + intent.setPackage(installingPackageName); + mContext.sendBroadcastAsUser(intent, UserHandle.of(userId)); } } @@ -291,8 +286,7 @@ public class LocaleManagerService extends SystemService { */ private boolean isPackageOwnedByCaller(String appPackageName, int userId, @Nullable AppLocaleChangedAtomRecord atomRecordForMetrics) { - final int uid = mPackageManagerInternal - .getPackageUid(appPackageName, /* flags */ 0, userId); + final int uid = getPackageUid(appPackageName, userId); if (uid < 0) { Slog.w(TAG, "Unknown package " + appPackageName + " for user " + userId); if (atomRecordForMetrics != null) { @@ -336,13 +330,17 @@ public class LocaleManagerService extends SystemService { false /* allowAll */, ActivityManagerInternal.ALLOW_NON_FULL, "getApplicationLocales", appPackageName); - // This function handles two types of query operations: + // This function handles three types of query operations: // 1.) A normal, non-privileged app querying its own locale. - // 2.) A privileged system service querying locales of another package. + // 2.) The installer of the given app querying locales of a package installed + // by said installer. + // 3.) A privileged system service querying locales of another package. // The least privileged case is a normal app performing a query, so check that first and - // get locales if the package name is owned by the app. Next, check if the caller has the - // necessary permission and get locales. - if (!isPackageOwnedByCaller(appPackageName, userId)) { + // get locales if the package name is owned by the app. Next check if the calling app + // is the installer of the given app and get locales. If neither conditions matched, + // check if the caller has the necessary permission and fetch locales. + if (!isPackageOwnedByCaller(appPackageName, userId) + && !isCallerInstaller(appPackageName, userId)) { enforceReadAppSpecificLocalesPermission(); } final long token = Binder.clearCallingIdentity(); @@ -374,12 +372,41 @@ public class LocaleManagerService extends SystemService { return locales != null ? locales : LocaleList.getEmptyLocaleList(); } + /** + * Checks if the calling app is the installer of the app whose locale changed. + */ + private boolean isCallerInstaller(String appPackageName, int userId) { + String installingPackageName = getInstallingPackageName(appPackageName); + if (installingPackageName != null) { + // Get the uid of installer-on-record to compare with the calling uid. + int installerUid = getPackageUid(installingPackageName, userId); + return installerUid >= 0 && UserHandle.isSameApp(Binder.getCallingUid(), installerUid); + } + return false; + } + private void enforceReadAppSpecificLocalesPermission() { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.READ_APP_SPECIFIC_LOCALES, "getApplicationLocales"); } + private int getPackageUid(String appPackageName, int userId) { + return mPackageManagerInternal + .getPackageUid(appPackageName, /* flags */ 0, userId); + } + + @Nullable + private String getInstallingPackageName(String packageName) { + try { + return mContext.getPackageManager() + .getInstallSourceInfo(packageName).getInstallingPackageName(); + } catch (PackageManager.NameNotFoundException e) { + Slog.w(TAG, "Package not found " + packageName); + } + return null; + } + /** * Dumps useful info related to service. */ 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/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 8b1416a14653..bbc31b573669 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -151,6 +151,8 @@ import android.app.IUidObserver; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; +import android.app.usage.NetworkStats; +import android.app.usage.NetworkStatsManager; import android.app.usage.UsageStatsManagerInternal; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -181,7 +183,6 @@ import android.net.NetworkRequest; import android.net.NetworkSpecifier; import android.net.NetworkStack; import android.net.NetworkStateSnapshot; -import android.net.NetworkStats; import android.net.NetworkTemplate; import android.net.TelephonyNetworkSpecifier; import android.net.TrafficStats; @@ -441,7 +442,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { private final Context mContext; private final IActivityManager mActivityManager; - private NetworkStatsManagerInternal mNetworkStats; + private NetworkStatsManager mNetworkStats; private final INetworkManagementService mNetworkManager; private UsageStatsManagerInternal mUsageStats; private AppStandbyInternal mAppStandby; @@ -453,6 +454,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { private ConnectivityManager mConnManager; private PowerManagerInternal mPowerManagerInternal; private PowerWhitelistManager mPowerWhitelistManager; + @NonNull + private final Dependencies mDeps; /** Current cached value of the current Battery Saver mode's setting for restrict background. */ @GuardedBy("mUidRulesFirstLock") @@ -704,7 +707,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { public NetworkPolicyManagerService(Context context, IActivityManager activityManager, INetworkManagementService networkManagement) { this(context, activityManager, networkManagement, AppGlobals.getPackageManager(), - getDefaultClock(), getDefaultSystemDir(), false); + getDefaultClock(), getDefaultSystemDir(), false, new Dependencies(context)); } private static @NonNull File getDefaultSystemDir() { @@ -716,9 +719,59 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { Clock.systemUTC()); } + static class Dependencies { + final Context mContext; + final NetworkStatsManager mNetworkStatsManager; + Dependencies(Context context) { + mContext = context; + mNetworkStatsManager = mContext.getSystemService(NetworkStatsManager.class); + // Query stats from NetworkStatsService will trigger a poll by default. + // But since NPMS listens stats updated event, and will query stats + // after the event. A polling -> updated -> query -> polling loop will be introduced + // if polls on open. Hence, while NPMS manages it's poll requests explicitly, set + // flag to false to prevent a polling loop. + mNetworkStatsManager.setPollOnOpen(false); + } + + long getNetworkTotalBytes(NetworkTemplate template, long start, long end) { + Trace.traceBegin(TRACE_TAG_NETWORK, "getNetworkTotalBytes"); + try { + final NetworkStats.Bucket ret = mNetworkStatsManager + .querySummaryForDevice(template, start, end); + return ret.getRxBytes() + ret.getTxBytes(); + } catch (RuntimeException e) { + Slog.w(TAG, "Failed to read network stats: " + e); + return 0; + } finally { + Trace.traceEnd(TRACE_TAG_NETWORK); + } + } + + @NonNull + List<NetworkStats.Bucket> getNetworkUidBytes( + @NonNull NetworkTemplate template, long start, long end) { + Trace.traceBegin(TRACE_TAG_NETWORK, "getNetworkUidBytes"); + final List<NetworkStats.Bucket> buckets = new ArrayList<>(); + try { + final NetworkStats stats = mNetworkStatsManager.querySummary(template, start, end); + while (stats.hasNextBucket()) { + final NetworkStats.Bucket bucket = new NetworkStats.Bucket(); + stats.getNextBucket(bucket); + buckets.add(bucket); + } + } catch (RuntimeException e) { + Slog.w(TAG, "Failed to read network stats: " + e); + } finally { + Trace.traceEnd(TRACE_TAG_NETWORK); + } + return buckets; + } + } + + @VisibleForTesting public NetworkPolicyManagerService(Context context, IActivityManager activityManager, INetworkManagementService networkManagement, IPackageManager pm, Clock clock, - File systemDir, boolean suppressDefaultPolicy) { + File systemDir, boolean suppressDefaultPolicy, Dependencies deps) { mContext = Objects.requireNonNull(context, "missing context"); mActivityManager = Objects.requireNonNull(activityManager, "missing activityManager"); mNetworkManager = Objects.requireNonNull(networkManagement, "missing networkManagement"); @@ -739,10 +792,12 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { mUidEventHandler = new Handler(mUidEventThread.getLooper(), mUidEventHandlerCallback); mSuppressDefaultPolicy = suppressDefaultPolicy; + mDeps = Objects.requireNonNull(deps, "missing Dependencies"); mPolicyFile = new AtomicFile(new File(systemDir, "netpolicy.xml"), "net-policy"); mAppOps = context.getSystemService(AppOpsManager.class); + mNetworkStats = context.getSystemService(NetworkStatsManager.class); mMultipathPolicyTracker = new MultipathPolicyTracker(mContext, mHandler); // Expose private service for system components to use. LocalServices.addService(NetworkPolicyManagerInternal.class, @@ -842,7 +897,6 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { mUsageStats = LocalServices.getService(UsageStatsManagerInternal.class); mAppStandby = LocalServices.getService(AppStandbyInternal.class); - mNetworkStats = LocalServices.getService(NetworkStatsManagerInternal.class); synchronized (mUidRulesFirstLock) { synchronized (mNetworkPoliciesSecondLock) { @@ -1161,21 +1215,34 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { }; /** - * Receiver that watches for {@link INetworkStatsService} updates, which we + * Receiver that watches for {@link NetworkStatsManager} updates, which we * use to check against {@link NetworkPolicy#warningBytes}. */ - final private BroadcastReceiver mStatsReceiver = new BroadcastReceiver() { + private final NetworkStatsBroadcastReceiver mStatsReceiver = + new NetworkStatsBroadcastReceiver(); + private class NetworkStatsBroadcastReceiver extends BroadcastReceiver { + private boolean mIsAnyIntentReceived = false; @Override public void onReceive(Context context, Intent intent) { // on background handler thread, and verified // READ_NETWORK_USAGE_HISTORY permission above. + mIsAnyIntentReceived = true; + synchronized (mNetworkPoliciesSecondLock) { updateNetworkRulesNL(); updateNetworkEnabledNL(); updateNotificationsNL(); } } + + /** + * Return whether any {@code ACTION_NETWORK_STATS_UPDATED} intent is received. + * Used to determine if NetworkStatsService is ready. + */ + public boolean isAnyIntentReceived() { + return mIsAnyIntentReceived; + } }; /** @@ -1385,15 +1452,17 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { long maxBytes = 0; int maxUid = 0; - final NetworkStats stats = getNetworkUidBytes(template, start, end); - NetworkStats.Entry entry = null; - for (int i = 0; i < stats.size(); i++) { - entry = stats.getValues(i, entry); - final long bytes = entry.rxBytes + entry.txBytes; + // Skip if not ready. NetworkStatsService will block public API calls until it is + // ready. To prevent NPMS be blocked on that, skip and fail fast instead. + if (!mStatsReceiver.isAnyIntentReceived()) return null; + + final List<NetworkStats.Bucket> stats = mDeps.getNetworkUidBytes(template, start, end); + for (final NetworkStats.Bucket entry : stats) { + final long bytes = entry.getRxBytes() + entry.getTxBytes(); totalBytes += bytes; if (bytes > maxBytes) { maxBytes = bytes; - maxUid = entry.uid; + maxUid = entry.getUid(); } } @@ -2618,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"); @@ -3383,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) { @@ -5009,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: { @@ -5378,25 +5448,10 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { @Deprecated private long getTotalBytes(NetworkTemplate template, long start, long end) { - return getNetworkTotalBytes(template, start, end); - } - - private long getNetworkTotalBytes(NetworkTemplate template, long start, long end) { - try { - return mNetworkStats.getNetworkTotalBytes(template, start, end); - } catch (RuntimeException e) { - Slog.w(TAG, "Failed to read network stats: " + e); - return 0; - } - } - - private NetworkStats getNetworkUidBytes(NetworkTemplate template, long start, long end) { - try { - return mNetworkStats.getNetworkUidBytes(template, start, end); - } catch (RuntimeException e) { - Slog.w(TAG, "Failed to read network stats: " + e); - return new NetworkStats(SystemClock.elapsedRealtime(), 0); - } + // Skip if not ready. NetworkStatsService will block public API calls until it is + // ready. To prevent NPMS be blocked on that, skip and fail fast instead. + if (!mStatsReceiver.isAnyIntentReceived()) return 0; + return mDeps.getNetworkTotalBytes(template, start, end); } private boolean isBandwidthControlEnabled() { 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/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java index 047a70170587..38781fad76fd 100644 --- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java +++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java @@ -33,7 +33,7 @@ import android.content.om.CriticalOverlayInfo; import android.content.om.OverlayIdentifier; import android.content.om.OverlayInfo; import android.content.pm.overlay.OverlayPaths; -import android.content.pm.parsing.ParsingPackageUtils; +import android.content.pm.parsing.FrameworkParsingPackageUtils; import android.os.FabricatedOverlayInfo; import android.os.FabricatedOverlayInternal; import android.text.TextUtils; @@ -492,7 +492,7 @@ final class OverlayManagerServiceImpl { Set<PackageAndUser> registerFabricatedOverlay( @NonNull final FabricatedOverlayInternal overlay) throws OperationFailedException { - if (ParsingPackageUtils.validateName(overlay.overlayName, + if (FrameworkParsingPackageUtils.validateName(overlay.overlayName, false /* requireSeparator */, true /* requireFilename */) != null) { throw new OperationFailedException( "overlay name can only consist of alphanumeric characters, '_', and '.'"); diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java index a1c97a84b882..2e9ad50f23f6 100644 --- a/services/core/java/com/android/server/pm/ApexManager.java +++ b/services/core/java/com/android/server/pm/ApexManager.java @@ -32,9 +32,9 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.pm.SigningDetails; -import android.content.pm.parsing.PackageInfoWithoutStateUtils; -import android.content.pm.parsing.ParsingPackageUtils; -import android.content.pm.parsing.component.ParsedApexSystemService; +import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; +import com.android.server.pm.pkg.component.ParsedApexSystemService; import android.content.pm.parsing.result.ParseResult; import android.content.pm.parsing.result.ParseTypeImpl; import android.os.Binder; @@ -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; @@ -413,11 +414,9 @@ public abstract class ApexManager { throws PackageManagerException; /** - * Get a list of apex system services implemented in an apex. - * - * <p>The list is sorted by initOrder for consistency. + * Get a map of system services defined in an apex mapped to the jar files they reside in. */ - public abstract List<ApexSystemServiceInfo> getApexSystemServices(); + public abstract Map<String, String> getApexSystemServices(); /** * Dumps various state information to the provided {@link PrintWriter} object. @@ -450,7 +449,7 @@ public abstract class ApexManager { * Map of all apex system services to the jar files they are contained in. */ @GuardedBy("mLock") - private List<ApexSystemServiceInfo> mApexSystemServices = new ArrayList<>(); + private Map<String, String> mApexSystemServices = new ArrayMap<>(); /** * Contains the list of {@code packageName}s of apks-in-apex for given @@ -606,19 +605,14 @@ public abstract class ApexManager { } String name = service.getName(); - for (ApexSystemServiceInfo info : mApexSystemServices) { - if (info.getName().equals(name)) { - throw new IllegalStateException(String.format( - "Duplicate apex-system-service %s from %s, %s", - name, info.mJarPath, service.getJarPath())); - } + if (mApexSystemServices.containsKey(name)) { + throw new IllegalStateException(String.format( + "Duplicate apex-system-service %s from %s, %s", + name, mApexSystemServices.get(name), service.getJarPath())); } - ApexSystemServiceInfo info = new ApexSystemServiceInfo( - service.getName(), service.getJarPath(), service.getInitOrder()); - mApexSystemServices.add(info); + mApexSystemServices.put(name, service.getJarPath()); } - Collections.sort(mApexSystemServices); mPackageNameToApexModuleName.put(packageInfo.packageName, ai.moduleName); if (ai.isActive) { if (activePackagesSet.contains(packageInfo.packageName)) { @@ -1139,7 +1133,7 @@ public abstract class ApexManager { } @Override - public List<ApexSystemServiceInfo> getApexSystemServices() { + public Map<String, String> getApexSystemServices() { synchronized (mLock) { Preconditions.checkState(mApexSystemServices != null, "APEX packages have not been scanned"); @@ -1171,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(); @@ -1425,10 +1423,10 @@ public abstract class ApexManager { } @Override - public List<ApexSystemServiceInfo> getApexSystemServices() { + public Map<String, String> getApexSystemServices() { // TODO(satayev): we can't really support flattened apex use case, and need to migrate // the manifest entries into system's manifest asap. - return Collections.emptyList(); + return Collections.emptyMap(); } @Override diff --git a/services/core/java/com/android/server/pm/ApexSystemServiceInfo.java b/services/core/java/com/android/server/pm/ApexSystemServiceInfo.java deleted file mode 100644 index f75ba6d165c9..000000000000 --- a/services/core/java/com/android/server/pm/ApexSystemServiceInfo.java +++ /dev/null @@ -1,58 +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.server.pm; - -import android.annotation.Nullable; - -/** - * A helper class that contains information about apex-system-service to be used within system - * server process. - */ -public final class ApexSystemServiceInfo implements Comparable<ApexSystemServiceInfo> { - - final String mName; - @Nullable - final String mJarPath; - final int mInitOrder; - - public ApexSystemServiceInfo(String name, String jarPath, int initOrder) { - this.mName = name; - this.mJarPath = jarPath; - this.mInitOrder = initOrder; - } - - public String getName() { - return mName; - } - - public String getJarPath() { - return mJarPath; - } - - public int getInitOrder() { - return mInitOrder; - } - - @Override - public int compareTo(ApexSystemServiceInfo other) { - if (mInitOrder == other.mInitOrder) { - return mName.compareTo(other.mName); - } - // higher initOrder values take precedence - return -Integer.compare(mInitOrder, other.mInitOrder); - } -} diff --git a/services/core/java/com/android/server/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java index 22cd06dfd16b..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 android.content.pm.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/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java index 6f54625224bf..b916de328038 100644 --- a/services/core/java/com/android/server/pm/AppsFilter.java +++ b/services/core/java/com/android/server/pm/AppsFilter.java @@ -33,11 +33,11 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.SigningDetails; import android.content.pm.UserInfo; -import android.content.pm.parsing.component.ParsedComponent; -import android.content.pm.parsing.component.ParsedInstrumentation; -import android.content.pm.parsing.component.ParsedIntentInfo; -import android.content.pm.parsing.component.ParsedMainComponent; -import android.content.pm.parsing.component.ParsedProvider; +import com.android.server.pm.pkg.component.ParsedComponent; +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 android.os.Binder; import android.os.Process; import android.os.Trace; diff --git a/services/core/java/com/android/server/pm/ComponentResolver.java b/services/core/java/com/android/server/pm/ComponentResolver.java index 6ec3405727eb..cd4244bf1c50 100644 --- a/services/core/java/com/android/server/pm/ComponentResolver.java +++ b/services/core/java/com/android/server/pm/ComponentResolver.java @@ -36,14 +36,14 @@ import android.content.pm.PackageManagerInternal; import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; -import android.content.pm.parsing.component.ComponentMutateUtils; -import android.content.pm.parsing.component.ParsedActivity; -import android.content.pm.parsing.component.ParsedComponent; -import android.content.pm.parsing.component.ParsedIntentInfo; -import android.content.pm.parsing.component.ParsedMainComponent; -import android.content.pm.parsing.component.ParsedProvider; -import android.content.pm.parsing.component.ParsedProviderImpl; -import android.content.pm.parsing.component.ParsedService; +import com.android.server.pm.pkg.component.ComponentMutateUtils; +import com.android.server.pm.pkg.component.ParsedActivity; +import com.android.server.pm.pkg.component.ParsedComponent; +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.ParsedProviderImpl; +import com.android.server.pm.pkg.component.ParsedService; import android.os.UserHandle; import android.util.ArrayMap; import android.util.ArraySet; diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java index e37aaa5a9509..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 android.content.pm.parsing.PackageInfoWithoutStateUtils; -import android.content.pm.parsing.component.ParsedActivity; -import android.content.pm.parsing.component.ParsedInstrumentation; -import android.content.pm.parsing.component.ParsedIntentInfo; -import android.content.pm.parsing.component.ParsedMainComponent; -import android.content.pm.parsing.component.ParsedProvider; -import android.content.pm.parsing.component.ParsedService; -import android.content.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/InitAppsHelper.java b/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java index a5e6d6f0536d..9efe81aed24d 100644 --- a/services/core/java/com/android/server/pm/InitAppsHelper.java +++ b/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java @@ -31,9 +31,7 @@ import static com.android.server.pm.PackageManagerService.SCAN_REQUIRE_KNOWN; import static com.android.server.pm.PackageManagerService.SYSTEM_PARTITIONS; import static com.android.server.pm.PackageManagerService.TAG; -import android.annotation.NonNull; import android.annotation.Nullable; -import android.content.pm.parsing.ParsingPackageUtils; import android.os.Environment; import android.os.SystemClock; import android.os.Trace; @@ -48,6 +46,7 @@ import com.android.server.EventLogTags; import com.android.server.pm.parsing.PackageCacher; import com.android.server.pm.parsing.PackageParser2; import com.android.server.pm.parsing.pkg.AndroidPackage; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import com.android.server.utils.WatchedArrayMap; import java.io.File; @@ -60,25 +59,14 @@ import java.util.concurrent.ExecutorService; * further cleanup and eventually all the installation/scanning related logic will go to another * class. */ -final class InitAppsHelper { +final class InitAndSystemPackageHelper { private final PackageManagerService mPm; + private final List<ScanPartition> mDirsToScanAsSystem; private final int mScanFlags; private final int mSystemParseFlags; private final int mSystemScanFlags; private final InstallPackageHelper mInstallPackageHelper; - private final ApexManager mApexManager; - private final PackageParser2 mPackageParser; - private final ExecutorService mExecutorService; - /* Tracks how long system scan took */ - private long mSystemScanTime; - /* Track of the number of cached system apps */ - private int mCachedSystemApps; - /* Track of the number of system apps */ - private int mSystemPackagesCount; - private final boolean mIsDeviceUpgrading; - private final boolean mIsOnlyCoreApps; - private final List<ScanPartition> mSystemPartitions; /** * Tracks new system packages [received in an OTA] that we expect to @@ -86,39 +74,26 @@ final class InitAppsHelper { * are package location. */ private final ArrayMap<String, File> mExpectingBetter = new ArrayMap<>(); - /* Tracks of any system packages that no longer exist that needs to be pruned. */ - private final List<String> mPossiblyDeletedUpdatedSystemApps = new ArrayList<>(); - // Tracks of stub packages that must either be replaced with full versions in the /data - // partition or be disabled. - private final List<String> mStubSystemApps = new ArrayList<>(); // TODO(b/198166813): remove PMS dependency - InitAppsHelper(PackageManagerService pm, ApexManager apexManager, - InstallPackageHelper installPackageHelper, PackageParser2 packageParser, - List<ScanPartition> systemPartitions) { + InitAndSystemPackageHelper(PackageManagerService pm) { mPm = pm; - mApexManager = apexManager; - mInstallPackageHelper = installPackageHelper; - mPackageParser = packageParser; - mSystemPartitions = systemPartitions; + mInstallPackageHelper = new InstallPackageHelper(pm); mDirsToScanAsSystem = getSystemScanPartitions(); - mIsDeviceUpgrading = mPm.isDeviceUpgrading(); - mIsOnlyCoreApps = mPm.isOnlyCoreApps(); // Set flag to monitor and not change apk file paths when scanning install directories. int scanFlags = SCAN_BOOTING | SCAN_INITIAL; - if (mIsDeviceUpgrading || mPm.isFirstBoot()) { + if (mPm.isDeviceUpgrading() || mPm.isFirstBoot()) { mScanFlags = scanFlags | SCAN_FIRST_BOOT_OR_UPGRADE; } else { mScanFlags = scanFlags; } mSystemParseFlags = mPm.getDefParseFlags() | ParsingPackageUtils.PARSE_IS_SYSTEM_DIR; mSystemScanFlags = scanFlags | SCAN_AS_SYSTEM; - mExecutorService = ParallelPackageParser.makeExecutorService(); } private List<ScanPartition> getSystemScanPartitions() { final List<ScanPartition> scanPartitions = new ArrayList<>(); - scanPartitions.addAll(mSystemPartitions); + scanPartitions.addAll(mPm.mInjector.getSystemPartitions()); scanPartitions.addAll(getApexScanPartitions()); Slog.d(TAG, "Directories scanned as system partitions: " + scanPartitions); return scanPartitions; @@ -126,7 +101,8 @@ final class InitAppsHelper { private List<ScanPartition> getApexScanPartitions() { final List<ScanPartition> scanPartitions = new ArrayList<>(); - final List<ApexManager.ActiveApexInfo> activeApexInfos = mApexManager.getActiveApexInfos(); + final List<ApexManager.ActiveApexInfo> activeApexInfos = + mPm.mApexManager.getActiveApexInfos(); for (int i = 0; i < activeApexInfos.size(); i++) { final ScanPartition scanPartition = resolveApexToScanPartition(activeApexInfos.get(i)); if (scanPartition != null) { @@ -143,133 +119,116 @@ final class InitAppsHelper { if (apexInfo.preInstalledApexPath.getAbsolutePath().equals( sp.getFolder().getAbsolutePath()) || apexInfo.preInstalledApexPath.getAbsolutePath().startsWith( - sp.getFolder().getAbsolutePath() + File.separator)) { + sp.getFolder().getAbsolutePath() + File.separator)) { return new ScanPartition(apexInfo.apexDirectory, sp, SCAN_AS_APK_IN_APEX); } } return null; } - /** - * Install apps from system dirs. - */ - @GuardedBy({"mPm.mInstallLock", "mPm.mLock"}) - public OverlayConfig initSystemApps(WatchedArrayMap<String, PackageSetting> packageSettings, - int[] userIds, long startTime) { + public OverlayConfig initPackages( + WatchedArrayMap<String, PackageSetting> packageSettings, int[] userIds, + long startTime) { + PackageParser2 packageParser = mPm.mInjector.getScanningCachingPackageParser(); + + ExecutorService executorService = ParallelPackageParser.makeExecutorService(); // Prepare apex package info before scanning APKs, this information is needed when // scanning apk in apex. - mApexManager.scanApexPackagesTraced(mPackageParser, mExecutorService); + mPm.mApexManager.scanApexPackagesTraced(packageParser, executorService); - scanSystemDirs(mPackageParser, mExecutorService); + scanSystemDirs(packageParser, executorService); // Parse overlay configuration files to set default enable state, mutability, and // priority of system overlays. final ArrayMap<String, File> apkInApexPreInstalledPaths = new ArrayMap<>(); - for (ApexManager.ActiveApexInfo apexInfo : mApexManager.getActiveApexInfos()) { - for (String packageName : mApexManager.getApksInApex(apexInfo.apexModuleName)) { + for (ApexManager.ActiveApexInfo apexInfo : mPm.mApexManager.getActiveApexInfos()) { + for (String packageName : mPm.mApexManager.getApksInApex(apexInfo.apexModuleName)) { apkInApexPreInstalledPaths.put(packageName, apexInfo.preInstalledApexPath); } } - final OverlayConfig overlayConfig = OverlayConfig.initializeSystemInstance( + OverlayConfig overlayConfig = OverlayConfig.initializeSystemInstance( consumer -> mPm.forEachPackage( pkg -> consumer.accept(pkg, pkg.isSystem(), - apkInApexPreInstalledPaths.get(pkg.getPackageName())))); - - if (!mIsOnlyCoreApps) { + apkInApexPreInstalledPaths.get(pkg.getPackageName())))); + // Prune any system packages that no longer exist. + final List<String> possiblyDeletedUpdatedSystemApps = new ArrayList<>(); + // Stub packages must either be replaced with full versions in the /data + // partition or be disabled. + final List<String> stubSystemApps = new ArrayList<>(); + + if (!mPm.isOnlyCoreApps()) { // do this first before mucking with mPackages for the "expecting better" case - updateStubSystemAppsList(mStubSystemApps); + updateStubSystemAppsList(stubSystemApps); mInstallPackageHelper.prepareSystemPackageCleanUp(packageSettings, - mPossiblyDeletedUpdatedSystemApps, mExpectingBetter, userIds); + possiblyDeletedUpdatedSystemApps, mExpectingBetter, userIds); } - logSystemAppsScanningTime(startTime); - return overlayConfig; - } - - @GuardedBy({"mPm.mInstallLock", "mPm.mLock"}) - private void logSystemAppsScanningTime(long startTime) { - mCachedSystemApps = PackageCacher.sCachedPackageReadCount.get(); + final int cachedSystemApps = PackageCacher.sCachedPackageReadCount.get(); // Remove any shared userIDs that have no associated packages mPm.mSettings.pruneSharedUsersLPw(); - mSystemScanTime = SystemClock.uptimeMillis() - startTime; - mSystemPackagesCount = mPm.mPackages.size(); - Slog.i(TAG, "Finished scanning system apps. Time: " + mSystemScanTime - + " ms, packageCount: " + mSystemPackagesCount + final long systemScanTime = SystemClock.uptimeMillis() - startTime; + final int systemPackagesCount = mPm.mPackages.size(); + Slog.i(TAG, "Finished scanning system apps. Time: " + systemScanTime + + " ms, packageCount: " + systemPackagesCount + " , timePerPackage: " - + (mSystemPackagesCount == 0 ? 0 : mSystemScanTime / mSystemPackagesCount) - + " , cached: " + mCachedSystemApps); - if (mIsDeviceUpgrading && mSystemPackagesCount > 0) { + + (systemPackagesCount == 0 ? 0 : systemScanTime / systemPackagesCount) + + " , cached: " + cachedSystemApps); + if (mPm.isDeviceUpgrading() && systemPackagesCount > 0) { //CHECKSTYLE:OFF IndentationCheck FrameworkStatsLog.write(FrameworkStatsLog.BOOT_TIME_EVENT_DURATION_REPORTED, BOOT_TIME_EVENT_DURATION__EVENT__OTA_PACKAGE_MANAGER_SYSTEM_APP_AVG_SCAN_TIME, - mSystemScanTime / mSystemPackagesCount); + systemScanTime / systemPackagesCount); //CHECKSTYLE:ON IndentationCheck } - } - /** - * Install apps/updates from data dir and fix system apps that are affected. - */ - @GuardedBy({"mPm.mInstallLock", "mPm.mLock"}) - public void initNonSystemApps(@NonNull int[] userIds, long startTime) { - if (!mIsOnlyCoreApps) { + if (!mPm.isOnlyCoreApps()) { EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_DATA_SCAN_START, SystemClock.uptimeMillis()); - scanDirTracedLI(mPm.getAppInstallDir(), 0, mScanFlags | SCAN_REQUIRE_KNOWN, - mPackageParser, mExecutorService); + scanDirTracedLI(mPm.getAppInstallDir(), 0, mScanFlags | SCAN_REQUIRE_KNOWN, 0, + packageParser, executorService); } - List<Runnable> unfinishedTasks = mExecutorService.shutdownNow(); + List<Runnable> unfinishedTasks = executorService.shutdownNow(); if (!unfinishedTasks.isEmpty()) { throw new IllegalStateException("Not all tasks finished before calling close: " + unfinishedTasks); } - if (!mIsOnlyCoreApps) { - fixSystemPackages(userIds); - logNonSystemAppScanningTime(startTime); + + if (!mPm.isOnlyCoreApps()) { + mInstallPackageHelper.cleanupDisabledPackageSettings(possiblyDeletedUpdatedSystemApps, + userIds, mScanFlags); + mInstallPackageHelper.checkExistingBetterPackages(mExpectingBetter, + stubSystemApps, mSystemScanFlags, mSystemParseFlags); + + // Uncompress and install any stubbed system applications. + // This must be done last to ensure all stubs are replaced or disabled. + mInstallPackageHelper.installSystemStubPackages(stubSystemApps, mScanFlags); + + final int cachedNonSystemApps = PackageCacher.sCachedPackageReadCount.get() + - cachedSystemApps; + + final long dataScanTime = SystemClock.uptimeMillis() - systemScanTime - startTime; + final int dataPackagesCount = mPm.mPackages.size() - systemPackagesCount; + Slog.i(TAG, "Finished scanning non-system apps. Time: " + dataScanTime + + " ms, packageCount: " + dataPackagesCount + + " , timePerPackage: " + + (dataPackagesCount == 0 ? 0 : dataScanTime / dataPackagesCount) + + " , cached: " + cachedNonSystemApps); + if (mPm.isDeviceUpgrading() && dataPackagesCount > 0) { + //CHECKSTYLE:OFF IndentationCheck + FrameworkStatsLog.write( + FrameworkStatsLog.BOOT_TIME_EVENT_DURATION_REPORTED, + BOOT_TIME_EVENT_DURATION__EVENT__OTA_PACKAGE_MANAGER_DATA_APP_AVG_SCAN_TIME, + dataScanTime / dataPackagesCount); + //CHECKSTYLE:OFF IndentationCheck + } } mExpectingBetter.clear(); - mPm.mSettings.pruneRenamedPackagesLPw(); - mPackageParser.close(); - } - - /** - * Clean up system packages now that some system package updates have been installed from - * the data dir. Also install system stub packages as the last step. - */ - @GuardedBy({"mPm.mInstallLock", "mPm.mLock"}) - private void fixSystemPackages(@NonNull int[] userIds) { - mInstallPackageHelper.cleanupDisabledPackageSettings(mPossiblyDeletedUpdatedSystemApps, - userIds, mScanFlags); - mInstallPackageHelper.checkExistingBetterPackages(mExpectingBetter, - mStubSystemApps, mSystemScanFlags, mSystemParseFlags); - - // Uncompress and install any stubbed system applications. - // This must be done last to ensure all stubs are replaced or disabled. - mInstallPackageHelper.installSystemStubPackages(mStubSystemApps, mScanFlags); - } - @GuardedBy({"mPm.mInstallLock", "mPm.mLock"}) - private void logNonSystemAppScanningTime(long startTime) { - final int cachedNonSystemApps = PackageCacher.sCachedPackageReadCount.get() - - mCachedSystemApps; - - final long dataScanTime = SystemClock.uptimeMillis() - mSystemScanTime - startTime; - final int dataPackagesCount = mPm.mPackages.size() - mSystemPackagesCount; - Slog.i(TAG, "Finished scanning non-system apps. Time: " + dataScanTime - + " ms, packageCount: " + dataPackagesCount - + " , timePerPackage: " - + (dataPackagesCount == 0 ? 0 : dataScanTime / dataPackagesCount) - + " , cached: " + cachedNonSystemApps); - if (mIsDeviceUpgrading && dataPackagesCount > 0) { - //CHECKSTYLE:OFF IndentationCheck - FrameworkStatsLog.write( - FrameworkStatsLog.BOOT_TIME_EVENT_DURATION_REPORTED, - BOOT_TIME_EVENT_DURATION__EVENT__OTA_PACKAGE_MANAGER_DATA_APP_AVG_SCAN_TIME, - dataScanTime / dataPackagesCount); - //CHECKSTYLE:OFF IndentationCheck - } + mPm.mSettings.pruneRenamedPackagesLPw(); + packageParser.close(); + return overlayConfig; } /** @@ -289,12 +248,12 @@ final class InitAppsHelper { continue; } scanDirTracedLI(partition.getOverlayFolder(), mSystemParseFlags, - mSystemScanFlags | partition.scanFlag, + mSystemScanFlags | partition.scanFlag, 0, packageParser, executorService); } scanDirTracedLI(frameworkDir, mSystemParseFlags, - mSystemScanFlags | SCAN_NO_DEX | SCAN_AS_PRIVILEGED, + mSystemScanFlags | SCAN_NO_DEX | SCAN_AS_PRIVILEGED, 0, packageParser, executorService); if (!mPm.mPackages.containsKey("android")) { throw new IllegalStateException( @@ -305,11 +264,11 @@ final class InitAppsHelper { final ScanPartition partition = mDirsToScanAsSystem.get(i); if (partition.getPrivAppFolder() != null) { scanDirTracedLI(partition.getPrivAppFolder(), mSystemParseFlags, - mSystemScanFlags | SCAN_AS_PRIVILEGED | partition.scanFlag, + mSystemScanFlags | SCAN_AS_PRIVILEGED | partition.scanFlag, 0, packageParser, executorService); } scanDirTracedLI(partition.getAppFolder(), mSystemParseFlags, - mSystemScanFlags | partition.scanFlag, + mSystemScanFlags | partition.scanFlag, 0, packageParser, executorService); } } @@ -327,11 +286,11 @@ final class InitAppsHelper { @GuardedBy({"mPm.mInstallLock", "mPm.mLock"}) private void scanDirTracedLI(File scanDir, final int parseFlags, int scanFlags, - PackageParser2 packageParser, ExecutorService executorService) { + long currentTime, PackageParser2 packageParser, ExecutorService executorService) { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + scanDir.getAbsolutePath() + "]"); try { mInstallPackageHelper.installPackagesFromDir(scanDir, parseFlags, scanFlags, - packageParser, executorService); + currentTime, packageParser, executorService); } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index d002c2649de0..80699ac5dd82 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -112,11 +112,6 @@ import android.content.pm.Signature; import android.content.pm.SigningDetails; import android.content.pm.VerifierInfo; import android.content.pm.dex.DexMetadataHelper; -import android.content.pm.parsing.ParsingPackageUtils; -import android.content.pm.parsing.component.ComponentMutateUtils; -import android.content.pm.parsing.component.ParsedInstrumentation; -import android.content.pm.parsing.component.ParsedPermission; -import android.content.pm.parsing.component.ParsedPermissionGroup; import android.content.pm.parsing.result.ParseResult; import android.content.pm.parsing.result.ParseTypeImpl; import android.net.Uri; @@ -162,6 +157,11 @@ import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.permission.Permission; import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.android.server.pm.pkg.PackageStateInternal; +import com.android.server.pm.pkg.component.ComponentMutateUtils; +import com.android.server.pm.pkg.component.ParsedInstrumentation; +import com.android.server.pm.pkg.component.ParsedPermission; +import com.android.server.pm.pkg.component.ParsedPermissionGroup; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import com.android.server.rollback.RollbackManagerInternal; import com.android.server.utils.WatchedArrayMap; import com.android.server.utils.WatchedLongSparseArray; @@ -1207,21 +1207,36 @@ final class InstallPackageHelper { } PackageSetting ps = mPm.mSettings.getPackageLPr(pkgName); - if (ps != null) { - if (DEBUG_INSTALL) Slog.d(TAG, "Existing package: " + ps); + PackageSetting signatureCheckPs = ps; + + // SDK libs can have other major versions with different package names. + if (signatureCheckPs == null && parsedPackage.isSdkLibrary()) { + WatchedLongSparseArray<SharedLibraryInfo> libraryInfos = + mSharedLibraries.getSharedLibraryInfos( + parsedPackage.getSdkLibName()); + if (libraryInfos != null && libraryInfos.size() > 0) { + // Any existing version would do. + SharedLibraryInfo libraryInfo = libraryInfos.valueAt(0); + signatureCheckPs = mPm.mSettings.getPackageLPr(libraryInfo.getPackageName()); + } + } - // Static shared libs have same package with different versions where - // we internally use a synthetic package name to allow multiple versions - // of the same package, therefore we need to compare signatures against - // the package setting for the latest library version. - PackageSetting signatureCheckPs = ps; - if (parsedPackage.isStaticSharedLibrary()) { - SharedLibraryInfo libraryInfo = mSharedLibraries.getLatestSharedLibraVersionLPr( - parsedPackage); - if (libraryInfo != null) { - signatureCheckPs = mPm.mSettings.getPackageLPr( - libraryInfo.getPackageName()); - } + // Static shared libs have same package with different versions where + // we internally use a synthetic package name to allow multiple versions + // of the same package, therefore we need to compare signatures against + // the package setting for the latest library version. + if (parsedPackage.isStaticSharedLibrary()) { + SharedLibraryInfo libraryInfo = + mSharedLibraries.getLatestStaticSharedLibraVersionLPr(parsedPackage); + if (libraryInfo != null) { + signatureCheckPs = mPm.mSettings.getPackageLPr(libraryInfo.getPackageName()); + } + } + + if (signatureCheckPs != null) { + if (DEBUG_INSTALL) { + Slog.d(TAG, + "Existing package for signature checking: " + signatureCheckPs); } // Quick validity check that we're signed correctly if updating; @@ -1256,6 +1271,10 @@ final class InstallPackageHelper { throw new PrepareFailure(e.error, e.getMessage()); } } + } + + if (ps != null) { + if (DEBUG_INSTALL) Slog.d(TAG, "Existing package: " + ps); if (ps.getPkg() != null) { systemApp = ps.getPkg().isSystem(); @@ -2226,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) { @@ -3054,7 +3084,7 @@ final class InstallPackageHelper { final RemovePackageHelper removePackageHelper = new RemovePackageHelper(mPm); removePackageHelper.removePackageLI(stubPkg, true /*chatty*/); try { - return scanSystemPackageTracedLI(scanFile, parseFlags, scanFlags, null); + return scanSystemPackageTracedLI(scanFile, parseFlags, scanFlags, 0, null); } catch (PackageManagerException e) { Slog.w(TAG, "Failed to install compressed system package:" + stubPkg.getPackageName(), e); @@ -3187,7 +3217,7 @@ final class InstallPackageHelper { | ParsingPackageUtils.PARSE_IS_SYSTEM_DIR; @PackageManagerService.ScanFlags int scanFlags = mPm.getSystemPackageScanFlags(codePath); final AndroidPackage pkg = scanSystemPackageTracedLI( - codePath, parseFlags, scanFlags, null); + codePath, parseFlags, scanFlags, 0 /*currentTime*/, null); PackageSetting pkgSetting = mPm.mSettings.getPackageLPr(pkg.getPackageName()); @@ -3362,7 +3392,7 @@ final class InstallPackageHelper { mRemovePackageHelper.removePackageLI(pkg, true); try { final File codePath = new File(pkg.getPath()); - scanSystemPackageTracedLI(codePath, 0, scanFlags, null); + scanSystemPackageTracedLI(codePath, 0, scanFlags, 0, null); } catch (PackageManagerException e) { Slog.e(TAG, "Failed to parse updated, ex-system package: " + e.getMessage()); @@ -3383,7 +3413,7 @@ final class InstallPackageHelper { @GuardedBy({"mPm.mInstallLock", "mPm.mLock"}) public void installPackagesFromDir(File scanDir, int parseFlags, int scanFlags, - PackageParser2 packageParser, ExecutorService executorService) { + long currentTime, PackageParser2 packageParser, ExecutorService executorService) { final File[] files = scanDir.listFiles(); if (ArrayUtils.isEmpty(files)) { Log.d(TAG, "No files in app dir " + scanDir); @@ -3425,7 +3455,7 @@ final class InstallPackageHelper { parseResult.parsedPackage); } try { - addForInitLI(parseResult.parsedPackage, parseFlags, scanFlags, + addForInitLI(parseResult.parsedPackage, parseFlags, scanFlags, currentTime, null); } catch (PackageManagerException e) { errorCode = e.error; @@ -3488,7 +3518,7 @@ final class InstallPackageHelper { try { final AndroidPackage newPkg = scanSystemPackageTracedLI( - scanFile, reparseFlags, rescanFlags, null); + scanFile, reparseFlags, rescanFlags, 0, null); // We rescanned a stub, add it to the list of stubbed system packages if (newPkg.isStub()) { stubSystemApps.add(packageName); @@ -3502,14 +3532,14 @@ final class InstallPackageHelper { /** * Traces a package scan. - * @see #scanSystemPackageLI(File, int, int, UserHandle) + * @see #scanSystemPackageLI(File, int, int, long, UserHandle) */ @GuardedBy({"mPm.mInstallLock", "mPm.mLock"}) public AndroidPackage scanSystemPackageTracedLI(File scanFile, final int parseFlags, - int scanFlags, UserHandle user) throws PackageManagerException { + int scanFlags, long currentTime, UserHandle user) throws PackageManagerException { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanPackage [" + scanFile.toString() + "]"); try { - return scanSystemPackageLI(scanFile, parseFlags, scanFlags, user); + return scanSystemPackageLI(scanFile, parseFlags, scanFlags, currentTime, user); } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } @@ -3521,7 +3551,7 @@ final class InstallPackageHelper { */ @GuardedBy({"mPm.mInstallLock", "mPm.mLock"}) private AndroidPackage scanSystemPackageLI(File scanFile, int parseFlags, int scanFlags, - UserHandle user) throws PackageManagerException { + long currentTime, UserHandle user) throws PackageManagerException { if (DEBUG_INSTALL) Slog.d(TAG, "Parsing: " + scanFile); Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage"); @@ -3537,7 +3567,7 @@ final class InstallPackageHelper { PackageManagerService.renameStaticSharedLibraryPackage(parsedPackage); } - return addForInitLI(parsedPackage, parseFlags, scanFlags, user); + return addForInitLI(parsedPackage, parseFlags, scanFlags, currentTime, user); } /** @@ -3556,11 +3586,11 @@ final class InstallPackageHelper { @GuardedBy({"mPm.mLock", "mPm.mInstallLock"}) private AndroidPackage addForInitLI(ParsedPackage parsedPackage, @ParsingPackageUtils.ParseFlags int parseFlags, - @PackageManagerService.ScanFlags int scanFlags, + @PackageManagerService.ScanFlags int scanFlags, long currentTime, @Nullable UserHandle user) throws PackageManagerException { final Pair<ScanResult, Boolean> scanResultPair = scanSystemPackageLI( - parsedPackage, parseFlags, scanFlags, user); + parsedPackage, parseFlags, scanFlags, currentTime, user); final ScanResult scanResult = scanResultPair.first; boolean shouldHideSystemApp = scanResultPair.second; if (scanResult.mSuccess) { @@ -3738,7 +3768,7 @@ final class InstallPackageHelper { private Pair<ScanResult, Boolean> scanSystemPackageLI(ParsedPackage parsedPackage, @ParsingPackageUtils.ParseFlags int parseFlags, - @PackageManagerService.ScanFlags int scanFlags, + @PackageManagerService.ScanFlags int scanFlags, long currentTime, @Nullable UserHandle user) throws PackageManagerException { final boolean scanSystemPartition = (parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) != 0; @@ -3925,7 +3955,7 @@ final class InstallPackageHelper { } final ScanResult scanResult = scanPackageNewLI(parsedPackage, parseFlags, - scanFlags | SCAN_UPDATE_SIGNATURE, 0 /* currentTime */, user, null); + scanFlags | SCAN_UPDATE_SIGNATURE, currentTime, user, null); return new Pair<>(scanResult, shouldHideSystemApp); } diff --git a/services/core/java/com/android/server/pm/KeySetManagerService.java b/services/core/java/com/android/server/pm/KeySetManagerService.java index 1e1d169f4830..db346dabaa2b 100644 --- a/services/core/java/com/android/server/pm/KeySetManagerService.java +++ b/services/core/java/com/android/server/pm/KeySetManagerService.java @@ -17,11 +17,11 @@ package com.android.server.pm; import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK; -import static android.content.pm.parsing.ParsingPackageUtils.parsePublicKey; import static com.android.server.pm.PackageManagerService.SCAN_INITIAL; import android.annotation.NonNull; +import android.content.pm.parsing.FrameworkParsingPackageUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Base64; @@ -340,9 +340,10 @@ public class KeySetManagerService { if (p == null || p.getKeySetData() == null) { return null; } - Long keySetId = p.getKeySetData().getAliases().get(alias); + final ArrayMap<String, Long> aliases = p.getKeySetData().getAliases(); + Long keySetId = aliases.get(alias); if (keySetId == null) { - throw new IllegalArgumentException("Unknown KeySet alias: " + alias); + throw new IllegalArgumentException("Unknown KeySet alias: " + alias + ", aliases = " + aliases); } return mKeySets.get(keySetId); } @@ -811,7 +812,7 @@ public class KeySetManagerService { long identifier = parser.getAttributeLong(null, "identifier"); int refCount = 0; byte[] publicKey = parser.getAttributeBytesBase64(null, "value", null); - PublicKey pub = parsePublicKey(publicKey); + PublicKey pub = FrameworkParsingPackageUtils.parsePublicKey(publicKey); if (pub != null) { PublicKeyHandle pkh = new PublicKeyHandle(identifier, refCount, pub); mPublicKeys.put(identifier, pkh); diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index b3bb26c098f2..1f10d77086d3 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -1661,9 +1661,16 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements } synchronized (mSessions) { // Child sessions will be removed along with its parent as a whole - if (!session.hasParentSessionId() - && (!session.isStaged() || session.isDestroyed())) { - removeActiveSession(session); + if (!session.hasParentSessionId()) { + // Retain policy: + // 1. Don't keep non-staged sessions + // 2. Don't keep explicitly abandoned sessions + // 3. Don't keep sessions that fail validation (isCommitted() is false) + boolean shouldRemove = !session.isStaged() || session.isDestroyed() + || !session.isCommitted(); + if (shouldRemove) { + removeActiveSession(session); + } } final File appIconFile = buildAppIconFile(session.sessionId); diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index c8133174b6db..e0f1b0b44cf8 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -91,7 +91,7 @@ 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 android.content.pm.parsing.ParsingPackageUtils; +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; @@ -1734,61 +1734,22 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @WorkerThread private void handleStreamValidateAndCommit() { - PackageManagerException unrecoverableFailure = null; - // This will track whether the session and any children were validated and are ready to - // progress to the next phase of install - boolean allSessionsReady = false; try { - allSessionsReady = streamValidateAndCommit(); - } catch (PackageManagerException e) { - unrecoverableFailure = e; - } - - if (isMultiPackage()) { - final List<PackageInstallerSession> childSessions; - synchronized (mLock) { - childSessions = getChildSessionsLocked(); - } - int childCount = childSessions.size(); - - // This will contain all child sessions that do not encounter an unrecoverable failure - ArrayList<PackageInstallerSession> nonFailingSessions = new ArrayList<>(childCount); - - for (int i = childCount - 1; i >= 0; --i) { - // commit all children, regardless if any of them fail; we'll throw/return - // as appropriate once all children have been processed - try { - PackageInstallerSession session = childSessions.get(i); - allSessionsReady &= session.streamValidateAndCommit(); - nonFailingSessions.add(session); - } catch (PackageManagerException e) { - allSessionsReady = false; - if (unrecoverableFailure == null) { - unrecoverableFailure = e; - } - } + // This will track whether the session and any children were validated and are ready to + // progress to the next phase of install + boolean allSessionsReady = true; + for (PackageInstallerSession child : getChildSessions()) { + allSessionsReady &= child.streamValidateAndCommit(); } - // If we encountered any unrecoverable failures, destroy all other sessions including - // the parent - if (unrecoverableFailure != null) { - // {@link #streamValidateAndCommit()} calls - // {@link #onSessionValidationFailure(PackageManagerException)}, but we don't - // expect it to ever do so for parent sessions. Call that on this parent to clean - // it up and notify listeners of the error. - onSessionValidationFailure(unrecoverableFailure); - // fail other child sessions that did not already fail - for (int i = nonFailingSessions.size() - 1; i >= 0; --i) { - PackageInstallerSession session = nonFailingSessions.get(i); - session.onSessionValidationFailure(unrecoverableFailure); - } + if (allSessionsReady && streamValidateAndCommit()) { + mHandler.obtainMessage(MSG_INSTALL).sendToTarget(); } + } catch (PackageManagerException e) { + destroy(); + String msg = ExceptionUtils.getCompleteMessage(e); + dispatchSessionFinished(e.error, msg, null); + maybeFinishChildSessions(e.error, msg); } - - if (!allSessionsReady) { - return; - } - - mHandler.obtainMessage(MSG_INSTALL).sendToTarget(); } private final class FileSystemConnector extends @@ -2026,11 +1987,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } return true; } catch (PackageManagerException e) { - throw onSessionValidationFailure(e); + throw e; } catch (Throwable e) { // Convert all exceptions into package manager exceptions as only those are handled // in the code above. - throw onSessionValidationFailure(new PackageManagerException(e)); + throw new PackageManagerException(e); } } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 548eb580ad5d..e00f4f591d54 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -130,9 +130,6 @@ import android.content.pm.VerifierDeviceIdentity; import android.content.pm.VersionedPackage; import android.content.pm.dex.IArtManager; import android.content.pm.overlay.OverlayPaths; -import android.content.pm.parsing.ParsingPackageUtils; -import android.content.pm.parsing.component.ParsedInstrumentation; -import android.content.pm.parsing.component.ParsedMainComponent; import android.content.res.Resources; import android.database.ContentObserver; import android.graphics.Bitmap; @@ -170,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; @@ -237,10 +233,11 @@ 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; import com.android.server.pm.pkg.mutate.PackageStateWrite; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import com.android.server.pm.verify.domain.DomainVerificationManagerInternal; import com.android.server.pm.verify.domain.DomainVerificationService; import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy; @@ -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(); @@ -964,12 +961,13 @@ public class PackageManagerService extends IPackageManager.Stub private final BroadcastHelper mBroadcastHelper; private final RemovePackageHelper mRemovePackageHelper; private final DeletePackageHelper mDeletePackageHelper; - private final InitAppsHelper mInitAppsHelper; + private final InitAndSystemPackageHelper mInitAndSystemPackageHelper; private final AppDataHelper mAppDataHelper; private final InstallPackageHelper mInstallPackageHelper; 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; @@ -1700,11 +1699,12 @@ public class PackageManagerService extends IPackageManager.Stub mAppDataHelper = testParams.appDataHelper; mInstallPackageHelper = testParams.installPackageHelper; mRemovePackageHelper = testParams.removePackageHelper; - mInitAppsHelper = testParams.initAndSystemPackageHelper; + mInitAndSystemPackageHelper = testParams.initAndSystemPackageHelper; mDeletePackageHelper = testParams.deletePackageHelper; mPreferredActivityHelper = testParams.preferredActivityHelper; mResolveIntentHelper = testParams.resolveIntentHelper; mDexOptHelper = testParams.dexOptHelper; + mSuspendPackageHelper = testParams.suspendPackageHelper; mSharedLibraries.setDeletePackageHelper(mDeletePackageHelper); invalidatePackageInfoCache(); @@ -1844,14 +1844,15 @@ public class PackageManagerService extends IPackageManager.Stub mAppDataHelper = new AppDataHelper(this); mInstallPackageHelper = new InstallPackageHelper(this, mAppDataHelper); mRemovePackageHelper = new RemovePackageHelper(this, mAppDataHelper); - mInitAppsHelper = new InitAppsHelper(this, mApexManager, mInstallPackageHelper, - mInjector.getScanningPackageParser(), mInjector.getSystemPartitions()); + mInitAndSystemPackageHelper = new InitAndSystemPackageHelper(this); mDeletePackageHelper = new DeletePackageHelper(this, mRemovePackageHelper, mAppDataHelper); mSharedLibraries.setDeletePackageHelper(mDeletePackageHelper); 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 @@ -1977,8 +1978,8 @@ public class PackageManagerService extends IPackageManager.Stub mIsEngBuild, mIsUserDebugBuild, mIncrementalVersion); final int[] userIds = mUserManager.getUserIds(); - mOverlayConfig = mInitAppsHelper.initSystemApps(packageSettings, userIds, startTime); - mInitAppsHelper.initNonSystemApps(userIds, startTime); + mOverlayConfig = mInitAndSystemPackageHelper.initPackages(packageSettings, + userIds, startTime); // Resolve the storage manager. mStorageManagerPackage = getStorageManagerPackageName(); @@ -1996,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. @@ -2683,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); } @@ -4295,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. @@ -4382,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; } @@ -4390,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]; @@ -4470,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 @@ -4557,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 @@ -4619,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); @@ -4699,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"); @@ -4727,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 @@ -5446,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); @@ -5519,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) { @@ -5981,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)); @@ -7421,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); @@ -7481,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 @@ -9084,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: @@ -9099,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: @@ -9147,7 +8751,7 @@ public class PackageManagerService extends IPackageManager.Stub } boolean isExpectingBetter(String packageName) { - return mInitAppsHelper.isExpectingBetter(packageName); + return mInitAndSystemPackageHelper.isExpectingBetter(packageName); } int getDefParseFlags() { @@ -9256,7 +8860,7 @@ public class PackageManagerService extends IPackageManager.Stub @ScanFlags int getSystemPackageScanFlags(File codePath) { List<ScanPartition> dirsToScanAsSystem = - mInitAppsHelper.getDirsToScanAsSystem(); + mInitAndSystemPackageHelper.getDirsToScanAsSystem(); @PackageManagerService.ScanFlags int scanFlags = SCAN_AS_SYSTEM; for (int i = dirsToScanAsSystem.size() - 1; i >= 0; i--) { ScanPartition partition = dirsToScanAsSystem.get(i); @@ -9274,7 +8878,7 @@ public class PackageManagerService extends IPackageManager.Stub Pair<Integer, Integer> getSystemPackageRescanFlagsAndReparseFlags(File scanFile, int systemScanFlags, int systemParseFlags) { List<ScanPartition> dirsToScanAsSystem = - mInitAppsHelper.getDirsToScanAsSystem(); + mInitAndSystemPackageHelper.getDirsToScanAsSystem(); @ParsingPackageUtils.ParseFlags int reparseFlags = 0; @PackageManagerService.ScanFlags int rescanFlags = 0; for (int i1 = dirsToScanAsSystem.size() - 1; i1 >= 0; i1--) { diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java index 168401a30828..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; @@ -106,9 +107,10 @@ public final class PackageManagerServiceTestParams { public AppDataHelper appDataHelper; public InstallPackageHelper installPackageHelper; public RemovePackageHelper removePackageHelper; - public InitAppsHelper initAndSystemPackageHelper; + public InitAndSystemPackageHelper initAndSystemPackageHelper; public DeletePackageHelper deletePackageHelper; 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 898f67345031..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 android.content.pm.parsing.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/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index e27ad17c488d..fd2256f14963 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -230,6 +230,8 @@ class PackageManagerShellCommand extends ShellCommand { return runDexoptJob(); case "cancel-bg-dexopt-job": return cancelBgDexOptJob(); + case "delete-dexopt": + return runDeleteDexOpt(); case "dump-profiles": return runDumpProfiles(); case "snapshot-profile": @@ -1917,6 +1919,24 @@ class PackageManagerShellCommand extends ShellCommand { return 0; } + private int runDeleteDexOpt() throws RemoteException { + PrintWriter pw = getOutPrintWriter(); + String packageName = getNextArg(); + if (TextUtils.isEmpty(packageName)) { + pw.println("Error: no package name"); + return 1; + } + long freedBytes = LocalServices.getService( + PackageManagerInternal.class).deleteOatArtifactsOfPackage(packageName); + if (freedBytes < 0) { + pw.println("Error: delete failed"); + return 1; + } + pw.println("Success: freed " + freedBytes + " bytes"); + Slog.i(TAG, "delete-dexopt " + packageName + " ,freed " + freedBytes + " bytes"); + return 0; + } + private int runDumpProfiles() throws RemoteException { String packageName = getNextArg(); mInterface.dumpProfiles(packageName); @@ -4000,6 +4020,9 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(" force-dex-opt PACKAGE"); pw.println(" Force immediate execution of dex opt for the given PACKAGE."); pw.println(""); + pw.println(" delete-dexopt PACKAGE"); + pw.println(" Delete dex optimization results for the given PACKAGE."); + pw.println(""); pw.println(" bg-dexopt-job"); pw.println(" Execute the background optimizations immediately."); pw.println(" Note that the command only runs the background optimizer logic. It may"); diff --git a/services/core/java/com/android/server/pm/PackageProperty.java b/services/core/java/com/android/server/pm/PackageProperty.java index ee9ed3b90599..2055537b4cc8 100644 --- a/services/core/java/com/android/server/pm/PackageProperty.java +++ b/services/core/java/com/android/server/pm/PackageProperty.java @@ -27,7 +27,7 @@ import android.annotation.Nullable; import android.content.pm.PackageManager; import android.content.pm.PackageManager.Property; import android.content.pm.PackageManager.PropertyLocation; -import android.content.pm.parsing.component.ParsedComponent; +import com.android.server.pm.pkg.component.ParsedComponent; import android.os.Binder; import android.os.UserHandle; import android.util.ArrayMap; diff --git a/services/core/java/com/android/server/pm/PackageSessionVerifier.java b/services/core/java/com/android/server/pm/PackageSessionVerifier.java index ccabce719402..9bfb7d19eee1 100644 --- a/services/core/java/com/android/server/pm/PackageSessionVerifier.java +++ b/services/core/java/com/android/server/pm/PackageSessionVerifier.java @@ -28,7 +28,7 @@ import android.content.pm.PackageInstaller.SessionInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.SigningDetails; -import android.content.pm.parsing.PackageInfoWithoutStateUtils; +import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils; import android.content.pm.parsing.result.ParseResult; import android.content.pm.parsing.result.ParseTypeImpl; import android.content.rollback.RollbackInfo; diff --git a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java index 67f6b123d99b..f3d88edf40dd 100644 --- a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java +++ b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java @@ -28,7 +28,7 @@ import android.content.pm.PackageManager; import android.content.pm.SharedLibraryInfo; import android.content.pm.Signature; import android.content.pm.SigningDetails; -import android.content.pm.parsing.ParsingPackageUtils; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import android.os.SystemProperties; import android.util.ArrayMap; import android.util.Log; diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java index d60d01971534..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 android.content.pm.parsing.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/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java index 6d2ec0da896a..79ab563e4e6e 100644 --- a/services/core/java/com/android/server/pm/ScanPackageUtils.java +++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java @@ -52,13 +52,6 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.SharedLibraryInfo; import android.content.pm.SigningDetails; -import android.content.pm.parsing.ParsingPackageUtils; -import android.content.pm.parsing.component.ComponentMutateUtils; -import android.content.pm.parsing.component.ParsedActivity; -import android.content.pm.parsing.component.ParsedMainComponent; -import android.content.pm.parsing.component.ParsedProcess; -import android.content.pm.parsing.component.ParsedProvider; -import android.content.pm.parsing.component.ParsedService; import android.content.pm.parsing.result.ParseResult; import android.content.pm.parsing.result.ParseTypeImpl; import android.os.Build; @@ -85,6 +78,13 @@ import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.parsing.pkg.AndroidPackageUtils; import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.pkg.PackageStateUtils; +import com.android.server.pm.pkg.component.ComponentMutateUtils; +import com.android.server.pm.pkg.component.ParsedActivity; +import com.android.server.pm.pkg.component.ParsedMainComponent; +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.parsing.ParsingPackageUtils; import dalvik.system.VMRuntime; diff --git a/services/core/java/com/android/server/pm/ScanRequest.java b/services/core/java/com/android/server/pm/ScanRequest.java index 482b79cf8378..34abdb108068 100644 --- a/services/core/java/com/android/server/pm/ScanRequest.java +++ b/services/core/java/com/android/server/pm/ScanRequest.java @@ -18,12 +18,12 @@ package com.android.server.pm; import android.annotation.NonNull; import android.annotation.Nullable; -import android.content.pm.parsing.ParsingPackageUtils; import android.os.UserHandle; import com.android.internal.annotations.VisibleForTesting; import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.parsing.pkg.ParsedPackage; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; /** A package to be scanned */ @VisibleForTesting diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 7085682662e6..4583771794ba 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -50,13 +50,13 @@ import android.content.pm.SuspendDialogInfo; import android.content.pm.UserInfo; import android.content.pm.VerifierDeviceIdentity; import android.content.pm.overlay.OverlayPaths; -import android.content.pm.parsing.PackageInfoWithoutStateUtils; -import android.content.pm.parsing.component.ParsedComponent; -import android.content.pm.parsing.component.ParsedIntentInfo; -import android.content.pm.parsing.component.ParsedMainComponent; -import android.content.pm.parsing.component.ParsedPermission; -import android.content.pm.parsing.component.ParsedProcess; -import android.content.pm.pkg.PackageUserStateUtils; +import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils; +import com.android.server.pm.pkg.component.ParsedComponent; +import com.android.server.pm.pkg.component.ParsedIntentInfo; +import com.android.server.pm.pkg.component.ParsedMainComponent; +import com.android.server.pm.pkg.component.ParsedPermission; +import com.android.server.pm.pkg.component.ParsedProcess; +import com.android.server.pm.pkg.PackageUserStateUtils; import android.net.Uri; import android.os.Binder; import android.os.Build; diff --git a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java index aa230508287e..2227a7810dec 100644 --- a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java +++ b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java @@ -384,7 +384,7 @@ public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable * @return The latest version of shared library info. */ @GuardedBy("mPm.mLock") - @Nullable SharedLibraryInfo getLatestSharedLibraVersionLPr(@NonNull AndroidPackage pkg) { + @Nullable SharedLibraryInfo getLatestStaticSharedLibraVersionLPr(@NonNull AndroidPackage pkg) { WatchedLongSparseArray<SharedLibraryInfo> versionedLib = mSharedLibraries.get( pkg.getStaticSharedLibName()); if (versionedLib == null) { @@ -416,7 +416,7 @@ public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable PackageSetting sharedLibPackage = null; synchronized (mPm.mLock) { final SharedLibraryInfo latestSharedLibraVersionLPr = - getLatestSharedLibraVersionLPr(scanResult.mRequest.mParsedPackage); + getLatestStaticSharedLibraVersionLPr(scanResult.mRequest.mParsedPackage); if (latestSharedLibraVersionLPr != null) { sharedLibPackage = mPm.mSettings.getPackageLPr( latestSharedLibraVersionLPr.getPackageName()); diff --git a/services/core/java/com/android/server/pm/SharedUserSetting.java b/services/core/java/com/android/server/pm/SharedUserSetting.java index 9df0edb211f1..bc484618bb25 100644 --- a/services/core/java/com/android/server/pm/SharedUserSetting.java +++ b/services/core/java/com/android/server/pm/SharedUserSetting.java @@ -18,9 +18,9 @@ package com.android.server.pm; import android.annotation.NonNull; import android.content.pm.ApplicationInfo; -import android.content.pm.parsing.component.ComponentMutateUtils; -import android.content.pm.parsing.component.ParsedProcess; -import android.content.pm.parsing.component.ParsedProcessImpl; +import com.android.server.pm.pkg.component.ComponentMutateUtils; +import com.android.server.pm.pkg.component.ParsedProcess; +import com.android.server.pm.pkg.component.ParsedProcessImpl; import android.service.pm.PackageServiceDumpProto; import android.util.ArrayMap; import android.util.ArraySet; 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/StorageEventHelper.java b/services/core/java/com/android/server/pm/StorageEventHelper.java index de6440536202..bb7e55a4bf40 100644 --- a/services/core/java/com/android/server/pm/StorageEventHelper.java +++ b/services/core/java/com/android/server/pm/StorageEventHelper.java @@ -32,7 +32,7 @@ import android.content.pm.PackageManager; import android.content.pm.PackagePartitions; import android.content.pm.UserInfo; import android.content.pm.VersionedPackage; -import android.content.pm.parsing.ParsingPackageUtils; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import android.os.Environment; import android.os.FileUtils; import android.os.UserHandle; @@ -150,7 +150,7 @@ public final class StorageEventHelper extends StorageEventListener { final AndroidPackage pkg; try { pkg = installPackageHelper.scanSystemPackageTracedLI( - ps.getPath(), parseFlags, SCAN_INITIAL, null); + ps.getPath(), parseFlags, SCAN_INITIAL, 0, null); loaded.add(pkg); } catch (PackageManagerException e) { 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/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java index 01bf63483829..e28a6ea8ea6b 100644 --- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java +++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java @@ -31,7 +31,7 @@ import android.content.pm.dex.ArtManagerInternal; import android.content.pm.dex.DexMetadataHelper; import android.content.pm.dex.ISnapshotRuntimeProfileCallback; import android.content.pm.dex.PackageOptimizationInfo; -import android.content.pm.parsing.PackageInfoWithoutStateUtils; +import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils; import android.os.Binder; import android.os.Build; import android.os.Handler; diff --git a/services/core/java/com/android/server/pm/dex/ViewCompiler.java b/services/core/java/com/android/server/pm/dex/ViewCompiler.java index 8afe62aabd59..61aedd8bd1cf 100644 --- a/services/core/java/com/android/server/pm/dex/ViewCompiler.java +++ b/services/core/java/com/android/server/pm/dex/ViewCompiler.java @@ -16,7 +16,7 @@ package com.android.server.pm.dex; -import android.content.pm.parsing.PackageInfoWithoutStateUtils; +import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils; import android.os.Binder; import android.os.UserHandle; import android.util.Log; diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java index 07cc3d0e42af..0fa0dc39b8bd 100644 --- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java +++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java @@ -34,19 +34,19 @@ import android.content.pm.ProcessInfo; import android.content.pm.ProviderInfo; import android.content.pm.ServiceInfo; import android.content.pm.SharedLibraryInfo; -import android.content.pm.parsing.PackageInfoWithoutStateUtils; -import android.content.pm.parsing.ParsingUtils; -import android.content.pm.parsing.component.ComponentParseUtils; -import android.content.pm.parsing.component.ParsedActivity; -import android.content.pm.parsing.component.ParsedComponent; -import android.content.pm.parsing.component.ParsedInstrumentation; -import android.content.pm.parsing.component.ParsedMainComponent; -import android.content.pm.parsing.component.ParsedPermission; -import android.content.pm.parsing.component.ParsedPermissionGroup; -import android.content.pm.parsing.component.ParsedProcess; -import android.content.pm.parsing.component.ParsedProvider; -import android.content.pm.parsing.component.ParsedService; -import android.content.pm.pkg.PackageUserStateUtils; +import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils; +import com.android.server.pm.pkg.parsing.ParsingUtils; +import com.android.server.pm.pkg.component.ComponentParseUtils; +import com.android.server.pm.pkg.component.ParsedActivity; +import com.android.server.pm.pkg.component.ParsedComponent; +import com.android.server.pm.pkg.component.ParsedInstrumentation; +import com.android.server.pm.pkg.component.ParsedMainComponent; +import com.android.server.pm.pkg.component.ParsedPermission; +import com.android.server.pm.pkg.component.ParsedPermissionGroup; +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.PackageUserStateUtils; import android.os.UserHandle; import android.util.ArrayMap; import android.util.ArraySet; diff --git a/services/core/java/com/android/server/pm/parsing/PackageParser2.java b/services/core/java/com/android/server/pm/parsing/PackageParser2.java index f467a7f2023f..08e2f7da1ebf 100644 --- a/services/core/java/com/android/server/pm/parsing/PackageParser2.java +++ b/services/core/java/com/android/server/pm/parsing/PackageParser2.java @@ -22,9 +22,9 @@ import android.annotation.Nullable; import android.app.ActivityThread; import android.content.Context; import android.content.pm.ApplicationInfo; -import android.content.pm.parsing.ParsingPackage; -import android.content.pm.parsing.ParsingPackageUtils; -import android.content.pm.parsing.ParsingUtils; +import com.android.server.pm.pkg.parsing.ParsingPackage; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; +import com.android.server.pm.pkg.parsing.ParsingUtils; import android.content.pm.parsing.result.ParseInput; import android.content.pm.parsing.result.ParseResult; import android.content.pm.parsing.result.ParseTypeImpl; diff --git a/services/core/java/com/android/server/pm/parsing/ParsedComponentStateUtils.java b/services/core/java/com/android/server/pm/parsing/ParsedComponentStateUtils.java index 3a4921644175..564585b4cbe9 100644 --- a/services/core/java/com/android/server/pm/parsing/ParsedComponentStateUtils.java +++ b/services/core/java/com/android/server/pm/parsing/ParsedComponentStateUtils.java @@ -18,10 +18,9 @@ package com.android.server.pm.parsing; import android.annotation.NonNull; import android.annotation.Nullable; -import android.content.pm.parsing.component.ParsedComponent; +import com.android.server.pm.pkg.component.ParsedComponent; import android.util.Pair; -import com.android.server.pm.PackageSetting; import com.android.server.pm.pkg.PackageStateInternal; /** diff --git a/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java b/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java index bbf584df0405..dc3bf781034c 100644 --- a/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java +++ b/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java @@ -21,7 +21,7 @@ import static com.android.server.pm.parsing.library.SharedLibraryNames.ANDROID_T import static com.android.server.pm.parsing.library.SharedLibraryNames.ANDROID_TEST_RUNNER; import static com.android.server.pm.parsing.library.SharedLibraryNames.ORG_APACHE_HTTP_LEGACY; -import android.content.pm.parsing.ParsingPackage; +import com.android.server.pm.pkg.parsing.ParsingPackage; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackage.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackage.java index bf7d897ac3e6..b357ba0a6b4b 100644 --- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackage.java +++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackage.java @@ -17,7 +17,9 @@ package com.android.server.pm.parsing.pkg; import android.annotation.NonNull; -import android.content.pm.parsing.ParsingPackageRead; + +import com.android.internal.content.om.OverlayConfig; +import com.android.server.pm.pkg.parsing.ParsingPackageRead; import com.android.server.pm.pkg.AndroidPackageApi; @@ -31,8 +33,8 @@ import com.android.server.pm.pkg.AndroidPackageApi; * * @hide */ -public interface AndroidPackage extends ParsingPackageRead, AndroidPackageApi { - +public interface AndroidPackage extends ParsingPackageRead, AndroidPackageApi, + OverlayConfig.PackageProvider.Package { /** * The package name as declared in the manifest, since the package can be renamed. For example, diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java index 8b2c3a12eda7..7e59bd669824 100644 --- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java +++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java @@ -23,12 +23,12 @@ import android.content.pm.PackageManager; import android.content.pm.SharedLibraryInfo; import android.content.pm.VersionedPackage; import android.content.pm.dex.DexMetadataHelper; -import android.content.pm.parsing.ParsingPackageRead; -import android.content.pm.parsing.ParsingPackageUtils; -import android.content.pm.parsing.component.ParsedActivity; -import android.content.pm.parsing.component.ParsedInstrumentation; -import android.content.pm.parsing.component.ParsedProvider; -import android.content.pm.parsing.component.ParsedService; +import com.android.server.pm.pkg.parsing.ParsingPackageRead; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; +import com.android.server.pm.pkg.component.ParsedActivity; +import com.android.server.pm.pkg.component.ParsedInstrumentation; +import com.android.server.pm.pkg.component.ParsedProvider; +import com.android.server.pm.pkg.component.ParsedService; import android.content.pm.parsing.result.ParseResult; import android.content.pm.parsing.result.ParseTypeImpl; import android.os.incremental.IncrementalManager; diff --git a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java index 6846ac5cbb0a..193e1a22d787 100644 --- a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java +++ b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java @@ -22,14 +22,8 @@ import android.annotation.Nullable; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; -import android.content.pm.SELinuxUtil; +import com.android.server.pm.pkg.SELinuxUtil; import android.content.pm.SigningDetails; -import android.content.pm.parsing.ParsingPackage; -import android.content.pm.parsing.ParsingPackageImpl; -import android.content.pm.parsing.component.ComponentMutateUtils; -import android.content.pm.parsing.component.ParsedActivity; -import android.content.pm.parsing.component.ParsedProvider; -import android.content.pm.parsing.component.ParsedService; import android.content.res.TypedArray; import android.os.Environment; import android.os.Parcel; @@ -41,6 +35,12 @@ import com.android.internal.util.CollectionUtils; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling.BuiltIn.ForInternedString; import com.android.server.pm.parsing.PackageInfoUtils; +import com.android.server.pm.pkg.component.ComponentMutateUtils; +import com.android.server.pm.pkg.component.ParsedActivity; +import com.android.server.pm.pkg.component.ParsedProvider; +import com.android.server.pm.pkg.component.ParsedService; +import com.android.server.pm.pkg.parsing.ParsingPackage; +import com.android.server.pm.pkg.parsing.ParsingPackageImpl; import java.io.File; diff --git a/services/core/java/com/android/server/pm/parsing/pkg/PkgAppInfo.java b/services/core/java/com/android/server/pm/parsing/pkg/PkgAppInfo.java index a13f2975da8d..6ddae9bf4f8f 100644 --- a/services/core/java/com/android/server/pm/parsing/pkg/PkgAppInfo.java +++ b/services/core/java/com/android/server/pm/parsing/pkg/PkgAppInfo.java @@ -18,7 +18,7 @@ package com.android.server.pm.parsing.pkg; import android.annotation.Nullable; import android.content.pm.ApplicationInfo; -import android.content.pm.parsing.PkgWithoutStateAppInfo; +import com.android.server.pm.pkg.parsing.PkgWithoutStateAppInfo; import com.android.server.pm.PackageManagerService; import com.android.server.pm.pkg.PackageState; diff --git a/services/core/java/com/android/server/pm/parsing/pkg/PkgPackageInfo.java b/services/core/java/com/android/server/pm/parsing/pkg/PkgPackageInfo.java index e2efbe1482c3..da7f1dc0f47e 100644 --- a/services/core/java/com/android/server/pm/parsing/pkg/PkgPackageInfo.java +++ b/services/core/java/com/android/server/pm/parsing/pkg/PkgPackageInfo.java @@ -17,7 +17,7 @@ package com.android.server.pm.parsing.pkg; import android.content.pm.PackageInfo; -import android.content.pm.parsing.PkgWithoutStatePackageInfo; +import com.android.server.pm.pkg.parsing.PkgWithoutStatePackageInfo; import com.android.server.pm.PackageManagerService; diff --git a/core/java/android/content/pm/permission/CompatibilityPermissionInfo.java b/services/core/java/com/android/server/pm/permission/CompatibilityPermissionInfo.java index b70353a4023c..d9625050d28c 100644 --- a/core/java/android/content/pm/permission/CompatibilityPermissionInfo.java +++ b/services/core/java/com/android/server/pm/permission/CompatibilityPermissionInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 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,11 +14,10 @@ * limitations under the License. */ -package android.content.pm.permission; +package com.android.server.pm.permission; import android.Manifest; import android.annotation.NonNull; -import android.content.pm.parsing.component.ParsedUsesPermission; import com.android.internal.util.DataClass; 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/Permission.java b/services/core/java/com/android/server/pm/permission/Permission.java index 041c4fea587e..d5456e3c8dc3 100644 --- a/services/core/java/com/android/server/pm/permission/Permission.java +++ b/services/core/java/com/android/server/pm/permission/Permission.java @@ -23,7 +23,7 @@ import android.annotation.UserIdInt; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.PermissionInfo; -import android.content.pm.parsing.component.ParsedPermission; +import com.android.server.pm.pkg.component.ParsedPermission; import android.os.Build; import android.os.UserHandle; import android.util.Log; 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 7833c4341a94..9b3d6d6eb3de 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java @@ -73,11 +73,11 @@ import android.content.pm.PackageManagerInternal; import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; import android.content.pm.SigningDetails; -import android.content.pm.parsing.component.ComponentMutateUtils; -import android.content.pm.parsing.component.ParsedPermission; -import android.content.pm.parsing.component.ParsedPermissionGroup; -import android.content.pm.parsing.component.ParsedPermissionUtils; -import android.content.pm.permission.CompatibilityPermissionInfo; +import com.android.server.pm.pkg.component.ComponentMutateUtils; +import com.android.server.pm.pkg.component.ParsedPermission; +import com.android.server.pm.pkg.component.ParsedPermissionGroup; +import com.android.server.pm.pkg.component.ParsedPermissionUtils; + import android.content.pm.permission.SplitPermissionInfoParcelable; import android.metrics.LogMaker; import android.os.AsyncTask; @@ -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/permission/PermissionRegistry.java b/services/core/java/com/android/server/pm/permission/PermissionRegistry.java index 0e3fda7b937a..3a617041d55e 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionRegistry.java +++ b/services/core/java/com/android/server/pm/permission/PermissionRegistry.java @@ -18,7 +18,7 @@ package com.android.server.pm.permission; import android.annotation.NonNull; import android.annotation.Nullable; -import android.content.pm.parsing.component.ParsedPermissionGroup; +import com.android.server.pm.pkg.component.ParsedPermissionGroup; import android.util.ArrayMap; import android.util.ArraySet; diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java b/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java index 6c8e0b72198d..656c445a0ed5 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java +++ b/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java @@ -20,8 +20,8 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.pm.ComponentInfo; import android.content.pm.PackageManager; -import android.content.pm.parsing.component.ParsedMainComponent; -import android.content.pm.pkg.PackageUserStateUtils; +import com.android.server.pm.pkg.component.ParsedMainComponent; + import android.util.SparseArray; import com.android.server.pm.parsing.pkg.AndroidPackage; diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserState.java b/services/core/java/com/android/server/pm/pkg/PackageUserState.java index 03b16929bed8..d47c5eca18ea 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageUserState.java +++ b/services/core/java/com/android/server/pm/pkg/PackageUserState.java @@ -20,7 +20,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.pm.PackageManager; import android.content.pm.overlay.OverlayPaths; -import android.content.pm.pkg.FrameworkPackageUserState; import android.os.UserHandle; import java.util.Map; @@ -34,7 +33,7 @@ import java.util.Set; */ // TODO(b/173807334): Expose API //@SystemApi(client = SystemApi.Client.SYSTEM_SERVER) -public interface PackageUserState extends FrameworkPackageUserState { +public interface PackageUserState { PackageUserState DEFAULT = PackageUserStateInternal.DEFAULT; diff --git a/core/java/android/content/pm/pkg/PackageUserStateUtils.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java index 468bff1b53ec..917c4af017d2 100644 --- a/core/java/android/content/pm/pkg/PackageUserStateUtils.java +++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.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 android.content.pm.pkg; +package com.android.server.pm.pkg; import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS; import static android.content.pm.PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS; @@ -22,26 +22,27 @@ import static android.content.pm.PackageManager.MATCH_DISABLED_UNTIL_USED_COMPON import android.annotation.NonNull; import android.content.pm.ComponentInfo; import android.content.pm.PackageManager; -import android.content.pm.parsing.ParsingPackageRead; -import android.content.pm.parsing.component.ParsedMainComponent; import android.os.Debug; import android.util.DebugUtils; import android.util.Slog; +import com.android.server.pm.pkg.component.ParsedMainComponent; +import com.android.server.pm.pkg.parsing.ParsingPackageRead; + /** @hide */ public class PackageUserStateUtils { private static final boolean DEBUG = false; private static final String TAG = "PackageUserStateUtils"; - public static boolean isMatch(@NonNull FrameworkPackageUserState state, + public static boolean isMatch(@NonNull PackageUserState state, ComponentInfo componentInfo, long flags) { return isMatch(state, componentInfo.applicationInfo.isSystemApp(), componentInfo.applicationInfo.enabled, componentInfo.enabled, componentInfo.directBootAware, componentInfo.name, flags); } - public static boolean isMatch(@NonNull FrameworkPackageUserState state, boolean isSystem, + public static boolean isMatch(@NonNull PackageUserState state, boolean isSystem, boolean isPackageEnabled, ParsedMainComponent component, long flags) { return isMatch(state, isSystem, isPackageEnabled, component.isEnabled(), component.isDirectBootAware(), component.getName(), flags); @@ -56,7 +57,7 @@ public class PackageUserStateUtils { * PackageManager#MATCH_DIRECT_BOOT_UNAWARE} are specified in {@code flags}. * </p> */ - public static boolean isMatch(@NonNull FrameworkPackageUserState state, boolean isSystem, + public static boolean isMatch(@NonNull PackageUserState state, boolean isSystem, boolean isPackageEnabled, boolean isComponentEnabled, boolean isComponentDirectBootAware, String componentName, long flags) { final boolean matchUninstalled = (flags & PackageManager.MATCH_KNOWN_PACKAGES) != 0; @@ -81,7 +82,7 @@ public class PackageUserStateUtils { return reportIfDebug(matchesUnaware || matchesAware, flags); } - public static boolean isAvailable(@NonNull FrameworkPackageUserState state, long flags) { + public static boolean isAvailable(@NonNull PackageUserState state, long flags) { // True if it is installed for this user and it is not hidden. If it is hidden, // still return true if the caller requested MATCH_UNINSTALLED_PACKAGES final boolean matchAnyUser = (flags & PackageManager.MATCH_ANY_USER) != 0; @@ -100,13 +101,13 @@ public class PackageUserStateUtils { return result; } - public static boolean isEnabled(@NonNull FrameworkPackageUserState state, ComponentInfo componentInfo, + public static boolean isEnabled(@NonNull PackageUserState state, ComponentInfo componentInfo, long flags) { return isEnabled(state, componentInfo.applicationInfo.enabled, componentInfo.enabled, componentInfo.name, flags); } - public static boolean isEnabled(@NonNull FrameworkPackageUserState state, boolean isPackageEnabled, + public static boolean isEnabled(@NonNull PackageUserState state, boolean isPackageEnabled, ParsedMainComponent parsedComponent, long flags) { return isEnabled(state, isPackageEnabled, parsedComponent.isEnabled(), parsedComponent.getName(), flags); @@ -115,7 +116,7 @@ public class PackageUserStateUtils { /** * Test if the given component is considered enabled. */ - public static boolean isEnabled(@NonNull FrameworkPackageUserState state, + public static boolean isEnabled(@NonNull PackageUserState state, boolean isPackageEnabled, boolean isComponentEnabled, String componentName, long flags) { if ((flags & MATCH_DISABLED_COMPONENTS) != 0) { @@ -153,7 +154,7 @@ public class PackageUserStateUtils { return isComponentEnabled; } - public static boolean isPackageEnabled(@NonNull FrameworkPackageUserState state, + public static boolean isPackageEnabled(@NonNull PackageUserState state, @NonNull ParsingPackageRead pkg) { switch (state.getEnabledState()) { case PackageManager.COMPONENT_ENABLED_STATE_ENABLED: diff --git a/core/java/android/content/pm/SELinuxUtil.java b/services/core/java/com/android/server/pm/pkg/SELinuxUtil.java index 898dddf45d04..6cbc1de75010 100644 --- a/core/java/android/content/pm/SELinuxUtil.java +++ b/services/core/java/com/android/server/pm/pkg/SELinuxUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 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,9 +14,7 @@ * limitations under the License. */ -package android.content.pm; - -import android.content.pm.pkg.FrameworkPackageUserState; +package com.android.server.pm.pkg; /** * Utility methods that need to be used in application space. @@ -31,7 +29,7 @@ public final class SELinuxUtil { public static final String COMPLETE_STR = ":complete"; /** @hide */ - public static String getSeinfoUser(FrameworkPackageUserState userState) { + public static String getSeinfoUser(PackageUserState userState) { if (userState.isInstantApp()) { return INSTANT_APP_STR + COMPLETE_STR; } diff --git a/core/java/android/content/pm/parsing/component/ComponentMutateUtils.java b/services/core/java/com/android/server/pm/pkg/component/ComponentMutateUtils.java index 73d0b9fd8504..1deb8d055e20 100644 --- a/core/java/android/content/pm/parsing/component/ComponentMutateUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ComponentMutateUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 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 android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/core/java/android/content/pm/parsing/component/ComponentParseUtils.java b/services/core/java/com/android/server/pm/pkg/component/ComponentParseUtils.java index 0334601a0a06..a8fb79a52837 100644 --- a/core/java/android/content/pm/parsing/component/ComponentParseUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ComponentParseUtils.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,27 +14,27 @@ * limitations under the License. */ -package android.content.pm.parsing.component; - -import static android.content.pm.parsing.ParsingPackageUtils.validateName; +package com.android.server.pm.pkg.component; import android.annotation.AttrRes; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Intent; import android.content.IntentFilter; -import android.content.pm.parsing.ParsingPackage; -import android.content.pm.parsing.ParsingPackageUtils; -import android.content.pm.parsing.ParsingUtils; +import android.content.pm.parsing.FrameworkParsingPackageUtils; import android.content.pm.parsing.result.ParseInput; import android.content.pm.parsing.result.ParseResult; -import android.content.pm.pkg.FrameworkPackageUserState; -import android.content.pm.pkg.PackageUserStateUtils; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.text.TextUtils; +import com.android.server.pm.pkg.PackageUserState; +import com.android.server.pm.pkg.PackageUserStateUtils; +import com.android.server.pm.pkg.parsing.ParsingPackage; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; +import com.android.server.pm.pkg.parsing.ParsingUtils; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -125,7 +125,8 @@ public class ComponentParseUtils { + ": must be at least two characters"); } String subName = proc.substring(1); - final ParseResult<?> nameResult = validateName(input, subName, false, false); + final ParseResult<?> nameResult = FrameworkParsingPackageUtils.validateName(input, + subName, false, false); if (nameResult.isError()) { return input.error("Invalid " + type + " name " + proc + " in package " + pkg + ": " + nameResult.getErrorMessage()); @@ -133,7 +134,8 @@ public class ComponentParseUtils { return input.success(pkg + proc); } if (!"system".equals(proc)) { - final ParseResult<?> nameResult = validateName(input, proc, true, false); + final ParseResult<?> nameResult = FrameworkParsingPackageUtils.validateName(input, proc, + true, false); if (nameResult.isError()) { return input.error("Invalid " + type + " name " + proc + " in package " + pkg + ": " + nameResult.getErrorMessage()); @@ -169,13 +171,13 @@ public class ComponentParseUtils { return component.getIcon(); } - public static boolean isMatch(FrameworkPackageUserState state, boolean isSystem, + public static boolean isMatch(PackageUserState state, boolean isSystem, boolean isPackageEnabled, ParsedMainComponent component, long flags) { return PackageUserStateUtils.isMatch(state, isSystem, isPackageEnabled, component.isEnabled(), component.isDirectBootAware(), component.getName(), flags); } - public static boolean isEnabled(FrameworkPackageUserState state, boolean isPackageEnabled, + public static boolean isEnabled(PackageUserState state, boolean isPackageEnabled, ParsedMainComponent parsedComponent, long flags) { return PackageUserStateUtils.isEnabled(state, isPackageEnabled, parsedComponent.isEnabled(), parsedComponent.getName(), flags); diff --git a/core/java/android/content/pm/parsing/component/ParsedActivity.java b/services/core/java/com/android/server/pm/pkg/component/ParsedActivity.java index a661b51dbf81..6d978c40454d 100644 --- a/core/java/android/content/pm/parsing/component/ParsedActivity.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedActivity.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 android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/core/java/android/content/pm/parsing/component/ParsedActivityImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java index 93dc5a4ce317..ff97c13a998b 100644 --- a/core/java/android/content/pm/parsing/component/ParsedActivityImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 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,13 +14,13 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE; import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; -import static android.content.pm.parsing.ParsingPackageImpl.sForInternedString; +import static com.android.server.pm.pkg.parsing.ParsingPackageImpl.sForInternedString; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED; import android.annotation.NonNull; @@ -29,7 +29,7 @@ import android.app.ActivityTaskManager; import android.content.ComponentName; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; -import android.content.pm.parsing.ParsingUtils; +import com.android.server.pm.pkg.parsing.ParsingUtils; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; diff --git a/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java index 2ddf923d7dd9..db8815e6555c 100644 --- a/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.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,12 +14,12 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE_PER_TASK; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; -import static android.content.pm.parsing.ParsingUtils.NOT_SET; -import static android.content.pm.parsing.component.ComponentParseUtils.flag; +import static com.android.server.pm.pkg.parsing.ParsingUtils.NOT_SET; +import static com.android.server.pm.pkg.component.ComponentParseUtils.flag; import android.annotation.NonNull; import android.annotation.Nullable; @@ -27,9 +27,9 @@ import android.app.ActivityTaskManager; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ActivityInfo; -import android.content.pm.parsing.ParsingPackage; -import android.content.pm.parsing.ParsingPackageUtils; -import android.content.pm.parsing.ParsingUtils; +import com.android.server.pm.pkg.parsing.ParsingPackage; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; +import com.android.server.pm.pkg.parsing.ParsingUtils; import android.content.pm.parsing.result.ParseInput; import android.content.pm.parsing.result.ParseInput.DeferredError; import android.content.pm.parsing.result.ParseResult; @@ -58,7 +58,9 @@ import java.util.List; import java.util.Objects; import java.util.Set; -/** @hide */ +/** + * @hide + */ public class ParsedActivityUtils { private static final String TAG = ParsingUtils.TAG; @@ -601,7 +603,7 @@ public class ParsedActivityUtils { * AndroidManifest.xml. * @hide */ - static int getActivityConfigChanges(int configChanges, int recreateOnConfigChanges) { + public static int getActivityConfigChanges(int configChanges, int recreateOnConfigChanges) { return configChanges | ((~recreateOnConfigChanges) & RECREATE_ON_CONFIG_CHANGES_MASK); } } diff --git a/core/java/android/content/pm/parsing/component/ParsedApexSystemService.java b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemService.java index c89d3b21b1c1..7690818bf4f1 100644 --- a/core/java/android/content/pm/parsing/component/ParsedApexSystemService.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemService.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; import android.annotation.NonNull; import android.annotation.Nullable; @@ -35,6 +35,4 @@ public interface ParsedApexSystemService extends Parcelable { @Nullable String getMaxSdkVersion(); - int getInitOrder(); - } diff --git a/core/java/android/content/pm/parsing/component/ParsedApexSystemServiceImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java index 65d26b90a228..8c4d8f0e8da4 100644 --- a/core/java/android/content/pm/parsing/component/ParsedApexSystemServiceImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 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 android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; import android.annotation.NonNull; @@ -45,11 +45,10 @@ public class ParsedApexSystemServiceImpl implements ParsedApexSystemService { @Nullable private String maxSdkVersion; - private int initOrder; - public ParsedApexSystemServiceImpl() { } + // Code below generated by codegen v1.0.23. // // DO NOT MODIFY! @@ -68,15 +67,13 @@ public class ParsedApexSystemServiceImpl implements ParsedApexSystemService { @NonNull String name, @Nullable String jarPath, @Nullable String minSdkVersion, - @Nullable String maxSdkVersion, - int initOrder) { + @Nullable String maxSdkVersion) { this.name = name; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, name); this.jarPath = jarPath; this.minSdkVersion = minSdkVersion; this.maxSdkVersion = maxSdkVersion; - this.initOrder = initOrder; // onConstructed(); // You can define this method to get a callback } @@ -102,11 +99,6 @@ public class ParsedApexSystemServiceImpl implements ParsedApexSystemService { } @DataClass.Generated.Member - public int getInitOrder() { - return initOrder; - } - - @DataClass.Generated.Member public @NonNull ParsedApexSystemServiceImpl setName(@NonNull String value) { name = value; com.android.internal.util.AnnotationValidations.validate( @@ -133,12 +125,6 @@ public class ParsedApexSystemServiceImpl implements ParsedApexSystemService { } @DataClass.Generated.Member - public @NonNull ParsedApexSystemServiceImpl setInitOrder( int value) { - initOrder = value; - return this; - } - - @DataClass.Generated.Member static Parcelling<String> sParcellingForName = Parcelling.Cache.get( Parcelling.BuiltIn.ForInternedString.class); @@ -197,7 +183,6 @@ public class ParsedApexSystemServiceImpl implements ParsedApexSystemService { sParcellingForJarPath.parcel(jarPath, dest, flags); sParcellingForMinSdkVersion.parcel(minSdkVersion, dest, flags); sParcellingForMaxSdkVersion.parcel(maxSdkVersion, dest, flags); - dest.writeInt(initOrder); } @Override @@ -216,7 +201,6 @@ public class ParsedApexSystemServiceImpl implements ParsedApexSystemService { String _jarPath = sParcellingForJarPath.unparcel(in); String _minSdkVersion = sParcellingForMinSdkVersion.unparcel(in); String _maxSdkVersion = sParcellingForMaxSdkVersion.unparcel(in); - int _initOrder = in.readInt(); this.name = _name; com.android.internal.util.AnnotationValidations.validate( @@ -224,7 +208,6 @@ public class ParsedApexSystemServiceImpl implements ParsedApexSystemService { this.jarPath = _jarPath; this.minSdkVersion = _minSdkVersion; this.maxSdkVersion = _maxSdkVersion; - this.initOrder = _initOrder; // onConstructed(); // You can define this method to get a callback } @@ -244,10 +227,10 @@ public class ParsedApexSystemServiceImpl implements ParsedApexSystemService { }; @DataClass.Generated( - time = 1641307133386L, + time = 1638903241144L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedApexSystemServiceImpl.java", - inputSignatures = "private @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.NonNull java.lang.String name\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String jarPath\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String minSdkVersion\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String maxSdkVersion\nprivate int initOrder\nclass ParsedApexSystemServiceImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedApexSystemService]\n@com.android.internal.util.DataClass(genGetters=true, genAidl=false, genSetters=true, genParcelable=true)") + inputSignatures = "private @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.NonNull java.lang.String name\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String jarPath\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String minSdkVersion\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String maxSdkVersion\nclass ParsedApexSystemServiceImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedApexSystemService]\n@com.android.internal.util.DataClass(genGetters=true, genAidl=false, genSetters=true, genParcelable=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/content/pm/parsing/component/ParsedApexSystemServiceUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceUtils.java index eca897602d40..38a6f5a356e7 100644 --- a/core/java/android/content/pm/parsing/component/ParsedApexSystemServiceUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 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 android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; import android.R; import android.annotation.NonNull; @@ -53,13 +53,10 @@ public class ParsedApexSystemServiceUtils { R.styleable.AndroidManifestApexSystemService_minSdkVersion); String maxSdkVersion = sa.getString( R.styleable.AndroidManifestApexSystemService_maxSdkVersion); - int initOrder = sa.getInt(R.styleable.AndroidManifestApexSystemService_initOrder, 0); systemService.setName(className) .setMinSdkVersion(minSdkVersion) - .setMaxSdkVersion(maxSdkVersion) - .setInitOrder(initOrder); - + .setMaxSdkVersion(maxSdkVersion); if (!TextUtils.isEmpty(jarPath)) { systemService.setJarPath(jarPath); } diff --git a/core/java/android/content/pm/parsing/component/ParsedAttribution.java b/services/core/java/com/android/server/pm/pkg/component/ParsedAttribution.java index ac7a9284d89b..3b91f289fe4c 100644 --- a/core/java/android/content/pm/parsing/component/ParsedAttribution.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedAttribution.java @@ -14,18 +14,12 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; import android.annotation.NonNull; -import android.annotation.Nullable; import android.annotation.StringRes; -import android.os.Parcel; import android.os.Parcelable; -import android.util.ArraySet; -import com.android.internal.util.DataClass; - -import java.util.ArrayList; import java.util.List; /** diff --git a/core/java/android/content/pm/parsing/component/ParsedAttributionImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionImpl.java index 510425fd8584..a4eb4f19c8b5 100644 --- a/core/java/android/content/pm/parsing/component/ParsedAttributionImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionImpl.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,14 +14,12 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; import android.annotation.NonNull; -import android.annotation.Nullable; import android.annotation.StringRes; import android.os.Parcel; import android.os.Parcelable; -import android.util.ArraySet; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.DataClass; diff --git a/core/java/android/content/pm/parsing/component/ParsedAttributionUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionUtils.java index 84f1d44b2a84..98e94c5214f0 100644 --- a/core/java/android/content/pm/parsing/component/ParsedAttributionUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionUtils.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 android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/core/java/android/content/pm/parsing/component/ParsedComponent.java b/services/core/java/com/android/server/pm/pkg/component/ParsedComponent.java index c1372f6234ad..1a8230dcfb86 100644 --- a/core/java/android/content/pm/parsing/component/ParsedComponent.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedComponent.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/core/java/android/content/pm/parsing/component/ParsedComponentImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedComponentImpl.java index 1c46a107d118..9125e8c0f3f2 100644 --- a/core/java/android/content/pm/parsing/component/ParsedComponentImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedComponentImpl.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,9 +14,9 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; -import static android.content.pm.parsing.ParsingPackageImpl.sForInternedString; +import static com.android.server.pm.pkg.parsing.ParsingPackageImpl.sForInternedString; import static java.util.Collections.emptyMap; @@ -25,7 +25,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; import android.content.pm.PackageManager.Property; -import android.content.pm.parsing.ParsingUtils; +import com.android.server.pm.pkg.parsing.ParsingUtils; import android.os.Bundle; import android.os.Parcel; import android.text.TextUtils; diff --git a/core/java/android/content/pm/parsing/component/ParsedComponentUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedComponentUtils.java index 5c33cfd5192d..e208854f2637 100644 --- a/core/java/android/content/pm/parsing/component/ParsedComponentUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedComponentUtils.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,16 +14,16 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; -import static android.content.pm.parsing.ParsingUtils.NOT_SET; +import static com.android.server.pm.pkg.parsing.ParsingUtils.NOT_SET; import android.annotation.NonNull; import android.content.pm.PackageManager; import android.content.pm.PackageManager.Property; -import android.content.pm.parsing.ParsingPackage; -import android.content.pm.parsing.ParsingPackageUtils; -import android.content.pm.parsing.ParsingUtils; +import com.android.server.pm.pkg.parsing.ParsingPackage; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; +import com.android.server.pm.pkg.parsing.ParsingUtils; import android.content.pm.parsing.result.ParseInput; import android.content.pm.parsing.result.ParseResult; import android.content.res.Resources; diff --git a/core/java/android/content/pm/parsing/component/ParsedInstrumentation.java b/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentation.java index e8fcc00a05bc..a0eae8c4c8ff 100644 --- a/core/java/android/content/pm/parsing/component/ParsedInstrumentation.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentation.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 android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; import android.annotation.Nullable; diff --git a/core/java/android/content/pm/parsing/component/ParsedInstrumentationImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationImpl.java index d2b536893012..c8baa9e1b504 100644 --- a/core/java/android/content/pm/parsing/component/ParsedInstrumentationImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationImpl.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,9 +14,9 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; -import static android.content.pm.parsing.ParsingPackageImpl.sForInternedString; +import static com.android.server.pm.pkg.parsing.ParsingPackageImpl.sForInternedString; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/core/java/android/content/pm/parsing/component/ParsedInstrumentationUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationUtils.java index df5e73e26f17..51e14280ffe4 100644 --- a/core/java/android/content/pm/parsing/component/ParsedInstrumentationUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationUtils.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,12 +14,12 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; -import static android.content.pm.parsing.ParsingUtils.NOT_SET; +import static com.android.server.pm.pkg.parsing.ParsingUtils.NOT_SET; import android.annotation.NonNull; -import android.content.pm.parsing.ParsingPackage; +import com.android.server.pm.pkg.parsing.ParsingPackage; import android.content.pm.parsing.result.ParseInput; import android.content.pm.parsing.result.ParseResult; import android.content.res.Resources; diff --git a/core/java/android/content/pm/parsing/component/ParsedIntentInfo.java b/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfo.java index 1e36cccae45f..57b486abdfd7 100644 --- a/core/java/android/content/pm/parsing/component/ParsedIntentInfo.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfo.java @@ -14,20 +14,12 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.IntentFilter; -import android.os.Parcel; import android.os.Parcelable; -import android.util.Pair; - -import com.android.internal.util.DataClass; -import com.android.internal.util.Parcelling; - -import java.util.ArrayList; -import java.util.List; /** @hide **/ public interface ParsedIntentInfo extends Parcelable { diff --git a/core/java/android/content/pm/parsing/component/ParsedIntentInfoImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoImpl.java index 9ff7a167c093..1c816da34019 100644 --- a/core/java/android/content/pm/parsing/component/ParsedIntentInfoImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoImpl.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,23 +14,20 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.IntentFilter; import android.os.Parcel; import android.os.Parcelable; -import android.util.Pair; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.DataClass; -import com.android.internal.util.Parcelling; -import java.util.ArrayList; -import java.util.List; - -/** @hide **/ +/** + * @hide + **/ @DataClass(genGetters = true, genSetters = true, genParcelable = true, genAidl = false, genBuilder = false, genConstructor = false) @DataClass.Suppress({"setIntentFilter"}) diff --git a/core/java/android/content/pm/parsing/component/ParsedIntentInfoUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoUtils.java index cb72c2b11189..1e6f6306258c 100644 --- a/core/java/android/content/pm/parsing/component/ParsedIntentInfoUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoUtils.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,16 +14,16 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; -import static android.content.pm.parsing.ParsingUtils.ANDROID_RES_NAMESPACE; +import static com.android.server.pm.pkg.parsing.ParsingUtils.ANDROID_RES_NAMESPACE; import android.annotation.NonNull; import android.content.Intent; import android.content.IntentFilter; -import android.content.pm.parsing.ParsingPackage; -import android.content.pm.parsing.ParsingPackageUtils; -import android.content.pm.parsing.ParsingUtils; +import com.android.server.pm.pkg.parsing.ParsingPackage; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; +import com.android.server.pm.pkg.parsing.ParsingUtils; import android.content.pm.parsing.result.ParseInput; import android.content.pm.parsing.result.ParseResult; import android.content.res.Resources; diff --git a/core/java/android/content/pm/parsing/component/ParsedMainComponent.java b/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponent.java index 2507205df257..8c1d6c8ebaf8 100644 --- a/core/java/android/content/pm/parsing/component/ParsedMainComponent.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponent.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 android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; import android.annotation.Nullable; diff --git a/core/java/android/content/pm/parsing/component/ParsedMainComponentImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentImpl.java index 6051435bf8f9..9b57f4830671 100644 --- a/core/java/android/content/pm/parsing/component/ParsedMainComponentImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentImpl.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,9 +14,9 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; -import static android.content.pm.parsing.ParsingPackageImpl.sForInternedString; +import static com.android.server.pm.pkg.parsing.ParsingPackageImpl.sForInternedString; import android.annotation.Nullable; import android.os.Parcel; diff --git a/core/java/android/content/pm/parsing/component/ParsedMainComponentUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentUtils.java index 87f75b06f9fd..2a3e6534c1e2 100644 --- a/core/java/android/content/pm/parsing/component/ParsedMainComponentUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentUtils.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,15 +14,15 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; -import static android.content.pm.parsing.ParsingUtils.NOT_SET; +import static com.android.server.pm.pkg.parsing.ParsingUtils.NOT_SET; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.IntentFilter; -import android.content.pm.parsing.ParsingPackage; -import android.content.pm.parsing.ParsingUtils; +import com.android.server.pm.pkg.parsing.ParsingPackage; +import com.android.server.pm.pkg.parsing.ParsingUtils; import android.content.pm.parsing.result.ParseInput; import android.content.pm.parsing.result.ParseResult; import android.content.res.Configuration; diff --git a/core/java/android/content/pm/parsing/component/ParsedPermission.java b/services/core/java/com/android/server/pm/pkg/component/ParsedPermission.java index 6acdb6e872ae..4a6d2c3ac80d 100644 --- a/core/java/android/content/pm/parsing/component/ParsedPermission.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedPermission.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 android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; import android.annotation.Nullable; diff --git a/core/java/android/content/pm/parsing/component/ParsedPermissionGroup.java b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroup.java index 22aa0854f2a3..73b5ffaa298d 100644 --- a/core/java/android/content/pm/parsing/component/ParsedPermissionGroup.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroup.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 android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; /** @hide */ public interface ParsedPermissionGroup extends ParsedComponent { diff --git a/core/java/android/content/pm/parsing/component/ParsedPermissionGroupImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.java index 1fa04cfd065c..f47fb75e9d92 100644 --- a/core/java/android/content/pm/parsing/component/ParsedPermissionGroupImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.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 android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; import android.os.Parcel; diff --git a/core/java/android/content/pm/parsing/component/ParsedPermissionImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionImpl.java index 2145e441553a..98007ff94972 100644 --- a/core/java/android/content/pm/parsing/component/ParsedPermissionImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionImpl.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 android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; import android.annotation.NonNull; import android.annotation.Nullable; @@ -109,14 +109,13 @@ public class ParsedPermissionImpl extends ParsedComponentImpl implements ParsedP protected ParsedPermissionImpl(Parcel in) { super(in); - // We use the boot classloader for all classes that we load. - final ClassLoader boot = Object.class.getClassLoader(); this.backgroundPermission = in.readString(); this.group = TextUtils.safeIntern(in.readString()); this.requestRes = in.readInt(); this.protectionLevel = in.readInt(); this.tree = in.readBoolean(); - this.parsedPermissionGroup = in.readParcelable(boot); + this.parsedPermissionGroup = in.readParcelable(ParsedPermissionGroup.class.getClassLoader(), + ParsedPermissionGroup.class); this.knownCerts = sForStringSet.unparcel(in); } diff --git a/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java index 86c8f02f9fd9..8562fdfe2571 100644 --- a/core/java/android/content/pm/parsing/component/ParsedPermissionUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.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,14 +14,14 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; -import static android.content.pm.parsing.ParsingUtils.NOT_SET; +import static com.android.server.pm.pkg.parsing.ParsingUtils.NOT_SET; import android.annotation.NonNull; import android.content.pm.PermissionInfo; -import android.content.pm.parsing.ParsingPackage; -import android.content.pm.parsing.ParsingUtils; +import com.android.server.pm.pkg.parsing.ParsingPackage; +import com.android.server.pm.pkg.parsing.ParsingUtils; import android.content.pm.parsing.result.ParseInput; import android.content.pm.parsing.result.ParseResult; import android.content.res.Resources; diff --git a/core/java/android/content/pm/parsing/component/ParsedProcess.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProcess.java index 27a540d25891..ff391ff97fcc 100644 --- a/core/java/android/content/pm/parsing/component/ParsedProcess.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProcess.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; import android.annotation.NonNull; import android.content.pm.ApplicationInfo; diff --git a/core/java/android/content/pm/parsing/component/ParsedProcessImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProcessImpl.java index d404ecfd38c7..96560c7d66ae 100644 --- a/core/java/android/content/pm/parsing/component/ParsedProcessImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProcessImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 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 android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; import static java.util.Collections.emptySet; diff --git a/core/java/android/content/pm/parsing/component/ParsedProcessUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProcessUtils.java index 5e4cf661b194..d03f15338f7e 100644 --- a/core/java/android/content/pm/parsing/component/ParsedProcessUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProcessUtils.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,12 +14,12 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; import android.annotation.NonNull; import android.content.pm.ApplicationInfo; -import android.content.pm.parsing.ParsingPackage; -import android.content.pm.parsing.ParsingUtils; +import com.android.server.pm.pkg.parsing.ParsingPackage; +import com.android.server.pm.pkg.parsing.ParsingUtils; import android.content.pm.parsing.result.ParseInput; import android.content.pm.parsing.result.ParseResult; import android.content.res.Resources; diff --git a/core/java/android/content/pm/parsing/component/ParsedProvider.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProvider.java index 1211ce274aea..8cc6fc745efb 100644 --- a/core/java/android/content/pm/parsing/component/ParsedProvider.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProvider.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 android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; import android.annotation.Nullable; import android.content.pm.PathPermission; diff --git a/core/java/android/content/pm/parsing/component/ParsedProviderImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java index 774c3fca1cf0..e04fc868e350 100644 --- a/core/java/android/content/pm/parsing/component/ParsedProviderImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.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,9 +14,9 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; -import static android.content.pm.parsing.ParsingPackageImpl.sForInternedString; +import static com.android.server.pm.pkg.parsing.ParsingPackageImpl.sForInternedString; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/core/java/android/content/pm/parsing/component/ParsedProviderUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProviderUtils.java index de9dd44b3b8f..9d3129be4e78 100644 --- a/core/java/android/content/pm/parsing/component/ParsedProviderUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProviderUtils.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,18 +14,18 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; -import static android.content.pm.parsing.ParsingPackageUtils.RIGID_PARSER; -import static android.content.pm.parsing.component.ComponentParseUtils.flag; +import static com.android.server.pm.pkg.parsing.ParsingPackageUtils.RIGID_PARSER; +import static com.android.server.pm.pkg.component.ComponentParseUtils.flag; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.IntentFilter; import android.content.pm.PathPermission; import android.content.pm.ProviderInfo; -import android.content.pm.parsing.ParsingPackage; -import android.content.pm.parsing.ParsingUtils; +import com.android.server.pm.pkg.parsing.ParsingPackage; +import com.android.server.pm.pkg.parsing.ParsingUtils; import android.content.pm.parsing.result.ParseInput; import android.content.pm.parsing.result.ParseResult; import android.content.res.Resources; diff --git a/core/java/android/content/pm/parsing/component/ParsedService.java b/services/core/java/com/android/server/pm/pkg/component/ParsedService.java index 6736afaa8714..11696be89367 100644 --- a/core/java/android/content/pm/parsing/component/ParsedService.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedService.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 android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; import android.annotation.Nullable; diff --git a/core/java/android/content/pm/parsing/component/ParsedServiceImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedServiceImpl.java index a85fb5cfcf46..0171c49f1bef 100644 --- a/core/java/android/content/pm/parsing/component/ParsedServiceImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedServiceImpl.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,9 +14,9 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; -import static android.content.pm.parsing.ParsingPackageImpl.sForInternedString; +import static com.android.server.pm.pkg.parsing.ParsingPackageImpl.sForInternedString; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/core/java/android/content/pm/parsing/component/ParsedServiceUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java index d27a0ed81a0a..6fe9411a7fb9 100644 --- a/core/java/android/content/pm/parsing/component/ParsedServiceUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.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,17 +14,17 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; -import static android.content.pm.parsing.component.ComponentParseUtils.flag; +import static com.android.server.pm.pkg.component.ComponentParseUtils.flag; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.ServiceInfo; -import android.content.pm.parsing.ParsingPackage; -import android.content.pm.parsing.ParsingUtils; +import com.android.server.pm.pkg.parsing.ParsingPackage; +import com.android.server.pm.pkg.parsing.ParsingUtils; import android.content.pm.parsing.result.ParseInput; import android.content.pm.parsing.result.ParseInput.DeferredError; import android.content.pm.parsing.result.ParseResult; diff --git a/core/java/android/content/pm/parsing/component/ParsedUsesPermission.java b/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermission.java index e2f5f14ab725..8e3401ed29bd 100644 --- a/core/java/android/content/pm/parsing/component/ParsedUsesPermission.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermission.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; import android.annotation.IntDef; import android.annotation.NonNull; diff --git a/core/java/android/content/pm/parsing/component/ParsedUsesPermissionImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermissionImpl.java index d3c7afbb2012..70d6f24aa81b 100644 --- a/core/java/android/content/pm/parsing/component/ParsedUsesPermissionImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermissionImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 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 android.content.pm.parsing.component; +package com.android.server.pm.pkg.component; import android.annotation.NonNull; import android.os.Parcel; diff --git a/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/PackageInfoWithoutStateUtils.java index 28290d75cdfa..2d6c616d7f76 100644 --- a/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/PackageInfoWithoutStateUtils.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 android.content.pm.parsing; +package com.android.server.pm.pkg.parsing; import android.annotation.CheckResult; import android.annotation.NonNull; @@ -35,29 +35,29 @@ import android.content.pm.PackageManager; import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; import android.content.pm.ProviderInfo; -import android.content.pm.SELinuxUtil; import android.content.pm.ServiceInfo; import android.content.pm.Signature; import android.content.pm.SigningDetails; import android.content.pm.SigningInfo; import android.content.pm.overlay.OverlayPaths; -import android.content.pm.parsing.component.ComponentParseUtils; -import android.content.pm.parsing.component.ParsedActivity; -import android.content.pm.parsing.component.ParsedAttribution; -import android.content.pm.parsing.component.ParsedComponent; -import android.content.pm.parsing.component.ParsedInstrumentation; -import android.content.pm.parsing.component.ParsedMainComponent; -import android.content.pm.parsing.component.ParsedPermission; -import android.content.pm.parsing.component.ParsedPermissionGroup; -import android.content.pm.parsing.component.ParsedProvider; -import android.content.pm.parsing.component.ParsedService; -import android.content.pm.parsing.component.ParsedUsesPermission; -import android.content.pm.pkg.FrameworkPackageUserState; -import android.content.pm.pkg.PackageUserStateUtils; import android.os.Environment; import android.os.UserHandle; import com.android.internal.util.ArrayUtils; +import com.android.server.pm.pkg.PackageUserState; +import com.android.server.pm.pkg.PackageUserStateUtils; +import com.android.server.pm.pkg.SELinuxUtil; +import com.android.server.pm.pkg.component.ComponentParseUtils; +import com.android.server.pm.pkg.component.ParsedActivity; +import com.android.server.pm.pkg.component.ParsedAttribution; +import com.android.server.pm.pkg.component.ParsedComponent; +import com.android.server.pm.pkg.component.ParsedInstrumentation; +import com.android.server.pm.pkg.component.ParsedMainComponent; +import com.android.server.pm.pkg.component.ParsedPermission; +import com.android.server.pm.pkg.component.ParsedPermissionGroup; +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 libcore.util.EmptyArray; @@ -77,7 +77,7 @@ public class PackageInfoWithoutStateUtils { @Nullable public static PackageInfo generate(ParsingPackageRead pkg, int[] gids, @PackageManager.PackageInfoFlagsBits long flags, long firstInstallTime, - long lastUpdateTime, Set<String> grantedPermissions, FrameworkPackageUserState state, + long lastUpdateTime, Set<String> grantedPermissions, PackageUserState state, int userId) { return generateWithComponents(pkg, gids, flags, firstInstallTime, lastUpdateTime, grantedPermissions, state, userId, null); @@ -86,13 +86,13 @@ public class PackageInfoWithoutStateUtils { @Nullable public static PackageInfo generate(ParsingPackageRead pkg, ApexInfo apexInfo, int flags) { return generateWithComponents(pkg, EmptyArray.INT, flags, 0, 0, Collections.emptySet(), - FrameworkPackageUserState.DEFAULT, UserHandle.getCallingUserId(), apexInfo); + PackageUserState.DEFAULT, UserHandle.getCallingUserId(), apexInfo); } @Nullable private static PackageInfo generateWithComponents(ParsingPackageRead pkg, int[] gids, @PackageManager.PackageInfoFlagsBits long flags, long firstInstallTime, - long lastUpdateTime, Set<String> grantedPermissions, FrameworkPackageUserState state, + long lastUpdateTime, Set<String> grantedPermissions, PackageUserState state, int userId, @Nullable ApexInfo apexInfo) { ApplicationInfo applicationInfo = generateApplicationInfo(pkg, flags, state, userId); if (applicationInfo == null) { @@ -192,7 +192,7 @@ public class PackageInfoWithoutStateUtils { @Nullable public static PackageInfo generateWithoutComponents(ParsingPackageRead pkg, int[] gids, @PackageManager.PackageInfoFlagsBits long flags, long firstInstallTime, - long lastUpdateTime, Set<String> grantedPermissions, FrameworkPackageUserState state, + long lastUpdateTime, Set<String> grantedPermissions, PackageUserState state, int userId, @Nullable ApexInfo apexInfo, @NonNull ApplicationInfo applicationInfo) { if (!checkUseInstalled(pkg, state, flags)) { return null; @@ -207,12 +207,12 @@ public class PackageInfoWithoutStateUtils { * server. * <p> * Prefer {@link #generateWithoutComponents(ParsingPackageRead, int[], int, long, long, Set, - * FrameworkPackageUserState, int, ApexInfo, ApplicationInfo)}. + * PackageUserState, int, ApexInfo, ApplicationInfo)}. */ @NonNull public static PackageInfo generateWithoutComponentsUnchecked(ParsingPackageRead pkg, int[] gids, @PackageManager.PackageInfoFlagsBits long flags, long firstInstallTime, - long lastUpdateTime, Set<String> grantedPermissions, FrameworkPackageUserState state, + long lastUpdateTime, Set<String> grantedPermissions, PackageUserState state, int userId, @Nullable ApexInfo apexInfo, @NonNull ApplicationInfo applicationInfo) { PackageInfo pi = new PackageInfo(); pi.packageName = pkg.getPackageName(); @@ -366,7 +366,7 @@ public class PackageInfoWithoutStateUtils { @Nullable public static ApplicationInfo generateApplicationInfo(ParsingPackageRead pkg, - @PackageManager.ApplicationInfoFlagsBits long flags, FrameworkPackageUserState state, + @PackageManager.ApplicationInfoFlagsBits long flags, PackageUserState state, int userId) { if (pkg == null) { return null; @@ -384,7 +384,7 @@ public class PackageInfoWithoutStateUtils { * This bypasses critical checks that are necessary for usage with data passed outside of system * server. * <p> - * Prefer {@link #generateApplicationInfo(ParsingPackageRead, int, FrameworkPackageUserState, int)}. + * Prefer {@link #generateApplicationInfo(ParsingPackageRead, int, PackageUserState, int)}. * * @param assignUserFields whether to fill the returned {@link ApplicationInfo} with user * specific fields. This can be skipped when building from a system @@ -395,7 +395,7 @@ public class PackageInfoWithoutStateUtils { @NonNull public static ApplicationInfo generateApplicationInfoUnchecked(@NonNull ParsingPackageRead pkg, @PackageManager.ApplicationInfoFlagsBits long flags, - @NonNull FrameworkPackageUserState state, int userId, boolean assignUserFields) { + @NonNull PackageUserState state, int userId, boolean assignUserFields) { // Make shallow copy so we can store the metadata/libraries safely ApplicationInfo ai = ((ParsingPackageHidden) pkg).toAppInfoWithoutState(); @@ -409,7 +409,7 @@ public class PackageInfoWithoutStateUtils { } private static void updateApplicationInfo(ApplicationInfo ai, long flags, - FrameworkPackageUserState state) { + PackageUserState state) { if ((flags & PackageManager.GET_META_DATA) == 0) { ai.metaData = null; } @@ -455,7 +455,7 @@ public class PackageInfoWithoutStateUtils { @Nullable public static ApplicationInfo generateDelegateApplicationInfo(@Nullable ApplicationInfo ai, @PackageManager.ApplicationInfoFlagsBits long flags, - @NonNull FrameworkPackageUserState state, int userId) { + @NonNull PackageUserState state, int userId) { if (ai == null || !checkUseInstalledOrHidden(flags, state, ai)) { return null; } @@ -472,7 +472,7 @@ public class PackageInfoWithoutStateUtils { @Nullable public static ActivityInfo generateDelegateActivityInfo(@Nullable ActivityInfo a, @PackageManager.ComponentInfoFlagsBits long flags, - @NonNull FrameworkPackageUserState state, int userId) { + @NonNull PackageUserState state, int userId) { if (a == null || !checkUseInstalledOrHidden(flags, state, a.applicationInfo)) { return null; } @@ -486,7 +486,7 @@ public class PackageInfoWithoutStateUtils { @Nullable public static ActivityInfo generateActivityInfo(ParsingPackageRead pkg, ParsedActivity a, - @PackageManager.ComponentInfoFlagsBits long flags, FrameworkPackageUserState state, + @PackageManager.ComponentInfoFlagsBits long flags, PackageUserState state, @Nullable ApplicationInfo applicationInfo, int userId) { if (a == null) return null; if (!checkUseInstalled(pkg, state, flags)) { @@ -507,7 +507,7 @@ public class PackageInfoWithoutStateUtils { * server. * <p> * Prefer {@link #generateActivityInfo(ParsingPackageRead, ParsedActivity, long, - * FrameworkPackageUserState, ApplicationInfo, int)}. + * PackageUserState, ApplicationInfo, int)}. */ @NonNull public static ActivityInfo generateActivityInfoUnchecked(@NonNull ParsedActivity a, @@ -552,14 +552,14 @@ public class PackageInfoWithoutStateUtils { @Nullable public static ActivityInfo generateActivityInfo(ParsingPackageRead pkg, ParsedActivity a, - @PackageManager.ComponentInfoFlagsBits long flags, FrameworkPackageUserState state, + @PackageManager.ComponentInfoFlagsBits long flags, PackageUserState state, int userId) { return generateActivityInfo(pkg, a, flags, state, null, userId); } @Nullable public static ServiceInfo generateServiceInfo(ParsingPackageRead pkg, ParsedService s, - @PackageManager.ComponentInfoFlagsBits long flags, FrameworkPackageUserState state, + @PackageManager.ComponentInfoFlagsBits long flags, PackageUserState state, @Nullable ApplicationInfo applicationInfo, int userId) { if (s == null) return null; if (!checkUseInstalled(pkg, state, flags)) { @@ -579,8 +579,8 @@ public class PackageInfoWithoutStateUtils { * This bypasses critical checks that are necessary for usage with data passed outside of system * server. * <p> - * Prefer {@link #generateServiceInfo(ParsingPackageRead, ParsedService, long, - * FrameworkPackageUserState, ApplicationInfo, int)}. + * Prefer {@link #generateServiceInfo(ParsingPackageRead, ParsedService, long, PackageUserState, + * ApplicationInfo, int)}. */ @NonNull public static ServiceInfo generateServiceInfoUnchecked(@NonNull ParsedService s, @@ -603,14 +603,14 @@ public class PackageInfoWithoutStateUtils { @Nullable public static ServiceInfo generateServiceInfo(ParsingPackageRead pkg, ParsedService s, - @PackageManager.ComponentInfoFlagsBits long flags, FrameworkPackageUserState state, + @PackageManager.ComponentInfoFlagsBits long flags, PackageUserState state, int userId) { return generateServiceInfo(pkg, s, flags, state, null, userId); } @Nullable public static ProviderInfo generateProviderInfo(ParsingPackageRead pkg, ParsedProvider p, - @PackageManager.ComponentInfoFlagsBits long flags, FrameworkPackageUserState state, + @PackageManager.ComponentInfoFlagsBits long flags, PackageUserState state, @Nullable ApplicationInfo applicationInfo, int userId) { if (p == null) return null; if (!checkUseInstalled(pkg, state, flags)) { @@ -631,7 +631,7 @@ public class PackageInfoWithoutStateUtils { * server. * <p> * Prefer {@link #generateProviderInfo(ParsingPackageRead, ParsedProvider, long, - * FrameworkPackageUserState, ApplicationInfo, int)}. + * PackageUserState, ApplicationInfo, int)}. */ @NonNull public static ProviderInfo generateProviderInfoUnchecked(@NonNull ParsedProvider p, @@ -665,14 +665,14 @@ public class PackageInfoWithoutStateUtils { @Nullable public static ProviderInfo generateProviderInfo(ParsingPackageRead pkg, ParsedProvider p, - @PackageManager.ComponentInfoFlagsBits long flags, FrameworkPackageUserState state, + @PackageManager.ComponentInfoFlagsBits long flags, PackageUserState state, int userId) { return generateProviderInfo(pkg, p, flags, state, null, userId); } /** * @param assignUserFields see {@link #generateApplicationInfoUnchecked(ParsingPackageRead, - * long, FrameworkPackageUserState, int, boolean)} + * long, PackageUserState, int, boolean)} */ @Nullable public static InstrumentationInfo generateInstrumentationInfo(ParsedInstrumentation i, @@ -759,7 +759,7 @@ public class PackageInfoWithoutStateUtils { } private static boolean checkUseInstalledOrHidden(long flags, - @NonNull FrameworkPackageUserState state, @Nullable ApplicationInfo appInfo) { + @NonNull PackageUserState state, @Nullable ApplicationInfo appInfo) { // Returns false if the package is hidden system app until installed. if ((flags & PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS) == 0 && !state.isInstalled() @@ -888,7 +888,7 @@ public class PackageInfoWithoutStateUtils { } private static boolean checkUseInstalled(ParsingPackageRead pkg, - FrameworkPackageUserState state, @PackageManager.PackageInfoFlagsBits long flags) { + PackageUserState state, @PackageManager.PackageInfoFlagsBits long flags) { // If available for the target user return PackageUserStateUtils.isAvailable(state, flags); } diff --git a/core/java/android/content/pm/parsing/ParsingPackage.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java index fc9f1a55d84c..52d9b7a3abc1 100644 --- a/core/java/android/content/pm/parsing/ParsingPackage.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.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 android.content.pm.parsing; +package com.android.server.pm.pkg.parsing; import android.annotation.CallSuper; import android.annotation.NonNull; @@ -26,21 +26,22 @@ import android.content.pm.FeatureGroupInfo; import android.content.pm.FeatureInfo; import android.content.pm.PackageManager.Property; import android.content.pm.SigningDetails; -import android.content.pm.parsing.component.ParsedActivity; -import android.content.pm.parsing.component.ParsedApexSystemService; -import android.content.pm.parsing.component.ParsedAttribution; -import android.content.pm.parsing.component.ParsedInstrumentation; -import android.content.pm.parsing.component.ParsedIntentInfo; -import android.content.pm.parsing.component.ParsedPermission; -import android.content.pm.parsing.component.ParsedPermissionGroup; -import android.content.pm.parsing.component.ParsedProcess; -import android.content.pm.parsing.component.ParsedProvider; -import android.content.pm.parsing.component.ParsedService; -import android.content.pm.parsing.component.ParsedUsesPermission; 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; +import com.android.server.pm.pkg.component.ParsedInstrumentation; +import com.android.server.pm.pkg.component.ParsedIntentInfo; +import com.android.server.pm.pkg.component.ParsedPermission; +import com.android.server.pm.pkg.component.ParsedPermissionGroup; +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 java.security.PublicKey; import java.util.Map; import java.util.Set; @@ -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); @@ -375,6 +379,8 @@ public interface ParsingPackage extends ParsingPackageRead { ParsingPackage setResetEnabledSettingsOnAppDataCleared( boolean resetEnabledSettingsOnAppDataCleared); + ParsingPackage setLocaleConfigRes(int localeConfigRes); + // TODO(b/135203078): This class no longer has access to ParsedPackage, find a replacement // for moving to the next step @CallSuper diff --git a/core/java/android/content/pm/parsing/ParsingPackageHidden.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageHidden.java index c49d11e738a7..66e01a6bfb09 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageHidden.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageHidden.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 android.content.pm.parsing; +package com.android.server.pm.pkg.parsing; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; diff --git a/core/java/android/content/pm/parsing/ParsingPackageImpl.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java index ddab207437c2..1f21938fc706 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageImpl.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.content.pm.parsing; +package com.android.server.pm.pkg.parsing; import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; @@ -33,28 +33,6 @@ import android.content.pm.FeatureInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager.Property; import android.content.pm.SigningDetails; -import android.content.pm.parsing.component.ParsedActivity; -import android.content.pm.parsing.component.ParsedActivityImpl; -import android.content.pm.parsing.component.ParsedApexSystemService; -import android.content.pm.parsing.component.ParsedApexSystemServiceImpl; -import android.content.pm.parsing.component.ParsedAttribution; -import android.content.pm.parsing.component.ParsedAttributionImpl; -import android.content.pm.parsing.component.ParsedComponent; -import android.content.pm.parsing.component.ParsedInstrumentation; -import android.content.pm.parsing.component.ParsedInstrumentationImpl; -import android.content.pm.parsing.component.ParsedIntentInfo; -import android.content.pm.parsing.component.ParsedMainComponent; -import android.content.pm.parsing.component.ParsedPermission; -import android.content.pm.parsing.component.ParsedPermissionGroup; -import android.content.pm.parsing.component.ParsedPermissionGroupImpl; -import android.content.pm.parsing.component.ParsedPermissionImpl; -import android.content.pm.parsing.component.ParsedProcess; -import android.content.pm.parsing.component.ParsedProvider; -import android.content.pm.parsing.component.ParsedProviderImpl; -import android.content.pm.parsing.component.ParsedService; -import android.content.pm.parsing.component.ParsedServiceImpl; -import android.content.pm.parsing.component.ParsedUsesPermission; -import android.content.pm.parsing.component.ParsedUsesPermissionImpl; import android.content.res.TypedArray; import android.os.Build; import android.os.Bundle; @@ -81,6 +59,33 @@ import com.android.internal.util.Parcelling.BuiltIn.ForInternedStringList; import com.android.internal.util.Parcelling.BuiltIn.ForInternedStringSet; import com.android.internal.util.Parcelling.BuiltIn.ForInternedStringValueMap; import com.android.internal.util.Parcelling.BuiltIn.ForStringSet; +import com.android.server.pm.pkg.component.ParsedActivity; +import com.android.server.pm.pkg.component.ParsedActivityImpl; +import com.android.server.pm.pkg.component.ParsedApexSystemService; +import com.android.server.pm.pkg.component.ParsedApexSystemServiceImpl; +import com.android.server.pm.pkg.component.ParsedAttribution; +import com.android.server.pm.pkg.component.ParsedAttributionImpl; +import com.android.server.pm.pkg.component.ParsedComponent; +import com.android.server.pm.pkg.component.ParsedInstrumentation; +import com.android.server.pm.pkg.component.ParsedInstrumentationImpl; +import com.android.server.pm.pkg.component.ParsedIntentInfo; +import com.android.server.pm.pkg.component.ParsedMainComponent; +import com.android.server.pm.pkg.component.ParsedPermission; +import com.android.server.pm.pkg.component.ParsedPermissionGroup; +import com.android.server.pm.pkg.component.ParsedPermissionGroupImpl; +import com.android.server.pm.pkg.component.ParsedPermissionImpl; +import com.android.server.pm.pkg.component.ParsedProcess; +import com.android.server.pm.pkg.component.ParsedProvider; +import com.android.server.pm.pkg.component.ParsedProviderImpl; +import com.android.server.pm.pkg.component.ParsedService; +import com.android.server.pm.pkg.component.ParsedServiceImpl; +import com.android.server.pm.pkg.component.ParsedUsesPermission; +import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl; +import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils; +import com.android.server.pm.pkg.parsing.ParsingPackage; +import com.android.server.pm.pkg.parsing.ParsingPackageHidden; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; +import com.android.server.pm.pkg.parsing.ParsingUtils; import java.security.PublicKey; import java.util.Collections; @@ -487,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; @@ -539,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) { @@ -559,6 +569,8 @@ public class ParsingPackageImpl implements ParsingPackage, ParsingPackageHidden, private UUID mStorageUuid; private long mLongVersionCode; + private int mLocaleConfigRes; + @VisibleForTesting public ParsingPackageImpl(@NonNull String packageName, @NonNull String baseApkPath, @NonNull String path, @Nullable TypedArray manifestArray) { @@ -1136,6 +1148,7 @@ public class ParsingPackageImpl implements ParsingPackage, ParsingPackageHidden, appInfo.setSplitResourcePaths(splitCodePaths); appInfo.setVersionCode(mLongVersionCode); appInfo.setAppClassNamesByProcess(buildAppClassNamesByProcess()); + appInfo.setLocaleConfigRes(mLocaleConfigRes); return appInfo; } @@ -1314,6 +1327,7 @@ public class ParsingPackageImpl implements ParsingPackage, ParsingPackageHidden, dest.writeInt(this.memtagMode); dest.writeInt(this.nativeHeapZeroInitialized); sForBoolean.parcel(this.requestRawExternalStorageAccess, dest, flags); + dest.writeInt(this.mLocaleConfigRes); } public ParsingPackageImpl(Parcel in) { @@ -1405,7 +1419,7 @@ public class ParsingPackageImpl implements ParsingPackage, ParsingPackageHidden, this.instrumentations = ParsingUtils.createTypedInterfaceList(in, ParsedInstrumentationImpl.CREATOR); this.preferredActivityFilters = sForIntentInfoPairs.unparcel(in); - this.processes = in.readHashMap(boot); + this.processes = in.readHashMap(ParsedProcess.class.getClassLoader()); this.metaData = in.readBundle(boot); this.volumeUuid = sForInternedString.unparcel(in); this.signingDetails = in.readParcelable(boot); @@ -1461,6 +1475,7 @@ public class ParsingPackageImpl implements ParsingPackage, ParsingPackageHidden, this.memtagMode = in.readInt(); this.nativeHeapZeroInitialized = in.readInt(); this.requestRawExternalStorageAccess = sForBoolean.unparcel(in); + this.mLocaleConfigRes = in.readInt(); assignDerivedFields(); } @@ -2279,6 +2294,11 @@ public class ParsingPackageImpl implements ParsingPackage, ParsingPackageHidden, return nativeHeapZeroInitialized; } + @Override + public int getLocaleConfigRes() { + return mLocaleConfigRes; + } + @Nullable @Override public Boolean hasRequestRawExternalStorageAccess() { @@ -2356,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; @@ -2499,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; @@ -2936,4 +2966,10 @@ public class ParsingPackageImpl implements ParsingPackage, ParsingPackageHidden, resetEnabledSettingsOnAppDataCleared); return this; } + + @Override + public ParsingPackageImpl setLocaleConfigRes(int value) { + mLocaleConfigRes = value; + return this; + } } diff --git a/core/java/android/content/pm/parsing/ParsingPackageInternal.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageInternal.java index ca16fa2d97eb..5457785a8519 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageInternal.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageInternal.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 android.content.pm.parsing; +package com.android.server.pm.pkg.parsing; import android.annotation.Nullable; import android.content.pm.PackageInfo; diff --git a/core/java/android/content/pm/parsing/ParsingPackageRead.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageRead.java index c8113efcc7c1..4b659a14418f 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageRead.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageRead.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,26 +14,26 @@ * limitations under the License. */ -package android.content.pm.parsing; +package com.android.server.pm.pkg.parsing; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager.Property; -import android.content.pm.PackageParser; import android.content.pm.SigningDetails; -import android.content.pm.parsing.component.ParsedApexSystemService; -import android.content.pm.parsing.component.ParsedAttribution; -import android.content.pm.parsing.component.ParsedIntentInfo; -import android.content.pm.parsing.component.ParsedPermissionGroup; -import android.content.pm.parsing.component.ParsedProcess; -import android.content.pm.parsing.component.ParsedUsesPermission; import android.os.Bundle; import android.util.ArraySet; import android.util.Pair; import android.util.SparseIntArray; +import com.android.server.pm.pkg.component.ParsedApexSystemService; +import com.android.server.pm.pkg.component.ParsedAttribution; +import com.android.server.pm.pkg.component.ParsedIntentInfo; +import com.android.server.pm.pkg.component.ParsedPermissionGroup; +import com.android.server.pm.pkg.component.ParsedProcess; +import com.android.server.pm.pkg.component.ParsedUsesPermission; + import java.security.PublicKey; import java.util.List; import java.util.Map; @@ -49,7 +49,7 @@ public interface ParsingPackageRead extends PkgWithoutStateAppInfo, PkgWithoutSt /** * The names of packages to adopt ownership of permissions from, parsed under {@link - * PackageParser#TAG_ADOPT_PERMISSIONS}. + * ParsingPackageUtils#TAG_ADOPT_PERMISSIONS}. * * @see R.styleable#AndroidManifestOriginalPackage_name */ @@ -77,7 +77,7 @@ public interface ParsingPackageRead extends PkgWithoutStateAppInfo, PkgWithoutSt /** * For use with {@link com.android.server.pm.KeySetManagerService}. Parsed in {@link - * PackageParser#TAG_KEY_SETS}. + * ParsingPackageUtils#TAG_KEY_SETS}. * * @see R.styleable#AndroidManifestKeySet * @see R.styleable#AndroidManifestPublicKey @@ -226,7 +226,7 @@ public interface ParsingPackageRead extends PkgWithoutStateAppInfo, PkgWithoutSt /** * For use with {@link com.android.server.pm.KeySetManagerService}. Parsed in {@link - * PackageParser#TAG_KEY_SETS}. + * ParsingPackageUtils#TAG_KEY_SETS}. * * @see R.styleable#AndroidManifestUpgradeKeySet */ @@ -343,4 +343,16 @@ public interface ParsingPackageRead extends PkgWithoutStateAppInfo, PkgWithoutSt * @see R.styleable#AndroidManifestApplication_resetEnabledSettingsOnAppDataCleared */ boolean isResetEnabledSettingsOnAppDataCleared(); + + /** + * The resource ID used to provide the application's locales configuration. + * + * @see R.styleable#AndroidManifestApplication_localeConfig + */ + int getLocaleConfigRes(); + + /** + * @see R.styleable#AndroidManifest_inheritKeyStoreKeys + */ + boolean shouldInheritKeyStoreKeys(); } diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java index e02eb7cca090..bf7c55f0f59e 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.content.pm.parsing; +package com.android.server.pm.pkg.parsing; import static android.content.pm.ActivityInfo.FLAG_SUPPORTS_PICTURE_IN_PICTURE; import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; @@ -33,7 +33,6 @@ import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; import android.annotation.AnyRes; import android.annotation.CheckResult; import android.annotation.IntDef; -import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StyleableRes; @@ -50,39 +49,13 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.Property; import android.content.pm.Signature; import android.content.pm.SigningDetails; -import android.content.pm.parsing.component.ComponentMutateUtils; -import android.content.pm.parsing.component.ComponentParseUtils; -import android.content.pm.parsing.component.ParsedActivity; -import android.content.pm.parsing.component.ParsedActivityUtils; -import android.content.pm.parsing.component.ParsedApexSystemService; -import android.content.pm.parsing.component.ParsedApexSystemServiceUtils; -import android.content.pm.parsing.component.ParsedAttribution; -import android.content.pm.parsing.component.ParsedAttributionUtils; -import android.content.pm.parsing.component.ParsedComponent; -import android.content.pm.parsing.component.ParsedInstrumentation; -import android.content.pm.parsing.component.ParsedInstrumentationUtils; -import android.content.pm.parsing.component.ParsedIntentInfo; -import android.content.pm.parsing.component.ParsedIntentInfoUtils; -import android.content.pm.parsing.component.ParsedMainComponent; -import android.content.pm.parsing.component.ParsedPermission; -import android.content.pm.parsing.component.ParsedPermissionGroup; -import android.content.pm.parsing.component.ParsedPermissionUtils; -import android.content.pm.parsing.component.ParsedProcess; -import android.content.pm.parsing.component.ParsedProcessUtils; -import android.content.pm.parsing.component.ParsedProvider; -import android.content.pm.parsing.component.ParsedProviderUtils; -import android.content.pm.parsing.component.ParsedService; -import android.content.pm.parsing.component.ParsedServiceUtils; -import android.content.pm.parsing.component.ParsedUsesPermission; -import android.content.pm.parsing.component.ParsedUsesPermissionImpl; +import android.content.pm.parsing.ApkLiteParseUtils; +import android.content.pm.parsing.FrameworkParsingPackageUtils; +import android.content.pm.parsing.PackageLite; import android.content.pm.parsing.result.ParseInput; import android.content.pm.parsing.result.ParseInput.DeferredError; import android.content.pm.parsing.result.ParseResult; import android.content.pm.parsing.result.ParseTypeImpl; -import android.content.pm.permission.CompatibilityPermissionInfo; -import android.content.pm.split.DefaultSplitAssetLoader; -import android.content.pm.split.SplitAssetDependencyLoader; -import android.content.pm.split.SplitAssetLoader; import android.content.res.ApkAssets; import android.content.res.AssetManager; import android.content.res.Configuration; @@ -92,7 +65,6 @@ import android.content.res.XmlResourceParser; import android.net.Uri; import android.os.Build; import android.os.Bundle; -import android.os.FileUtils; import android.os.Parcel; import android.os.RemoteException; import android.os.SystemProperties; @@ -104,7 +76,6 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AttributeSet; -import android.util.Base64; import android.util.DisplayMetrics; import android.util.Pair; import android.util.Slog; @@ -117,6 +88,35 @@ import com.android.internal.R; import com.android.internal.os.ClassLoaderFactory; import com.android.internal.util.ArrayUtils; import com.android.internal.util.XmlUtils; +import com.android.server.pm.permission.CompatibilityPermissionInfo; +import com.android.server.pm.pkg.component.ComponentMutateUtils; +import com.android.server.pm.pkg.component.ComponentParseUtils; +import com.android.server.pm.pkg.component.ParsedActivity; +import com.android.server.pm.pkg.component.ParsedActivityUtils; +import com.android.server.pm.pkg.component.ParsedApexSystemService; +import com.android.server.pm.pkg.component.ParsedApexSystemServiceUtils; +import com.android.server.pm.pkg.component.ParsedAttribution; +import com.android.server.pm.pkg.component.ParsedAttributionUtils; +import com.android.server.pm.pkg.component.ParsedComponent; +import com.android.server.pm.pkg.component.ParsedInstrumentation; +import com.android.server.pm.pkg.component.ParsedInstrumentationUtils; +import com.android.server.pm.pkg.component.ParsedIntentInfo; +import com.android.server.pm.pkg.component.ParsedIntentInfoUtils; +import com.android.server.pm.pkg.component.ParsedMainComponent; +import com.android.server.pm.pkg.component.ParsedPermission; +import com.android.server.pm.pkg.component.ParsedPermissionGroup; +import com.android.server.pm.pkg.component.ParsedPermissionUtils; +import com.android.server.pm.pkg.component.ParsedProcess; +import com.android.server.pm.pkg.component.ParsedProcessUtils; +import com.android.server.pm.pkg.component.ParsedProvider; +import com.android.server.pm.pkg.component.ParsedProviderUtils; +import com.android.server.pm.pkg.component.ParsedService; +import com.android.server.pm.pkg.component.ParsedServiceUtils; +import com.android.server.pm.pkg.component.ParsedUsesPermission; +import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl; +import com.android.server.pm.split.DefaultSplitAssetLoader; +import com.android.server.pm.split.SplitAssetDependencyLoader; +import com.android.server.pm.split.SplitAssetLoader; import libcore.io.IoUtils; import libcore.util.EmptyArray; @@ -129,14 +129,8 @@ import java.io.File; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.security.KeyFactory; -import java.security.NoSuchAlgorithmException; import java.security.PublicKey; -import java.security.spec.EncodedKeySpec; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.X509EncodedKeySpec; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Objects; @@ -158,10 +152,14 @@ public class ParsingPackageUtils { public static final float DEFAULT_PRE_O_MAX_ASPECT_RATIO = 1.86f; public static final float ASPECT_RATIO_NOT_SET = -1f; - /** File name in an APK for the Android manifest. */ + /** + * File name in an APK for the Android manifest. + */ public static final String ANDROID_MANIFEST_FILENAME = "AndroidManifest.xml"; - /** Path prefix for apps on expanded storage */ + /** + * Path prefix for apps on expanded storage + */ public static final String MNT_EXPAND = "/mnt/expand/"; public static final String TAG_ADOPT_PERMISSIONS = "adopt-permissions"; @@ -214,10 +212,11 @@ public class ParsingPackageUtils { PackageInfo.INSTALL_LOCATION_UNSPECIFIED; public static final int PARSE_DEFAULT_TARGET_SANDBOX = 1; - /** If set to true, we will only allow package files that exactly match - * the DTD. Otherwise, we try to get as much from the package as we - * can without failing. This should normally be set to false, to - * support extensions to the DTD in future versions. */ + /** + * If set to true, we will only allow package files that exactly match the DTD. Otherwise, we + * try to get as much from the package as we can without failing. This should normally be set to + * false, to support extensions to the DTD in future versions. + */ public static final boolean RIGID_PARSER = false; public static final int PARSE_MUST_BE_APK = 1 << 0; @@ -227,8 +226,8 @@ public class ParsingPackageUtils { public static final int PARSE_COLLECT_CERTIFICATES = 1 << 5; public static final int PARSE_ENFORCE_CODE = 1 << 6; /** - * This flag is applied in the ApkLiteParser. Used by OverlayConfigParser to ignore the - * checks of required system property within the overlay tag. + * This flag is applied in the ApkLiteParser. Used by OverlayConfigParser to ignore the checks + * of required system property within the overlay tag. */ public static final int PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY = 1 << 7; public static final int PARSE_CHATTY = 1 << 31; @@ -247,12 +246,6 @@ public class ParsingPackageUtils { public @interface ParseFlags {} /** - * For those names would be used as a part of the file name. Limits size to 223 and reserves 32 - * for the OS. - */ - static final int MAX_FILE_NAME_SIZE = 223; - - /** * @see #parseDefault(ParseInput, File, int, List, boolean) */ @NonNull @@ -266,8 +259,8 @@ public class ParsingPackageUtils { /** * For cases outside of PackageManagerService when an APK needs to be parsed as a one-off - * request, without caching the input object and without querying the internal system state - * for feature support. + * request, without caching the input object and without querying the internal system state for + * feature support. */ @NonNull public static ParseResult<ParsingPackage> parseDefault(ParseInput input, File file, @@ -293,8 +286,7 @@ public class ParsingPackageUtils { @NonNull String baseApkPath, @NonNull String path, @NonNull TypedArray manifestArray, boolean isCoreApp) { - return new ParsingPackageImpl(packageName, baseApkPath, path, - manifestArray); + return new ParsingPackageImpl(packageName, baseApkPath, path, manifestArray); } }); result = parser.parsePackage(input, file, parseFlags); @@ -337,21 +329,19 @@ public class ParsingPackageUtils { } /** - * Parse the package at the given location. Automatically detects if the - * package is a monolithic style (single APK file) or cluster style - * (directory of APKs). + * Parse the package at the given location. Automatically detects if the package is a monolithic + * style (single APK file) or cluster style (directory of APKs). * <p> - * This performs validity checking on cluster style packages, such as - * requiring identical package name and version codes, a single base APK, - * and unique split names. + * This performs validity checking on cluster style packages, such as requiring identical + * package name and version codes, a single base APK, and unique split names. * <p> - * Note that this <em>does not</em> perform signature verification; that must - * be done separately in {@link #getSigningDetails(ParseInput, ParsingPackageRead, boolean)}. - * - * If {@code useCaches} is true, the package parser might return a cached - * result from a previous parse of the same {@code packageFile} with the same - * {@code flags}. Note that this method does not check whether {@code packageFile} - * has changed since the last parse, it's up to callers to do so. + * Note that this <em>does not</em> perform signature verification; that must be done separately + * in {@link #getSigningDetails(ParseInput, ParsingPackageRead, boolean)}. + * <p> + * If {@code useCaches} is true, the package parser might return a cached result from a previous + * parse of the same {@code packageFile} with the same {@code flags}. Note that this method does + * not check whether {@code packageFile} has changed since the last parse, it's up to callers to + * do so. */ public ParseResult<ParsingPackage> parsePackage(ParseInput input, File packageFile, int flags) { if (packageFile.isDirectory()) { @@ -362,13 +352,12 @@ public class ParsingPackageUtils { } /** - * Parse all APKs contained in the given directory, treating them as a - * single package. This also performs validity checking, such as requiring - * identical package name and version codes, a single base APK, and unique - * split names. + * Parse all APKs contained in the given directory, treating them as a single package. This also + * performs validity checking, such as requiring identical package name and version codes, a + * single base APK, and unique split names. * <p> - * Note that this <em>does not</em> perform signature verification; that must - * be done separately in {@link #getSigningDetails(ParseInput, ParsingPackageRead, boolean)}. + * Note that this <em>does not</em> perform signature verification; that must be done separately + * in {@link #getSigningDetails(ParseInput, ParsingPackageRead, boolean)}. */ private ParseResult<ParsingPackage> parseClusterPackage(ParseInput input, File packageDir, int flags) { @@ -439,8 +428,8 @@ public class ParsingPackageUtils { /** * Parse the given APK file, treating it as as a single monolithic package. * <p> - * Note that this <em>does not</em> perform signature verification; that must - * be done separately in {@link #getSigningDetails(ParseInput, ParsingPackageRead, boolean)}. + * Note that this <em>does not</em> perform signature verification; that must be done separately + * in {@link #getSigningDetails(ParseInput, ParsingPackageRead, boolean)}. */ private ParseResult<ParsingPackage> parseMonolithicPackage(ParseInput input, File apkFile, int flags) { @@ -599,9 +588,8 @@ public class ParsingPackageUtils { } /** - * Parse the manifest of a <em>base APK</em>. When adding new features you - * need to consider whether they should be supported by split APKs and child - * packages. + * Parse the manifest of a <em>base APK</em>. When adding new features you need to consider + * whether they should be supported by split APKs and child packages. * * @param apkPath The package apk file path * @param res The resources from which to resolve values @@ -653,9 +641,8 @@ public class ParsingPackageUtils { /** * Parse the manifest of a <em>split APK</em>. * <p> - * Note that split APKs have many more restrictions on what they're capable - * of doing, so many valid features of a base APK have been carefully - * omitted here. + * Note that split APKs have many more restrictions on what they're capable of doing, so many + * valid features of a base APK have been carefully omitted here. * * @param pkg builder to fill * @return false on failure @@ -718,9 +705,8 @@ public class ParsingPackageUtils { * Parse the {@code application} XML tree at the current parse location in a * <em>split APK</em> manifest. * <p> - * Note that split APKs have many more restrictions on what they're capable - * of doing, so many valid features of a base APK have been carefully - * omitted here. + * Note that split APKs have many more restrictions on what they're capable of doing, so many + * valid features of a base APK have been carefully omitted here. */ private ParseResult<ParsingPackage> parseSplitApplication(ParseInput input, ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags, int splitIndex) @@ -882,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(); @@ -1030,7 +1018,8 @@ public class ParsingPackageUtils { } if (!"android".equals(pkg.getPackageName())) { - ParseResult<?> nameResult = validateName(input, str, true, true); + ParseResult<?> nameResult = FrameworkParsingPackageUtils.validateName(input, str, + true, true); if (nameResult.isError()) { return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID, "<manifest> specifies bad sharedUserId name \"" + str + "\": " @@ -1101,7 +1090,8 @@ public class ParsingPackageUtils { + " must define a public-key value on first use at " + parser.getPositionDescription()); } else if (encodedKey != null) { - PublicKey currentKey = parsePublicKey(encodedKey); + PublicKey currentKey = + FrameworkParsingPackageUtils.parsePublicKey(encodedKey); if (currentKey == null) { Slog.w(TAG, "No recognized valid key in 'public-key' tag at " + parser.getPositionDescription() + " key-set " @@ -1534,8 +1524,8 @@ public class ParsingPackageUtils { targetCode = minCode; } - ParseResult<Integer> targetSdkVersionResult = computeTargetSdkVersion( - targetVers, targetCode, SDK_CODENAMES, input); + ParseResult<Integer> targetSdkVersionResult = FrameworkParsingPackageUtils + .computeTargetSdkVersion(targetVers, targetCode, SDK_CODENAMES, input); if (targetSdkVersionResult.isError()) { return input.error(targetSdkVersionResult); } @@ -1548,8 +1538,8 @@ public class ParsingPackageUtils { return input.error(deferResult); } - ParseResult<Integer> minSdkVersionResult = computeMinSdkVersion(minVers, minCode, - SDK_VERSION, SDK_CODENAMES, input); + ParseResult<Integer> minSdkVersionResult = FrameworkParsingPackageUtils + .computeMinSdkVersion(minVers, minCode, SDK_VERSION, SDK_CODENAMES, input); if (minSdkVersionResult.isError()) { return input.error(minSdkVersionResult); } @@ -1644,146 +1634,6 @@ public class ParsingPackageUtils { return input.success(minExtensionVersions); } - /** - * Computes the minSdkVersion to use at runtime. If the package is not - * compatible with this platform, populates {@code outError[0]} with an - * error message. - * <p> - * If {@code minCode} is not specified, e.g. the value is {@code null}, - * then behavior varies based on the {@code platformSdkVersion}: - * <ul> - * <li>If the platform SDK version is greater than or equal to the - * {@code minVers}, returns the {@code mniVers} unmodified. - * <li>Otherwise, returns -1 to indicate that the package is not - * compatible with this platform. - * </ul> - * <p> - * Otherwise, the behavior varies based on whether the current platform - * is a pre-release version, e.g. the {@code platformSdkCodenames} array - * has length > 0: - * <ul> - * <li>If this is a pre-release platform and the value specified by - * {@code targetCode} is contained within the array of allowed pre-release - * codenames, this method will return {@link Build.VERSION_CODES#CUR_DEVELOPMENT}. - * <li>If this is a released platform, this method will return -1 to - * indicate that the package is not compatible with this platform. - * </ul> - * - * @param minVers minSdkVersion number, if specified in the application - * manifest, or 1 otherwise - * @param minCode minSdkVersion code, if specified in the application - * manifest, or {@code null} otherwise - * @param platformSdkVersion platform SDK version number, typically - * Build.VERSION.SDK_INT - * @param platformSdkCodenames array of allowed prerelease SDK codenames - * for this platform - * @return the minSdkVersion to use at runtime if successful - */ - public static ParseResult<Integer> computeMinSdkVersion(@IntRange(from = 1) int minVers, - @Nullable String minCode, @IntRange(from = 1) int platformSdkVersion, - @NonNull String[] platformSdkCodenames, @NonNull ParseInput input) { - // If it's a release SDK, make sure we meet the minimum SDK requirement. - if (minCode == null) { - if (minVers <= platformSdkVersion) { - return input.success(minVers); - } - - // We don't meet the minimum SDK requirement. - return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK, - "Requires newer sdk version #" + minVers - + " (current version is #" + platformSdkVersion + ")"); - } - - // If it's a pre-release SDK and the codename matches this platform, we - // definitely meet the minimum SDK requirement. - if (matchTargetCode(platformSdkCodenames, minCode)) { - return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT); - } - - // Otherwise, we're looking at an incompatible pre-release SDK. - if (platformSdkCodenames.length > 0) { - return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK, - "Requires development platform " + minCode - + " (current platform is any of " - + Arrays.toString(platformSdkCodenames) + ")"); - } else { - return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK, - "Requires development platform " + minCode - + " but this is a release platform."); - } - } - - /** - * Computes the targetSdkVersion to use at runtime. If the package is not - * compatible with this platform, populates {@code outError[0]} with an - * error message. - * <p> - * If {@code targetCode} is not specified, e.g. the value is {@code null}, - * then the {@code targetVers} will be returned unmodified. - * <p> - * Otherwise, the behavior varies based on whether the current platform - * is a pre-release version, e.g. the {@code platformSdkCodenames} array - * has length > 0: - * <ul> - * <li>If this is a pre-release platform and the value specified by - * {@code targetCode} is contained within the array of allowed pre-release - * codenames, this method will return {@link Build.VERSION_CODES#CUR_DEVELOPMENT}. - * <li>If this is a released platform, this method will return -1 to - * indicate that the package is not compatible with this platform. - * </ul> - * - * @param targetVers targetSdkVersion number, if specified in the - * application manifest, or 0 otherwise - * @param targetCode targetSdkVersion code, if specified in the application - * manifest, or {@code null} otherwise - * @param platformSdkCodenames array of allowed pre-release SDK codenames - * for this platform - * @return the targetSdkVersion to use at runtime if successful - */ - public static ParseResult<Integer> computeTargetSdkVersion(@IntRange(from = 0) int targetVers, - @Nullable String targetCode, @NonNull String[] platformSdkCodenames, - @NonNull ParseInput input) { - // If it's a release SDK, return the version number unmodified. - if (targetCode == null) { - return input.success(targetVers); - } - - // If it's a pre-release SDK and the codename matches this platform, it - // definitely targets this SDK. - if (matchTargetCode(platformSdkCodenames, targetCode)) { - return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT); - } - - // Otherwise, we're looking at an incompatible pre-release SDK. - if (platformSdkCodenames.length > 0) { - return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK, - "Requires development platform " + targetCode - + " (current platform is any of " - + Arrays.toString(platformSdkCodenames) + ")"); - } else { - return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK, - "Requires development platform " + targetCode - + " but this is a release platform."); - } - } - - /** - * Matches a given {@code targetCode} against a set of release codeNames. Target codes can - * either be of the form {@code [codename]}" (e.g {@code "Q"}) or of the form - * {@code [codename].[fingerprint]} (e.g {@code "Q.cafebc561"}). - */ - private static boolean matchTargetCode(@NonNull String[] codeNames, - @NonNull String targetCode) { - final String targetCodeName; - final int targetCodeIdx = targetCode.indexOf('.'); - if (targetCodeIdx == -1) { - targetCodeName = targetCode; - } else { - targetCodeName = targetCode.substring(0, targetCodeIdx); - } - return ArrayUtils.contains(codeNames, targetCodeName); - } - private static ParseResult<ParsingPackage> parseRestrictUpdateHash(int flags, ParseInput input, ParsingPackage pkg, Resources res, XmlResourceParser parser) { if ((flags & PARSE_IS_SYSTEM_DIR) != 0) { @@ -1925,12 +1775,11 @@ public class ParsingPackageUtils { * Parse the {@code application} XML tree at the current parse location in a * <em>base APK</em> manifest. * <p> - * When adding new features, carefully consider if they should also be - * supported by split APKs. - * - * This method should avoid using a getter for fields set by this method. Prefer assigning - * a local variable and using it. Otherwise there's an ordering problem which can be broken - * if any code moves around. + * When adding new features, carefully consider if they should also be supported by split APKs. + * <p> + * This method should avoid using a getter for fields set by this method. Prefer assigning a + * local variable and using it. Otherwise there's an ordering problem which can be broken if any + * code moves around. */ private ParseResult<ParsingPackage> parseBaseApplication(ParseInput input, ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags) @@ -2276,7 +2125,7 @@ public class ParsingPackageUtils { /** * Collection of single-line, no (or little) logic assignments. Separated for readability. - * + * <p> * Flags are separated by type and by default value. They are sorted alphabetically within each * section. */ @@ -2339,6 +2188,7 @@ public class ParsingPackageUtils { .setTheme(resId(R.styleable.AndroidManifestApplication_theme, sa)) .setDataExtractionRules( resId(R.styleable.AndroidManifestApplication_dataExtractionRules, sa)) + .setLocaleConfigRes(resId(R.styleable.AndroidManifestApplication_localeConfig, sa)) // Strings .setClassLoaderName(string(R.styleable.AndroidManifestApplication_classLoader, sa)) .setRequiredAccountType(string(R.styleable.AndroidManifestApplication_requiredAccountType, sa)) @@ -2892,7 +2742,7 @@ public class ParsingPackageUtils { R.styleable.AndroidManifestResourceOverlay_requiredSystemPropertyName); String propValue = sa.getString( R.styleable.AndroidManifestResourceOverlay_requiredSystemPropertyValue); - if (!checkRequiredSystemProperties(propName, propValue)) { + if (!FrameworkParsingPackageUtils.checkRequiredSystemProperties(propName, propValue)) { String message = "Skipping target and overlay pair " + target + " and " + pkg.getBaseApkPath() + ": overlay ignored due to required system property: " @@ -3055,60 +2905,6 @@ public class ParsingPackageUtils { } /** - * Check if the given name is valid. - * - * @param name The name to check. - * @param requireSeparator {@code true} if the name requires containing a separator at least. - * @param requireFilename {@code true} to apply file name validation to the given name. It also - * limits length of the name to the {@link #MAX_FILE_NAME_SIZE}. - * @return Success if it's valid. - */ - public static String validateName(String name, boolean requireSeparator, - boolean requireFilename) { - final int N = name.length(); - boolean hasSep = false; - boolean front = true; - for (int i = 0; i < N; i++) { - final char c = name.charAt(i); - if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { - front = false; - continue; - } - if (!front) { - if ((c >= '0' && c <= '9') || c == '_') { - continue; - } - } - if (c == '.') { - hasSep = true; - front = true; - continue; - } - return "bad character '" + c + "'"; - } - if (requireFilename) { - if (!FileUtils.isValidExtFilename(name)) { - return "Invalid filename"; - } else if (N > MAX_FILE_NAME_SIZE) { - return "the length of the name is greater than " + MAX_FILE_NAME_SIZE; - } - } - return hasSep || !requireSeparator ? null : "must have at least one '.' separator"; - } - - /** - * @see #validateName(String, boolean, boolean) - */ - public static ParseResult validateName(ParseInput input, String name, boolean requireSeparator, - boolean requireFilename) { - final String errorMessage = validateName(name, requireSeparator, requireFilename); - if (errorMessage != null) { - return input.error(errorMessage); - } - return input.success(null); - } - - /** * Parse a meta data defined on the enclosing tag. * <p>Meta data can be defined by either <meta-data> or <property> elements. */ @@ -3168,114 +2964,6 @@ public class ParsingPackageUtils { } /** - * @return {@link PublicKey} of a given encoded public key. - */ - public static final PublicKey parsePublicKey(final String encodedPublicKey) { - if (encodedPublicKey == null) { - Slog.w(TAG, "Could not parse null public key"); - return null; - } - - try { - return parsePublicKey(Base64.decode(encodedPublicKey, Base64.DEFAULT)); - } catch (IllegalArgumentException e) { - Slog.w(TAG, "Could not parse verifier public key; invalid Base64"); - return null; - } - } - - /** - * @return {@link PublicKey} of the given byte array of a public key. - */ - public static final PublicKey parsePublicKey(final byte[] publicKey) { - if (publicKey == null) { - Slog.w(TAG, "Could not parse null public key"); - return null; - } - - final EncodedKeySpec keySpec; - try { - keySpec = new X509EncodedKeySpec(publicKey); - } catch (IllegalArgumentException e) { - Slog.w(TAG, "Could not parse verifier public key; invalid Base64"); - return null; - } - - /* First try the key as an RSA key. */ - try { - final KeyFactory keyFactory = KeyFactory.getInstance("RSA"); - return keyFactory.generatePublic(keySpec); - } catch (NoSuchAlgorithmException e) { - Slog.wtf(TAG, "Could not parse public key: RSA KeyFactory not included in build"); - } catch (InvalidKeySpecException e) { - // Not a RSA public key. - } - - /* Now try it as a ECDSA key. */ - try { - final KeyFactory keyFactory = KeyFactory.getInstance("EC"); - return keyFactory.generatePublic(keySpec); - } catch (NoSuchAlgorithmException e) { - Slog.wtf(TAG, "Could not parse public key: EC KeyFactory not included in build"); - } catch (InvalidKeySpecException e) { - // Not a ECDSA public key. - } - - /* Now try it as a DSA key. */ - try { - final KeyFactory keyFactory = KeyFactory.getInstance("DSA"); - return keyFactory.generatePublic(keySpec); - } catch (NoSuchAlgorithmException e) { - Slog.wtf(TAG, "Could not parse public key: DSA KeyFactory not included in build"); - } catch (InvalidKeySpecException e) { - // Not a DSA public key. - } - - /* Not a supported key type */ - return null; - } - - /** - * Returns {@code true} if both the property name and value are empty or if the given system - * property is set to the specified value. Properties can be one or more, and if properties are - * more than one, they must be separated by comma, and count of names and values must be equal, - * and also every given system property must be set to the corresponding value. - * In all other cases, returns {@code false} - */ - public static boolean checkRequiredSystemProperties(@Nullable String rawPropNames, - @Nullable String rawPropValues) { - if (TextUtils.isEmpty(rawPropNames) || TextUtils.isEmpty(rawPropValues)) { - if (!TextUtils.isEmpty(rawPropNames) || !TextUtils.isEmpty(rawPropValues)) { - // malformed condition - incomplete - Slog.w(TAG, "Disabling overlay - incomplete property :'" + rawPropNames - + "=" + rawPropValues + "' - require both requiredSystemPropertyName" - + " AND requiredSystemPropertyValue to be specified."); - return false; - } - // no valid condition set - so no exclusion criteria, overlay will be included. - return true; - } - - final String[] propNames = rawPropNames.split(","); - final String[] propValues = rawPropValues.split(","); - - if (propNames.length != propValues.length) { - Slog.w(TAG, "Disabling overlay - property :'" + rawPropNames - + "=" + rawPropValues + "' - require both requiredSystemPropertyName" - + " AND requiredSystemPropertyValue lists to have the same size."); - return false; - } - for (int i = 0; i < propNames.length; i++) { - // Check property value: make sure it is both set and equal to expected value - final String currValue = SystemProperties.get(propNames[i]); - if (!TextUtils.equals(currValue, propValues[i])) { - return false; - } - } - return true; - } - - /** * Collect certificates from all the APKs described in the given package. Also asserts that * all APK contents are signed correctly and consistently. * diff --git a/core/java/android/content/pm/parsing/ParsingUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java index cce984eb93a7..95fec369b95a 100644 --- a/core/java/android/content/pm/parsing/ParsingUtils.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.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,14 +14,14 @@ * limitations under the License. */ -package android.content.pm.parsing; +package com.android.server.pm.pkg.parsing; -import static android.content.pm.parsing.ParsingPackageUtils.RIGID_PARSER; +import static com.android.server.pm.pkg.parsing.ParsingPackageUtils.RIGID_PARSER; import android.annotation.NonNull; import android.annotation.Nullable; -import android.content.pm.parsing.component.ParsedIntentInfo; -import android.content.pm.parsing.component.ParsedIntentInfoImpl; +import com.android.server.pm.pkg.component.ParsedIntentInfo; +import com.android.server.pm.pkg.component.ParsedIntentInfoImpl; import android.content.pm.parsing.result.ParseInput; import android.content.pm.parsing.result.ParseResult; import android.content.res.XmlResourceParser; @@ -138,7 +138,7 @@ public class ParsingUtils { final List<Pair<String, ParsedIntentInfo>> list = new ArrayList<>(size); for (int i = 0; i < size; ++i) { list.add(Pair.create(source.readString(), source.readParcelable( - ParsedIntentInfoImpl.class.getClassLoader()))); + ParsedIntentInfoImpl.class.getClassLoader(), ParsedIntentInfo.class))); } return list; diff --git a/core/java/android/content/pm/parsing/PkgWithoutStateAppInfo.java b/services/core/java/com/android/server/pm/pkg/parsing/PkgWithoutStateAppInfo.java index 625b9d1bb479..a323e2098e54 100644 --- a/core/java/android/content/pm/parsing/PkgWithoutStateAppInfo.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/PkgWithoutStateAppInfo.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 android.content.pm.parsing; +package com.android.server.pm.pkg.parsing; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/core/java/android/content/pm/parsing/PkgWithoutStatePackageInfo.java b/services/core/java/com/android/server/pm/pkg/parsing/PkgWithoutStatePackageInfo.java index 7d758a858ea5..2bc4ee7cbc9a 100644 --- a/core/java/android/content/pm/parsing/PkgWithoutStatePackageInfo.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/PkgWithoutStatePackageInfo.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 android.content.pm.parsing; +package com.android.server.pm.pkg.parsing; import android.annotation.NonNull; import android.annotation.Nullable; @@ -29,14 +29,14 @@ import android.content.pm.PackageInfo; import android.content.pm.PermissionInfo; import android.content.pm.ProviderInfo; import android.content.pm.ServiceInfo; -import android.content.pm.parsing.component.ParsedActivity; -import android.content.pm.parsing.component.ParsedInstrumentation; -import android.content.pm.parsing.component.ParsedPermission; -import android.content.pm.parsing.component.ParsedProvider; -import android.content.pm.parsing.component.ParsedService; -import android.content.pm.pkg.FrameworkPackageUserState; import com.android.internal.R; +import com.android.server.pm.pkg.PackageUserState; +import com.android.server.pm.pkg.component.ParsedActivity; +import com.android.server.pm.pkg.component.ParsedInstrumentation; +import com.android.server.pm.pkg.component.ParsedPermission; +import com.android.server.pm.pkg.component.ParsedProvider; +import com.android.server.pm.pkg.component.ParsedService; import java.util.List; @@ -82,7 +82,7 @@ public interface PkgWithoutStatePackageInfo { * provide the same information as {@link ActivityInfo}. Effective state can be queried through * {@link android.content.pm.PackageManager#getActivityInfo(ComponentName, int)} or by * combining state from from com.android.server.pm.pkg.PackageState and - * {@link FrameworkPackageUserState}. + * {@link PackageUserState}. * * @see ActivityInfo * @see PackageInfo#activities @@ -153,7 +153,7 @@ public interface PkgWithoutStatePackageInfo { * provide the same information as {@link ProviderInfo}. Effective state can be queried through * {@link android.content.pm.PackageManager#getProviderInfo(ComponentName, int)} or by * combining state from from com.android.server.pm.pkg.PackageState and - * {@link FrameworkPackageUserState}. + * {@link PackageUserState}. * * @see ProviderInfo * @see PackageInfo#providers @@ -168,7 +168,7 @@ public interface PkgWithoutStatePackageInfo { * provide the same information as {@link ActivityInfo}. Effective state can be queried through * {@link android.content.pm.PackageManager#getReceiverInfo(ComponentName, int)} or by * combining state from from com.android.server.pm.pkg.PackageState and - * {@link FrameworkPackageUserState}. + * {@link PackageUserState}. * * Since they share several attributes, receivers are parsed as {@link ParsedActivity}, even * though they represent different functionality. @@ -222,7 +222,7 @@ public interface PkgWithoutStatePackageInfo { * provide the same information as {@link ServiceInfo}. Effective state can be queried through * {@link android.content.pm.PackageManager#getServiceInfo(ComponentName, int)} or by * combining state from from com.android.server.pm.pkg.PackageState and - * {@link FrameworkPackageUserState}. + * {@link PackageUserState}. * * @see ServiceInfo * @see PackageInfo#services diff --git a/core/java/android/content/pm/split/DefaultSplitAssetLoader.java b/services/core/java/com/android/server/pm/split/DefaultSplitAssetLoader.java index 47cf28b1d9d2..2bd7cf848196 100644 --- a/core/java/android/content/pm/split/DefaultSplitAssetLoader.java +++ b/services/core/java/com/android/server/pm/split/DefaultSplitAssetLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 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. @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.content.pm.split; +package com.android.server.pm.split; import android.content.pm.parsing.ApkLiteParseUtils; import android.content.pm.parsing.PackageLite; -import android.content.pm.parsing.ParsingPackageUtils; -import android.content.pm.parsing.ParsingPackageUtils.ParseFlags; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils.ParseFlags; import android.content.res.ApkAssets; import android.content.res.AssetManager; import android.os.Build; diff --git a/core/java/android/content/pm/split/SplitAssetDependencyLoader.java b/services/core/java/com/android/server/pm/split/SplitAssetDependencyLoader.java index a0c3f752243c..ae42e0980fb7 100644 --- a/core/java/android/content/pm/split/SplitAssetDependencyLoader.java +++ b/services/core/java/com/android/server/pm/split/SplitAssetDependencyLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 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. @@ -13,18 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.content.pm.split; +package com.android.server.pm.split; import android.annotation.NonNull; import android.content.pm.parsing.ApkLiteParseUtils; import android.content.pm.parsing.PackageLite; -import android.content.pm.parsing.ParsingPackageUtils; -import android.content.pm.parsing.ParsingPackageUtils.ParseFlags; +import android.content.pm.split.SplitDependencyLoader; import android.content.res.ApkAssets; import android.content.res.AssetManager; import android.os.Build; import android.util.SparseArray; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils.ParseFlags; + import libcore.io.IoUtils; import java.io.IOException; diff --git a/core/java/android/content/pm/split/SplitAssetLoader.java b/services/core/java/com/android/server/pm/split/SplitAssetLoader.java index d314e06cb4a8..845015916e60 100644 --- a/core/java/android/content/pm/split/SplitAssetLoader.java +++ b/services/core/java/com/android/server/pm/split/SplitAssetLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 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. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.content.pm.split; +package com.android.server.pm.split; import android.content.res.ApkAssets; import android.content.res.AssetManager; diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java index d47f510c8338..e07812036a0c 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java @@ -22,8 +22,8 @@ import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledSince; import android.content.Intent; import android.content.IntentFilter; -import android.content.pm.parsing.component.ParsedActivity; -import android.content.pm.parsing.component.ParsedIntentInfo; +import com.android.server.pm.pkg.component.ParsedActivity; +import com.android.server.pm.pkg.component.ParsedIntentInfo; import android.os.Build; import android.text.TextUtils; import android.util.ArraySet; diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java index d1603f54eb37..d0b50d271140 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java @@ -31,8 +31,6 @@ import android.content.pm.IntentFilterVerificationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; -import android.content.pm.parsing.component.ParsedActivity; -import android.content.pm.pkg.PackageUserStateUtils; import android.content.pm.verify.domain.DomainOwner; import android.content.pm.verify.domain.DomainVerificationInfo; import android.content.pm.verify.domain.DomainVerificationManager; @@ -63,6 +61,8 @@ import com.android.server.pm.parsing.pkg.AndroidPackage; 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.PackageUserStateUtils; +import com.android.server.pm.pkg.component.ParsedActivity; import com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState; import com.android.server.pm.verify.domain.models.DomainVerificationPkgState; import com.android.server.pm.verify.domain.models.DomainVerificationStateMap; 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/speech/RemoteSpeechRecognitionService.java b/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java index 068626588745..21d4cbbbcca7 100644 --- a/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java +++ b/services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java @@ -28,6 +28,7 @@ import android.os.Bundle; import android.os.RemoteException; import android.speech.IRecognitionListener; import android.speech.IRecognitionService; +import android.speech.IRecognitionSupportCallback; import android.speech.RecognitionService; import android.speech.SpeechRecognizer; import android.util.Log; @@ -211,6 +212,30 @@ final class RemoteSpeechRecognitionService extends ServiceConnector.Impl<IRecogn } } + void checkRecognitionSupport( + Intent recognizerIntent, + IRecognitionSupportCallback callback) { + + if (!mConnected) { + try { + callback.onError(SpeechRecognizer.ERROR_SERVER_DISCONNECTED); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to report the connection broke to the caller.", e); + e.printStackTrace(); + } + return; + } + run(service -> service.checkRecognitionSupport(recognizerIntent, callback)); + } + + void triggerModelDownload(Intent recognizerIntent) { + if (!mConnected) { + Slog.e(TAG, "#downloadModel failed due to connection."); + return; + } + run(service -> service.triggerModelDownload(recognizerIntent)); + } + void shutdown() { synchronized (mLock) { if (this.mListener == null) { diff --git a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java index 5442e5b0413f..ae23b9e46d23 100644 --- a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java +++ b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java @@ -33,6 +33,7 @@ import android.permission.PermissionManager; import android.speech.IRecognitionListener; import android.speech.IRecognitionService; import android.speech.IRecognitionServiceManagerCallback; +import android.speech.IRecognitionSupportCallback; import android.speech.RecognitionService; import android.speech.SpeechRecognizer; import android.util.Slog; @@ -169,6 +170,18 @@ final class SpeechRecognitionManagerServiceImpl extends clientToken.unlinkToDeath(deathRecipient, 0); } } + + @Override + public void checkRecognitionSupport( + Intent recognizerIntent, + IRecognitionSupportCallback callback) { + service.checkRecognitionSupport(recognizerIntent, callback); + } + + @Override + public void triggerModelDownload(Intent recognizerIntent) { + service.triggerModelDownload(recognizerIntent); + } }); } catch (RemoteException e) { Slog.e(TAG, "Error creating a speech recognition session", e); 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 4b71742c86c8..9bed24d05f3d 100644 --- a/services/core/java/com/android/server/trust/TrustManagerService.java +++ b/services/core/java/com/android/server/trust/TrustManagerService.java @@ -46,6 +46,7 @@ import android.hardware.biometrics.BiometricSourceType; import android.net.Uri; import android.os.Binder; import android.os.Build; +import android.os.Bundle; import android.os.DeadObjectException; import android.os.Handler; import android.os.IBinder; @@ -74,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; @@ -122,6 +122,9 @@ 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"; private static final int TRUST_USUALLY_MANAGED_FLUSH_DELAY = 2 * 60 * 1000; private static final String TRUST_TIMEOUT_ALARM_TAG = "TrustManagerService.trustTimeoutForUser"; @@ -388,7 +391,6 @@ public class TrustManagerService extends SystemService { } } - public void updateTrust(int userId, int flags) { updateTrust(userId, flags, false /* isFromUnlock */); } @@ -428,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) { @@ -635,11 +637,18 @@ public class TrustManagerService extends SystemService { } } + private void refreshDeviceLockedForUser(int userId) { + refreshDeviceLockedForUser(userId, UserHandle.USER_NULL); + } + /** * Update the user's locked state. Only applicable to users with a real keyguard * ({@link UserInfo#supportsSwitchToByUser}) and unsecured managed profiles. + * + * If this is called due to an unlock operation set unlockedUser to prevent the lock from + * being prematurely reset for that user while keyguard is still in the process of going away. */ - private void refreshDeviceLockedForUser(int userId) { + private void refreshDeviceLockedForUser(int userId, int unlockedUser) { if (userId != UserHandle.USER_ALL && userId < UserHandle.USER_SYSTEM) { Log.e(TAG, "refreshDeviceLockedForUser(userId=" + userId + "): Invalid user handle," + " must be USER_ALL or a specific user.", new Throwable("here")); @@ -675,6 +684,7 @@ public class TrustManagerService extends SystemService { boolean trusted = aggregateIsTrusted(id); boolean showingKeyguard = true; boolean biometricAuthenticated = false; + boolean currentUserIsUnlocked = false; if (mCurrentUser == id) { synchronized(mUsersUnlockedByBiometric) { @@ -683,10 +693,17 @@ public class TrustManagerService extends SystemService { try { showingKeyguard = wm.isKeyguardLocked(); } catch (RemoteException e) { + Log.w(TAG, "Unable to check keyguard lock state", e); } + currentUserIsUnlocked = unlockedUser == id; + } + final boolean deviceLocked = secure && showingKeyguard && !trusted + && !biometricAuthenticated; + if (deviceLocked && currentUserIsUnlocked) { + // keyguard is finishing but may not have completed going away yet + continue; } - boolean deviceLocked = secure && showingKeyguard && !trusted && - !biometricAuthenticated; + setDeviceLockedForUser(id, deviceLocked); } } @@ -933,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; @@ -963,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); @@ -993,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) + ")"); @@ -1001,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); @@ -1092,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) @@ -1306,13 +1357,20 @@ public class TrustManagerService extends SystemService { } @Override - public void clearAllBiometricRecognized(BiometricSourceType biometricSource) { + public void clearAllBiometricRecognized( + BiometricSourceType biometricSource, int unlockedUser) { enforceReportPermission(); synchronized(mUsersUnlockedByBiometric) { mUsersUnlockedByBiometric.clear(); } - mHandler.obtainMessage(MSG_REFRESH_DEVICE_LOCKED_FOR_USER, UserHandle.USER_ALL, - 0 /* arg2 */).sendToTarget(); + Message message = mHandler.obtainMessage(MSG_REFRESH_DEVICE_LOCKED_FOR_USER, + UserHandle.USER_ALL, 0 /* arg2 */); + if (unlockedUser >= 0) { + Bundle bundle = new Bundle(); + bundle.putInt(REFRESH_DEVICE_LOCKED_EXCEPT_USER, unlockedUser); + message.setData(bundle); + } + message.sendToTarget(); } }; @@ -1364,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; @@ -1404,9 +1465,11 @@ public class TrustManagerService extends SystemService { break; case MSG_REFRESH_DEVICE_LOCKED_FOR_USER: if (msg.arg2 == 1) { - updateTrust(msg.arg1, 0 /* flags */, true); + updateTrust(msg.arg1, 0 /* flags */, true /* isFromUnlock */); } - refreshDeviceLockedForUser(msg.arg1); + final int unlockedUser = msg.getData().getInt( + REFRESH_DEVICE_LOCKED_EXCEPT_USER, UserHandle.USER_NULL); + refreshDeviceLockedForUser(msg.arg1, unlockedUser); break; case MSG_SCHEDULE_TRUST_TIMEOUT: handleScheduleTrustTimeout(msg.arg1, msg.arg2); 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..9ead7fe0534b 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,15 @@ 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.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 +79,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 +95,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 +110,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 +126,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 +260,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 +292,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 +633,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 +642,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()) { @@ -1361,7 +1370,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 +1687,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, @@ -1866,6 +1875,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 { @@ -1968,14 +1987,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 +2091,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 +2229,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/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 0cc625d203ad..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())); @@ -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/ActivityInterceptorCallback.java b/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java index 48dd2f4238c6..4df2e1782e4f 100644 --- a/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java +++ b/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java @@ -53,7 +53,6 @@ public abstract class ActivityInterceptorCallback { */ @IntDef(suffix = { "_ORDERED_ID" }, value = { FIRST_ORDERED_ID, - COMMUNAL_MODE_ORDERED_ID, PERMISSION_POLICY_ORDERED_ID, VIRTUAL_DEVICE_SERVICE_ORDERED_ID, LAST_ORDERED_ID // Update this when adding new ids @@ -67,14 +66,9 @@ public abstract class ActivityInterceptorCallback { static final int FIRST_ORDERED_ID = 0; /** - * The identifier for {@link com.android.server.communal.CommunalManagerService} interceptor. - */ - public static final int COMMUNAL_MODE_ORDERED_ID = 1; - - /** * The identifier for {@link com.android.server.policy.PermissionPolicyService} interceptor */ - public static final int PERMISSION_POLICY_ORDERED_ID = 2; + public static final int PERMISSION_POLICY_ORDERED_ID = 1; /** * The identifier for {@link com.android.server.companion.virtual.VirtualDeviceManagerService} diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java index 4b33f0ec1723..a8dd856c2191 100644 --- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java +++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java @@ -11,8 +11,6 @@ import static android.app.WaitResult.LAUNCH_STATE_WARM; 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.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_ACTIVITY_START; @@ -483,10 +481,6 @@ class ActivityMetricsLogger { case WINDOWING_MODE_FULLSCREEN: mWindowState = WINDOW_STATE_STANDARD; break; - case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY: - case WINDOWING_MODE_SPLIT_SCREEN_SECONDARY: - mWindowState = WINDOW_STATE_SIDE_BY_SIDE; - break; case WINDOWING_MODE_FREEFORM: mWindowState = WINDOW_STATE_FREEFORM; break; diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index c2765db4ccff..7b5d215a24e6 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(); } @@ -7365,7 +7369,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mSizeCompatBounds = null; mCompatDisplayInsets = null; - onRequestedOverrideConfigurationChanged(getRequestedOverrideConfiguration()); + // Clear config override in #updateCompatDisplayInsets(). + onRequestedOverrideConfigurationChanged(EMPTY); } @Override @@ -7680,10 +7685,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; @@ -8199,8 +8210,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 9353f6dfdabf..316bf2017585 100644 --- a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java +++ b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java @@ -22,10 +22,10 @@ import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; import android.app.compat.CompatChanges; import android.compat.annotation.ChangeId; -import android.compat.annotation.Disabled; import android.os.IBinder; import android.os.InputConstants; import android.os.Looper; +import android.os.Process; import android.util.Slog; import android.view.InputChannel; import android.view.InputEvent; @@ -46,7 +46,6 @@ class ActivityRecordInputSink { * Feature flag for making Activities consume all touches within their task bounds. */ @ChangeId - @Disabled static final long ENABLE_TOUCH_OPAQUE_ACTIVITIES = 194480991L; private static final String TAG = "ActivityRecordInputSink"; @@ -113,14 +112,12 @@ class ActivityRecordInputSink { } private InputWindowHandle createInputWindowHandle() { - InputWindowHandle inputWindowHandle = new InputWindowHandle( - mActivityRecord.getInputApplicationHandle(false), + InputWindowHandle inputWindowHandle = new InputWindowHandle(null, mActivityRecord.getDisplayId()); - inputWindowHandle.replaceTouchableRegionWithCrop( - mActivityRecord.getParentSurfaceControl()); + inputWindowHandle.replaceTouchableRegionWithCrop = true; inputWindowHandle.name = mName; - inputWindowHandle.ownerUid = mActivityRecord.getUid(); - inputWindowHandle.ownerPid = mActivityRecord.getPid(); + inputWindowHandle.ownerUid = Process.myUid(); + inputWindowHandle.ownerPid = Process.myPid(); inputWindowHandle.layoutParamsFlags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH; diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 65f9e83dc5df..e119a9a0373a 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -30,8 +30,6 @@ import static android.app.ActivityManager.START_SUCCESS; import static android.app.ActivityManager.START_TASK_TO_FRONT; import static android.app.ActivityTaskManager.INVALID_TASK_ID; 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_CLEAR_TASK; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; @@ -1926,27 +1924,6 @@ class ActivityStarter { private void computeLaunchParams(ActivityRecord r, ActivityRecord sourceRecord, Task targetTask) { - final Task sourceRootTask = mSourceRootTask != null ? mSourceRootTask - : mRootWindowContainer.getTopDisplayFocusedRootTask(); - if (sourceRootTask != null && sourceRootTask.inSplitScreenWindowingMode() - && (mOptions == null - || mOptions.getLaunchWindowingMode() == WINDOWING_MODE_UNDEFINED)) { - int windowingMode = - targetTask != null ? targetTask.getWindowingMode() : WINDOWING_MODE_UNDEFINED; - if ((mLaunchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0) { - if (sourceRootTask.inSplitScreenPrimaryWindowingMode()) { - windowingMode = WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; - } else if (sourceRootTask.inSplitScreenSecondaryWindowingMode()) { - windowingMode = WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; - } - } - - if (mOptions == null) { - mOptions = ActivityOptions.makeBasic(); - } - mOptions.setLaunchWindowingMode(windowingMode); - } - mSupervisor.getLaunchParamsController().calculate(targetTask, r.info.windowLayout, r, sourceRecord, mOptions, mRequest, PHASE_BOUNDS, mLaunchParams); mPreferredTaskDisplayArea = mLaunchParams.hasPreferredTaskDisplayArea() 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/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index 9d53e5a82231..dd394ca3da67 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -36,7 +36,6 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; 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_UNDEFINED; import static android.content.pm.PackageManager.NOTIFY_PACKAGE_USE_ACTIVITY; import static android.content.pm.PackageManager.PERMISSION_DENIED; @@ -2199,10 +2198,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { boolean forceNonResizable) { final boolean isSecondaryDisplayPreferred = preferredTaskDisplayArea != null && preferredTaskDisplayArea.getDisplayId() != DEFAULT_DISPLAY; - final boolean inSplitScreenMode = actualRootTask != null - && actualRootTask.getDisplayArea().isSplitScreenModeActivated(); - if (((!inSplitScreenMode && preferredWindowingMode != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) - && !isSecondaryDisplayPreferred) || !task.isActivityTypeStandardOrUndefined()) { + if (!task.isActivityTypeStandardOrUndefined()) { return; } @@ -2543,10 +2539,8 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { == PERMISSION_GRANTED) { mRecentTasks.setFreezeTaskListReordering(); } - if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY - || activityOptions.getLaunchRootTask() != null) { - // Don't move home activity forward if we are launching into primary split or - // there is a launch root set. + if (activityOptions.getLaunchRootTask() != null) { + // Don't move home activity forward if there is a launch root set. moveHomeTaskForward = false; } } 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 d91f48e2dac3..f70d7dcbeb1b 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; @@ -1050,8 +1052,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mAppTransition = new AppTransition(mWmService.mContext, mWmService, this); mAppTransition.registerListenerLocked(mWmService.mActivityManagerAppTransitionNotifier); - mTransitionController.registerLegacyListener( - mWmService.mActivityManagerAppTransitionNotifier); mAppTransition.registerListenerLocked(mFixedRotationTransitionListener); mAppTransitionController = new AppTransitionController(mWmService, this); mUnknownAppVisibilityController = new UnknownAppVisibilityController(mWmService, this); @@ -2003,6 +2003,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } void configureDisplayPolicy() { + mRootWindowContainer.updateDisplayImePolicyCache(); mDisplayPolicy.updateConfigurationAndScreenSizeDependentBehaviors(); mDisplayRotation.configure(mBaseDisplayWidth, mBaseDisplayHeight); } @@ -2720,6 +2721,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp void onDisplayChanged(DisplayContent dc) { super.onDisplayChanged(dc); updateSystemGestureExclusionLimit(); + updateKeepClearAreas(); } void updateSystemGestureExclusionLimit() { @@ -3329,6 +3331,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); } @@ -3388,6 +3393,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); @@ -3615,6 +3627,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 @@ -3941,6 +3954,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; @@ -3989,11 +4005,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(); } @@ -4103,7 +4120,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp "IME-snapshot-surface"); t.setBuffer(imeSurface, buffer); t.setColorSpace(mSurfaceControl, ColorSpace.get(ColorSpace.Named.SRGB)); - t.setRelativeLayer(imeSurface, activity.getSurfaceControl(), 1); + t.setLayer(imeSurface, 1); t.setPosition(imeSurface, mInputMethodWindow.getDisplayFrame().left, mInputMethodWindow.getDisplayFrame().top); return imeSurface; @@ -4148,9 +4165,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp * which controls the visibility and animation of the input method window. */ void updateImeInputAndControlTarget(WindowState target) { - if (target != null && target.mActivityRecord != null) { - target.mActivityRecord.mImeInsetsFrozenUntilStartInput = false; - } if (mImeInputTarget != target) { ProtoLog.i(WM_DEBUG_IME, "setInputMethodInputTarget %s", target); setImeInputTarget(target); @@ -4158,6 +4172,11 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp .getRawInsetsState().getSourceOrDefaultVisibility(ITYPE_IME)); updateImeControlTarget(); } + // Unfreeze IME insets after the new target updated, in case updateAboveInsetsState may + // deliver unrelated IME insets change to the non-IME requester. + if (target != null && target.mActivityRecord != null) { + target.mActivityRecord.mImeInsetsFrozenUntilStartInput = false; + } } void updateImeControlTarget() { @@ -5477,6 +5496,30 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mSystemGestureExclusionListeners.unregister(listener); } + void updateKeepClearAreas() { + mWmService.mDisplayNotificationController.dispatchKeepClearAreasChanged( + this, getKeepClearAreas()); + } + + /** + * Returns all keep-clear areas from visible windows on this display. + */ + 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; + } + /** * @see IWindowManager#setForwardedInsets */ diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 6e94dfedee35..0745b3b6d971 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -18,7 +18,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_SPLIT_SCREEN_PRIMARY; import static android.view.Display.TYPE_INTERNAL; import static android.view.InsetsState.ITYPE_BOTTOM_MANDATORY_GESTURES; import static android.view.InsetsState.ITYPE_BOTTOM_TAPPABLE_ELEMENT; @@ -1719,8 +1718,7 @@ public class DisplayPolicy { // and mTopIsFullscreen is that mTopIsFullscreen is set only if the window // requests to hide the status bar. Not sure if there is another way that to be the // case though. - if (!topIsFullscreen || mDisplayContent.getDefaultTaskDisplayArea() - .isRootTaskVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)) { + if (!topIsFullscreen) { topAppHidesStatusBar = false; } } @@ -2427,8 +2425,7 @@ public class DisplayPolicy { private int updateSystemBarsLw(WindowState win, int disableFlags) { final TaskDisplayArea defaultTaskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea(); final boolean multiWindowTaskVisible = - defaultTaskDisplayArea.isRootTaskVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) - || defaultTaskDisplayArea.isRootTaskVisible(WINDOWING_MODE_MULTI_WINDOW); + defaultTaskDisplayArea.isRootTaskVisible(WINDOWING_MODE_MULTI_WINDOW); final boolean freeformRootTaskVisible = defaultTaskDisplayArea.isRootTaskVisible(WINDOWING_MODE_FREEFORM); @@ -2854,6 +2851,7 @@ public class DisplayPolicy { } void release() { + mDisplayContent.mTransitionController.unregisterLegacyListener(mAppTransitionListener); mHandler.post(mGestureNavigationSettingsObserver::unregister); mImmersiveModeConfirmation.release(); } 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/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index 4225f214eebd..44818a8c9ee4 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -399,10 +399,11 @@ final class InputMonitor { if (recentsAnimationInputConsumer != null && focus != null) { final RecentsAnimationController recentsAnimationController = mService.getRecentsAnimationController(); + // Apply recents input consumer when the focusing window is in recents animation. final boolean shouldApplyRecentsInputConsumer = (recentsAnimationController != null && recentsAnimationController.shouldApplyInputConsumer(focus.mActivityRecord)) // Shell transitions doesn't use RecentsAnimationController - || getWeak(mActiveRecentsActivity) != null; + || getWeak(mActiveRecentsActivity) != null && focus.inTransition(); if (shouldApplyRecentsInputConsumer) { requestFocus(recentsAnimationInputConsumer.mWindowHandle.token, recentsAnimationInputConsumer.mName); 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/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java index dca0bbda78cf..a049d6547396 100644 --- a/services/core/java/com/android/server/wm/RecentTasks.java +++ b/services/core/java/com/android/server/wm/RecentTasks.java @@ -27,7 +27,6 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; 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_UNDEFINED; import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; @@ -1370,16 +1369,6 @@ class RecentTasks { switch (task.getWindowingMode()) { case WINDOWING_MODE_PINNED: return false; - case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY: - if (DEBUG_RECENTS_TRIM_TASKS) { - Slog.d(TAG, "\ttop=" + task.getRootTask().getTopMostTask()); - } - final Task rootTask = task.getRootTask(); - if (rootTask != null && rootTask.getTopMostTask() == task) { - // Only the non-top task of the primary split screen mode is visible - return false; - } - break; case WINDOWING_MODE_MULTI_WINDOW: // Ignore tasks that are always on top if (task.isAlwaysOnTopWhenVisible()) { 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..1416715e272d 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -490,6 +490,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); diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index cfd1f6e2e36b..2331dc4dff52 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -30,7 +30,6 @@ import static android.app.WindowConfiguration.PINNED_WINDOWING_MODE_ELEVATION_IN import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; 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_UNDEFINED; import static android.app.WindowConfiguration.activityTypeToString; import static android.app.WindowConfiguration.windowingModeToString; @@ -929,13 +928,6 @@ class Task extends TaskFragment { if (!animate) { mTaskSupervisor.mNoAnimActivities.add(topActivity); } - - if (toRootTaskWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY - && moveRootTaskMode == REPARENT_KEEP_ROOT_TASK_AT_FRONT) { - // Move recents to front so it is not behind root home task when going into docked - // mode - mTaskSupervisor.moveRecentsRootTaskToFront(reason); - } } finally { mAtmService.continueWindowLayout(); } @@ -2330,8 +2322,7 @@ class Task extends TaskFragment { final int windowingMode = getWindowingMode(); if (!isActivityTypeStandardOrUndefined() - || windowingMode == WINDOWING_MODE_FULLSCREEN - || (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY && !isResizeable())) { + || windowingMode == WINDOWING_MODE_FULLSCREEN) { return isResizeable() ? rootTask.getRequestedOverrideBounds() : null; } else if (!getWindowConfiguration().persistTaskBounds()) { return rootTask.getRequestedOverrideBounds(); @@ -2716,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); } /** @@ -4638,25 +4633,7 @@ class Task extends TaskFragment { } void moveToFront(String reason, Task task) { - if (inSplitScreenSecondaryWindowingMode()) { - // If the root task is in split-screen secondary mode, we need to make sure we move the - // primary split-screen root task forward in the case it is currently behind a - // fullscreen root task so both halves of the split-screen appear on-top and the - // fullscreen root task isn't cutting between them. - // TODO(b/70677280): This is a workaround until we can fix as part of b/70677280. - final TaskDisplayArea taskDisplayArea = getDisplayArea(); - final Task topFullScreenRootTask = - taskDisplayArea.getTopRootTaskInWindowingMode(WINDOWING_MODE_FULLSCREEN); - if (topFullScreenRootTask != null) { - final Task primarySplitScreenRootTask = - taskDisplayArea.getRootSplitScreenPrimaryTask(); - if (primarySplitScreenRootTask != null - && topFullScreenRootTask.compareTo(primarySplitScreenRootTask) > 0) { - primarySplitScreenRootTask.moveToFrontInner(reason + " splitScreenToTop", - null /* task */); - } - } - } else if (mMoveAdjacentTogether && getAdjacentTaskFragment() != null) { + if (mMoveAdjacentTogether && getAdjacentTaskFragment() != null) { final Task adjacentTask = getAdjacentTaskFragment().asTask(); if (adjacentTask != null) { adjacentTask.moveToFrontInner(reason + " adjacentTaskToTop", null /* task */); 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..c8781ae62384 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -39,6 +39,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 +101,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 +256,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 = @@ -1683,6 +1689,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 +1704,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 +2309,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/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java index 364246e1134e..348cfb62582e 100644 --- a/services/core/java/com/android/server/wm/TaskPositioner.java +++ b/services/core/java/com/android/server/wm/TaskPositioner.java @@ -40,18 +40,17 @@ import android.graphics.Point; import android.graphics.Rect; import android.os.Binder; import android.os.IBinder; -import android.os.Looper; import android.os.Process; import android.os.RemoteException; import android.os.Trace; import android.util.DisplayMetrics; import android.util.Slog; import android.view.BatchedInputEventReceiver; -import android.view.Choreographer; import android.view.InputApplicationHandle; import android.view.InputChannel; import android.view.InputDevice; import android.view.InputEvent; +import android.view.InputEventReceiver; import android.view.InputWindowHandle; import android.view.MotionEvent; import android.view.WindowManager; @@ -73,7 +72,7 @@ class TaskPositioner implements IBinder.DeathRecipient { public static final int RESIZING_HINT_DURATION_MS = 0; private final WindowManagerService mService; - private WindowPositionerEventReceiver mInputEventReceiver; + private InputEventReceiver mInputEventReceiver; private DisplayContent mDisplayContent; private Rect mTmpRect = new Rect(); private int mMinVisibleWidth; @@ -100,103 +99,91 @@ class TaskPositioner implements IBinder.DeathRecipient { InputApplicationHandle mDragApplicationHandle; InputWindowHandle mDragWindowHandle; - private final class WindowPositionerEventReceiver extends BatchedInputEventReceiver { - public WindowPositionerEventReceiver( - InputChannel inputChannel, Looper looper, Choreographer choreographer) { - super(inputChannel, looper, choreographer); + /** Use {@link #create(WindowManagerService)} instead. */ + @VisibleForTesting + TaskPositioner(WindowManagerService service) { + mService = service; + } + + private boolean onInputEvent(InputEvent event) { + // All returns need to be in the try block to make sure the finishInputEvent is + // called correctly. + if (!(event instanceof MotionEvent) + || (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) == 0) { + return false; } + final MotionEvent motionEvent = (MotionEvent) event; + if (mDragEnded) { + // The drag has ended but the clean-up message has not been processed by + // window manager. Drop events that occur after this until window manager + // has a chance to clean-up the input handle. + return true; + } + + final float newX = motionEvent.getRawX(); + final float newY = motionEvent.getRawY(); + + switch (motionEvent.getAction()) { + case MotionEvent.ACTION_DOWN: { + if (DEBUG_TASK_POSITIONING) { + Slog.w(TAG, "ACTION_DOWN @ {" + newX + ", " + newY + "}"); + } + } + break; - @Override - public void onInputEvent(InputEvent event) { - boolean handled = false; - try { - // All returns need to be in the try block to make sure the finishInputEvent is - // called correctly. - if (!(event instanceof MotionEvent) - || (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) == 0) { - return; + case MotionEvent.ACTION_MOVE: { + if (DEBUG_TASK_POSITIONING) { + Slog.w(TAG, "ACTION_MOVE @ {" + newX + ", " + newY + "}"); + } + synchronized (mService.mGlobalLock) { + mDragEnded = notifyMoveLocked(newX, newY); + mTask.getDimBounds(mTmpRect); } - final MotionEvent motionEvent = (MotionEvent) event; - if (mDragEnded) { - // The drag has ended but the clean-up message has not been processed by - // window manager. Drop events that occur after this until window manager - // has a chance to clean-up the input handle. - handled = true; - return; + if (!mTmpRect.equals(mWindowDragBounds)) { + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, + "wm.TaskPositioner.resizeTask"); + mService.mAtmService.resizeTask( + mTask.mTaskId, mWindowDragBounds, RESIZE_MODE_USER); + Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); } + } + break; - final float newX = motionEvent.getRawX(); - final float newY = motionEvent.getRawY(); - - switch (motionEvent.getAction()) { - case MotionEvent.ACTION_DOWN: { - if (DEBUG_TASK_POSITIONING) { - Slog.w(TAG, "ACTION_DOWN @ {" + newX + ", " + newY + "}"); - } - } break; - - case MotionEvent.ACTION_MOVE: { - if (DEBUG_TASK_POSITIONING){ - Slog.w(TAG, "ACTION_MOVE @ {" + newX + ", " + newY + "}"); - } - synchronized (mService.mGlobalLock) { - mDragEnded = notifyMoveLocked(newX, newY); - mTask.getDimBounds(mTmpRect); - } - if (!mTmpRect.equals(mWindowDragBounds)) { - Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, - "wm.TaskPositioner.resizeTask"); - mService.mAtmService.resizeTask( - mTask.mTaskId, mWindowDragBounds, RESIZE_MODE_USER); - Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); - } - } break; - - case MotionEvent.ACTION_UP: { - if (DEBUG_TASK_POSITIONING) { - Slog.w(TAG, "ACTION_UP @ {" + newX + ", " + newY + "}"); - } - mDragEnded = true; - } break; - - case MotionEvent.ACTION_CANCEL: { - if (DEBUG_TASK_POSITIONING) { - Slog.w(TAG, "ACTION_CANCEL @ {" + newX + ", " + newY + "}"); - } - mDragEnded = true; - } break; + case MotionEvent.ACTION_UP: { + if (DEBUG_TASK_POSITIONING) { + Slog.w(TAG, "ACTION_UP @ {" + newX + ", " + newY + "}"); } + mDragEnded = true; + } + break; - if (mDragEnded) { - final boolean wasResizing = mResizing; - synchronized (mService.mGlobalLock) { - endDragLocked(); - mTask.getDimBounds(mTmpRect); - } - if (wasResizing && !mTmpRect.equals(mWindowDragBounds)) { - // We were using fullscreen surface during resizing. Request - // resizeTask() one last time to restore surface to window size. - mService.mAtmService.resizeTask( - mTask.mTaskId, mWindowDragBounds, RESIZE_MODE_USER_FORCED); - } - - // Post back to WM to handle clean-ups. We still need the input - // event handler for the last finishInputEvent()! - mService.mTaskPositioningController.finishTaskPositioning(); + case MotionEvent.ACTION_CANCEL: { + if (DEBUG_TASK_POSITIONING) { + Slog.w(TAG, "ACTION_CANCEL @ {" + newX + ", " + newY + "}"); } - handled = true; - } catch (Exception e) { - Slog.e(TAG, "Exception caught by drag handleMotion", e); - } finally { - finishInputEvent(event, handled); + mDragEnded = true; } + break; } - } - /** Use {@link #create(WindowManagerService)} instead. */ - @VisibleForTesting - TaskPositioner(WindowManagerService service) { - mService = service; + if (mDragEnded) { + final boolean wasResizing = mResizing; + synchronized (mService.mGlobalLock) { + endDragLocked(); + mTask.getDimBounds(mTmpRect); + } + if (wasResizing && !mTmpRect.equals(mWindowDragBounds)) { + // We were using fullscreen surface during resizing. Request + // resizeTask() one last time to restore surface to window size. + mService.mAtmService.resizeTask( + mTask.mTaskId, mWindowDragBounds, RESIZE_MODE_USER_FORCED); + } + + // Post back to WM to handle clean-ups. We still need the input + // event handler for the last finishInputEvent()! + mService.mTaskPositioningController.finishTaskPositioning(); + } + return true; } @VisibleForTesting @@ -221,9 +208,9 @@ class TaskPositioner implements IBinder.DeathRecipient { mDisplayContent = displayContent; mClientChannel = mService.mInputManager.createInputChannel(TAG); - mInputEventReceiver = new WindowPositionerEventReceiver( + mInputEventReceiver = new BatchedInputEventReceiver.SimpleBatchedInputEventReceiver( mClientChannel, mService.mAnimationHandler.getLooper(), - mService.mAnimator.getChoreographer()); + mService.mAnimator.getChoreographer(), this::onInputEvent); mDragApplicationHandle = new InputApplicationHandle(new Binder(), TAG, DEFAULT_DISPATCHING_TIMEOUT_MILLIS); diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index a4771082b3e9..b13c9a9e3e14 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -79,6 +79,7 @@ import android.window.RemoteTransition; import android.window.TransitionInfo; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.graphics.ColorUtils; import com.android.internal.protolog.ProtoLogGroup; import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.function.pooled.PooledLambda; @@ -1271,6 +1272,14 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe change.setAllowEnterPip(topMostActivity != null && topMostActivity.checkEnterPictureInPictureAppOpsState()); } + final ActivityRecord activityRecord = target.asActivityRecord(); + if (activityRecord != null) { + final Task arTask = activityRecord.getTask(); + final int backgroundColor = ColorUtils.setAlphaComponent( + arTask.getTaskDescription().getBackgroundColor(), 255); + change.setBackgroundColor(backgroundColor); + } + out.addChange(change); } diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index ffe146219c6c..fe968ec2e57b 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -462,6 +462,10 @@ class TransitionController { mLegacyListeners.add(listener); } + void unregisterLegacyListener(WindowManagerInternal.AppTransitionListener listener) { + mLegacyListeners.remove(listener); + } + void dispatchLegacyAppTransitionPending() { for (int i = 0; i < mLegacyListeners.size(); ++i) { mLegacyListeners.get(i).onAppTransitionPendingLocked(); diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 137d7f869e4e..400684840467 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -393,6 +393,8 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< if (mParent != null) { mParent.onChildAdded(this); + } else if (mSurfaceAnimator.hasLeash()) { + mSurfaceAnimator.cancelAnimation(); } if (!mReparenting) { onSyncReparent(oldParent, mParent); 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..054904df35ed 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; @@ -172,12 +173,11 @@ 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 +229,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 +281,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 +439,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 +587,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 +713,7 @@ public class WindowManagerService extends IWindowManager.Stub final TaskSnapshotController mTaskSnapshotController; final BlurController mBlurController; + final TaskFpsCallbackController mTaskFpsCallbackController; boolean mIsTouchDevice; boolean mIsFakeTouchDevice; @@ -738,6 +744,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 +1360,7 @@ public class WindowManagerService extends IWindowManager.Stub mStartingSurfaceController = new StartingSurfaceController(this); mBlurController = new BlurController(mContext, mPowerManager); + mTaskFpsCallbackController = new TaskFpsCallbackController(mContext); mAccessibilityController = new AccessibilityController(this); } @@ -2436,23 +2445,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 +3828,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 +4340,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 +4921,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 +4983,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 +5010,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); @@ -6905,6 +6905,7 @@ public class WindowManagerService extends IWindowManager.Stub void setForceDesktopModeOnExternalDisplays(boolean forceDesktopModeOnExternalDisplays) { synchronized (mGlobalLock) { mForceDesktopModeOnExternalDisplays = forceDesktopModeOnExternalDisplays; + mRoot.updateDisplayImePolicyCache(); } } @@ -7063,6 +7064,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 +7337,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 +7693,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 +7731,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 +7834,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 +8188,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 +8205,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 @@ -8720,20 +8729,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 +8787,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/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 79dcbcb23870..455856ce22cd 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -139,6 +139,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub void setWindowManager(WindowManagerService wms) { mTransitionController = new TransitionController(mService, wms.mTaskSnapshotController); + mTransitionController.registerLegacyListener(wms.mActivityManagerAppTransitionNotifier); } TransitionController getTransitionController() { diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 94d4a77a7ecc..236104432488 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; @@ -471,6 +473,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 +1020,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); @@ -4063,6 +4120,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 +4291,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()) { @@ -5985,4 +6046,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..4524bb71d218 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 */, @@ -2186,6 +2199,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( @@ -2370,6 +2395,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/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/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd index 2f4dd57ab15b..baf2ede07fa3 100644 --- a/services/core/xsd/display-device-config/display-device-config.xsd +++ b/services/core/xsd/display-device-config/display-device-config.xsd @@ -58,6 +58,15 @@ <xs:element type="sensorDetails" name="proxSensor"> <xs:annotation name="final"/> </xs:element> + + <!-- Length of the ambient light horizon used to calculate the long & short term + estimates of ambient light in milliseconds.--> + <xs:element type="xs:nonNegativeInteger" name="ambientLightHorizonLong"> + <xs:annotation name="final"/> + </xs:element> + <xs:element type="xs:nonNegativeInteger" name="ambientLightHorizonShort"> + <xs:annotation name="final"/> + </xs:element> </xs:sequence> </xs:complexType> </xs:element> diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt index 5b2b87c3f14e..6f97431b4873 100644 --- a/services/core/xsd/display-device-config/schema/current.txt +++ b/services/core/xsd/display-device-config/schema/current.txt @@ -18,6 +18,8 @@ package com.android.server.display.config { public class DisplayConfiguration { ctor public DisplayConfiguration(); + method public final java.math.BigInteger getAmbientLightHorizonLong(); + method public final java.math.BigInteger getAmbientLightHorizonShort(); method @Nullable public final com.android.server.display.config.DensityMap getDensityMap(); method public com.android.server.display.config.HighBrightnessMode getHighBrightnessMode(); method public final com.android.server.display.config.SensorDetails getLightSensor(); @@ -29,6 +31,8 @@ package com.android.server.display.config { method public final java.math.BigDecimal getScreenBrightnessRampFastIncrease(); method public final java.math.BigDecimal getScreenBrightnessRampSlowDecrease(); method public final java.math.BigDecimal getScreenBrightnessRampSlowIncrease(); + method public final void setAmbientLightHorizonLong(java.math.BigInteger); + method public final void setAmbientLightHorizonShort(java.math.BigInteger); method public final void setDensityMap(@Nullable com.android.server.display.config.DensityMap); method public void setHighBrightnessMode(com.android.server.display.config.HighBrightnessMode); method public final void setLightSensor(com.android.server.display.config.SensorDetails); 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 55ab8c3b1af6..9b87b9d6104b 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java @@ -17,16 +17,21 @@ package com.android.server.devicepolicy; 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; +import android.app.admin.ParcelableResource; import android.content.ComponentName; import android.os.UserHandle; import android.util.Slog; import com.android.server.SystemService; +import java.util.List; + /** * Defines the required interface for IDevicePolicyManager implemenation. * @@ -161,4 +166,27 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub { public boolean isKeyPairGrantedToWifiAuth(String callerPackage, String alias) { return false; } + + @Override + public void setDrawables(@NonNull List<DevicePolicyDrawableResource> drawables){} + + @Override + public void resetDrawables(@NonNull int[] drawableIds){} + + @Override + public ParcelableResource getDrawable( + 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 new file mode 100644 index 000000000000..9a982357afca --- /dev/null +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceManagementResourcesProvider.java @@ -0,0 +1,508 @@ +/* + * 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.devicepolicy; + +import static android.app.admin.DevicePolicyResources.Drawable.Source.UPDATABLE_DRAWABLE_SOURCES; +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; + +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; +import android.util.Log; +import android.util.TypedXmlPullParser; +import android.util.TypedXmlSerializer; +import android.util.Xml; + +import libcore.io.IoUtils; + +import org.xmlpull.v1.XmlPullParserException; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * A helper class for {@link DevicePolicyManagerService} to store/retrieve updated device + * management resources. + */ +class DeviceManagementResourcesProvider { + private static final String TAG = "DevicePolicyManagerService"; + + private static final String UPDATED_RESOURCES_XML = "updated_resources.xml"; + private static final String TAG_ROOT = "root"; + private static final String TAG_DRAWABLE_STYLE_ENTRY = "drawable-style-entry"; + private static final String TAG_DRAWABLE_SOURCE_ENTRY = "drawable-source-entry"; + private static final String ATTR_DRAWABLE_STYLE_SIZE = "drawable-style-size"; + private static final String ATTR_DRAWABLE_SOURCE_SIZE = "drawable-source-size"; + 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; + + DeviceManagementResourcesProvider() { + this(new Injector()); + } + + DeviceManagementResourcesProvider(Injector injector) { + mInjector = requireNonNull(injector); + } + + /** + * Returns {@code false} if no resources were updated. + */ + boolean updateDrawables(@NonNull List<DevicePolicyDrawableResource> drawables) { + boolean updated = false; + for (int i = 0; i < drawables.size(); i++) { + int drawableId = drawables.get(i).getDrawableId(); + int drawableStyle = drawables.get(i).getDrawableStyle(); + int drawableSource = drawables.get(i).getDrawableSource(); + ParcelableResource resource = drawables.get(i).getResource(); + + Objects.requireNonNull(resource, "ParcelableResource must be provided."); + + if (drawableSource == DevicePolicyResources.Drawable.Source.UNDEFINED) { + updated |= updateDrawable(drawableId, drawableStyle, resource); + } else { + updated |= updateDrawableForSource(drawableId, drawableSource, resource); + } + } + if (!updated) { + return false; + } + synchronized (mLock) { + write(); + return true; + } + } + + private boolean updateDrawable( + int drawableId, int drawableStyle, ParcelableResource updatableResource) { + if (!UPDATABLE_DRAWABLE_IDS.contains(drawableId)) { + Log.w(TAG, "Updating a resource for an unknown drawable id " + drawableId); + } + if (!UPDATABLE_DRAWABLE_STYLES.contains(drawableStyle)) { + Log.w(TAG, "Updating a resource for an unknown style id " + drawableStyle); + } + synchronized (mLock) { + if (!mUpdatedDrawablesForStyle.containsKey(drawableId)) { + mUpdatedDrawablesForStyle.put(drawableId, new HashMap<>()); + } + ParcelableResource current = mUpdatedDrawablesForStyle.get(drawableId).get( + drawableStyle); + if (updatableResource.equals(current)) { + return false; + } + mUpdatedDrawablesForStyle.get(drawableId).put(drawableStyle, updatableResource); + return true; + } + } + + // TODO(b/214576716): change this to respect style + private boolean updateDrawableForSource( + int drawableId, int drawableSource, ParcelableResource updatableResource) { + if (!UPDATABLE_DRAWABLE_IDS.contains(drawableId)) { + Log.w(TAG, "Updating a resource for an unknown drawable id " + drawableId); + } + if (!UPDATABLE_DRAWABLE_SOURCES.contains(drawableSource)) { + Log.w(TAG, "Updating a resource for an unknown source id " + drawableSource); + } + synchronized (mLock) { + if (!mUpdatedDrawablesForSource.containsKey(drawableId)) { + mUpdatedDrawablesForSource.put(drawableId, new HashMap<>()); + } + ParcelableResource current = mUpdatedDrawablesForSource.get(drawableId).get( + drawableSource); + if (updatableResource.equals(current)) { + return false; + } + mUpdatedDrawablesForSource.get(drawableId).put(drawableSource, updatableResource); + return true; + } + } + + /** + * Returns {@code false} if no resources were removed. + */ + boolean removeDrawables(@NonNull int[] drawableIds) { + synchronized (mLock) { + boolean removed = false; + for (int i = 0; i < drawableIds.length; i++) { + int drawableId = drawableIds[i]; + removed |= mUpdatedDrawablesForStyle.remove(drawableId) != null + || mUpdatedDrawablesForSource.remove(drawableId) != null; + } + if (!removed) { + return false; + } + write(); + return true; + } + } + + @Nullable + ParcelableResource getDrawable( + int drawableId, int drawableStyle, int drawableSource) { + if (!UPDATABLE_DRAWABLE_IDS.contains(drawableId)) { + Log.w(TAG, "Getting an updated resource for an unknown drawable id " + drawableId); + } + if (!UPDATABLE_DRAWABLE_STYLES.contains(drawableStyle)) { + Log.w(TAG, "Getting an updated resource for an unknown drawable style " + + drawableStyle); + } + if (!UPDATABLE_DRAWABLE_SOURCES.contains(drawableSource)) { + Log.w(TAG, "Getting an updated resource for an unknown drawable Source " + + drawableSource); + } + if (mUpdatedDrawablesForSource.containsKey(drawableId) + && mUpdatedDrawablesForSource.get(drawableId).containsKey(drawableSource)) { + return mUpdatedDrawablesForSource.get(drawableId).get(drawableSource); + } + if (!mUpdatedDrawablesForStyle.containsKey(drawableId)) { + Log.d(TAG, "No updated drawable found for drawable id " + drawableId); + return null; + } + if (mUpdatedDrawablesForStyle.get(drawableId).containsKey(drawableStyle)) { + return mUpdatedDrawablesForStyle.get(drawableId).get(drawableStyle); + } + + if (mUpdatedDrawablesForStyle.get(drawableId).containsKey(Style.DEFAULT)) { + return mUpdatedDrawablesForStyle.get(drawableId).get(Style.DEFAULT); + } + Log.d(TAG, "No updated drawable found for drawable id " + drawableId); + 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(); + } + + void load() { + synchronized (mLock) { + new ResourcesReaderWriter().readFromFileLocked(); + } + } + + private File getResourcesFile() { + return new File(mInjector.environmentGetDataSystemDirectory(), UPDATED_RESOURCES_XML); + } + + private class ResourcesReaderWriter { + private final File mFile; + private ResourcesReaderWriter() { + mFile = getResourcesFile(); + } + + void writeToFileLocked() { + Log.d(TAG, "Writing to " + mFile); + + AtomicFile f = new AtomicFile(mFile); + FileOutputStream outputStream = null; + try { + outputStream = f.startWrite(); + TypedXmlSerializer out = Xml.resolveSerializer(outputStream); + + // Root tag + out.startDocument(null, true); + out.startTag(null, TAG_ROOT); + + // Actual content + writeInner(out); + + // Close root + out.endTag(null, TAG_ROOT); + out.endDocument(); + out.flush(); + + // Commit the content. + f.finishWrite(outputStream); + outputStream = null; + + } catch (IOException e) { + Log.e(TAG, "Exception when writing", e); + if (outputStream != null) { + f.failWrite(outputStream); + } + } + } + + void readFromFileLocked() { + if (!mFile.exists()) { + Log.d(TAG, "" + mFile + " doesn't exist"); + return; + } + + Log.d(TAG, "Reading from " + mFile); + AtomicFile f = new AtomicFile(mFile); + InputStream input = null; + try { + input = f.openRead(); + TypedXmlPullParser parser = Xml.resolvePullParser(input); + + int type; + int depth = 0; + while ((type = parser.next()) != TypedXmlPullParser.END_DOCUMENT) { + switch (type) { + case TypedXmlPullParser.START_TAG: + depth++; + break; + case TypedXmlPullParser.END_TAG: + depth--; + // fallthrough + default: + continue; + } + // Check the root tag + String tag = parser.getName(); + if (depth == 1) { + if (!TAG_ROOT.equals(tag)) { + Log.e(TAG, "Invalid root tag: " + tag); + return; + } + continue; + } + // readInner() will only see START_TAG at depth >= 2. + if (!readInner(parser, depth, tag)) { + return; // Error + } + } + } catch (XmlPullParserException | IOException e) { + Log.e(TAG, "Error parsing resources file", e); + } finally { + IoUtils.closeQuietly(input); + } + } + + void writeInner(TypedXmlSerializer out) throws IOException { + if (mUpdatedDrawablesForStyle != null && !mUpdatedDrawablesForStyle.isEmpty()) { + for (Map.Entry<Integer, Map<Integer, ParcelableResource>> drawableEntry + : mUpdatedDrawablesForStyle.entrySet()) { + out.startTag(/* namespace= */ null, TAG_DRAWABLE_STYLE_ENTRY); + out.attributeInt( + /* namespace= */ null, ATTR_DRAWABLE_ID, drawableEntry.getKey()); + out.attributeInt( + /* namespace= */ null, + ATTR_DRAWABLE_STYLE_SIZE, + drawableEntry.getValue().size()); + int counter = 0; + for (Map.Entry<Integer, ParcelableResource> styleEntry + : drawableEntry.getValue().entrySet()) { + out.attributeInt( + /* namespace= */ null, + ATTR_DRAWABLE_STYLE + (counter++), + styleEntry.getKey()); + styleEntry.getValue().writeToXmlFile(out); + } + out.endTag(/* namespace= */ null, TAG_DRAWABLE_STYLE_ENTRY); + } + } + if (mUpdatedDrawablesForSource != null && !mUpdatedDrawablesForSource.isEmpty()) { + for (Map.Entry<Integer, Map<Integer, ParcelableResource>> drawableEntry + : mUpdatedDrawablesForSource.entrySet()) { + out.startTag(/* namespace= */ null, TAG_DRAWABLE_SOURCE_ENTRY); + out.attributeInt( + /* namespace= */ null, ATTR_DRAWABLE_ID, drawableEntry.getKey()); + out.attributeInt( + /* namespace= */ null, + ATTR_DRAWABLE_SOURCE_SIZE, + drawableEntry.getValue().size()); + int counter = 0; + for (Map.Entry<Integer, ParcelableResource> sourceEntry + : drawableEntry.getValue().entrySet()) { + out.attributeInt( + /* namespace= */ null, + ATTR_DRAWABLE_SOURCE + (counter++), + sourceEntry.getKey()); + sourceEntry.getValue().writeToXmlFile(out); + } + 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( + TypedXmlPullParser parser, int depth, String tag) + throws XmlPullParserException, IOException { + if (depth > 2) { + return true; // Ignore + } + switch (tag) { + case TAG_DRAWABLE_STYLE_ENTRY: + int drawableId = parser.getAttributeInt( + /* namespace= */ null, ATTR_DRAWABLE_ID); + mUpdatedDrawablesForStyle.put( + drawableId, + new HashMap<>()); + int size = parser.getAttributeInt( + /* namespace= */ null, ATTR_DRAWABLE_STYLE_SIZE); + for (int i = 0; i < size; i++) { + int style = parser.getAttributeInt( + /* namespace= */ null, ATTR_DRAWABLE_STYLE + i); + mUpdatedDrawablesForStyle.get(drawableId).put( + style, + ParcelableResource.createFromXml(parser)); + } + break; + case TAG_DRAWABLE_SOURCE_ENTRY: + drawableId = parser.getAttributeInt( + /* namespace= */ null, ATTR_DRAWABLE_ID); + mUpdatedDrawablesForSource.put(drawableId, new HashMap<>()); + size = parser.getAttributeInt( + /* namespace= */ null, ATTR_DRAWABLE_SOURCE_SIZE); + for (int i = 0; i < size; i++) { + int source = parser.getAttributeInt( + /* namespace= */ null, ATTR_DRAWABLE_SOURCE + i); + mUpdatedDrawablesForSource.get(drawableId).put( + source, + 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; + } + return true; + } + } + + public static class Injector { + File environmentGetDataSystemDirectory() { + return Environment.getDataSystemDirectory(); + } + } +} diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 6caf7317ed0a..cbc08b49f6b4 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -26,6 +26,7 @@ import static android.app.AppOpsManager.MODE_DEFAULT; import static android.app.admin.DeviceAdminReceiver.ACTION_COMPLIANCE_ACKNOWLEDGEMENT_REQUIRED; import static android.app.admin.DeviceAdminReceiver.EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE; import static android.app.admin.DevicePolicyManager.ACTION_CHECK_POLICY_COMPLIANCE; +import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED; import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE; import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE; import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_USER; @@ -56,6 +57,9 @@ import static android.app.admin.DevicePolicyManager.DELEGATION_PACKAGE_ACCESS; import static android.app.admin.DevicePolicyManager.DELEGATION_PERMISSION_GRANT; 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; @@ -161,6 +165,7 @@ import android.app.StatusBarManager; import android.app.admin.DeviceAdminInfo; import android.app.admin.DeviceAdminReceiver; import android.app.admin.DevicePolicyCache; +import android.app.admin.DevicePolicyDrawableResource; import android.app.admin.DevicePolicyEventLogger; import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManager.DeviceOwnerType; @@ -171,12 +176,14 @@ 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; import android.app.admin.ManagedProfileProvisioningParams; import android.app.admin.NetworkEvent; import android.app.admin.ParcelableGranteeMap; +import android.app.admin.ParcelableResource; import android.app.admin.PasswordMetrics; import android.app.admin.PasswordPolicy; import android.app.admin.SecurityLog; @@ -716,6 +723,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // Guarded by mHandler private @UserIdInt int mNetworkLoggingNotificationUserId = UserHandle.USER_NULL; + private final DeviceManagementResourcesProvider mDeviceManagementResourcesProvider; + private static final boolean ENABLE_LOCK_GUARD = true; /** @@ -1740,6 +1749,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { void setDevicePolicySafetyChecker(DevicePolicySafetyChecker safetyChecker) { mSafetyChecker = safetyChecker; } + + DeviceManagementResourcesProvider getDeviceManagementResourcesProvider() { + return new DeviceManagementResourcesProvider(); + } } /** @@ -1792,6 +1805,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mTransferOwnershipMetadataManager = mInjector.newTransferOwnershipMetadataManager(); mBugreportCollectionManager = new RemoteBugreportManager(this, mInjector); + mDeviceManagementResourcesProvider = mInjector.getDeviceManagementResourcesProvider(); + // "Lite" interface is available even when the device doesn't have the feature LocalServices.addService(DevicePolicyManagerLiteInternal.class, mLocalService); if (!mHasFeature) { @@ -1838,6 +1853,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { loadOwners(); performPolicyVersionUpgrade(); + + mDeviceManagementResourcesProvider.load(); } /** @@ -13214,12 +13231,17 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { /** * @param restriction The restriction enforced by admin. It could be any user restriction or - * policy like {@link DevicePolicyManager#POLICY_DISABLE_CAMERA} and - * {@link DevicePolicyManager#POLICY_DISABLE_SCREEN_CAPTURE}. + * policy like {@link DevicePolicyManager#POLICY_DISABLE_CAMERA}, + * {@link DevicePolicyManager#POLICY_DISABLE_SCREEN_CAPTURE} and {@link + * DevicePolicyManager#POLICY_SUSPEND_PACKAGES}. */ private Bundle getEnforcingAdminAndUserDetailsInternal(int userId, String restriction) { Bundle result = null; - if (restriction == null) { + + // For POLICY_SUSPEND_PACKAGES return PO or DO to keep the behavior same as + // before the bug fix for b/192245204. + if (restriction == null || DevicePolicyManager.POLICY_SUSPEND_PACKAGES.equals( + restriction)) { ComponentName profileOwner = mOwners.getProfileOwnerComponent(userId); if (profileOwner != null) { result = new Bundle(); @@ -17956,4 +17978,211 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { && mInjector.getUsbManager().getUsbHalVersion() >= UsbManager.USB_HAL_V1_3 ); } + + 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( + android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES)); + + Objects.requireNonNull(drawables, "drawables must be provided."); + + mInjector.binderWithCleanCallingIdentity(() -> { + if (mDeviceManagementResourcesProvider.updateDrawables(drawables)) { + sendDrawableUpdatedBroadcast( + drawables.stream().mapToInt(d -> d.getDrawableId()).toArray()); + } + }); + } + + @Override + public void resetDrawables(@NonNull int[] drawableIds) { + Preconditions.checkCallAuthorization(hasCallingOrSelfPermission( + android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES)); + + Objects.requireNonNull(drawableIds, "drawableIds must be provided."); + + mInjector.binderWithCleanCallingIdentity(() -> { + if (mDeviceManagementResourcesProvider.removeDrawables(drawableIds)) { + sendDrawableUpdatedBroadcast(drawableIds); + } + }); + } + + @Override + public ParcelableResource getDrawable(int drawableId, int drawableStyle, int drawableSource) { + return mInjector.binderWithCleanCallingIdentity(() -> + mDeviceManagementResourcesProvider.getDrawable( + drawableId, drawableStyle, drawableSource)); + } + + private void sendDrawableUpdatedBroadcast(int[] drawableIds) { + final Intent intent = new Intent(ACTION_DEVICE_POLICY_RESOURCE_UPDATED); + intent.putExtra(EXTRA_RESOURCE_ID, drawableIds); + intent.putExtra(EXTRA_RESOURCE_TYPE_DRAWABLE, /* 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); + } + } + + @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 ee8288e3b2df..ad8753d4c136 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; @@ -115,7 +117,6 @@ import com.android.server.biometrics.sensors.iris.IrisService; import com.android.server.broadcastradio.BroadcastRadioService; import com.android.server.camera.CameraServiceProxy; import com.android.server.clipboard.ClipboardService; -import com.android.server.communal.CommunalManagerService; import com.android.server.compat.PlatformCompat; import com.android.server.compat.PlatformCompatNative; import com.android.server.connectivity.PacProxyService; @@ -152,7 +153,6 @@ import com.android.server.os.NativeTombstoneManagerService; import com.android.server.os.SchedulingPolicyService; import com.android.server.people.PeopleService; import com.android.server.pm.ApexManager; -import com.android.server.pm.ApexSystemServiceInfo; import com.android.server.pm.CrossProfileAppsService; import com.android.server.pm.DataLoaderManagerService; import com.android.server.pm.DynamicCodeLoggingService; @@ -196,7 +196,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; @@ -221,8 +221,8 @@ import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Date; import java.util.LinkedList; -import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Timer; import java.util.TreeSet; import java.util.concurrent.CountDownLatch; @@ -261,12 +261,10 @@ public final class SystemServer implements Dumpable { "com.android.server.companion.virtual.VirtualDeviceManagerService"; private static final String STATS_COMPANION_APEX_PATH = "/apex/com.android.os.statsd/javalib/service-statsd.jar"; - private static final String SCHEDULING_APEX_PATH = - "/apex/com.android.scheduling/javalib/service-scheduling.jar"; - private static final String REBOOT_READINESS_LIFECYCLE_CLASS = - "com.android.server.scheduling.RebootReadinessManagerService$Lifecycle"; 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 = @@ -277,6 +275,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 = @@ -409,8 +409,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"; @@ -1460,7 +1458,7 @@ public final class SystemServer implements Dumpable { // TelecomLoader hooks into classes with defined HFP logic, // so check for either telephony or microphone. if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_MICROPHONE) || - mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) { + mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) { t.traceBegin("StartTelecomLoaderService"); mSystemServiceManager.startService(TelecomLoaderService.class); t.traceEnd(); @@ -1468,7 +1466,7 @@ public final class SystemServer implements Dumpable { t.traceBegin("StartTelephonyRegistry"); telephonyRegistry = new TelephonyRegistry( - context, new TelephonyRegistry.ConfigurationProvider()); + context, new TelephonyRegistry.ConfigurationProvider()); ServiceManager.addService("telephony.registry", telephonyRegistry); t.traceEnd(); @@ -1813,6 +1811,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"); @@ -1907,6 +1906,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); } @@ -1982,6 +1982,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 @@ -2373,8 +2383,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(); } @@ -2547,12 +2557,6 @@ public final class SystemServer implements Dumpable { STATS_COMPANION_LIFECYCLE_CLASS, STATS_COMPANION_APEX_PATH); t.traceEnd(); - // Reboot Readiness - t.traceBegin("StartRebootReadinessManagerService"); - mSystemServiceManager.startServiceFromJar( - REBOOT_READINESS_LIFECYCLE_CLASS, SCHEDULING_APEX_PATH); - t.traceEnd(); - // Statsd pulled atoms t.traceBegin("StartStatsPullAtomService"); mSystemServiceManager.startService(STATS_PULL_ATOM_SERVICE_CLASS); @@ -2570,8 +2574,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) { @@ -2758,12 +2761,6 @@ public final class SystemServer implements Dumpable { mSystemServiceManager.startService(APP_COMPAT_OVERRIDES_SERVICE_CLASS); t.traceEnd(); - if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_COMMUNAL_MODE)) { - t.traceBegin("CommunalManagerService"); - mSystemServiceManager.startService(CommunalManagerService.class); - t.traceEnd(); - } - // These are needed to propagate to the runnable below. final NetworkManagementService networkManagementF = networkManagement; final NetworkStatsService networkStatsF = networkStats; @@ -2999,9 +2996,7 @@ public final class SystemServer implements Dumpable { t.traceEnd(); t.traceBegin("MakeTelephonyRegistryReady"); try { - if (telephonyRegistryF != null) { - telephonyRegistryF.systemRunning(); - } + if (telephonyRegistryF != null) telephonyRegistryF.systemRunning(); } catch (Throwable e) { reportWtf("Notifying TelephonyRegistry running", e); } @@ -3066,12 +3061,10 @@ public final class SystemServer implements Dumpable { */ private void startApexServices(@NonNull TimingsTraceAndSlog t) { t.traceBegin("startApexServices"); - // TODO(b/192880996): get the list from "android" package, once the manifest entries - // are migrated to system manifest. - List<ApexSystemServiceInfo> services = ApexManager.getInstance().getApexSystemServices(); - for (ApexSystemServiceInfo info : services) { - String name = info.getName(); - String jarPath = info.getJarPath(); + Map<String, String> services = ApexManager.getInstance().getApexSystemServices(); + // TODO(satayev): introduce android:order for services coming the same apexes + for (String name : new TreeSet<>(services.keySet())) { + String jarPath = services.get(name); t.traceBegin("starting " + name); if (TextUtils.isEmpty(jarPath)) { mSystemServiceManager.startService(name); @@ -3171,6 +3164,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..6e724792b6e9 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); } } @@ -797,11 +892,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 +1084,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/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt index 8203c1b731c4..5220c8fa7a72 100644 --- a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt +++ b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt @@ -20,7 +20,7 @@ import android.app.PropertyInvalidatedCache import android.content.ComponentName import android.content.Context import android.content.pm.PackageManager -import android.content.pm.parsing.component.ParsedActivity +import com.android.server.pm.pkg.component.ParsedActivity import android.os.Binder import android.os.UserHandle import android.util.ArrayMap diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/UsesStaticLibrary/Android.bp b/services/tests/PackageManagerServiceTests/host/test-apps/UsesStaticLibrary/Android.bp index ebad5afac625..93d70bb6cdba 100644 --- a/services/tests/PackageManagerServiceTests/host/test-apps/UsesStaticLibrary/Android.bp +++ b/services/tests/PackageManagerServiceTests/host/test-apps/UsesStaticLibrary/Android.bp @@ -24,7 +24,7 @@ package { android_test_helper_app { name: "PackageManagerTestAppDeclaresStaticLibrary", manifest: "AndroidManifestDeclaresStaticLibrary.xml", - certificate: ":FrameworksCoreTests_keyset_A_cert", + certificate: ":FrameworksServicesTests_keyset_A_cert", } android_test_helper_app { 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 d7e3195765ef..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 @@ -23,8 +23,7 @@ import android.content.pm.FeatureGroupInfo import android.content.pm.FeatureInfo import android.content.pm.PackageManager import android.content.pm.SigningDetails -import android.content.pm.parsing.ParsingPackage -import android.content.pm.parsing.component.* +import com.android.server.pm.pkg.parsing.ParsingPackage import android.net.Uri import android.os.Bundle import android.os.Parcelable @@ -34,6 +33,18 @@ import android.util.SparseIntArray import com.android.internal.R import com.android.server.pm.parsing.pkg.AndroidPackage import com.android.server.pm.parsing.pkg.PackageImpl +import com.android.server.pm.pkg.component.ParsedActivityImpl +import com.android.server.pm.pkg.component.ParsedApexSystemServiceImpl +import com.android.server.pm.pkg.component.ParsedAttributionImpl +import com.android.server.pm.pkg.component.ParsedComponentImpl +import com.android.server.pm.pkg.component.ParsedInstrumentationImpl +import com.android.server.pm.pkg.component.ParsedIntentInfoImpl +import com.android.server.pm.pkg.component.ParsedPermissionGroupImpl +import com.android.server.pm.pkg.component.ParsedPermissionImpl +import com.android.server.pm.pkg.component.ParsedProcessImpl +import com.android.server.pm.pkg.component.ParsedProviderImpl +import com.android.server.pm.pkg.component.ParsedServiceImpl +import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl import com.android.server.testutils.mockThrowOnUnmocked import com.android.server.testutils.whenever import java.security.KeyPairGenerator @@ -130,6 +141,7 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag AndroidPackage::getLabelRes, AndroidPackage::getLargestWidthLimitDp, AndroidPackage::getLogo, + AndroidPackage::getLocaleConfigRes, AndroidPackage::getManageSpaceActivityName, AndroidPackage::getMemtagMode, AndroidPackage::getMinSdkVersion, @@ -280,7 +292,13 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag PackageImpl::addAttribution, Triple("testTag", 13, listOf("testInherit")), transformGet = { it.singleOrNull()?.let { Triple(it.tag, it.label, it.inheritFrom) } }, - transformSet = { it?.let { ParsedAttributionImpl(it.first, it.second, it.third) } } + transformSet = { it?.let { + ParsedAttributionImpl( + it.first, + it.second, + it.third + ) + } } ), getSetByValue2( AndroidPackage::getKeySetMapping, @@ -293,12 +311,14 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag PackageImpl::addPermissionGroup, "test.permission.GROUP", transformGet = { it.singleOrNull()?.name }, - transformSet = { ParsedPermissionGroupImpl().apply { setName(it) } } + transformSet = { ParsedPermissionGroupImpl() + .apply { setName(it) } } ), getSetByValue2( AndroidPackage::getPreferredActivityFilters, PackageImpl::addPreferredActivityFilter, - "TestClassName" to ParsedIntentInfoImpl().apply { + "TestClassName" to ParsedIntentInfoImpl() + .apply { intentFilter.apply { addDataScheme("http") addDataAuthority("test.pm.server.android.com", null) @@ -347,42 +367,48 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag PackageImpl::addActivity, "TestActivityName", transformGet = { it.singleOrNull()?.name.orEmpty() }, - transformSet = { ParsedActivityImpl().apply { name = it }.withMimeGroups() } + transformSet = { ParsedActivityImpl() + .apply { name = it }.withMimeGroups() } ), getSetByValue( AndroidPackage::getApexSystemServices, PackageImpl::addApexSystemService, "TestApexSystemServiceName", transformGet = { it.singleOrNull()?.name.orEmpty() }, - transformSet = { ParsedApexSystemServiceImpl().apply { name = it } } + transformSet = { ParsedApexSystemServiceImpl() + .apply { name = it } } ), getSetByValue( AndroidPackage::getReceivers, PackageImpl::addReceiver, "TestReceiverName", transformGet = { it.singleOrNull()?.name.orEmpty() }, - transformSet = { ParsedActivityImpl().apply { name = it }.withMimeGroups() } + transformSet = { ParsedActivityImpl() + .apply { name = it }.withMimeGroups() } ), getSetByValue( AndroidPackage::getServices, PackageImpl::addService, "TestServiceName", transformGet = { it.singleOrNull()?.name.orEmpty() }, - transformSet = { ParsedServiceImpl().apply { name = it }.withMimeGroups() } + transformSet = { ParsedServiceImpl() + .apply { name = it }.withMimeGroups() } ), getSetByValue( AndroidPackage::getProviders, PackageImpl::addProvider, "TestProviderName", transformGet = { it.singleOrNull()?.name.orEmpty() }, - transformSet = { ParsedProviderImpl().apply { name = it }.withMimeGroups() } + transformSet = { ParsedProviderImpl() + .apply { name = it }.withMimeGroups() } ), getSetByValue( AndroidPackage::getInstrumentations, PackageImpl::addInstrumentation, "TestInstrumentationName", transformGet = { it.singleOrNull()?.name.orEmpty() }, - transformSet = { ParsedInstrumentationImpl().apply { name = it } } + transformSet = { ParsedInstrumentationImpl() + .apply { name = it } } ), getSetByValue( AndroidPackage::getConfigPreferences, @@ -407,7 +433,8 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag PackageImpl::addPermission, "test.PERMISSION", transformGet = { it.singleOrNull()?.name.orEmpty() }, - transformSet = { ParsedPermissionImpl().apply { name = it } } + transformSet = { ParsedPermissionImpl() + .apply { name = it } } ), getSetByValue( AndroidPackage::getUsesPermissions, @@ -418,7 +445,12 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag it.filterNot { it.name == "test.implicit.PERMISSION" } .singleOrNull()?.name.orEmpty() }, - transformSet = { ParsedUsesPermissionImpl(it, 0) } + transformSet = { + ParsedUsesPermissionImpl( + it, + 0 + ) + } ), getSetByValue( AndroidPackage::getRequestedFeatures, @@ -443,7 +475,8 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag getSetByValue( AndroidPackage::getProcesses, PackageImpl::setProcesses, - mapOf("testProcess" to ParsedProcessImpl().apply { name = "testProcessName" }), + mapOf("testProcess" to ParsedProcessImpl() + .apply { name = "testProcessName" }), compare = { first, second -> equalBy( first, second, @@ -470,6 +503,11 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag PackageManager.Property::getString ) } + ), + getSetByValue( + AndroidPackage::shouldInheritKeyStoreKeys, + ParsingPackage::setInheritKeyStoreKeys, + true ) ) diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt index 8170acfb02b7..a89b717fd90d 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt @@ -17,8 +17,8 @@ package com.android.server.pm.test.parsing.parcelling import android.content.pm.ActivityInfo -import android.content.pm.parsing.component.ParsedActivity -import android.content.pm.parsing.component.ParsedActivityImpl +import com.android.server.pm.pkg.component.ParsedActivity +import com.android.server.pm.pkg.component.ParsedActivityImpl import kotlin.contracts.ExperimentalContracts @ExperimentalContracts @@ -27,7 +27,8 @@ class ParsedActivityTest : ParsedMainComponentTest( ParsedActivityImpl::class ) { - override val defaultImpl = ParsedActivityImpl() + override val defaultImpl = + ParsedActivityImpl() override val creator = ParsedActivityImpl.CREATOR override val mainComponentSubclassBaseParams = listOf( diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedAttributionTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedAttributionTest.kt index 503301b5151b..4e44e96aa710 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedAttributionTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedAttributionTest.kt @@ -16,15 +16,21 @@ package com.android.server.pm.test.parsing.parcelling -import android.content.pm.parsing.component.ParsedAttribution -import android.content.pm.parsing.component.ParsedAttributionImpl +import com.android.server.pm.pkg.component.ParsedAttribution +import com.android.server.pm.pkg.component.ParsedAttributionImpl import kotlin.contracts.ExperimentalContracts @ExperimentalContracts -class ParsedAttributionTest : ParcelableComponentTest(ParsedAttribution::class, +class ParsedAttributionTest : ParcelableComponentTest( + ParsedAttribution::class, ParsedAttributionImpl::class) { - override val defaultImpl = ParsedAttributionImpl("", 0, emptyList()) + override val defaultImpl = + ParsedAttributionImpl( + "", + 0, + emptyList() + ) override val creator = ParsedAttributionImpl.CREATOR override val baseParams = listOf( diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedComponentTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedComponentTest.kt index e978dd389543..058f6d69f3e7 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedComponentTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedComponentTest.kt @@ -17,10 +17,9 @@ package com.android.server.pm.test.parsing.parcelling import android.content.pm.PackageManager -import android.content.pm.parsing.component.ParsedComponent -import android.content.pm.parsing.component.ParsedComponentImpl -import android.content.pm.parsing.component.ParsedIntentInfo -import android.content.pm.parsing.component.ParsedIntentInfoImpl +import com.android.server.pm.pkg.component.ParsedComponent +import com.android.server.pm.pkg.component.ParsedComponentImpl +import com.android.server.pm.pkg.component.ParsedIntentInfoImpl import android.os.Bundle import android.os.Parcelable import kotlin.contracts.ExperimentalContracts @@ -65,7 +64,8 @@ abstract class ParsedComponentTest(getterType: KClass<*>, setterType: KClass<out ParsedComponentImpl::addIntent, "TestLabel", transformGet = { it.singleOrNull()?.nonLocalizedLabel }, - transformSet = { ParsedIntentInfoImpl().setNonLocalizedLabel(it!!) }, + transformSet = { ParsedIntentInfoImpl() + .setNonLocalizedLabel(it!!) }, ), getSetByValue( ParsedComponent::getProperties, diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedInstrumentationTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedInstrumentationTest.kt index f15b911294b3..eeb30b70c143 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedInstrumentationTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedInstrumentationTest.kt @@ -16,8 +16,8 @@ package com.android.server.pm.test.parsing.parcelling -import android.content.pm.parsing.component.ParsedInstrumentation -import android.content.pm.parsing.component.ParsedInstrumentationImpl +import com.android.server.pm.pkg.component.ParsedInstrumentation +import com.android.server.pm.pkg.component.ParsedInstrumentationImpl import kotlin.contracts.ExperimentalContracts @ExperimentalContracts @@ -26,7 +26,8 @@ class ParsedInstrumentationTest : ParsedComponentTest( ParsedInstrumentationImpl::class ) { - override val defaultImpl = ParsedInstrumentationImpl() + override val defaultImpl = + ParsedInstrumentationImpl() override val creator = ParsedInstrumentationImpl.CREATOR override val subclassBaseParams = listOf( diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedIntentInfoTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedIntentInfoTest.kt index f04e85128c14..f27a51f63049 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedIntentInfoTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedIntentInfoTest.kt @@ -16,8 +16,8 @@ package com.android.server.pm.test.parsing.parcelling -import android.content.pm.parsing.component.ParsedIntentInfo -import android.content.pm.parsing.component.ParsedIntentInfoImpl +import com.android.server.pm.pkg.component.ParsedIntentInfo +import com.android.server.pm.pkg.component.ParsedIntentInfoImpl import android.os.Parcelable import android.os.PatternMatcher import kotlin.contracts.ExperimentalContracts @@ -28,7 +28,8 @@ class ParsedIntentInfoTest : ParcelableComponentTest( ParsedIntentInfoImpl::class, ) { - override val defaultImpl = ParsedIntentInfoImpl() + override val defaultImpl = + ParsedIntentInfoImpl() override val creator = ParsedIntentInfoImpl.CREATOR override val excludedMethods = listOf( @@ -43,7 +44,8 @@ class ParsedIntentInfoTest : ParcelableComponentTest( ParsedIntentInfo::getNonLocalizedLabel, ) - override fun initialObject() = ParsedIntentInfoImpl().apply { + override fun initialObject() = ParsedIntentInfoImpl() + .apply { intentFilter.apply { addAction("test.ACTION") addDataAuthority("testAuthority", "404") diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedMainComponentTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedMainComponentTest.kt index 214734a5fdbe..a0d8c44899d8 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedMainComponentTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedMainComponentTest.kt @@ -16,9 +16,8 @@ package com.android.server.pm.test.parsing.parcelling -import android.content.pm.parsing.component.ParsedMainComponent -import android.content.pm.parsing.component.ParsedMainComponentImpl -import android.content.pm.parsing.component.ParsedService +import com.android.server.pm.pkg.component.ParsedMainComponent +import com.android.server.pm.pkg.component.ParsedMainComponentImpl import android.os.Parcelable import java.util.Arrays import kotlin.contracts.ExperimentalContracts diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt index f876ed05e0e1..57562ef8588c 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt @@ -16,8 +16,8 @@ package com.android.server.pm.test.parsing.parcelling -import android.content.pm.parsing.component.ParsedPermissionGroup -import android.content.pm.parsing.component.ParsedPermissionGroupImpl +import com.android.server.pm.pkg.component.ParsedPermissionGroup +import com.android.server.pm.pkg.component.ParsedPermissionGroupImpl import kotlin.contracts.ExperimentalContracts @ExperimentalContracts @@ -26,7 +26,8 @@ class ParsedPermissionGroupTest : ParsedComponentTest( ParsedPermissionGroupImpl::class, ) { - override val defaultImpl = ParsedPermissionGroupImpl() + override val defaultImpl = + ParsedPermissionGroupImpl() override val creator = ParsedPermissionGroupImpl.CREATOR override val subclassBaseParams = listOf( diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionTest.kt index 6f48e2442e56..c72a44e4c4e0 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionTest.kt @@ -16,10 +16,10 @@ package com.android.server.pm.test.parsing.parcelling -import android.content.pm.parsing.component.ParsedPermission -import android.content.pm.parsing.component.ParsedPermissionGroup -import android.content.pm.parsing.component.ParsedPermissionGroupImpl -import android.content.pm.parsing.component.ParsedPermissionImpl +import com.android.server.pm.pkg.component.ParsedPermission +import com.android.server.pm.pkg.component.ParsedPermissionGroup +import com.android.server.pm.pkg.component.ParsedPermissionGroupImpl +import com.android.server.pm.pkg.component.ParsedPermissionImpl import kotlin.contracts.ExperimentalContracts @ExperimentalContracts @@ -28,7 +28,8 @@ class ParsedPermissionTest : ParsedComponentTest( ParsedPermissionImpl::class ) { - override val defaultImpl = ParsedPermissionImpl() + override val defaultImpl = + ParsedPermissionImpl() override val creator = ParsedPermissionImpl.CREATOR override val subclassExcludedMethods = listOf( @@ -53,7 +54,8 @@ class ParsedPermissionTest : ParsedComponentTest( getSetByValue( ParsedPermission::getParsedPermissionGroup, ParsedPermissionImpl::setParsedPermissionGroup, - ParsedPermissionGroupImpl().apply { name = "test.permission.group" }, + ParsedPermissionGroupImpl() + .apply { name = "test.permission.group" }, compare = { first, second -> equalBy(first, second, ParsedPermissionGroup::getName) } ), ) diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt index 005d3e81d3a3..8b9361a31d0a 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt @@ -16,15 +16,16 @@ package com.android.server.pm.test.parsing.parcelling -import android.content.pm.parsing.component.ParsedProcess -import android.content.pm.parsing.component.ParsedProcessImpl +import com.android.server.pm.pkg.component.ParsedProcess +import com.android.server.pm.pkg.component.ParsedProcessImpl import android.util.ArrayMap import kotlin.contracts.ExperimentalContracts @ExperimentalContracts class ParsedProcessTest : ParcelableComponentTest(ParsedProcess::class, ParsedProcessImpl::class) { - override val defaultImpl = ParsedProcessImpl() + override val defaultImpl = + ParsedProcessImpl() override val creator = ParsedProcessImpl.CREATOR override val excludedMethods = listOf( diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt index 78e4b796b44f..037da24a3304 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt @@ -17,15 +17,16 @@ package com.android.server.pm.test.parsing.parcelling import android.content.pm.PathPermission -import android.content.pm.parsing.component.ParsedProvider -import android.content.pm.parsing.component.ParsedProviderImpl +import com.android.server.pm.pkg.component.ParsedProvider +import com.android.server.pm.pkg.component.ParsedProviderImpl import android.os.PatternMatcher import kotlin.contracts.ExperimentalContracts @ExperimentalContracts class ParsedProviderTest : ParsedMainComponentTest(ParsedProvider::class, ParsedProviderImpl::class) { - override val defaultImpl = ParsedProviderImpl() + override val defaultImpl = + ParsedProviderImpl() override val creator = ParsedProviderImpl.CREATOR override val mainComponentSubclassBaseParams = listOf( diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt index 9363aa37360c..e2c9439df9cf 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt @@ -16,14 +16,15 @@ package com.android.server.pm.test.parsing.parcelling -import android.content.pm.parsing.component.ParsedService -import android.content.pm.parsing.component.ParsedServiceImpl +import com.android.server.pm.pkg.component.ParsedService +import com.android.server.pm.pkg.component.ParsedServiceImpl import kotlin.contracts.ExperimentalContracts @ExperimentalContracts class ParsedServiceTest : ParsedMainComponentTest(ParsedService::class, ParsedServiceImpl::class) { - override val defaultImpl = ParsedServiceImpl() + override val defaultImpl = + ParsedServiceImpl() override val creator = ParsedServiceImpl.CREATOR override val mainComponentSubclassBaseParams = listOf( diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedUsesPermissionTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedUsesPermissionTest.kt index 81e800f2d6d2..ad607366967f 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedUsesPermissionTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedUsesPermissionTest.kt @@ -16,8 +16,8 @@ package com.android.server.pm.test.parsing.parcelling -import android.content.pm.parsing.component.ParsedUsesPermission -import android.content.pm.parsing.component.ParsedUsesPermissionImpl +import com.android.server.pm.pkg.component.ParsedUsesPermission +import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl import kotlin.contracts.ExperimentalContracts @ExperimentalContracts @@ -26,7 +26,8 @@ class ParsedUsesPermissionTest : ParcelableComponentTest( ParsedUsesPermissionImpl::class ) { - override val defaultImpl = ParsedUsesPermissionImpl("", 0) + override val defaultImpl = + ParsedUsesPermissionImpl("", 0) override val creator = ParsedUsesPermissionImpl.CREATOR override val baseParams = listOf( @@ -34,5 +35,6 @@ class ParsedUsesPermissionTest : ParcelableComponentTest( ParsedUsesPermission::getUsesPermissionFlags ) - override fun initialObject() = ParsedUsesPermissionImpl("", 0) + override fun initialObject() = + ParsedUsesPermissionImpl("", 0) } diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt index 33234d52c9ff..652dc38fa6ed 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt @@ -18,8 +18,8 @@ package com.android.server.pm.test.verify.domain import android.content.Intent import android.content.pm.ApplicationInfo -import android.content.pm.parsing.component.ParsedActivityImpl -import android.content.pm.parsing.component.ParsedIntentInfoImpl +import com.android.server.pm.pkg.component.ParsedActivityImpl +import com.android.server.pm.pkg.component.ParsedIntentInfoImpl import android.os.Build import android.os.PatternMatcher import android.util.ArraySet @@ -94,7 +94,8 @@ class DomainVerificationCollectorTest { val activityList = listOf( ParsedActivityImpl().apply { addIntent( - ParsedIntentInfoImpl().apply { + ParsedIntentInfoImpl() + .apply { intentFilter.apply { addAction(Intent.ACTION_VIEW) addCategory(Intent.CATEGORY_BROWSABLE) @@ -110,7 +111,8 @@ class DomainVerificationCollectorTest { }, ParsedActivityImpl().apply { addIntent( - ParsedIntentInfoImpl().apply { + ParsedIntentInfoImpl() + .apply { intentFilter.apply { setAutoVerify(true) addAction(Intent.ACTION_VIEW) @@ -270,7 +272,8 @@ class DomainVerificationCollectorTest { val activityList = listOf( ParsedActivityImpl().apply { addIntent( - ParsedIntentInfoImpl().apply { + ParsedIntentInfoImpl() + .apply { intentFilter.apply { setAutoVerify(autoVerify) addAction(Intent.ACTION_VIEW) @@ -285,7 +288,8 @@ class DomainVerificationCollectorTest { } ) addIntent( - ParsedIntentInfoImpl().apply { + ParsedIntentInfoImpl() + .apply { intentFilter.apply { addAction(Intent.ACTION_VIEW) addCategory(Intent.CATEGORY_BROWSABLE) @@ -300,7 +304,8 @@ class DomainVerificationCollectorTest { }, ParsedActivityImpl().apply { addIntent( - ParsedIntentInfoImpl().apply { + ParsedIntentInfoImpl() + .apply { intentFilter.apply { setAutoVerify(autoVerify) addAction(Intent.ACTION_VIEW) @@ -316,7 +321,8 @@ class DomainVerificationCollectorTest { }, ParsedActivityImpl().apply { addIntent( - ParsedIntentInfoImpl().apply { + ParsedIntentInfoImpl() + .apply { intentFilter.apply { setAutoVerify(autoVerify) addAction(Intent.ACTION_VIEW) @@ -329,7 +335,8 @@ class DomainVerificationCollectorTest { } ) addIntent( - ParsedIntentInfoImpl().apply { + ParsedIntentInfoImpl() + .apply { intentFilter.apply { setAutoVerify(autoVerify) addAction(Intent.ACTION_VIEW) @@ -342,7 +349,8 @@ class DomainVerificationCollectorTest { } ) addIntent( - ParsedIntentInfoImpl().apply { + ParsedIntentInfoImpl() + .apply { intentFilter.apply { setAutoVerify(autoVerify) addCategory(Intent.CATEGORY_BROWSABLE) @@ -355,7 +363,8 @@ class DomainVerificationCollectorTest { } ) addIntent( - ParsedIntentInfoImpl().apply { + ParsedIntentInfoImpl() + .apply { intentFilter.apply { setAutoVerify(autoVerify) addCategory(Intent.CATEGORY_BROWSABLE) @@ -365,7 +374,8 @@ class DomainVerificationCollectorTest { } ) addIntent( - ParsedIntentInfoImpl().apply { + ParsedIntentInfoImpl() + .apply { intentFilter.apply { setAutoVerify(autoVerify) addCategory(Intent.CATEGORY_BROWSABLE) @@ -375,7 +385,8 @@ class DomainVerificationCollectorTest { } ) addIntent( - ParsedIntentInfoImpl().apply { + ParsedIntentInfoImpl() + .apply { intentFilter.apply { setAutoVerify(autoVerify) addCategory(Intent.CATEGORY_BROWSABLE) diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt index 089e9db9a755..92cdb348e60d 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt @@ -20,8 +20,8 @@ import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.content.pm.SigningDetails -import android.content.pm.parsing.component.ParsedActivityImpl -import android.content.pm.parsing.component.ParsedIntentInfoImpl +import com.android.server.pm.pkg.component.ParsedActivityImpl +import com.android.server.pm.pkg.component.ParsedIntentInfoImpl import android.content.pm.verify.domain.DomainVerificationManager import android.content.pm.verify.domain.DomainVerificationState import android.os.Build @@ -308,7 +308,8 @@ class DomainVerificationEnforcerTest { listOf( ParsedActivityImpl().apply { addIntent( - ParsedIntentInfoImpl().apply { + ParsedIntentInfoImpl() + .apply { intentFilter.apply { autoVerify = true addAction(Intent.ACTION_VIEW) diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt index 334f503a4bfb..878bee012635 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt @@ -19,8 +19,8 @@ package com.android.server.pm.test.verify.domain import android.content.Context import android.content.Intent import android.content.pm.PackageManager -import android.content.pm.parsing.component.ParsedActivityImpl -import android.content.pm.parsing.component.ParsedIntentInfoImpl +import com.android.server.pm.pkg.component.ParsedActivityImpl +import com.android.server.pm.pkg.component.ParsedIntentInfoImpl import com.android.server.pm.pkg.PackageUserStateInternal import android.content.pm.verify.domain.DomainOwner import android.content.pm.verify.domain.DomainVerificationInfo @@ -526,7 +526,8 @@ class DomainVerificationManagerApiTest { ParsedActivityImpl().apply { domains.forEach { addIntent( - ParsedIntentInfoImpl().apply { + ParsedIntentInfoImpl() + .apply { intentFilter.apply { autoVerify = true addAction(Intent.ACTION_VIEW) diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt index fb581d70a5fc..0369bab61f0f 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt @@ -20,8 +20,8 @@ import android.content.Intent import android.content.pm.PackageManager import android.content.pm.Signature import android.content.pm.SigningDetails -import android.content.pm.parsing.component.ParsedActivityImpl -import android.content.pm.parsing.component.ParsedIntentInfoImpl +import com.android.server.pm.pkg.component.ParsedActivityImpl +import com.android.server.pm.pkg.component.ParsedIntentInfoImpl import com.android.server.pm.pkg.PackageUserStateInternal import android.content.pm.verify.domain.DomainOwner import android.content.pm.verify.domain.DomainVerificationInfo.STATE_MODIFIABLE_VERIFIED @@ -867,7 +867,8 @@ class DomainVerificationPackageTest { whenever(targetSdkVersion) { Build.VERSION_CODES.S } whenever(isEnabled) { true } - fun baseIntent(domain: String) = ParsedIntentInfoImpl().apply { + fun baseIntent(domain: String) = ParsedIntentInfoImpl() + .apply { intentFilter.apply { addAction(Intent.ACTION_VIEW) addCategory(Intent.CATEGORY_BROWSABLE) diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt index a397d563144d..3a602a8b3c46 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt @@ -19,8 +19,8 @@ package com.android.server.pm.test.verify.domain import android.content.Context import android.content.Intent import android.content.pm.PackageManager -import android.content.pm.parsing.component.ParsedActivityImpl -import android.content.pm.parsing.component.ParsedIntentInfoImpl +import com.android.server.pm.pkg.component.ParsedActivityImpl +import com.android.server.pm.pkg.component.ParsedIntentInfoImpl import com.android.server.pm.pkg.PackageUserStateInternal import android.content.pm.verify.domain.DomainVerificationState import android.os.Build @@ -196,7 +196,8 @@ class DomainVerificationSettingsMutationTest { listOf( ParsedActivityImpl().apply { addIntent( - ParsedIntentInfoImpl().apply { + ParsedIntentInfoImpl() + .apply { intentFilter.apply { autoVerify = true addAction(Intent.ACTION_VIEW) diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt index 728da4992893..ffc287736066 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt @@ -18,8 +18,8 @@ package com.android.server.pm.test.verify.domain import android.content.Intent import android.content.pm.PackageManager -import android.content.pm.parsing.component.ParsedActivityImpl -import android.content.pm.parsing.component.ParsedIntentInfoImpl +import com.android.server.pm.pkg.component.ParsedActivityImpl +import com.android.server.pm.pkg.component.ParsedIntentInfoImpl import android.content.pm.verify.domain.DomainVerificationManager import android.content.pm.verify.domain.DomainVerificationState import android.content.pm.verify.domain.DomainVerificationUserState @@ -112,7 +112,8 @@ class DomainVerificationUserStateOverrideTest { val activityList = listOf( ParsedActivityImpl().apply { addIntent( - ParsedIntentInfoImpl().apply { + ParsedIntentInfoImpl() + .apply { intentFilter.apply { autoVerify = true addAction(Intent.ACTION_VIEW) @@ -126,7 +127,8 @@ class DomainVerificationUserStateOverrideTest { } ) addIntent( - ParsedIntentInfoImpl().apply { + ParsedIntentInfoImpl() + .apply { intentFilter.apply { autoVerify = true addAction(Intent.ACTION_VIEW) diff --git a/services/tests/apexsystemservices/Android.bp b/services/tests/apexsystemservices/Android.bp index 01e90a8d880c..a6ed1ae56567 100644 --- a/services/tests/apexsystemservices/Android.bp +++ b/services/tests/apexsystemservices/Android.bp @@ -37,5 +37,8 @@ java_test_host { "truth-prebuilt", "modules-utils-build-testing", ], - test_suites: ["device-tests"], + test_suites: [ + "device-tests", + "mts-core", + ], } diff --git a/services/tests/apexsystemservices/apexes/test_com.android.server/Android.bp b/services/tests/apexsystemservices/apexes/test_com.android.server/Android.bp index 0a9b7b1302cc..16d624199d5a 100644 --- a/services/tests/apexsystemservices/apexes/test_com.android.server/Android.bp +++ b/services/tests/apexsystemservices/apexes/test_com.android.server/Android.bp @@ -32,7 +32,7 @@ apex_test { name: "test_com.android.server", manifest: "manifest.json", androidManifest: "AndroidManifest.xml", - java_libs: ["FakeApexSystemServices"], + java_libs: ["FakeApexSystemService"], file_contexts: ":apex.test-file_contexts", key: "test_com.android.server.key", updatable: false, diff --git a/services/tests/apexsystemservices/apexes/test_com.android.server/AndroidManifest.xml b/services/tests/apexsystemservices/apexes/test_com.android.server/AndroidManifest.xml index 6bec28463dc3..eb741cad4ad9 100644 --- a/services/tests/apexsystemservices/apexes/test_com.android.server/AndroidManifest.xml +++ b/services/tests/apexsystemservices/apexes/test_com.android.server/AndroidManifest.xml @@ -21,29 +21,21 @@ <application android:hasCode="false" android:testOnly="true"> <apex-system-service android:name="com.android.server.testing.FakeApexSystemService" - android:path="/apex/test_com.android.server/javalib/FakeApexSystemServices.jar" - android:minSdkVersion="30" - /> - - <apex-system-service - android:name="com.android.server.testing.FakeApexSystemService2" - android:path="/apex/test_com.android.server/javalib/FakeApexSystemServices.jar" - android:minSdkVersion="30" - android:initOrder="1" - /> + android:path="/apex/test_com.android.server/javalib/FakeApexSystemService.jar" + android:minSdkVersion="30"/> <!-- Always inactive system service, since maxSdkVersion is low --> <apex-system-service - android:name="com.android.server.testing.OldApexSystemService" - android:path="/apex/test_com.android.server/javalib/fake.jar" + android:name="com.android.apex.test.OldApexSystemService" + android:path="/apex/com.android.apex.test/javalib/fake.jar" android:minSdkVersion="1" android:maxSdkVersion="1" /> <!-- Always inactive system service, since minSdkVersion is high --> <apex-system-service - android:name="com.android.server.testing.NewApexSystemService" - android:path="/apex/test_com.android.server/javalib/fake.jar" + android:name="com.android.apex.test.NewApexSystemService" + android:path="/apex/com.android.apex.test/javalib/fake.jar" android:minSdkVersion="999999" /> </application> diff --git a/services/tests/apexsystemservices/services/Android.bp b/services/tests/apexsystemservices/service/Android.bp index 477ea4cdad37..9d04f39f2237 100644 --- a/services/tests/apexsystemservices/services/Android.bp +++ b/services/tests/apexsystemservices/service/Android.bp @@ -8,7 +8,7 @@ package { } java_library { - name: "FakeApexSystemServices", + name: "FakeApexSystemService", srcs: ["**/*.java"], sdk_version: "system_server_current", libs: [ diff --git a/services/tests/apexsystemservices/services/src/com/android/server/testing/FakeApexSystemService.java b/services/tests/apexsystemservices/service/src/com/android/server/testing/FakeApexSystemService.java index 4947c3455cc7..4947c3455cc7 100644 --- a/services/tests/apexsystemservices/services/src/com/android/server/testing/FakeApexSystemService.java +++ b/services/tests/apexsystemservices/service/src/com/android/server/testing/FakeApexSystemService.java diff --git a/services/tests/apexsystemservices/src/com/android/server/ApexSystemServicesTestCases.java b/services/tests/apexsystemservices/src/com/android/server/ApexSystemServicesTestCases.java index 7ab7b6ed763c..2b453a9265bb 100644 --- a/services/tests/apexsystemservices/src/com/android/server/ApexSystemServicesTestCases.java +++ b/services/tests/apexsystemservices/src/com/android/server/ApexSystemServicesTestCases.java @@ -37,10 +37,6 @@ import org.junit.rules.RuleChain; import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; -import java.util.Objects; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - @RunWith(DeviceJUnit4ClassRunner.class) public class ApexSystemServicesTestCases extends BaseHostJUnit4Test { @@ -71,7 +67,7 @@ public class ApexSystemServicesTestCases extends BaseHostJUnit4Test { } @Test - public void testNoApexSystemServiceStartsWithoutApex() throws Exception { + public void noApexSystemServerStartsWithoutApex() throws Exception { mPreparer.reboot(); assertThat(getFakeApexSystemServiceLogcat()) @@ -79,7 +75,7 @@ public class ApexSystemServicesTestCases extends BaseHostJUnit4Test { } @Test - public void testApexSystemServiceStarts() throws Exception { + public void apexSystemServerStarts() throws Exception { // Pre-install the apex String apex = "test_com.android.server.apex"; mPreparer.pushResourceFile(apex, "/system/apex/" + apex); @@ -90,40 +86,9 @@ public class ApexSystemServicesTestCases extends BaseHostJUnit4Test { .contains("FakeApexSystemService onStart"); } - @Test - public void testInitOrder() throws Exception { - // Pre-install the apex - String apex = "test_com.android.server.apex"; - mPreparer.pushResourceFile(apex, "/system/apex/" + apex); - // Reboot activates the apex - mPreparer.reboot(); - - assertThat(getFakeApexSystemServiceLogcat().lines() - .map(ApexSystemServicesTestCases::getDebugMessage) - .filter(Objects::nonNull) - .collect(Collectors.toList())) - .containsExactly( - // Second service has a higher initOrder and must be started first - "FakeApexSystemService2 onStart", - "FakeApexSystemService onStart" - ) - .inOrder(); - } - private String getFakeApexSystemServiceLogcat() throws DeviceNotAvailableException { return mDevice.executeAdbCommand("logcat", "-v", "brief", "-d", "FakeApexSystemService:D", "*:S"); } - private static final Pattern DEBUG_MESSAGE = - Pattern.compile("(FakeApexSystemService[0-9]* onStart)"); - - private static String getDebugMessage(String logcatLine) { - return DEBUG_MESSAGE.matcher(logcatLine) - .results() - .map(m -> m.group(1)) - .findFirst() - .orElse(null); - } - } 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/OWNERS b/services/tests/mockingservicestests/OWNERS index 0fb0c3021486..2bb16496e0f0 100644 --- a/services/tests/mockingservicestests/OWNERS +++ b/services/tests/mockingservicestests/OWNERS @@ -1 +1,5 @@ include platform/frameworks/base:/services/core/java/com/android/server/am/OWNERS +per-file FakeGameClassifier.java = file:/GAME_MANAGER_OWNERS +per-file FakeGameServiceProviderInstance = file:/GAME_MANAGER_OWNERS +per-file FakeServiceConnector.java = file:/GAME_MANAGER_OWNERS +per-file Game* = file:/GAME_MANAGER_OWNERS 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 b6c706ed2730..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,26 +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.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; @@ -45,19 +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.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; /** @@ -68,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 = @@ -77,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 { @@ -98,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 -> { @@ -112,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; @@ -122,6 +152,8 @@ public final class GameServiceProviderInstanceImplTest { ConcurrentUtils.DIRECT_EXECUTOR, mFakeGameClassifier, mMockActivityTaskManager, + mMockWindowManagerService, + mMockWindowManagerInternal, mFakeGameServiceConnector, mFakeGameSessionServiceConnector); } @@ -135,8 +167,7 @@ public final class GameServiceProviderInstanceImplTest { public void start_startsGameSession() throws Exception { mGameServiceProviderInstance.start(); - mInOrder.verify(mMockGameService).connected(); - mInOrder.verifyNoMoreInteractions(); + assertThat(mFakeGameService.getState()).isEqualTo(GameServiceState.CONNECTED); assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); @@ -145,9 +176,9 @@ public final class GameServiceProviderInstanceImplTest { @Test public void start_multipleTimes_startsGameSessionOnce() throws Exception { mGameServiceProviderInstance.start(); + mGameServiceProviderInstance.start(); - mInOrder.verify(mMockGameService).connected(); - mInOrder.verifyNoMoreInteractions(); + assertThat(mFakeGameService.getState()).isEqualTo(GameServiceState.CONNECTED); assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); @@ -157,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 @@ -167,9 +199,8 @@ public final class GameServiceProviderInstanceImplTest { mGameServiceProviderInstance.start(); mGameServiceProviderInstance.stop(); - mInOrder.verify(mMockGameService).connected(); - 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); @@ -183,11 +214,8 @@ public final class GameServiceProviderInstanceImplTest { mGameServiceProviderInstance.start(); mGameServiceProviderInstance.stop(); - mInOrder.verify(mMockGameService).connected(); - mInOrder.verify(mMockGameService).disconnected(); - mInOrder.verify(mMockGameService).connected(); - 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); @@ -199,9 +227,8 @@ public final class GameServiceProviderInstanceImplTest { mGameServiceProviderInstance.stop(); mGameServiceProviderInstance.stop(); - mInOrder.verify(mMockGameService).connected(); - 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); @@ -211,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); } @@ -220,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(); - 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(); - mInOrder.verifyNoMoreInteractions(); - assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); - assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); + assertThat(mFakeGameService.getGameStartedEvents()).isEmpty(); } @Test @@ -256,280 +272,410 @@ public final class GameServiceProviderInstanceImplTest { mGameServiceProviderInstance.start(); dispatchTaskCreated(10, null); - mInOrder.verify(mMockGameService).connected(); - mInOrder.verifyNoMoreInteractions(); - assertThat(mFakeGameServiceConnector.getIsConnected()).isTrue(); - assertThat(mFakeGameServiceConnector.getConnectCount()).isEqualTo(1); - assertThat(mFakeGameSessionServiceConnector.getConnectCount()).isEqualTo(0); + assertThat(mFakeGameService.getGameStartedEvents()).isEmpty(); } @Test - public void gameTaskStarted_createsGameSession() throws Exception { - CreateGameSessionRequest createGameSessionRequest = - new CreateGameSessionRequest(10, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> gameSession10Future = - captureCreateGameSessionFuture(createGameSessionRequest); + public void gameSessionRequested_withoutTaskDispatch_doesNotCrashAndDoesNotCreateGameSession() + throws Exception { + mGameServiceProviderInstance.start(); + + mFakeGameService.requestCreateGameSession(10); + + assertThat(mFakeGameSessionService.getCapturedCreateInvocations()).isEmpty(); + } + @Test + public void gameTaskStarted_noSessionRequest_callsStartGame() throws Exception { mGameServiceProviderInstance.start(); dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY); - IGameSessionStub gameSession10 = new IGameSessionStub(); - gameSession10Future.get().complete(gameSession10); - mInOrder.verify(mMockGameService).connected(); - 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); + GameStartedEvent expectedGameStartedEvent = new GameStartedEvent(10, GAME_A_PACKAGE); + assertThat(mFakeGameService.getGameStartedEvents()) + .containsExactly(expectedGameStartedEvent).inOrder(); } @Test - public void gameTaskRemoved_whileAwaitingGameSessionAttached_destroysGameSession() + public void gameTaskStarted_requestToCreateGameSessionIncludesTaskConfiguration() 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); + + 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(); + 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(gameSession10.mIsFocused).isFalse(); + } + + @Test + public void gameTaskStartedAndSessionRequested_secondSessionRequest_ignoredAndDoesNotCrash() + throws Exception { + 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(); + 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 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 { + ActivityTaskManager.RootTaskInfo gameATaskInfo = new ActivityTaskManager.RootTaskInfo(); + gameATaskInfo.taskId = 10; + when(mMockActivityTaskManager.getFocusedRootTaskInfo()).thenReturn(gameATaskInfo); + + 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).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(); - 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(); - dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY); - 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(); - 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 gameTaskStarted_multipleTimes_createsMultipleGameSessions() throws Exception { - CreateGameSessionRequest createGameSessionRequest10 = - new CreateGameSessionRequest(10, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> gameSession10Future = - captureCreateGameSessionFuture(createGameSessionRequest10); + 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)); - CreateGameSessionRequest createGameSessionRequest11 = - new CreateGameSessionRequest(11, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> gameSession11Future = - captureCreateGameSessionFuture(createGameSessionRequest11); + 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 { mGameServiceProviderInstance.start(); - dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY); - IGameSessionStub gameSession10 = new IGameSessionStub(); - gameSession10Future.get().complete(gameSession10); - dispatchTaskCreated(11, GAME_A_MAIN_ACTIVITY); - IGameSessionStub gameSession11 = new IGameSessionStub(); - gameSession11Future.get().complete(gameSession11); + 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)); - mInOrder.verify(mMockGameService).connected(); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest10), any()); - 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 gameTaskRemoved_afterMultipleCreated_destroysOnlyThatGameSession() + public void gameTaskStartedTwice_sessionRequestedSecondTimeOnly_createsOneGameSession() throws Exception { - CreateGameSessionRequest createGameSessionRequest10 = - new CreateGameSessionRequest(10, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> gameSession10Future = - captureCreateGameSessionFuture(createGameSessionRequest10); + mGameServiceProviderInstance.start(); + + startTask(10, GAME_A_MAIN_ACTIVITY); + startTask(11, GAME_A_MAIN_ACTIVITY); - CreateGameSessionRequest createGameSessionRequest11 = - new CreateGameSessionRequest(11, GAME_A_PACKAGE); - Supplier<AndroidFuture<IBinder>> gameSession11Future = - captureCreateGameSessionFuture(createGameSessionRequest11); + 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_multipleSessions_destroysOnlyThatGameSession() + throws Exception { mGameServiceProviderInstance.start(); - dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY); - IGameSessionStub gameSession10 = new IGameSessionStub(); - gameSession10Future.get().complete(gameSession10); - dispatchTaskCreated(11, GAME_A_MAIN_ACTIVITY); - IGameSessionStub gameSession11 = new IGameSessionStub(); - gameSession11Future.get().complete(gameSession11); + 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)); dispatchTaskRemoved(10); - mInOrder.verify(mMockGameService).connected(); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest10), any()); - 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(); - dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY); - IGameSessionStub gameSession10 = new IGameSessionStub(); - gameSession10Future.get().complete(gameSession10); + FakeGameSession gameSession10 = new FakeGameSession(); + SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(10) + .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); - dispatchTaskCreated(11, GAME_A_MAIN_ACTIVITY); - 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); - mInOrder.verify(mMockGameService).connected(); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest10), any()); - 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 gameTasksCreated_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(); - dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY); - IGameSessionStub gameSession10 = new IGameSessionStub(); - gameSession10Future.get().complete(gameSession10); + startTask(11, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(11); - dispatchTaskCreated(11, GAME_A_MAIN_ACTIVITY); - IGameSessionStub gameSession11 = new IGameSessionStub(); - gameSession11Future.get().complete(gameSession11); + FakeGameSession gameSession11 = new FakeGameSession(); + SurfacePackage mockSurfacePackage11 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(11) + .complete(new CreateGameSessionResult(gameSession11, mockSurfacePackage11)); dispatchTaskRemoved(10); dispatchTaskRemoved(11); - dispatchTaskCreated(12, GAME_A_MAIN_ACTIVITY); - IGameSessionStub gameSession12 = new IGameSessionStub(); - gameSession11Future.get().complete(gameSession12); + 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)); - mInOrder.verify(mMockGameService).connected(); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest10), any()); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest11), any()); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest12), any()); - mInOrder.verifyNoMoreInteractions(); 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(); - dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY); - IGameSessionStub gameSession10 = new IGameSessionStub(); - gameSession10Future.get().complete(gameSession10); - dispatchTaskCreated(11, GAME_A_MAIN_ACTIVITY); - IGameSessionStub gameSession11 = new IGameSessionStub(); - gameSession11Future.get().complete(gameSession11); mGameServiceProviderInstance.stop(); - mInOrder.verify(mMockGameService).connected(); - mInOrder.verify(mMockGameSessionService).create(eq(createGameSessionRequest10), any()); - 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); + } - return gameSessionFuture::get; + @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); + } + + private void stopTask(int taskId) { + mRunningTaskInfos.removeIf(runningTaskInfo -> runningTaskInfo.taskId == taskId); + dispatchTaskRemoved(taskId); + } + + private void dispatchTaskRemoved(int taskId) { dispatchTaskChangeEvent(taskStackListener -> { taskStackListener.onTaskRemoved(taskId); @@ -542,6 +688,12 @@ public final class GameServiceProviderInstanceImplTest { }); } + private void dispatchTaskFocused(int taskId, boolean focused) { + dispatchTaskChangeEvent(taskStackListener -> { + taskStackListener.onTaskFocusChanged(taskId, focused); + }); + } + private void dispatchTaskChangeEvent( ThrowingConsumer<ITaskStackListener> taskStackListenerConsumer) { for (ITaskStackListener taskStackListener : mTaskStackListeners) { @@ -549,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/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 04a6eeecb320..555f4b8b5cac 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt @@ -27,8 +27,6 @@ import android.content.pm.ServiceInfo import android.content.pm.Signature import android.content.pm.SigningDetails import android.content.pm.UserInfo -import android.content.pm.parsing.ParsingPackage -import android.content.pm.parsing.ParsingPackageUtils import android.content.pm.parsing.result.ParseTypeImpl import android.content.res.Resources import android.hardware.display.DisplayManager @@ -68,6 +66,8 @@ import com.android.server.pm.parsing.pkg.AndroidPackage import com.android.server.pm.parsing.pkg.PackageImpl import com.android.server.pm.parsing.pkg.ParsedPackage import com.android.server.pm.permission.PermissionManagerServiceInternal +import com.android.server.pm.pkg.parsing.ParsingPackage +import com.android.server.pm.pkg.parsing.ParsingPackageUtils import com.android.server.pm.verify.domain.DomainVerificationManagerInternal import com.android.server.testutils.TestHandler import com.android.server.testutils.mock @@ -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/SharedLibrariesImplTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt index 6de12cb98719..2735e3d21ef7 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt @@ -196,7 +196,8 @@ class SharedLibrariesImplTest { val newLibSetting = addPackage(STATIC_LIB_PACKAGE_NAME + "_" + 10, 10L, staticLibrary = STATIC_LIB_NAME, staticLibraryVersion = 10L) - val latestInfo = mSharedLibrariesImpl.getLatestSharedLibraVersionLPr(newLibSetting.pkg)!! + val latestInfo = + mSharedLibrariesImpl.getLatestStaticSharedLibraVersionLPr(newLibSetting.pkg)!! assertThat(latestInfo).isNotNull() assertThat(latestInfo.name).isEqualTo(STATIC_LIB_NAME) 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/Android.bp b/services/tests/servicestests/Android.bp index c3a364e723fb..f24059c22dd7 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -25,6 +25,7 @@ android_test { "test-apps/JobTestApp/src/**/*.java", "test-apps/SuspendTestApp/src/**/*.java", + ":service-bluetooth-tests-sources", // TODO(b/214988855) : Remove once framework-bluetooth jar is ready ], static_libs: [ "frameworks-base-testutils", @@ -181,18 +182,41 @@ filegroup { java_genrule { name: "FrameworksServicesTests_apks_as_resources", srcs: [ - ":FrameworksCoreTests_install_complete_package_info", + ":FrameworksServicesTests_install", + ":FrameworksServicesTests_install_bad_dex", + ":FrameworksServicesTests_install_complete_package_info", + ":FrameworksServicesTests_install_decl_perm", ":FrameworksServicesTests_install_intent_filters", + ":FrameworksServicesTests_install_loc_auto", + ":FrameworksServicesTests_install_loc_internal", + ":FrameworksServicesTests_install_loc_sdcard", + ":FrameworksServicesTests_install_loc_unspecified", ":FrameworksServicesTests_install_split_base", ":FrameworksServicesTests_install_split_feature_a", + ":FrameworksServicesTests_install_use_perm_good", + ":FrameworksServicesTests_install_uses_feature", ":FrameworksServicesTests_install_uses_sdk_0", ":FrameworksServicesTests_install_uses_sdk_q0", ":FrameworksServicesTests_install_uses_sdk_q0_r0", - ":FrameworksServicesTests_install_uses_sdk_r_none", ":FrameworksServicesTests_install_uses_sdk_r0", ":FrameworksServicesTests_install_uses_sdk_r5", + ":FrameworksServicesTests_install_uses_sdk_r_none", ":FrameworksServicesTests_install_uses_sdk_r0_s0", ":FrameworksServicesTests_install_uses_sdk_r0_s5", + ":FrameworksServicesTests_keyset_permdef_sa_unone", + ":FrameworksServicesTests_keyset_permuse_sa_ua_ub", + ":FrameworksServicesTests_keyset_permuse_sb_ua_ub", + ":FrameworksServicesTests_keyset_sa_ua", + ":FrameworksServicesTests_keyset_sa_ua_ub", + ":FrameworksServicesTests_keyset_sa_uab", + ":FrameworksServicesTests_keyset_sa_ub", + ":FrameworksServicesTests_keyset_sa_unone", + ":FrameworksServicesTests_keyset_sab_ua", + ":FrameworksServicesTests_keyset_sau_ub", + ":FrameworksServicesTests_keyset_sb_ua", + ":FrameworksServicesTests_keyset_sb_ub", + ":FrameworksServicesTests_keyset_splat_api", + ":FrameworksServicesTests_keyset_splata_api", ], out: ["FrameworkServicesTests_apks_as_resources.res.zip"], tools: ["soong_zip"], diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index 587447a0752a..d9f73d9aa54e 100644 --- a/services/tests/servicestests/AndroidManifest.xml +++ b/services/tests/servicestests/AndroidManifest.xml @@ -99,6 +99,8 @@ <uses-permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD"/> + <uses-permission android:name="android.permission.PACKAGE_VERIFICATION_AGENT" /> + <queries> <package android:name="com.android.servicestests.apps.suspendtestapp" /> </queries> @@ -269,4 +271,11 @@ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" android:targetPackage="com.android.frameworks.servicestests" android:label="Frameworks Services Tests"/> + <key-sets> + <key-set android:name="A" > + <public-key android:name="keyA" + android:value="MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsMpNthdOxud7roPDZMMomOqXgJJdRfIWpkKEqmC61Mv+Nf6QY3TorEwJeghjSmqj7IbBKrtvfQq4E2XJO1HuspmQO4Ng2gvn+r+6EwNfKc9k55d6s+27SR867jKurBbHNtZMG+tjL1yH4r+tNzcuJCsgyAFqLmxFdcxEwzNvREyRpoYc5RDR0mmTwkMCUhJ6CId1EYEKiCEdNzxv+fWPEb21u+/MWpleGCILs8kglRVb2q/WOzAAvGr4FY5plfaE6N+lr7+UschQ+aMi1+uqewo2o0qPFVmZP5hnwj55K4UMzu/NhhDqQQsX4cSGES1KgHo5MTqRqZjN/I7emw5pFQIDAQAB"/> + </key-set> + <upgrade-key-set android:name="A"/> + </key-sets> </manifest> diff --git a/services/tests/servicestests/OWNERS b/services/tests/servicestests/OWNERS index 0fb0c3021486..d07848e1bbe2 100644 --- a/services/tests/servicestests/OWNERS +++ b/services/tests/servicestests/OWNERS @@ -1 +1,2 @@ include platform/frameworks/base:/services/core/java/com/android/server/am/OWNERS +per-file GameManagerServiceSettingsTests.java = file:/GAME_MANAGER_OWNERS diff --git a/core/tests/coretests/apks/install/Android.bp b/services/tests/servicestests/apks/install/Android.bp index 652b49130433..12175fdb7327 100644 --- a/core/tests/coretests/apks/install/Android.bp +++ b/services/tests/servicestests/apks/install/Android.bp @@ -8,8 +8,8 @@ package { } android_test_helper_app { - name: "FrameworksCoreTests_install", - defaults: ["FrameworksCoreTests_apks_defaults"], + name: "FrameworksServicesTests_install", + defaults: ["FrameworksServicesTests_apks_defaults"], srcs: ["**/*.java"], } diff --git a/core/tests/coretests/apks/install/AndroidManifest.xml b/services/tests/servicestests/apks/install/AndroidManifest.xml index 60f1ba043c2c..60f1ba043c2c 100644 --- a/core/tests/coretests/apks/install/AndroidManifest.xml +++ b/services/tests/servicestests/apks/install/AndroidManifest.xml diff --git a/core/tests/coretests/apks/install/res/values/strings.xml b/services/tests/servicestests/apks/install/res/values/strings.xml index 984152fb5fa7..984152fb5fa7 100644 --- a/core/tests/coretests/apks/install/res/values/strings.xml +++ b/services/tests/servicestests/apks/install/res/values/strings.xml diff --git a/core/tests/coretests/apks/install_bad_dex/Android.bp b/services/tests/servicestests/apks/install_bad_dex/Android.bp index 7b96c9b47553..ad7566810a62 100644 --- a/core/tests/coretests/apks/install_bad_dex/Android.bp +++ b/services/tests/servicestests/apks/install_bad_dex/Android.bp @@ -8,25 +8,25 @@ package { } android_test_helper_app { - name: "FrameworksCoreTests_install_bad_dex_", - defaults: ["FrameworksCoreTests_apks_defaults"], + name: "FrameworksServicesTests_install_bad_dex_", + defaults: ["FrameworksServicesTests_apks_defaults"], srcs: ["src/**/*.java"], } // Inject bad classes.dex file. java_genrule { - name: "FrameworksCoreTests_install_bad_dex", + name: "FrameworksServicesTests_install_bad_dex", tools: [ "soong_zip", "merge_zips", ], srcs: [ - ":FrameworksCoreTests_install_bad_dex_", + ":FrameworksServicesTests_install_bad_dex_", "classes.dex", ], - out: ["FrameworksCoreTests_install_bad_dex.apk"], + out: ["FrameworksServicesTests_install_bad_dex.apk"], cmd: "$(location soong_zip) -o $(genDir)/classes.dex.zip -j -f $(location classes.dex) && " + "$(location merge_zips) -ignore-duplicates $(out) $(genDir)/classes.dex.zip " + - "$(location :FrameworksCoreTests_install_bad_dex_)", + "$(location :FrameworksServicesTests_install_bad_dex_)", } diff --git a/core/tests/coretests/apks/install_bad_dex/AndroidManifest.xml b/services/tests/servicestests/apks/install_bad_dex/AndroidManifest.xml index 76f0fe588f7b..76f0fe588f7b 100644 --- a/core/tests/coretests/apks/install_bad_dex/AndroidManifest.xml +++ b/services/tests/servicestests/apks/install_bad_dex/AndroidManifest.xml diff --git a/core/tests/coretests/apks/install_bad_dex/classes.dex b/services/tests/servicestests/apks/install_bad_dex/classes.dex index 284b6d400e15..284b6d400e15 100644 --- a/core/tests/coretests/apks/install_bad_dex/classes.dex +++ b/services/tests/servicestests/apks/install_bad_dex/classes.dex diff --git a/core/tests/coretests/apks/install_bad_dex/res/values/strings.xml b/services/tests/servicestests/apks/install_bad_dex/res/values/strings.xml index 984152fb5fa7..984152fb5fa7 100644 --- a/core/tests/coretests/apks/install_bad_dex/res/values/strings.xml +++ b/services/tests/servicestests/apks/install_bad_dex/res/values/strings.xml diff --git a/core/tests/coretests/apks/install_bad_dex/src/com/android/frameworks/coretests/TestActivity.java b/services/tests/servicestests/apks/install_bad_dex/src/com/android/frameworks/coretests/TestActivity.java index 10d0551a3a6f..10d0551a3a6f 100644 --- a/core/tests/coretests/apks/install_bad_dex/src/com/android/frameworks/coretests/TestActivity.java +++ b/services/tests/servicestests/apks/install_bad_dex/src/com/android/frameworks/coretests/TestActivity.java diff --git a/services/tests/servicestests/apks/install_complete_package_info/Android.bp b/services/tests/servicestests/apks/install_complete_package_info/Android.bp new file mode 100644 index 000000000000..98aa750231d7 --- /dev/null +++ b/services/tests/servicestests/apks/install_complete_package_info/Android.bp @@ -0,0 +1,15 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test_helper_app { + name: "FrameworksServicesTests_install_complete_package_info", + defaults: ["FrameworksServicesTests_apks_defaults"], + + srcs: ["**/*.java"], +} diff --git a/core/tests/coretests/apks/install_complete_package_info/AndroidManifest.xml b/services/tests/servicestests/apks/install_complete_package_info/AndroidManifest.xml index 2c430e08c16a..2c430e08c16a 100644 --- a/core/tests/coretests/apks/install_complete_package_info/AndroidManifest.xml +++ b/services/tests/servicestests/apks/install_complete_package_info/AndroidManifest.xml diff --git a/core/tests/coretests/apks/install_complete_package_info/src/com/android/frameworks/coretests/TestActivity.java b/services/tests/servicestests/apks/install_complete_package_info/src/com/android/frameworks/coretests/TestActivity.java index 10d0551a3a6f..10d0551a3a6f 100644 --- a/core/tests/coretests/apks/install_complete_package_info/src/com/android/frameworks/coretests/TestActivity.java +++ b/services/tests/servicestests/apks/install_complete_package_info/src/com/android/frameworks/coretests/TestActivity.java diff --git a/core/tests/coretests/apks/install_complete_package_info/src/com/android/frameworks/coretests/TestProvider.java b/services/tests/servicestests/apks/install_complete_package_info/src/com/android/frameworks/coretests/TestProvider.java index 59f9f10c6efe..59f9f10c6efe 100644 --- a/core/tests/coretests/apks/install_complete_package_info/src/com/android/frameworks/coretests/TestProvider.java +++ b/services/tests/servicestests/apks/install_complete_package_info/src/com/android/frameworks/coretests/TestProvider.java diff --git a/core/tests/coretests/apks/install_complete_package_info/src/com/android/frameworks/coretests/TestReceiver.java b/services/tests/servicestests/apks/install_complete_package_info/src/com/android/frameworks/coretests/TestReceiver.java index 21f6263a38bc..21f6263a38bc 100644 --- a/core/tests/coretests/apks/install_complete_package_info/src/com/android/frameworks/coretests/TestReceiver.java +++ b/services/tests/servicestests/apks/install_complete_package_info/src/com/android/frameworks/coretests/TestReceiver.java diff --git a/core/tests/coretests/apks/install_complete_package_info/src/com/android/frameworks/coretests/TestService.java b/services/tests/servicestests/apks/install_complete_package_info/src/com/android/frameworks/coretests/TestService.java index b330e75308f9..b330e75308f9 100644 --- a/core/tests/coretests/apks/install_complete_package_info/src/com/android/frameworks/coretests/TestService.java +++ b/services/tests/servicestests/apks/install_complete_package_info/src/com/android/frameworks/coretests/TestService.java diff --git a/core/tests/coretests/apks/install_decl_perm/Android.bp b/services/tests/servicestests/apks/install_decl_perm/Android.bp index bf1f0de9672a..ef65f5de6a1b 100644 --- a/core/tests/coretests/apks/install_decl_perm/Android.bp +++ b/services/tests/servicestests/apks/install_decl_perm/Android.bp @@ -8,8 +8,8 @@ package { } android_test_helper_app { - name: "FrameworksCoreTests_install_decl_perm", - defaults: ["FrameworksCoreTests_apks_defaults"], + name: "FrameworksServicesTests_install_decl_perm", + defaults: ["FrameworksServicesTests_apks_defaults"], srcs: ["**/*.java"], } diff --git a/core/tests/coretests/apks/install_decl_perm/AndroidManifest.xml b/services/tests/servicestests/apks/install_decl_perm/AndroidManifest.xml index 6a0c4215ce52..6a0c4215ce52 100644 --- a/core/tests/coretests/apks/install_decl_perm/AndroidManifest.xml +++ b/services/tests/servicestests/apks/install_decl_perm/AndroidManifest.xml diff --git a/core/tests/coretests/apks/install_decl_perm/res/values/strings.xml b/services/tests/servicestests/apks/install_decl_perm/res/values/strings.xml index 984152fb5fa7..984152fb5fa7 100644 --- a/core/tests/coretests/apks/install_decl_perm/res/values/strings.xml +++ b/services/tests/servicestests/apks/install_decl_perm/res/values/strings.xml diff --git a/core/tests/coretests/apks/install_loc_auto/Android.bp b/services/tests/servicestests/apks/install_loc_auto/Android.bp index 37daf7608a43..4e4ae526d0dc 100644 --- a/core/tests/coretests/apks/install_loc_auto/Android.bp +++ b/services/tests/servicestests/apks/install_loc_auto/Android.bp @@ -8,8 +8,8 @@ package { } android_test_helper_app { - name: "FrameworksCoreTests_install_loc_auto", - defaults: ["FrameworksCoreTests_apks_defaults"], + name: "FrameworksServicesTests_install_loc_auto", + defaults: ["FrameworksServicesTests_apks_defaults"], srcs: ["**/*.java"], } diff --git a/core/tests/coretests/apks/install_loc_auto/AndroidManifest.xml b/services/tests/servicestests/apks/install_loc_auto/AndroidManifest.xml index 5a903e2903d3..5a903e2903d3 100644 --- a/core/tests/coretests/apks/install_loc_auto/AndroidManifest.xml +++ b/services/tests/servicestests/apks/install_loc_auto/AndroidManifest.xml diff --git a/core/tests/coretests/apks/install_loc_auto/res/values/strings.xml b/services/tests/servicestests/apks/install_loc_auto/res/values/strings.xml index 984152fb5fa7..984152fb5fa7 100644 --- a/core/tests/coretests/apks/install_loc_auto/res/values/strings.xml +++ b/services/tests/servicestests/apks/install_loc_auto/res/values/strings.xml diff --git a/services/tests/servicestests/apks/install_loc_internal/Android.bp b/services/tests/servicestests/apks/install_loc_internal/Android.bp new file mode 100644 index 000000000000..39cdd5178a6d --- /dev/null +++ b/services/tests/servicestests/apks/install_loc_internal/Android.bp @@ -0,0 +1,15 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test_helper_app { + name: "FrameworksServicesTests_install_loc_internal", + defaults: ["FrameworksServicesTests_apks_defaults"], + + srcs: ["**/*.java"], +} diff --git a/core/tests/coretests/apks/install_loc_internal/AndroidManifest.xml b/services/tests/servicestests/apks/install_loc_internal/AndroidManifest.xml index 2568f3729523..2568f3729523 100644 --- a/core/tests/coretests/apks/install_loc_internal/AndroidManifest.xml +++ b/services/tests/servicestests/apks/install_loc_internal/AndroidManifest.xml diff --git a/core/tests/coretests/apks/install_loc_internal/res/values/strings.xml b/services/tests/servicestests/apks/install_loc_internal/res/values/strings.xml index 984152fb5fa7..984152fb5fa7 100644 --- a/core/tests/coretests/apks/install_loc_internal/res/values/strings.xml +++ b/services/tests/servicestests/apks/install_loc_internal/res/values/strings.xml diff --git a/core/tests/coretests/apks/install_loc_internal/Android.bp b/services/tests/servicestests/apks/install_loc_sdcard/Android.bp index 3e233132b58d..ed82793ff6e6 100644 --- a/core/tests/coretests/apks/install_loc_internal/Android.bp +++ b/services/tests/servicestests/apks/install_loc_sdcard/Android.bp @@ -8,8 +8,8 @@ package { } android_test_helper_app { - name: "FrameworksCoreTests_install_loc_internal", - defaults: ["FrameworksCoreTests_apks_defaults"], + name: "FrameworksServicesTests_install_loc_sdcard", + defaults: ["FrameworksServicesTests_apks_defaults"], srcs: ["**/*.java"], } diff --git a/core/tests/coretests/apks/install_loc_sdcard/AndroidManifest.xml b/services/tests/servicestests/apks/install_loc_sdcard/AndroidManifest.xml index 647f4e5f60ff..647f4e5f60ff 100644 --- a/core/tests/coretests/apks/install_loc_sdcard/AndroidManifest.xml +++ b/services/tests/servicestests/apks/install_loc_sdcard/AndroidManifest.xml diff --git a/core/tests/coretests/apks/install_loc_sdcard/res/values/strings.xml b/services/tests/servicestests/apks/install_loc_sdcard/res/values/strings.xml index 984152fb5fa7..984152fb5fa7 100644 --- a/core/tests/coretests/apks/install_loc_sdcard/res/values/strings.xml +++ b/services/tests/servicestests/apks/install_loc_sdcard/res/values/strings.xml diff --git a/services/tests/servicestests/apks/install_loc_unspecified/Android.bp b/services/tests/servicestests/apks/install_loc_unspecified/Android.bp new file mode 100644 index 000000000000..fd15cb8e9f92 --- /dev/null +++ b/services/tests/servicestests/apks/install_loc_unspecified/Android.bp @@ -0,0 +1,15 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test_helper_app { + name: "FrameworksServicesTests_install_loc_unspecified", + defaults: ["FrameworksServicesTests_apks_defaults"], + + srcs: ["**/*.java"], +} diff --git a/core/tests/coretests/apks/install_loc_unspecified/AndroidManifest.xml b/services/tests/servicestests/apks/install_loc_unspecified/AndroidManifest.xml index 07b1eb3105e3..07b1eb3105e3 100644 --- a/core/tests/coretests/apks/install_loc_unspecified/AndroidManifest.xml +++ b/services/tests/servicestests/apks/install_loc_unspecified/AndroidManifest.xml diff --git a/core/tests/coretests/apks/install_loc_unspecified/res/values/strings.xml b/services/tests/servicestests/apks/install_loc_unspecified/res/values/strings.xml index 984152fb5fa7..984152fb5fa7 100644 --- a/core/tests/coretests/apks/install_loc_unspecified/res/values/strings.xml +++ b/services/tests/servicestests/apks/install_loc_unspecified/res/values/strings.xml diff --git a/core/tests/coretests/apks/install_complete_package_info/Android.bp b/services/tests/servicestests/apks/install_use_perm_good/Android.bp index 3fee0c6e7f7c..959ffbcc48b1 100644 --- a/core/tests/coretests/apks/install_complete_package_info/Android.bp +++ b/services/tests/servicestests/apks/install_use_perm_good/Android.bp @@ -8,8 +8,8 @@ package { } android_test_helper_app { - name: "FrameworksCoreTests_install_complete_package_info", - defaults: ["FrameworksCoreTests_apks_defaults"], + name: "FrameworksServicesTests_install_use_perm_good", + defaults: ["FrameworksServicesTests_apks_defaults"], srcs: ["**/*.java"], } diff --git a/core/tests/coretests/apks/install_use_perm_good/AndroidManifest.xml b/services/tests/servicestests/apks/install_use_perm_good/AndroidManifest.xml index dadce7d55fba..dadce7d55fba 100644 --- a/core/tests/coretests/apks/install_use_perm_good/AndroidManifest.xml +++ b/services/tests/servicestests/apks/install_use_perm_good/AndroidManifest.xml diff --git a/core/tests/coretests/apks/install_use_perm_good/res/values/strings.xml b/services/tests/servicestests/apks/install_use_perm_good/res/values/strings.xml index 984152fb5fa7..984152fb5fa7 100644 --- a/core/tests/coretests/apks/install_use_perm_good/res/values/strings.xml +++ b/services/tests/servicestests/apks/install_use_perm_good/res/values/strings.xml diff --git a/services/tests/servicestests/apks/install_uses_feature/Android.bp b/services/tests/servicestests/apks/install_uses_feature/Android.bp new file mode 100644 index 000000000000..fa25af4c5b30 --- /dev/null +++ b/services/tests/servicestests/apks/install_uses_feature/Android.bp @@ -0,0 +1,15 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test_helper_app { + name: "FrameworksServicesTests_install_uses_feature", + defaults: ["FrameworksServicesTests_apks_defaults"], + + srcs: ["**/*.java"], +} diff --git a/core/tests/coretests/apks/install_uses_feature/AndroidManifest.xml b/services/tests/servicestests/apks/install_uses_feature/AndroidManifest.xml index ecbd7c423370..ecbd7c423370 100644 --- a/core/tests/coretests/apks/install_uses_feature/AndroidManifest.xml +++ b/services/tests/servicestests/apks/install_uses_feature/AndroidManifest.xml diff --git a/core/tests/coretests/apks/install_uses_feature/res/values/strings.xml b/services/tests/servicestests/apks/install_uses_feature/res/values/strings.xml index 984152fb5fa7..984152fb5fa7 100644 --- a/core/tests/coretests/apks/install_uses_feature/res/values/strings.xml +++ b/services/tests/servicestests/apks/install_uses_feature/res/values/strings.xml diff --git a/services/tests/servicestests/apks/keyset/Android.bp b/services/tests/servicestests/apks/keyset/Android.bp new file mode 100644 index 000000000000..ce7919c9d0a8 --- /dev/null +++ b/services/tests/servicestests/apks/keyset/Android.bp @@ -0,0 +1,129 @@ +//apks signed by keyset_A +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test_helper_app { + name: "FrameworksServicesTests_keyset_sa_unone", + defaults: ["FrameworksServicesTests_apks_defaults"], + srcs: ["**/*.java"], + certificate: ":FrameworksServicesTests_keyset_A_cert", + manifest: "uNone/AndroidManifest.xml", +} + +android_test_helper_app { + name: "FrameworksServicesTests_keyset_sa_ua", + defaults: ["FrameworksServicesTests_apks_defaults"], + srcs: ["**/*.java"], + certificate: ":FrameworksServicesTests_keyset_A_cert", + manifest: "uA/AndroidManifest.xml", +} + +android_test_helper_app { + name: "FrameworksServicesTests_keyset_sa_ub", + defaults: ["FrameworksServicesTests_apks_defaults"], + srcs: ["**/*.java"], + certificate: ":FrameworksServicesTests_keyset_A_cert", + manifest: "uB/AndroidManifest.xml", +} + +android_test_helper_app { + name: "FrameworksServicesTests_keyset_sa_uab", + defaults: ["FrameworksServicesTests_apks_defaults"], + srcs: ["**/*.java"], + certificate: ":FrameworksServicesTests_keyset_A_cert", + manifest: "uAB/AndroidManifest.xml", +} + +android_test_helper_app { + name: "FrameworksServicesTests_keyset_sa_ua_ub", + defaults: ["FrameworksServicesTests_apks_defaults"], + srcs: ["**/*.java"], + certificate: ":FrameworksServicesTests_keyset_A_cert", + manifest: "uAuB/AndroidManifest.xml", +} + +android_test_helper_app { + name: "FrameworksServicesTests_keyset_permdef_sa_unone", + defaults: ["FrameworksServicesTests_apks_defaults"], + srcs: ["**/*.java"], + certificate: ":FrameworksServicesTests_keyset_A_cert", + manifest: "permDef/AndroidManifest.xml", +} + +android_test_helper_app { + name: "FrameworksServicesTests_keyset_permuse_sa_ua_ub", + defaults: ["FrameworksServicesTests_apks_defaults"], + srcs: ["**/*.java"], + certificate: ":FrameworksServicesTests_keyset_A_cert", + manifest: "permUse/AndroidManifest.xml", +} + +//apks signed by keyset_B +android_test_helper_app { + name: "FrameworksServicesTests_keyset_sb_ua", + defaults: ["FrameworksServicesTests_apks_defaults"], + srcs: ["**/*.java"], + certificate: ":FrameworksServicesTests_keyset_B_cert", + manifest: "uA/AndroidManifest.xml", +} + +android_test_helper_app { + name: "FrameworksServicesTests_keyset_sb_ub", + defaults: ["FrameworksServicesTests_apks_defaults"], + srcs: ["**/*.java"], + certificate: ":FrameworksServicesTests_keyset_B_cert", + manifest: "uB/AndroidManifest.xml", +} + +android_test_helper_app { + name: "FrameworksServicesTests_keyset_permuse_sb_ua_ub", + defaults: ["FrameworksServicesTests_apks_defaults"], + srcs: ["**/*.java"], + certificate: ":FrameworksServicesTests_keyset_B_cert", + manifest: "permUse/AndroidManifest.xml", +} + +//apks signed by keyset_A and keyset_B +android_test_helper_app { + name: "FrameworksServicesTests_keyset_sab_ua", + defaults: ["FrameworksServicesTests_apks_defaults"], + srcs: ["**/*.java"], + certificate: ":FrameworksServicesTests_keyset_A_cert", + additional_certificates: [":FrameworksServicesTests_keyset_B_cert"], + manifest: "uA/AndroidManifest.xml", +} + +//apks signed by keyset_A and unit_test +android_test_helper_app { + name: "FrameworksServicesTests_keyset_sau_ub", + defaults: ["FrameworksServicesTests_apks_defaults"], + srcs: ["**/*.java"], + certificate: ":FrameworksServicesTests_keyset_A_cert", + additional_certificates: [":FrameworksServicesTests_keyset_B_cert"], + manifest: "uB/AndroidManifest.xml", +} + +//apks signed by platform only +android_test_helper_app { + name: "FrameworksServicesTests_keyset_splat_api", + defaults: ["FrameworksServicesTests_apks_defaults"], + srcs: ["**/*.java"], + certificate: "platform", + manifest: "api_test/AndroidManifest.xml", +} + +//apks signed by platform and keyset_A +android_test_helper_app { + name: "FrameworksServicesTests_keyset_splata_api", + defaults: ["FrameworksServicesTests_apks_defaults"], + srcs: ["**/*.java"], + certificate: "platform", + additional_certificates: [":FrameworksServicesTests_keyset_A_cert"], + manifest: "api_test/AndroidManifest.xml", +} diff --git a/core/tests/coretests/apks/keyset/api_test/AndroidManifest.xml b/services/tests/servicestests/apks/keyset/api_test/AndroidManifest.xml index 2897bd5d0b55..2897bd5d0b55 100644 --- a/core/tests/coretests/apks/keyset/api_test/AndroidManifest.xml +++ b/services/tests/servicestests/apks/keyset/api_test/AndroidManifest.xml diff --git a/core/tests/coretests/apks/keyset/permDef/AndroidManifest.xml b/services/tests/servicestests/apks/keyset/permDef/AndroidManifest.xml index 8f7ad4aec603..8f7ad4aec603 100644 --- a/core/tests/coretests/apks/keyset/permDef/AndroidManifest.xml +++ b/services/tests/servicestests/apks/keyset/permDef/AndroidManifest.xml diff --git a/core/tests/coretests/apks/keyset/permUse/AndroidManifest.xml b/services/tests/servicestests/apks/keyset/permUse/AndroidManifest.xml index c56079f8d3cf..c56079f8d3cf 100644 --- a/core/tests/coretests/apks/keyset/permUse/AndroidManifest.xml +++ b/services/tests/servicestests/apks/keyset/permUse/AndroidManifest.xml diff --git a/core/tests/coretests/apks/keyset/res/values/strings.xml b/services/tests/servicestests/apks/keyset/res/values/strings.xml index d811ec29ef19..d811ec29ef19 100644 --- a/core/tests/coretests/apks/keyset/res/values/strings.xml +++ b/services/tests/servicestests/apks/keyset/res/values/strings.xml diff --git a/core/tests/coretests/apks/keyset/uA/AndroidManifest.xml b/services/tests/servicestests/apks/keyset/uA/AndroidManifest.xml index 8c440f54993b..8c440f54993b 100644 --- a/core/tests/coretests/apks/keyset/uA/AndroidManifest.xml +++ b/services/tests/servicestests/apks/keyset/uA/AndroidManifest.xml diff --git a/core/tests/coretests/apks/keyset/uAB/AndroidManifest.xml b/services/tests/servicestests/apks/keyset/uAB/AndroidManifest.xml index 015c3ad2993e..015c3ad2993e 100644 --- a/core/tests/coretests/apks/keyset/uAB/AndroidManifest.xml +++ b/services/tests/servicestests/apks/keyset/uAB/AndroidManifest.xml diff --git a/core/tests/coretests/apks/keyset/uAuB/AndroidManifest.xml b/services/tests/servicestests/apks/keyset/uAuB/AndroidManifest.xml index 9491dbeacc48..9491dbeacc48 100644 --- a/core/tests/coretests/apks/keyset/uAuB/AndroidManifest.xml +++ b/services/tests/servicestests/apks/keyset/uAuB/AndroidManifest.xml diff --git a/core/tests/coretests/apks/keyset/uB/AndroidManifest.xml b/services/tests/servicestests/apks/keyset/uB/AndroidManifest.xml index f4918400f38b..f4918400f38b 100644 --- a/core/tests/coretests/apks/keyset/uB/AndroidManifest.xml +++ b/services/tests/servicestests/apks/keyset/uB/AndroidManifest.xml diff --git a/core/tests/coretests/apks/keyset/uNone/AndroidManifest.xml b/services/tests/servicestests/apks/keyset/uNone/AndroidManifest.xml index 9c9ef2b2ea6f..9c9ef2b2ea6f 100644 --- a/core/tests/coretests/apks/keyset/uNone/AndroidManifest.xml +++ b/services/tests/servicestests/apks/keyset/uNone/AndroidManifest.xml diff --git a/services/tests/servicestests/certs/Android.bp b/services/tests/servicestests/certs/Android.bp new file mode 100644 index 000000000000..61367c0a370b --- /dev/null +++ b/services/tests/servicestests/certs/Android.bp @@ -0,0 +1,20 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + // SPDX-license-identifier-MIT + // SPDX-license-identifier-Unicode-DFS + default_applicable_licenses: ["frameworks_base_license"], +} + +android_app_certificate { + name: "FrameworksServicesTests_keyset_A_cert", + certificate: "keyset_A", +} + +android_app_certificate { + name: "FrameworksServicesTests_keyset_B_cert", + certificate: "keyset_B", +} diff --git a/services/tests/servicestests/certs/README b/services/tests/servicestests/certs/README new file mode 100644 index 000000000000..00917a188934 --- /dev/null +++ b/services/tests/servicestests/certs/README @@ -0,0 +1,4 @@ +Generate with: + +development/tools/make_key unit_test '/CN=unit_test' +development/tools/make_key unit_test_diff '/CN=unit_test_diff' diff --git a/core/tests/coretests/certs/keyset_A.pk8 b/services/tests/servicestests/certs/keyset_A.pk8 Binary files differindex 407631376ed9..407631376ed9 100644 --- a/core/tests/coretests/certs/keyset_A.pk8 +++ b/services/tests/servicestests/certs/keyset_A.pk8 diff --git a/core/tests/coretests/certs/keyset_A.x509.pem b/services/tests/servicestests/certs/keyset_A.x509.pem index 548bf130593f..548bf130593f 100644 --- a/core/tests/coretests/certs/keyset_A.x509.pem +++ b/services/tests/servicestests/certs/keyset_A.x509.pem diff --git a/core/tests/coretests/certs/keyset_B.pk8 b/services/tests/servicestests/certs/keyset_B.pk8 Binary files differindex 839d96cb8622..839d96cb8622 100644 --- a/core/tests/coretests/certs/keyset_B.pk8 +++ b/services/tests/servicestests/certs/keyset_B.pk8 diff --git a/core/tests/coretests/certs/keyset_B.x509.pem b/services/tests/servicestests/certs/keyset_B.x509.pem index 537e047d90e8..537e047d90e8 100644 --- a/core/tests/coretests/certs/keyset_B.x509.pem +++ b/services/tests/servicestests/certs/keyset_B.x509.pem diff --git a/core/tests/coretests/res/raw/install_app1_cert1 b/services/tests/servicestests/res/raw/install_app1_cert1 Binary files differindex f880c0b1126b..f880c0b1126b 100644 --- a/core/tests/coretests/res/raw/install_app1_cert1 +++ b/services/tests/servicestests/res/raw/install_app1_cert1 diff --git a/core/tests/coretests/res/raw/install_app1_cert1_cert2 b/services/tests/servicestests/res/raw/install_app1_cert1_cert2 Binary files differindex ed89fbb2eb35..ed89fbb2eb35 100644 --- a/core/tests/coretests/res/raw/install_app1_cert1_cert2 +++ b/services/tests/servicestests/res/raw/install_app1_cert1_cert2 diff --git a/core/tests/coretests/res/raw/install_app1_cert2 b/services/tests/servicestests/res/raw/install_app1_cert2 Binary files differindex 5551c7ebff07..5551c7ebff07 100644 --- a/core/tests/coretests/res/raw/install_app1_cert2 +++ b/services/tests/servicestests/res/raw/install_app1_cert2 diff --git a/core/tests/coretests/res/raw/install_app1_cert3 b/services/tests/servicestests/res/raw/install_app1_cert3 Binary files differindex 0d1a4dcce854..0d1a4dcce854 100644 --- a/core/tests/coretests/res/raw/install_app1_cert3 +++ b/services/tests/servicestests/res/raw/install_app1_cert3 diff --git a/core/tests/coretests/res/raw/install_app1_cert3_cert4 b/services/tests/servicestests/res/raw/install_app1_cert3_cert4 Binary files differindex 29ff3b6c5971..29ff3b6c5971 100644 --- a/core/tests/coretests/res/raw/install_app1_cert3_cert4 +++ b/services/tests/servicestests/res/raw/install_app1_cert3_cert4 diff --git a/core/tests/coretests/res/raw/install_app1_cert5 b/services/tests/servicestests/res/raw/install_app1_cert5 Binary files differindex 138b6113ea6b..138b6113ea6b 100644 --- a/core/tests/coretests/res/raw/install_app1_cert5 +++ b/services/tests/servicestests/res/raw/install_app1_cert5 diff --git a/core/tests/coretests/res/raw/install_app1_cert5_rotated_cert6 b/services/tests/servicestests/res/raw/install_app1_cert5_rotated_cert6 Binary files differindex 2da2436d9b16..2da2436d9b16 100644 --- a/core/tests/coretests/res/raw/install_app1_cert5_rotated_cert6 +++ b/services/tests/servicestests/res/raw/install_app1_cert5_rotated_cert6 diff --git a/core/tests/coretests/res/raw/install_app1_cert6 b/services/tests/servicestests/res/raw/install_app1_cert6 Binary files differindex 256e03a2de54..256e03a2de54 100644 --- a/core/tests/coretests/res/raw/install_app1_cert6 +++ b/services/tests/servicestests/res/raw/install_app1_cert6 diff --git a/core/tests/coretests/res/raw/install_app1_unsigned b/services/tests/servicestests/res/raw/install_app1_unsigned Binary files differindex 01b39e28866f..01b39e28866f 100644 --- a/core/tests/coretests/res/raw/install_app1_unsigned +++ b/services/tests/servicestests/res/raw/install_app1_unsigned diff --git a/core/tests/coretests/res/raw/install_app2_cert1 b/services/tests/servicestests/res/raw/install_app2_cert1 Binary files differindex 12bfc6f5aa5d..12bfc6f5aa5d 100644 --- a/core/tests/coretests/res/raw/install_app2_cert1 +++ b/services/tests/servicestests/res/raw/install_app2_cert1 diff --git a/core/tests/coretests/res/raw/install_app2_cert1_cert2 b/services/tests/servicestests/res/raw/install_app2_cert1_cert2 Binary files differindex 39095ba7faa2..39095ba7faa2 100644 --- a/core/tests/coretests/res/raw/install_app2_cert1_cert2 +++ b/services/tests/servicestests/res/raw/install_app2_cert1_cert2 diff --git a/core/tests/coretests/res/raw/install_app2_cert2 b/services/tests/servicestests/res/raw/install_app2_cert2 Binary files differindex f6d965be6f37..f6d965be6f37 100644 --- a/core/tests/coretests/res/raw/install_app2_cert2 +++ b/services/tests/servicestests/res/raw/install_app2_cert2 diff --git a/core/tests/coretests/res/raw/install_app2_cert3 b/services/tests/servicestests/res/raw/install_app2_cert3 Binary files differindex 3d8b6f17f397..3d8b6f17f397 100644 --- a/core/tests/coretests/res/raw/install_app2_cert3 +++ b/services/tests/servicestests/res/raw/install_app2_cert3 diff --git a/core/tests/coretests/res/raw/install_app2_cert5_rotated_cert6 b/services/tests/servicestests/res/raw/install_app2_cert5_rotated_cert6 Binary files differindex 30bb6478d18d..30bb6478d18d 100644 --- a/core/tests/coretests/res/raw/install_app2_cert5_rotated_cert6 +++ b/services/tests/servicestests/res/raw/install_app2_cert5_rotated_cert6 diff --git a/core/tests/coretests/res/raw/install_app2_unsigned b/services/tests/servicestests/res/raw/install_app2_unsigned Binary files differindex b69d9fe5c6f9..b69d9fe5c6f9 100644 --- a/core/tests/coretests/res/raw/install_app2_unsigned +++ b/services/tests/servicestests/res/raw/install_app2_unsigned diff --git a/core/tests/coretests/res/raw/install_shared1_cert1 b/services/tests/servicestests/res/raw/install_shared1_cert1 Binary files differindex 714f9fffde4e..714f9fffde4e 100644 --- a/core/tests/coretests/res/raw/install_shared1_cert1 +++ b/services/tests/servicestests/res/raw/install_shared1_cert1 diff --git a/core/tests/coretests/res/raw/install_shared1_cert1_cert2 b/services/tests/servicestests/res/raw/install_shared1_cert1_cert2 Binary files differindex 83725e0d2b26..83725e0d2b26 100644 --- a/core/tests/coretests/res/raw/install_shared1_cert1_cert2 +++ b/services/tests/servicestests/res/raw/install_shared1_cert1_cert2 diff --git a/core/tests/coretests/res/raw/install_shared1_cert2 b/services/tests/servicestests/res/raw/install_shared1_cert2 Binary files differindex 6a3157e1196c..6a3157e1196c 100644 --- a/core/tests/coretests/res/raw/install_shared1_cert2 +++ b/services/tests/servicestests/res/raw/install_shared1_cert2 diff --git a/core/tests/coretests/res/raw/install_shared1_unsigned b/services/tests/servicestests/res/raw/install_shared1_unsigned Binary files differindex 2a2e5f5fdad8..2a2e5f5fdad8 100644 --- a/core/tests/coretests/res/raw/install_shared1_unsigned +++ b/services/tests/servicestests/res/raw/install_shared1_unsigned diff --git a/core/tests/coretests/res/raw/install_shared2_cert1 b/services/tests/servicestests/res/raw/install_shared2_cert1 Binary files differindex 7006edcc67b2..7006edcc67b2 100644 --- a/core/tests/coretests/res/raw/install_shared2_cert1 +++ b/services/tests/servicestests/res/raw/install_shared2_cert1 diff --git a/core/tests/coretests/res/raw/install_shared2_cert1_cert2 b/services/tests/servicestests/res/raw/install_shared2_cert1_cert2 Binary files differindex b7b084c0a779..b7b084c0a779 100644 --- a/core/tests/coretests/res/raw/install_shared2_cert1_cert2 +++ b/services/tests/servicestests/res/raw/install_shared2_cert1_cert2 diff --git a/core/tests/coretests/res/raw/install_shared2_cert2 b/services/tests/servicestests/res/raw/install_shared2_cert2 Binary files differindex 0f04388c371e..0f04388c371e 100644 --- a/core/tests/coretests/res/raw/install_shared2_cert2 +++ b/services/tests/servicestests/res/raw/install_shared2_cert2 diff --git a/core/tests/coretests/res/raw/install_shared2_unsigned b/services/tests/servicestests/res/raw/install_shared2_unsigned Binary files differindex 2794282512de..2794282512de 100644 --- a/core/tests/coretests/res/raw/install_shared2_unsigned +++ b/services/tests/servicestests/res/raw/install_shared2_unsigned diff --git a/services/tests/servicestests/src/com/android/server/BluetoothAirplaneModeListenerTest.java b/services/tests/servicestests/src/com/android/server/BluetoothAirplaneModeListenerTest.java deleted file mode 100644 index a1d4c203de18..000000000000 --- a/services/tests/servicestests/src/com/android/server/BluetoothAirplaneModeListenerTest.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright 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 com.android.server; - -import static org.mockito.Mockito.*; - -import android.bluetooth.BluetoothAdapter; -import android.content.Context; -import android.os.Looper; -import android.provider.Settings; - -import androidx.test.InstrumentationRegistry; -import androidx.test.filters.MediumTest; -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; - -@MediumTest -@RunWith(AndroidJUnit4.class) -public class BluetoothAirplaneModeListenerTest { - private Context mContext; - private BluetoothAirplaneModeListener mBluetoothAirplaneModeListener; - private BluetoothAdapter mBluetoothAdapter; - private BluetoothModeChangeHelper mHelper; - - @Mock BluetoothManagerService mBluetoothManagerService; - - @Before - public void setUp() throws Exception { - mContext = InstrumentationRegistry.getTargetContext(); - - mHelper = mock(BluetoothModeChangeHelper.class); - when(mHelper.getSettingsInt(BluetoothAirplaneModeListener.TOAST_COUNT)) - .thenReturn(BluetoothAirplaneModeListener.MAX_TOAST_COUNT); - doNothing().when(mHelper).setSettingsInt(anyString(), anyInt()); - doNothing().when(mHelper).showToastMessage(); - doNothing().when(mHelper).onAirplaneModeChanged(any(BluetoothManagerService.class)); - - mBluetoothAirplaneModeListener = new BluetoothAirplaneModeListener( - mBluetoothManagerService, Looper.getMainLooper(), mContext); - mBluetoothAirplaneModeListener.start(mHelper); - } - - @Test - public void testIgnoreOnAirplanModeChange() { - Assert.assertFalse(mBluetoothAirplaneModeListener.shouldSkipAirplaneModeChange()); - - when(mHelper.isBluetoothOn()).thenReturn(true); - Assert.assertFalse(mBluetoothAirplaneModeListener.shouldSkipAirplaneModeChange()); - - when(mHelper.isMediaProfileConnected()).thenReturn(true); - Assert.assertFalse(mBluetoothAirplaneModeListener.shouldSkipAirplaneModeChange()); - - when(mHelper.isAirplaneModeOn()).thenReturn(true); - Assert.assertTrue(mBluetoothAirplaneModeListener.shouldSkipAirplaneModeChange()); - } - - @Test - public void testHandleAirplaneModeChange_InvokeAirplaneModeChanged() { - mBluetoothAirplaneModeListener.handleAirplaneModeChange(); - verify(mHelper).onAirplaneModeChanged(mBluetoothManagerService); - } - - @Test - public void testHandleAirplaneModeChange_NotInvokeAirplaneModeChanged_NotPopToast() { - mBluetoothAirplaneModeListener.mToastCount = BluetoothAirplaneModeListener.MAX_TOAST_COUNT; - when(mHelper.isBluetoothOn()).thenReturn(true); - when(mHelper.isMediaProfileConnected()).thenReturn(true); - when(mHelper.isAirplaneModeOn()).thenReturn(true); - mBluetoothAirplaneModeListener.handleAirplaneModeChange(); - - verify(mHelper).setSettingsInt(Settings.Global.BLUETOOTH_ON, - BluetoothManagerService.BLUETOOTH_ON_AIRPLANE); - verify(mHelper, times(0)).showToastMessage(); - verify(mHelper, times(0)).onAirplaneModeChanged(mBluetoothManagerService); - } - - @Test - public void testHandleAirplaneModeChange_NotInvokeAirplaneModeChanged_PopToast() { - mBluetoothAirplaneModeListener.mToastCount = 0; - when(mHelper.isBluetoothOn()).thenReturn(true); - when(mHelper.isMediaProfileConnected()).thenReturn(true); - when(mHelper.isAirplaneModeOn()).thenReturn(true); - mBluetoothAirplaneModeListener.handleAirplaneModeChange(); - - verify(mHelper).setSettingsInt(Settings.Global.BLUETOOTH_ON, - BluetoothManagerService.BLUETOOTH_ON_AIRPLANE); - verify(mHelper).showToastMessage(); - verify(mHelper, times(0)).onAirplaneModeChanged(mBluetoothManagerService); - } - - @Test - public void testIsPopToast_PopToast() { - mBluetoothAirplaneModeListener.mToastCount = 0; - Assert.assertTrue(mBluetoothAirplaneModeListener.shouldPopToast()); - verify(mHelper).setSettingsInt(BluetoothAirplaneModeListener.TOAST_COUNT, 1); - } - - @Test - public void testIsPopToast_NotPopToast() { - mBluetoothAirplaneModeListener.mToastCount = BluetoothAirplaneModeListener.MAX_TOAST_COUNT; - Assert.assertFalse(mBluetoothAirplaneModeListener.shouldPopToast()); - verify(mHelper, times(0)).setSettingsInt(anyString(), anyInt()); - } -} diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java index f1a63bcb0602..6818d1f6851b 100644 --- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java @@ -16,6 +16,13 @@ package com.android.server.am; +import static android.Manifest.permission.INTERACT_ACROSS_PROFILES; +import static android.Manifest.permission.INTERACT_ACROSS_USERS; +import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; +import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY; +import static android.app.ActivityManagerInternal.ALLOW_NON_FULL; +import static android.app.ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE; +import static android.app.ActivityManagerInternal.ALLOW_PROFILES_OR_NON_FULL; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.testing.DexmakerShareClassLoaderRule.runWithDexmakerShareClassLoader; @@ -64,6 +71,7 @@ import android.app.KeyguardManager; import android.content.Context; import android.content.IIntentReceiver; import android.content.Intent; +import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.content.pm.UserInfo.UserInfoFlag; import android.os.Binder; @@ -617,6 +625,100 @@ public class UserControllerTest { assertProfileLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* expectLocking= */ true); } + /** Tests handleIncomingUser() for a variety of permissions and situations. */ + @Test + public void testHandleIncomingUser() throws Exception { + final UserInfo user1a = new UserInfo(111, "user1a", 0); + final UserInfo user1b = new UserInfo(112, "user1b", 0); + final UserInfo user2 = new UserInfo(113, "user2", 0); + // user1a and user2b are in the same profile group; user2 is in a different one. + user1a.profileGroupId = 5; + user1b.profileGroupId = 5; + user2.profileGroupId = 6; + + final List<UserInfo> users = Arrays.asList(user1a, user1b, user2); + when(mInjector.mUserManagerMock.getUsers(false)).thenReturn(users); + mUserController.onSystemReady(); // To set the profileGroupIds in UserController. + + + // Has INTERACT_ACROSS_USERS_FULL. + when(mInjector.checkComponentPermission( + eq(INTERACT_ACROSS_USERS_FULL), anyInt(), anyInt(), anyInt(), anyBoolean())) + .thenReturn(PackageManager.PERMISSION_GRANTED); + when(mInjector.checkComponentPermission( + eq(INTERACT_ACROSS_USERS), anyInt(), anyInt(), anyInt(), anyBoolean())) + .thenReturn(PackageManager.PERMISSION_DENIED); + when(mInjector.checkPermissionForPreflight( + eq(INTERACT_ACROSS_PROFILES), anyInt(), anyInt(), any())).thenReturn(false); + + checkHandleIncomingUser(user1a.id, user2.id, ALLOW_NON_FULL, true); + checkHandleIncomingUser(user1a.id, user2.id, ALLOW_NON_FULL_IN_PROFILE, true); + checkHandleIncomingUser(user1a.id, user2.id, ALLOW_FULL_ONLY, true); + checkHandleIncomingUser(user1a.id, user2.id, ALLOW_PROFILES_OR_NON_FULL, true); + + checkHandleIncomingUser(user1a.id, user1b.id, ALLOW_NON_FULL, true); + checkHandleIncomingUser(user1a.id, user1b.id, ALLOW_NON_FULL_IN_PROFILE, true); + checkHandleIncomingUser(user1a.id, user1b.id, ALLOW_FULL_ONLY, true); + checkHandleIncomingUser(user1a.id, user1b.id, ALLOW_PROFILES_OR_NON_FULL, true); + + + // Has INTERACT_ACROSS_USERS. + when(mInjector.checkComponentPermission( + eq(INTERACT_ACROSS_USERS_FULL), anyInt(), anyInt(), anyInt(), anyBoolean())) + .thenReturn(PackageManager.PERMISSION_DENIED); + when(mInjector.checkComponentPermission( + eq(INTERACT_ACROSS_USERS), anyInt(), anyInt(), anyInt(), anyBoolean())) + .thenReturn(PackageManager.PERMISSION_GRANTED); + when(mInjector.checkPermissionForPreflight( + eq(INTERACT_ACROSS_PROFILES), anyInt(), anyInt(), any())).thenReturn(false); + + checkHandleIncomingUser(user1a.id, user2.id, ALLOW_NON_FULL, true); + checkHandleIncomingUser(user1a.id, user2.id, ALLOW_NON_FULL_IN_PROFILE, false); + checkHandleIncomingUser(user1a.id, user2.id, ALLOW_FULL_ONLY, false); + checkHandleIncomingUser(user1a.id, user2.id, ALLOW_PROFILES_OR_NON_FULL, true); + + checkHandleIncomingUser(user1a.id, user1b.id, ALLOW_NON_FULL, true); + checkHandleIncomingUser(user1a.id, user1b.id, ALLOW_NON_FULL_IN_PROFILE, true); + checkHandleIncomingUser(user1a.id, user1b.id, ALLOW_FULL_ONLY, false); + checkHandleIncomingUser(user1a.id, user1b.id, ALLOW_PROFILES_OR_NON_FULL, true); + + + // Has INTERACT_ACROSS_PROFILES. + when(mInjector.checkComponentPermission( + eq(INTERACT_ACROSS_USERS_FULL), anyInt(), anyInt(), anyInt(), anyBoolean())) + .thenReturn(PackageManager.PERMISSION_DENIED); + when(mInjector.checkComponentPermission( + eq(INTERACT_ACROSS_USERS), anyInt(), anyInt(), anyInt(), anyBoolean())) + .thenReturn(PackageManager.PERMISSION_DENIED); + when(mInjector.checkPermissionForPreflight( + eq(INTERACT_ACROSS_PROFILES), anyInt(), anyInt(), any())).thenReturn(true); + + checkHandleIncomingUser(user1a.id, user2.id, ALLOW_NON_FULL, false); + checkHandleIncomingUser(user1a.id, user2.id, ALLOW_NON_FULL_IN_PROFILE, false); + checkHandleIncomingUser(user1a.id, user2.id, ALLOW_FULL_ONLY, false); + checkHandleIncomingUser(user1a.id, user2.id, ALLOW_PROFILES_OR_NON_FULL, false); + + checkHandleIncomingUser(user1a.id, user1b.id, ALLOW_NON_FULL, false); + checkHandleIncomingUser(user1a.id, user1b.id, ALLOW_NON_FULL_IN_PROFILE, false); + checkHandleIncomingUser(user1a.id, user1b.id, ALLOW_FULL_ONLY, false); + checkHandleIncomingUser(user1a.id, user1b.id, ALLOW_PROFILES_OR_NON_FULL, true); + } + + private void checkHandleIncomingUser(int fromUser, int toUser, int allowMode, boolean pass) { + final int pid = 100; + final int uid = fromUser * UserHandle.PER_USER_RANGE + 34567 + fromUser; + final String name = "whatever"; + final String pkg = "some.package"; + final boolean allowAll = false; + + if (pass) { + mUserController.handleIncomingUser(pid, uid, toUser, allowAll, allowMode, name, pkg); + } else { + assertThrows(SecurityException.class, () -> mUserController.handleIncomingUser( + pid, uid, toUser, allowAll, allowMode, name, pkg)); + } + } + private void setUpAndStartUserInBackground(int userId) throws Exception { setUpUser(userId, 0); mUserController.startUser(userId, /* foreground= */ false); @@ -784,6 +886,23 @@ public class UserControllerTest { } @Override + int checkComponentPermission(String permission, int pid, int uid, int owner, boolean exp) { + Log.i(TAG, "checkComponentPermission " + permission); + return PERMISSION_GRANTED; + } + + @Override + boolean checkPermissionForPreflight(String permission, int pid, int uid, String pkg) { + Log.i(TAG, "checkPermissionForPreflight " + permission); + return true; + } + + @Override + boolean isCallerRecents(int uid) { + return false; + } + + @Override WindowManagerService getWindowManager() { return mWindowManagerMock; } 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..fe7d34a55852 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,23 @@ 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.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 +77,10 @@ public class VirtualDeviceManagerServiceTest { private DisplayManagerInternal mDisplayManagerInternalMock; @Mock private VirtualDeviceImpl.PendingTrampolineCallback mPendingTrampolineCallback; + @Mock + private DevicePolicyManager mDevicePolicyManagerMock; + @Mock + private InputManagerInternal mInputManagerInternalMock; @Before public void setUp() { @@ -81,9 +89,16 @@ 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, @@ -92,6 +107,14 @@ public class VirtualDeviceManagerServiceTest { } @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 +177,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 +187,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 +197,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 +217,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 +242,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 +253,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 +283,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 +324,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 +335,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 +372,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 +391,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..d66f2f07704d 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -34,6 +34,10 @@ 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; @@ -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; @@ -7828,6 +7833,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( diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java index 176e5a9fdc51..54945e44fee5 100644 --- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java @@ -19,6 +19,7 @@ package com.android.server.display; import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyFloat; @@ -34,18 +35,22 @@ import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest; import android.os.Handler; +import android.os.test.TestLooper; import android.platform.test.annotations.Presubmit; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.server.testutils.OffsettableClock; + 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.MockitoAnnotations; @SmallTest @@ -61,7 +66,11 @@ public class AutomaticBrightnessControllerTest { private static final float DOZE_SCALE_FACTOR = 0.0f; private static final boolean RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG = false; private static final int LIGHT_SENSOR_WARMUP_TIME = 0; - + private static final int AMBIENT_LIGHT_HORIZON_SHORT = 1000; + private static final int AMBIENT_LIGHT_HORIZON_LONG = 2000; + private static final float EPSILON = 0.001f; + private OffsettableClock mClock = new OffsettableClock(); + private TestLooper mTestLooper; private Context mContext; private AutomaticBrightnessController mController; @@ -91,21 +100,36 @@ public class AutomaticBrightnessControllerTest { } } + private void advanceTime(long timeMs) { + mClock.fastForward(timeMs); + mTestLooper.dispatchAll(); + } + private AutomaticBrightnessController setupController(Sensor lightSensor) { + mClock = new OffsettableClock.Stopped(); + mTestLooper = new TestLooper(mClock::now); + AutomaticBrightnessController controller = new AutomaticBrightnessController( new AutomaticBrightnessController.Injector() { @Override public Handler getBackgroundThreadHandler() { return mNoOpHandler; } - }, - () -> { }, mContext.getMainLooper(), mSensorManager, lightSensor, + + @Override + AutomaticBrightnessController.Clock createClock() { + return mClock::now; + } + + }, // pass in test looper instead, pass in offsetable clock + () -> { }, mTestLooper.getLooper(), mSensorManager, lightSensor, mBrightnessMappingStrategy, LIGHT_SENSOR_WARMUP_TIME, BRIGHTNESS_MIN_FLOAT, BRIGHTNESS_MAX_FLOAT, DOZE_SCALE_FACTOR, LIGHT_SENSOR_RATE, INITIAL_LIGHT_SENSOR_RATE, BRIGHTENING_LIGHT_DEBOUNCE_CONFIG, DARKENING_LIGHT_DEBOUNCE_CONFIG, RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG, mAmbientBrightnessThresholds, mScreenBrightnessThresholds, - mContext, mHbmController, mIdleBrightnessMappingStrategy + mContext, mHbmController, mIdleBrightnessMappingStrategy, + AMBIENT_LIGHT_HORIZON_SHORT, AMBIENT_LIGHT_HORIZON_LONG ); when(mHbmController.getCurrentBrightnessMax()).thenReturn(BRIGHTNESS_MAX_FLOAT); @@ -276,4 +300,95 @@ public class AutomaticBrightnessControllerTest { // Ensure we use the correct mapping strategy verify(mIdleBrightnessMappingStrategy, times(1)).addUserDataPoint(1000f, 0.5f); } + + @Test + public void testAmbientLightHorizon() throws Exception { + // create abc + Sensor lightSensor = TestUtils.createSensor(Sensor.TYPE_LIGHT, "Light Sensor"); + mController = setupController(lightSensor); + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(lightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); + + long increment = 500; + // set autobrightness to low + // t = 0 + listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 0)); + + // t = 500 + mClock.fastForward(increment); + listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 0)); + + // t = 1000 + mClock.fastForward(increment); + listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 0)); + assertEquals(0.0f, mController.getAmbientLux(), EPSILON); + + // t = 1500 + mClock.fastForward(increment); + listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 0)); + assertEquals(0.0f, mController.getAmbientLux(), EPSILON); + + // t = 2000 + // ensure that our reading is at 0. + mClock.fastForward(increment); + listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 0)); + assertEquals(0.0f, mController.getAmbientLux(), EPSILON); + + // t = 2500 + // first 10000 lux sensor event reading + mClock.fastForward(increment); + listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 10000)); + assertTrue(mController.getAmbientLux() > 0.0f); + assertTrue(mController.getAmbientLux() < 10000.0f); + + // t = 3000 + // lux reading should still not yet be 10000. + mClock.fastForward(increment); + listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 10000)); + assertTrue(mController.getAmbientLux() > 0.0f); + assertTrue(mController.getAmbientLux() < 10000.0f); + + // t = 3500 + mClock.fastForward(increment); + // lux has been high (10000) for 1000ms. + // lux reading should be 10000 + // short horizon (ambient lux) is high, long horizon is still not high + listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 10000)); + assertEquals(10000.0f, mController.getAmbientLux(), EPSILON); + + // t = 4000 + // stay high + mClock.fastForward(increment); + listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 10000)); + assertEquals(10000.0f, mController.getAmbientLux(), EPSILON); + + // t = 4500 + Mockito.clearInvocations(mBrightnessMappingStrategy); + mClock.fastForward(increment); + // short horizon is high, long horizon is high too + listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 10000)); + verify(mBrightnessMappingStrategy, times(1)).getBrightness(10000, null, -1); + assertEquals(10000.0f, mController.getAmbientLux(), EPSILON); + + // t = 5000 + mClock.fastForward(increment); + listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 0)); + assertTrue(mController.getAmbientLux() > 0.0f); + assertTrue(mController.getAmbientLux() < 10000.0f); + + // t = 5500 + mClock.fastForward(increment); + listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 0)); + assertTrue(mController.getAmbientLux() > 0.0f); + assertTrue(mController.getAmbientLux() < 10000.0f); + + // t = 6000 + mClock.fastForward(increment); + // ambient lux goes to 0 + listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 0)); + assertEquals(0.0f, mController.getAmbientLux(), EPSILON); + } } diff --git a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java index beecdadcd546..4bb5d7482e25 100644 --- a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java @@ -28,7 +28,9 @@ import static com.android.server.display.AutomaticBrightnessController import static com.android.server.display.HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID; import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -504,6 +506,24 @@ public class HighBrightnessModeControllerTest { } @Test + public void tetHbmStats_NbmHdrNoReport() { + final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock()); + final int displayStatsId = mDisplayUniqueId.hashCode(); + + hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED); + hbmc.onBrightnessChanged(DEFAULT_MIN); + hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/, + DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/); + advanceTime(0); + assertEquals(HIGH_BRIGHTNESS_MODE_HDR, hbmc.getHighBrightnessMode()); + + // Verify Stats HBM_ON_HDR not report + verify(mInjectorMock, never()).reportHbmStateChange(eq(displayStatsId), + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR), + anyInt()); + } + + @Test public void testHbmStats_ThermalOff() throws Exception { final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock()); final int displayStatsId = mDisplayUniqueId.hashCode(); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java index b588db66a08f..18f264277b41 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java @@ -92,7 +92,6 @@ public class ActiveSourceActionTest { this.mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter()); mHdmiControlService.setCecController(hdmiCecController); mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); - mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); mHdmiControlService.initService(); mPowerManager = new FakePowerManagerWrapper(mContextSpy); mHdmiControlService.setPowerManager(mPowerManager); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java index ff01cb1a3a1d..e4c5ad6769d7 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java @@ -109,7 +109,6 @@ public class ArcInitiationActionFromAvrTest { hdmiControlService, mNativeWrapper, hdmiControlService.getAtomWriter()); hdmiControlService.setCecController(hdmiCecController); hdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(hdmiControlService)); - hdmiControlService.setMessageValidator(new HdmiCecMessageValidator(hdmiControlService)); hdmiControlService.initService(); mPowerManager = new FakePowerManagerWrapper(mContextSpy); hdmiControlService.setPowerManager(mPowerManager); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java index a44a5cde0276..d73cdb5f53b0 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java @@ -102,7 +102,6 @@ public class ArcTerminationActionFromAvrTest { hdmiControlService, mNativeWrapper, hdmiControlService.getAtomWriter()); hdmiControlService.setCecController(hdmiCecController); hdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(hdmiControlService)); - hdmiControlService.setMessageValidator(new HdmiCecMessageValidator(hdmiControlService)); hdmiControlService.initService(); mPowerManager = new FakePowerManagerWrapper(mContextSpy); hdmiControlService.setPowerManager(mPowerManager); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java index 9c99240628a4..5cec8ad1e63d 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java @@ -53,7 +53,7 @@ public class DetectTvSystemAudioModeSupportActionTest { @Before public void SetUp() { - mDeviceInfoForTests = new HdmiDeviceInfo(1001, 1234); + mDeviceInfoForTests = HdmiDeviceInfo.hardwarePort(1001, 1234); HdmiControlService hdmiControlService = new HdmiControlService(InstrumentationRegistry.getTargetContext(), Collections.emptyList()) { diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java index 638b38633cd2..52a0b6cdc2be 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java @@ -109,7 +109,6 @@ public class DevicePowerStatusActionTest { this.mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter()); mHdmiControlService.setCecController(hdmiCecController); mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); - mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); mHdmiControlService.initService(); mPowerManager = new FakePowerManagerWrapper(mContextSpy); mHdmiControlService.setPowerManager(mPowerManager); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java index 41231e0d673b..35432edfcf16 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java @@ -128,10 +128,8 @@ public class DeviceSelectActionFromPlaybackTest { mHdmiControlService.setCecController(mHdmiCecController); mHdmiMhlControllerStub = HdmiMhlControllerStub.create(mHdmiControlService); mHdmiControlService.setHdmiMhlController(mHdmiMhlControllerStub); - mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); mHdmiControlService.setCecController(mHdmiCecController); mHdmiControlService.setHdmiMhlController(mHdmiMhlControllerStub); - mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); mHdmiCecNetwork = new HdmiCecNetwork(mHdmiControlService, mHdmiCecController, mHdmiMhlControllerStub); mHdmiControlService.setHdmiCecNetwork(mHdmiCecNetwork); @@ -156,13 +154,13 @@ public class DeviceSelectActionFromPlaybackTest { mPlaybackLogicalAddress3 = mPlaybackLogicalAddress1 == ADDR_PLAYBACK_3 ? ADDR_PLAYBACK_1 : ADDR_PLAYBACK_3; - mReportPowerStatusOn = new HdmiCecMessage( + mReportPowerStatusOn = HdmiCecMessage.build( mPlaybackLogicalAddress2, mPlaybackLogicalAddress1, Constants.MESSAGE_REPORT_POWER_STATUS, POWER_ON); - mReportPowerStatusStandby = new HdmiCecMessage( + mReportPowerStatusStandby = HdmiCecMessage.build( mPlaybackLogicalAddress2, mPlaybackLogicalAddress1, Constants.MESSAGE_REPORT_POWER_STATUS, POWER_STANDBY); - mReportPowerStatusTransientToOn = new HdmiCecMessage( + mReportPowerStatusTransientToOn = HdmiCecMessage.build( mPlaybackLogicalAddress2, mPlaybackLogicalAddress1, Constants.MESSAGE_REPORT_POWER_STATUS, POWER_TRANSIENT_TO_ON); mSetStreamPath = HdmiCecMessageBuilder.buildSetStreamPath( @@ -173,21 +171,36 @@ public class DeviceSelectActionFromPlaybackTest { mActiveSource = HdmiCecMessageBuilder.buildActiveSource( mPlaybackLogicalAddress2, PHYSICAL_ADDRESS_PLAYBACK_2); - HdmiDeviceInfo infoPlayback1 = new HdmiDeviceInfo( - mPlaybackLogicalAddress1, PHYSICAL_ADDRESS_PLAYBACK_1, PORT_1, - HdmiDeviceInfo.DEVICE_PLAYBACK, - 0x1234, "Playback 1", - HdmiControlManager.POWER_STATUS_ON, HdmiControlManager.HDMI_CEC_VERSION_1_4_B); - HdmiDeviceInfo infoPlayback2 = new HdmiDeviceInfo( - mPlaybackLogicalAddress2, PHYSICAL_ADDRESS_PLAYBACK_2, PORT_2, - HdmiDeviceInfo.DEVICE_PLAYBACK, - 0x1234, "Playback 2", - HdmiControlManager.POWER_STATUS_ON, HdmiControlManager.HDMI_CEC_VERSION_1_4_B); - HdmiDeviceInfo infoPlayback3 = new HdmiDeviceInfo( - mPlaybackLogicalAddress3, PHYSICAL_ADDRESS_PLAYBACK_3, PORT_3, - HdmiDeviceInfo.DEVICE_PLAYBACK, - 0x1234, "Playback 3", - HdmiControlManager.POWER_STATUS_ON, HdmiControlManager.HDMI_CEC_VERSION_1_4_B); + HdmiDeviceInfo infoPlayback1 = HdmiDeviceInfo.cecDeviceBuilder() + .setLogicalAddress(mPlaybackLogicalAddress1) + .setPhysicalAddress(PHYSICAL_ADDRESS_PLAYBACK_1) + .setPortId(PORT_1) + .setDeviceType(HdmiDeviceInfo.DEVICE_PLAYBACK) + .setVendorId(0x1234) + .setDisplayName("Playback 1") + .setDevicePowerStatus(HdmiControlManager.POWER_STATUS_ON) + .setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_1_4_B) + .build(); + HdmiDeviceInfo infoPlayback2 = HdmiDeviceInfo.cecDeviceBuilder() + .setLogicalAddress(mPlaybackLogicalAddress2) + .setPhysicalAddress(PHYSICAL_ADDRESS_PLAYBACK_2) + .setPortId(PORT_2) + .setDeviceType(HdmiDeviceInfo.DEVICE_PLAYBACK) + .setVendorId(0x1234) + .setDisplayName("Playback 2") + .setDevicePowerStatus(HdmiControlManager.POWER_STATUS_ON) + .setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_1_4_B) + .build(); + HdmiDeviceInfo infoPlayback3 = HdmiDeviceInfo.cecDeviceBuilder() + .setLogicalAddress(mPlaybackLogicalAddress3) + .setPhysicalAddress(PHYSICAL_ADDRESS_PLAYBACK_3) + .setPortId(PORT_3) + .setDeviceType(HdmiDeviceInfo.DEVICE_PLAYBACK) + .setVendorId(0x1234) + .setDisplayName("Playback 3") + .setDevicePowerStatus(HdmiControlManager.POWER_STATUS_ON) + .setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_1_4_B) + .build(); mHdmiControlService.getHdmiCecNetwork().addCecDevice(infoPlayback1); mHdmiControlService.getHdmiCecNetwork().addCecDevice(infoPlayback2); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java index dd74864bd81f..e77cd91b46d8 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java @@ -64,22 +64,34 @@ public class DeviceSelectActionFromTvTest { private static final byte[] POWER_ON = new byte[] { POWER_STATUS_ON }; private static final byte[] POWER_STANDBY = new byte[] { POWER_STATUS_STANDBY }; private static final byte[] POWER_TRANSIENT_TO_ON = new byte[] { POWER_STATUS_TRANSIENT_TO_ON }; - private static final HdmiCecMessage REPORT_POWER_STATUS_ON = new HdmiCecMessage( + private static final HdmiCecMessage REPORT_POWER_STATUS_ON = HdmiCecMessage.build( ADDR_PLAYBACK_1, ADDR_TV, Constants.MESSAGE_REPORT_POWER_STATUS, POWER_ON); - private static final HdmiCecMessage REPORT_POWER_STATUS_STANDBY = new HdmiCecMessage( + private static final HdmiCecMessage REPORT_POWER_STATUS_STANDBY = HdmiCecMessage.build( ADDR_PLAYBACK_1, ADDR_TV, Constants.MESSAGE_REPORT_POWER_STATUS, POWER_STANDBY); - private static final HdmiCecMessage REPORT_POWER_STATUS_TRANSIENT_TO_ON = new HdmiCecMessage( + private static final HdmiCecMessage REPORT_POWER_STATUS_TRANSIENT_TO_ON = HdmiCecMessage.build( ADDR_PLAYBACK_1, ADDR_TV, Constants.MESSAGE_REPORT_POWER_STATUS, POWER_TRANSIENT_TO_ON); private static final HdmiCecMessage SET_STREAM_PATH = HdmiCecMessageBuilder.buildSetStreamPath( ADDR_TV, PHYSICAL_ADDRESS_PLAYBACK_1); - private static final HdmiDeviceInfo INFO_PLAYBACK_1 = new HdmiDeviceInfo( - ADDR_PLAYBACK_1, PHYSICAL_ADDRESS_PLAYBACK_1, PORT_1, HdmiDeviceInfo.DEVICE_PLAYBACK, - 0x1234, "Playback 1", - HdmiControlManager.POWER_STATUS_ON, HdmiControlManager.HDMI_CEC_VERSION_1_4_B); - private static final HdmiDeviceInfo INFO_PLAYBACK_2 = new HdmiDeviceInfo( - ADDR_PLAYBACK_2, PHYSICAL_ADDRESS_PLAYBACK_2, PORT_2, HdmiDeviceInfo.DEVICE_PLAYBACK, - 0x1234, "Playback 2", - HdmiControlManager.POWER_STATUS_ON, HdmiControlManager.HDMI_CEC_VERSION_1_4_B); + private static final HdmiDeviceInfo INFO_PLAYBACK_1 = HdmiDeviceInfo.cecDeviceBuilder() + .setLogicalAddress(ADDR_PLAYBACK_1) + .setPhysicalAddress(PHYSICAL_ADDRESS_PLAYBACK_1) + .setPortId(PORT_1) + .setDeviceType(HdmiDeviceInfo.DEVICE_PLAYBACK) + .setVendorId(0x1234) + .setDisplayName("Plyback 1") + .setDevicePowerStatus(HdmiControlManager.POWER_STATUS_ON) + .setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_1_4_B) + .build(); + private static final HdmiDeviceInfo INFO_PLAYBACK_2 = HdmiDeviceInfo.cecDeviceBuilder() + .setLogicalAddress(ADDR_PLAYBACK_2) + .setPhysicalAddress(PHYSICAL_ADDRESS_PLAYBACK_2) + .setPortId(PORT_2) + .setDeviceType(HdmiDeviceInfo.DEVICE_PLAYBACK) + .setVendorId(0x1234) + .setDisplayName("Playback 2") + .setDevicePowerStatus(HdmiControlManager.POWER_STATUS_ON) + .setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_1_4_B) + .build(); private HdmiControlService mHdmiControlService; private HdmiCecController mHdmiCecController; @@ -123,7 +135,6 @@ public class DeviceSelectActionFromTvTest { mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter()); mHdmiControlService.setCecController(mHdmiCecController); mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); - mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); mLocalDevices.add(mHdmiCecLocalDeviceTv); HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[2]; hdmiPortInfos[0] = diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java index d630ef6a5cd3..559a2c0d6a09 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java @@ -25,6 +25,7 @@ import com.android.server.hdmi.HdmiCecController.NativeWrapper; import com.google.common.collect.Iterables; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -77,7 +78,8 @@ final class FakeNativeWrapper implements NativeWrapper { if (body.length == 0) { return mPollAddressResponse[dstAddress]; } else { - HdmiCecMessage message = HdmiCecMessageBuilder.of(srcAddress, dstAddress, body); + HdmiCecMessage message = HdmiCecMessage.build(srcAddress, dstAddress, body[0], + Arrays.copyOfRange(body, 1, body.length)); mResultMessages.add(message); return mMessageSendResult.getOrDefault(message.getOpcode(), SendMessageResult.SUCCESS); } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/GiveFeaturesActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/GiveFeaturesActionTest.java new file mode 100644 index 000000000000..0b31db69f0a9 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/hdmi/GiveFeaturesActionTest.java @@ -0,0 +1,198 @@ +/* + * 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.hdmi; + +import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORTED; +import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORT_UNKNOWN; +import static android.hardware.hdmi.HdmiControlManager.HDMI_CEC_VERSION_2_0; +import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM; + +import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.spy; + +import android.content.Context; +import android.content.ContextWrapper; +import android.hardware.hdmi.DeviceFeatures; +import android.hardware.hdmi.HdmiControlManager; +import android.hardware.hdmi.HdmiDeviceInfo; +import android.hardware.hdmi.IHdmiControlCallback; +import android.os.Looper; +import android.os.RemoteException; +import android.os.test.TestLooper; +import android.platform.test.annotations.Presubmit; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; + +import com.android.server.SystemService; + +import com.google.android.collect.Lists; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; + +@SmallTest +@Presubmit +@RunWith(JUnit4.class) +public class GiveFeaturesActionTest { + private HdmiControlService mHdmiControlServiceSpy; + private HdmiCecController mHdmiCecController; + private HdmiCecLocalDevicePlayback mPlaybackDevice; + private FakeNativeWrapper mNativeWrapper; + private FakePowerManagerWrapper mPowerManager; + private Looper mLooper; + private Context mContextSpy; + private TestLooper mTestLooper = new TestLooper(); + private int mPhysicalAddress = 0x1100; + private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>(); + private int mPlaybackLogicalAddress; + + private TestCallback mTestCallback; + private GiveFeaturesAction mAction; + + /** + * Setup: Local Playback device queries the features of a connected TV. + */ + @Before + public void setUp() throws RemoteException { + mContextSpy = spy(new ContextWrapper( + InstrumentationRegistry.getInstrumentation().getTargetContext())); + + mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList())); + doNothing().when(mHdmiControlServiceSpy) + .writeStringSystemProperty(anyString(), anyString()); + + mLooper = mTestLooper.getLooper(); + mHdmiControlServiceSpy.setIoLooper(mLooper); + mHdmiControlServiceSpy.setHdmiCecConfig(new FakeHdmiCecConfig(mContextSpy)); + + mNativeWrapper = new FakeNativeWrapper(); + mNativeWrapper.setPhysicalAddress(mPhysicalAddress); + + mHdmiCecController = HdmiCecController.createWithNativeWrapper( + mHdmiControlServiceSpy, mNativeWrapper, mHdmiControlServiceSpy.getAtomWriter()); + mHdmiControlServiceSpy.setCecController(mHdmiCecController); + mHdmiControlServiceSpy.setHdmiMhlController( + HdmiMhlControllerStub.create(mHdmiControlServiceSpy)); + mHdmiControlServiceSpy.initService(); + mPowerManager = new FakePowerManagerWrapper(mContextSpy); + mHdmiControlServiceSpy.setPowerManager(mPowerManager); + + mPlaybackDevice = new HdmiCecLocalDevicePlayback(mHdmiControlServiceSpy); + mPlaybackDevice.init(); + mLocalDevices.add(mPlaybackDevice); + + mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + mHdmiControlServiceSpy.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); + mTestLooper.dispatchAll(); + + synchronized (mPlaybackDevice.mLock) { + mPlaybackLogicalAddress = mPlaybackDevice.getDeviceInfo().getLogicalAddress(); + } + + // Setup specific to these tests + mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( + Constants.ADDR_TV, 0x0000, HdmiDeviceInfo.DEVICE_TV)); + mTestLooper.dispatchAll(); + + mTestCallback = new TestCallback(); + mAction = new GiveFeaturesAction(mPlaybackDevice, Constants.ADDR_TV, mTestCallback); + } + + @Test + public void sendsGiveFeaturesMessage() { + mPlaybackDevice.addAndStartAction(mAction); + mTestLooper.dispatchAll(); + + HdmiCecMessage giveFeatures = HdmiCecMessageBuilder.buildGiveFeatures( + mPlaybackLogicalAddress, Constants.ADDR_TV); + assertThat(mNativeWrapper.getResultMessages()).contains(giveFeatures); + } + + @Test + public void noMatchingReportFeaturesReceived_actionFailsAndNetworkIsNotUpdated() { + mPlaybackDevice.addAndStartAction(mAction); + mTestLooper.dispatchAll(); + + // Wrong source + mNativeWrapper.onCecMessage(ReportFeaturesMessage.build( + Constants.ADDR_AUDIO_SYSTEM, HdmiControlManager.HDMI_CEC_VERSION_2_0, + Arrays.asList(DEVICE_AUDIO_SYSTEM), Constants.RC_PROFILE_SOURCE, + Collections.emptyList(), DeviceFeatures.NO_FEATURES_SUPPORTED)); + mTestLooper.dispatchAll(); + + mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); + mTestLooper.dispatchAll(); + + @DeviceFeatures.FeatureSupportStatus int avcSupport = + mHdmiControlServiceSpy.getHdmiCecNetwork().getCecDeviceInfo(Constants.ADDR_TV) + .getDeviceFeatures().getSetAudioVolumeLevelSupport(); + + assertThat(avcSupport).isEqualTo(FEATURE_SUPPORT_UNKNOWN); + assertThat(mTestCallback.getResult()).isEqualTo( + HdmiControlManager.RESULT_COMMUNICATION_FAILED); + } + + @Test + public void matchingReportFeaturesReceived_actionSucceedsAndNetworkIsUpdated() { + mPlaybackDevice.addAndStartAction(mAction); + mTestLooper.dispatchAll(); + + mNativeWrapper.onCecMessage( + ReportFeaturesMessage.build( + Constants.ADDR_TV, HDMI_CEC_VERSION_2_0, Collections.emptyList(), + Constants.RC_PROFILE_TV, Lists.newArrayList(Constants.RC_PROFILE_TV_NONE), + DeviceFeatures.NO_FEATURES_SUPPORTED.toBuilder() + .setSetAudioVolumeLevelSupport(FEATURE_SUPPORTED) + .build() + ) + ); + mTestLooper.dispatchAll(); + + @DeviceFeatures.FeatureSupportStatus int avcSupport = + mHdmiControlServiceSpy.getHdmiCecNetwork().getCecDeviceInfo(Constants.ADDR_TV) + .getDeviceFeatures().getSetAudioVolumeLevelSupport(); + + assertThat(avcSupport).isEqualTo(FEATURE_SUPPORTED); + assertThat(mTestCallback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS); + } + + private static class TestCallback extends IHdmiControlCallback.Stub { + private final ArrayList<Integer> mCallbackResult = new ArrayList<Integer>(); + + @Override + public void onComplete(int result) { + mCallbackResult.add(result); + } + + private int getResult() { + assertThat(mCallbackResult.size()).isEqualTo(1); + return mCallbackResult.get(0); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java index f30e97a98b70..30bcc7e8afa1 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java @@ -98,8 +98,6 @@ public class HdmiCecAtomLoggingTest { doReturn(hdmiCecConfig).when(mHdmiControlServiceSpy).getHdmiCecConfig(); mHdmiControlServiceSpy.setIoLooper(mLooper); - mHdmiControlServiceSpy.setMessageValidator( - new HdmiCecMessageValidator(mHdmiControlServiceSpy)); mHdmiControlServiceSpy.setCecMessageBuffer( new CecMessageBuffer(mHdmiControlServiceSpy)); @@ -226,7 +224,7 @@ public class HdmiCecAtomLoggingTest { @Test public void testMessageReported_writesAtom_userControlPressed_noParams() { - HdmiCecMessage message = new HdmiCecMessage( + HdmiCecMessage message = HdmiCecMessage.build( Constants.ADDR_TV, Constants.ADDR_PLAYBACK_1, Constants.MESSAGE_USER_CONTROL_PRESSED, @@ -279,7 +277,7 @@ public class HdmiCecAtomLoggingTest { @Test public void testMessageReported_writesAtom_featureAbort_noParams() { - HdmiCecMessage message = new HdmiCecMessage( + HdmiCecMessage message = HdmiCecMessage.build( Constants.ADDR_TV, Constants.ADDR_PLAYBACK_1, Constants.MESSAGE_FEATURE_ABORT, diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java index a4113924294b..70bc460411c8 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java @@ -188,7 +188,6 @@ public class HdmiCecLocalDeviceAudioSystemTest { mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter()); mHdmiControlService.setCecController(mHdmiCecController); mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); - mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); mLocalDevices.add(mHdmiCecLocalDeviceAudioSystem); mLocalDevices.add(mHdmiCecLocalDevicePlayback); mHdmiCecLocalDeviceAudioSystem.setRoutingControlFeatureEnabled(true); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java index 2d13e692a3a9..6fc3354f07e7 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java @@ -59,10 +59,16 @@ public class HdmiCecLocalDevicePlaybackTest { HotplugDetectionAction.POLLING_INTERVAL_MS_FOR_PLAYBACK; private static final int PORT_1 = 1; - private static final HdmiDeviceInfo INFO_TV = new HdmiDeviceInfo( - ADDR_TV, 0x0000, PORT_1, HdmiDeviceInfo.DEVICE_TV, - 0x1234, "TV", - HdmiControlManager.POWER_STATUS_ON, HdmiControlManager.HDMI_CEC_VERSION_1_4_B); + private static final HdmiDeviceInfo INFO_TV = HdmiDeviceInfo.cecDeviceBuilder() + .setLogicalAddress(ADDR_TV) + .setPhysicalAddress(0x0000) + .setPortId(PORT_1) + .setDeviceType(HdmiDeviceInfo.DEVICE_TV) + .setVendorId(0x1234) + .setDisplayName("TV") + .setDevicePowerStatus(HdmiControlManager.POWER_STATUS_ON) + .setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_1_4_B) + .build(); private HdmiControlService mHdmiControlService; private HdmiCecController mHdmiCecController; @@ -138,7 +144,6 @@ public class HdmiCecLocalDevicePlaybackTest { mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter()); mHdmiControlService.setCecController(mHdmiCecController); mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); - mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); mLocalDevices.add(mHdmiCecLocalDevicePlayback); HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1]; hdmiPortInfos[0] = @@ -1691,10 +1696,16 @@ public class HdmiCecLocalDevicePlaybackTest { public void hotplugDetectionAction_removeDevice() { mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mHdmiControlService.getHdmiCecNetwork().clearDeviceList(); - HdmiDeviceInfo infoPlayback = new HdmiDeviceInfo( - Constants.ADDR_PLAYBACK_2, 0x1234, PORT_1, - HdmiDeviceInfo.DEVICE_PLAYBACK, 0x1234, "Playback 2", - HdmiControlManager.POWER_STATUS_ON, HdmiControlManager.HDMI_CEC_VERSION_1_4_B); + HdmiDeviceInfo infoPlayback = HdmiDeviceInfo.cecDeviceBuilder() + .setLogicalAddress(Constants.ADDR_PLAYBACK_2) + .setPhysicalAddress(0x1234) + .setPortId(PORT_1) + .setDeviceType(HdmiDeviceInfo.DEVICE_PLAYBACK) + .setVendorId(0x1234) + .setDisplayName("Playback 2") + .setDevicePowerStatus(HdmiControlManager.POWER_STATUS_ON) + .setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_1_4_B) + .build(); mHdmiControlService.getHdmiCecNetwork().addCecDevice(infoPlayback); // This logical address (ADDR_PLAYBACK_2) won't acknowledge the poll message sent by the // HotplugDetectionAction so it shall be removed. @@ -1726,8 +1737,15 @@ public class HdmiCecLocalDevicePlaybackTest { @Test public void getActiveSource_deviceInNetworkIsActiveSource() { - HdmiDeviceInfo externalDevice = new HdmiDeviceInfo(Constants.ADDR_PLAYBACK_3, 0x3000, 0, - Constants.ADDR_PLAYBACK_1, 0, "Test Device"); + HdmiDeviceInfo externalDevice = HdmiDeviceInfo.cecDeviceBuilder() + .setLogicalAddress(Constants.ADDR_PLAYBACK_3) + .setPhysicalAddress(0x3000) + .setPortId(0) + .setDeviceType(Constants.ADDR_PLAYBACK_1) + .setVendorId(0) + .setDisplayName("Test Device") + .build(); + mHdmiControlService.getHdmiCecNetwork().addCecDevice(externalDevice); mTestLooper.dispatchAll(); @@ -1739,8 +1757,14 @@ public class HdmiCecLocalDevicePlaybackTest { @Test public void getActiveSource_unknownDeviceIsActiveSource() { - HdmiDeviceInfo externalDevice = new HdmiDeviceInfo(Constants.ADDR_PLAYBACK_3, 0x3000, 0, - Constants.ADDR_PLAYBACK_1, 0, "Test Device"); + HdmiDeviceInfo externalDevice = HdmiDeviceInfo.cecDeviceBuilder() + .setLogicalAddress(Constants.ADDR_PLAYBACK_3) + .setPhysicalAddress(0x3000) + .setPortId(0) + .setDeviceType(Constants.ADDR_PLAYBACK_1) + .setVendorId(0) + .setDisplayName("Test Device") + .build(); mHdmiControlService.setActiveSource(externalDevice.getLogicalAddress(), externalDevice.getPhysicalAddress(), "HdmiControlServiceTest"); @@ -1933,7 +1957,7 @@ public class HdmiCecLocalDevicePlaybackTest { @Test public void doesNotSupportRecordTvScreen() { - HdmiCecMessage recordTvScreen = new HdmiCecMessage(ADDR_TV, mPlaybackLogicalAddress, + HdmiCecMessage recordTvScreen = HdmiCecMessage.build(ADDR_TV, mPlaybackLogicalAddress, Constants.MESSAGE_RECORD_TV_SCREEN, HdmiCecMessage.EMPTY_PARAM); mNativeWrapper.onCecMessage(recordTvScreen); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java index f5af6dfc2aae..fb8baa30e6b4 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java @@ -109,11 +109,6 @@ public class HdmiCecLocalDeviceTest { protected List<Integer> getRcFeatures() { return Collections.emptyList(); } - - @Override - protected List<Integer> getDeviceFeatures() { - return Collections.emptyList(); - } } private MyHdmiCecLocalDevice mHdmiLocalDevice; @@ -187,14 +182,6 @@ public class HdmiCecLocalDeviceTest { mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter()); mHdmiControlService.setCecController(mHdmiCecController); mHdmiLocalDevice = new MyHdmiCecLocalDevice(mHdmiControlService, DEVICE_TV); - mMessageValidator = - new HdmiCecMessageValidator(mHdmiControlService) { - @Override - int isValid(HdmiCecMessage message, boolean isMessageReceived) { - return HdmiCecMessageValidator.OK; - } - }; - mHdmiControlService.setMessageValidator(mMessageValidator); mLocalDevices.add(mHdmiLocalDevice); HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1]; @@ -230,7 +217,7 @@ public class HdmiCecLocalDeviceTest { @Test public void dispatchMessage_logicalAddressDoesNotMatch() { HdmiCecMessage msg = - new HdmiCecMessage( + HdmiCecMessage.build( ADDR_TV, ADDR_PLAYBACK_1, Constants.MESSAGE_CEC_VERSION, diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java index a260a6d34920..b6c4bc23f0e4 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java @@ -121,7 +121,6 @@ public class HdmiCecLocalDeviceTvTest { mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter()); mHdmiControlService.setCecController(mHdmiCecController); mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); - mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); mLocalDevices.add(mHdmiCecLocalDeviceTv); HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[2]; hdmiPortInfos[0] = @@ -172,8 +171,14 @@ public class HdmiCecLocalDeviceTvTest { @Test public void getActiveSource_deviceInNetworkIsActiveSource() { - HdmiDeviceInfo externalDevice = new HdmiDeviceInfo(Constants.ADDR_PLAYBACK_3, 0x1000, 0, - Constants.ADDR_PLAYBACK_1, 0, "Test Device"); + HdmiDeviceInfo externalDevice = HdmiDeviceInfo.cecDeviceBuilder() + .setLogicalAddress(Constants.ADDR_PLAYBACK_3) + .setPhysicalAddress(0x3000) + .setPortId(0) + .setDeviceType(Constants.ADDR_PLAYBACK_1) + .setVendorId(0) + .setDisplayName("Test Device") + .build(); mHdmiControlService.getHdmiCecNetwork().addCecDevice(externalDevice); mTestLooper.dispatchAll(); @@ -185,7 +190,7 @@ public class HdmiCecLocalDeviceTvTest { @Test public void getActiveSource_unknownLogicalAddressInNetworkIsActiveSource() { - HdmiDeviceInfo externalDevice = new HdmiDeviceInfo(0x1000, 1); + HdmiDeviceInfo externalDevice = HdmiDeviceInfo.hardwarePort(0x1000, 1); mHdmiControlService.setActiveSource(Constants.ADDR_UNREGISTERED, externalDevice.getPhysicalAddress(), "HdmiControlServiceTest"); @@ -197,8 +202,14 @@ public class HdmiCecLocalDeviceTvTest { @Test public void getActiveSource_unknownDeviceIsActiveSource() { - HdmiDeviceInfo externalDevice = new HdmiDeviceInfo(Constants.ADDR_PLAYBACK_3, 0x1000, 0, - Constants.ADDR_PLAYBACK_1, 0, "Test Device"); + HdmiDeviceInfo externalDevice = HdmiDeviceInfo.cecDeviceBuilder() + .setLogicalAddress(Constants.ADDR_PLAYBACK_3) + .setPhysicalAddress(0x0000) + .setPortId(0) + .setDeviceType(ADDR_PLAYBACK_1) + .setVendorId(0) + .setDisplayName("Test Device") + .build(); mHdmiControlService.setActiveSource(externalDevice.getLogicalAddress(), externalDevice.getPhysicalAddress(), "HdmiControlServiceTest"); @@ -240,7 +251,7 @@ public class HdmiCecLocalDeviceTvTest { HdmiControlManager.TV_WAKE_ON_ONE_TOUCH_PLAY_ENABLED); mTestLooper.dispatchAll(); mPowerManager.setInteractive(false); - HdmiCecMessage imageViewOn = new HdmiCecMessage(ADDR_PLAYBACK_1, mTvLogicalAddress, + HdmiCecMessage imageViewOn = HdmiCecMessage.build(ADDR_PLAYBACK_1, mTvLogicalAddress, Constants.MESSAGE_IMAGE_VIEW_ON, HdmiCecMessage.EMPTY_PARAM); assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(imageViewOn)).isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); @@ -268,7 +279,7 @@ public class HdmiCecLocalDeviceTvTest { HdmiControlManager.TV_WAKE_ON_ONE_TOUCH_PLAY_DISABLED); mTestLooper.dispatchAll(); mPowerManager.setInteractive(false); - HdmiCecMessage imageViewOn = new HdmiCecMessage(ADDR_PLAYBACK_1, mTvLogicalAddress, + HdmiCecMessage imageViewOn = HdmiCecMessage.build(ADDR_PLAYBACK_1, mTvLogicalAddress, Constants.MESSAGE_IMAGE_VIEW_ON, HdmiCecMessage.EMPTY_PARAM); assertThat(mHdmiCecLocalDeviceTv.dispatchMessage(imageViewOn)).isEqualTo(Constants.HANDLED); mTestLooper.dispatchAll(); @@ -478,7 +489,7 @@ public class HdmiCecLocalDeviceTvTest { @Test public void supportsRecordTvScreen() { - HdmiCecMessage recordTvScreen = new HdmiCecMessage(ADDR_RECORDER_1, mTvLogicalAddress, + HdmiCecMessage recordTvScreen = HdmiCecMessage.build(ADDR_RECORDER_1, mTvLogicalAddress, Constants.MESSAGE_RECORD_TV_SCREEN, HdmiCecMessage.EMPTY_PARAM); mNativeWrapper.onCecMessage(recordTvScreen); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageBuilderTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageBuilderTest.java index 453303eb5715..f869462f46c5 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageBuilderTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageBuilderTest.java @@ -22,20 +22,15 @@ import static com.android.server.hdmi.HdmiUtils.buildMessage; import static com.google.common.truth.Truth.assertThat; -import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; -import com.google.android.collect.Lists; - import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -import java.util.Collections; - @SmallTest @Presubmit @RunWith(JUnit4.class) @@ -100,89 +95,4 @@ public class HdmiCecMessageBuilderTest { assertThat(message).isEqualTo(buildMessage("40:A5")); } - - @Test - public void buildReportFeatures_basicTv_1_4() { - HdmiCecMessage message = HdmiCecMessageBuilder.buildReportFeatures(ADDR_TV, - HdmiControlManager.HDMI_CEC_VERSION_1_4_B, - Lists.newArrayList(HdmiDeviceInfo.DEVICE_TV), Constants.RC_PROFILE_TV, - Lists.newArrayList(Constants.RC_PROFILE_TV_NONE), Collections.emptyList()); - - assertThat(message).isEqualTo(buildMessage("0F:A6:05:80:00:00")); - } - - @Test - public void buildReportFeatures_basicPlayback_1_4() { - HdmiCecMessage message = HdmiCecMessageBuilder.buildReportFeatures(ADDR_PLAYBACK_1, - HdmiControlManager.HDMI_CEC_VERSION_1_4_B, - Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK), Constants.RC_PROFILE_TV, - Lists.newArrayList(Constants.RC_PROFILE_TV_NONE), Collections.emptyList()); - - assertThat(message).isEqualTo(buildMessage("4F:A6:05:10:00:00")); - } - - @Test - public void buildReportFeatures_basicPlaybackAudioSystem_1_4() { - HdmiCecMessage message = HdmiCecMessageBuilder.buildReportFeatures(ADDR_PLAYBACK_1, - HdmiControlManager.HDMI_CEC_VERSION_1_4_B, - Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK, - HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM), Constants.RC_PROFILE_TV, - Lists.newArrayList(Constants.RC_PROFILE_TV_NONE), Collections.emptyList()); - - assertThat(message).isEqualTo(buildMessage("4F:A6:05:18:00:00")); - } - - @Test - public void buildReportFeatures_basicTv_2_0() { - HdmiCecMessage message = HdmiCecMessageBuilder.buildReportFeatures(ADDR_TV, - HdmiControlManager.HDMI_CEC_VERSION_2_0, - Lists.newArrayList(HdmiDeviceInfo.DEVICE_TV), Constants.RC_PROFILE_TV, - Lists.newArrayList(Constants.RC_PROFILE_TV_NONE), Collections.emptyList()); - - assertThat(message).isEqualTo(buildMessage("0F:A6:06:80:00:00")); - } - - @Test - public void buildReportFeatures_remoteControlTv_2_0() { - HdmiCecMessage message = HdmiCecMessageBuilder.buildReportFeatures(ADDR_TV, - HdmiControlManager.HDMI_CEC_VERSION_2_0, - Lists.newArrayList(HdmiDeviceInfo.DEVICE_TV), Constants.RC_PROFILE_TV, - Lists.newArrayList(Constants.RC_PROFILE_TV_ONE), Collections.emptyList()); - - assertThat(message).isEqualTo(buildMessage("0F:A6:06:80:02:00")); - } - - @Test - public void buildReportFeatures_remoteControlPlayback_2_0() { - HdmiCecMessage message = HdmiCecMessageBuilder.buildReportFeatures(ADDR_TV, - HdmiControlManager.HDMI_CEC_VERSION_2_0, - Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK), Constants.RC_PROFILE_SOURCE, - Lists.newArrayList(Constants.RC_PROFILE_SOURCE_HANDLES_TOP_MENU, - Constants.RC_PROFILE_SOURCE_HANDLES_SETUP_MENU), Collections.emptyList()); - - assertThat(message).isEqualTo(buildMessage("0F:A6:06:10:4A:00")); - } - - @Test - public void buildReportFeatures_deviceFeaturesTv_2_0() { - HdmiCecMessage message = HdmiCecMessageBuilder.buildReportFeatures(ADDR_TV, - HdmiControlManager.HDMI_CEC_VERSION_2_0, - Lists.newArrayList(HdmiDeviceInfo.DEVICE_TV), Constants.RC_PROFILE_TV, - Lists.newArrayList(Constants.RC_PROFILE_TV_NONE), - Lists.newArrayList(Constants.DEVICE_FEATURE_TV_SUPPORTS_RECORD_TV_SCREEN)); - - assertThat(message).isEqualTo(buildMessage("0F:A6:06:80:00:40")); - } - - @Test - public void buildReportFeatures_deviceFeaturesPlayback_2_0() { - HdmiCecMessage message = HdmiCecMessageBuilder.buildReportFeatures(ADDR_TV, - HdmiControlManager.HDMI_CEC_VERSION_2_0, - Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK), Constants.RC_PROFILE_SOURCE, - Lists.newArrayList(Constants.RC_PROFILE_SOURCE_HANDLES_TOP_MENU, - Constants.RC_PROFILE_SOURCE_HANDLES_SETUP_MENU), - Lists.newArrayList(Constants.DEVICE_FEATURE_SUPPORTS_DECK_CONTROL)); - - assertThat(message).isEqualTo(buildMessage("0F:A6:06:10:4A:10")); - } } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageTest.java index cca5094065e6..2984cfa06550 100755 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageTest.java @@ -41,12 +41,12 @@ public class HdmiCecMessageTest { new EqualsTester() .addEqualityGroup( - new HdmiCecMessage(source, destination, opcode, params1), - new HdmiCecMessage(source, destination, opcode, params1)) - .addEqualityGroup(new HdmiCecMessage(source, destination, opcode, params2)) - .addEqualityGroup(new HdmiCecMessage(source + 1, destination, opcode, params1)) - .addEqualityGroup(new HdmiCecMessage(source, destination + 1, opcode, params1)) - .addEqualityGroup(new HdmiCecMessage(source, destination, opcode + 1, params1)) + HdmiCecMessage.build(source, destination, opcode, params1), + HdmiCecMessage.build(source, destination, opcode, params1)) + .addEqualityGroup(HdmiCecMessage.build(source, destination, opcode, params2)) + .addEqualityGroup(HdmiCecMessage.build(source + 1, destination, opcode, params1)) + .addEqualityGroup(HdmiCecMessage.build(source, destination + 1, opcode, params1)) + .addEqualityGroup(HdmiCecMessage.build(source, destination, opcode + 1, params1)) .testEquals(); } } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java index 548a439024ca..50c9f70ccb03 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java @@ -54,7 +54,6 @@ public class HdmiCecMessageValidatorTest { InstrumentationRegistry.getTargetContext(), Collections.emptyList()); mHdmiControlService.setIoLooper(mTestLooper.getLooper()); - mHdmiCecMessageValidator = new HdmiCecMessageValidator(mHdmiControlService); } @Test @@ -400,16 +399,6 @@ public class HdmiCecMessageValidatorTest { } @Test - public void isValid_reportFeatures() { - assertMessageValidity("0F:A6:05:80:00:00").isEqualTo(OK); - - assertMessageValidity("04:A6:05:80:00:00").isEqualTo(ERROR_DESTINATION); - assertMessageValidity("FF:A6:05:80:00:00").isEqualTo(ERROR_SOURCE); - - assertMessageValidity("0F:A6").isEqualTo(ERROR_PARAMETER_SHORT); - } - - @Test public void isValid_deckControl() { assertMessageValidity("40:42:01:6E").isEqualTo(OK); assertMessageValidity("40:42:04").isEqualTo(OK); @@ -649,6 +638,6 @@ public class HdmiCecMessageValidatorTest { } private IntegerSubject assertMessageValidity(String message) { - return assertThat(mHdmiCecMessageValidator.isValid(HdmiUtils.buildMessage(message), false)); + return assertThat(HdmiUtils.buildMessage(message).getValidationResult()); } } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java index 1048eb5acd1a..42fa32cabc06 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java @@ -17,9 +17,12 @@ package com.android.server.hdmi; +import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORTED; + import static com.google.common.truth.Truth.assertThat; import android.content.Context; +import android.hardware.hdmi.DeviceFeatures; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.HdmiPortInfo; @@ -80,7 +83,6 @@ public class HdmiCecNetworkTest { mHdmiMhlControllerStub = HdmiMhlControllerStub.create(mHdmiControlService); mHdmiControlService.setCecController(mHdmiCecController); mHdmiControlService.setHdmiMhlController(mHdmiMhlControllerStub); - mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); mHdmiCecNetwork = new HdmiCecNetwork(mHdmiControlService, mHdmiCecController, mHdmiMhlControllerStub); @@ -178,7 +180,7 @@ public class HdmiCecNetworkTest { assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_RESERVED); assertThat(cecDeviceInfo.getDisplayName()).isEqualTo( HdmiUtils.getDefaultDeviceName(logicalAddress)); - assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.UNKNOWN_VENDOR_ID); + assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.VENDOR_ID_UNKNOWN); assertThat(cecDeviceInfo.getDevicePowerStatus()).isEqualTo( HdmiControlManager.POWER_STATUS_UNKNOWN); @@ -216,7 +218,7 @@ public class HdmiCecNetworkTest { assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(type); assertThat(cecDeviceInfo.getDisplayName()).isEqualTo( HdmiUtils.getDefaultDeviceName(logicalAddress)); - assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.UNKNOWN_VENDOR_ID); + assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.VENDOR_ID_UNKNOWN); assertThat(cecDeviceInfo.getDevicePowerStatus()).isEqualTo( HdmiControlManager.POWER_STATUS_UNKNOWN); } @@ -258,7 +260,7 @@ public class HdmiCecNetworkTest { assertThat(cecDeviceInfo.getPhysicalAddress()).isEqualTo( Constants.INVALID_PHYSICAL_ADDRESS); assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_RESERVED); - assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.UNKNOWN_VENDOR_ID); + assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.VENDOR_ID_UNKNOWN); assertThat(cecDeviceInfo.getDisplayName()).isEqualTo( HdmiUtils.getDefaultDeviceName(logicalAddress)); assertThat(cecDeviceInfo.getDevicePowerStatus()).isEqualTo(powerStatus); @@ -279,7 +281,7 @@ public class HdmiCecNetworkTest { assertThat(cecDeviceInfo.getPhysicalAddress()).isEqualTo( Constants.INVALID_PHYSICAL_ADDRESS); assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_RESERVED); - assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.UNKNOWN_VENDOR_ID); + assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.VENDOR_ID_UNKNOWN); assertThat(cecDeviceInfo.getDisplayName()).isEqualTo(osdName); assertThat(cecDeviceInfo.getDevicePowerStatus()).isEqualTo( HdmiControlManager.POWER_STATUS_UNKNOWN); @@ -471,7 +473,7 @@ public class HdmiCecNetworkTest { assertThat(cecDeviceInfo.getPhysicalAddress()).isEqualTo( Constants.INVALID_PHYSICAL_ADDRESS); assertThat(cecDeviceInfo.getDeviceType()).isEqualTo(HdmiDeviceInfo.DEVICE_RESERVED); - assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.UNKNOWN_VENDOR_ID); + assertThat(cecDeviceInfo.getVendorId()).isEqualTo(Constants.VENDOR_ID_UNKNOWN); assertThat(cecDeviceInfo.getDisplayName()).isEqualTo( HdmiUtils.getDefaultDeviceName(logicalAddress)); assertThat(cecDeviceInfo.getDevicePowerStatus()).isEqualTo(powerStatus); @@ -514,12 +516,14 @@ public class HdmiCecNetworkTest { int logicalAddress = Constants.ADDR_PLAYBACK_1; int cecVersion = HdmiControlManager.HDMI_CEC_VERSION_1_4_B; mHdmiCecNetwork.handleCecMessage( - HdmiCecMessageBuilder.buildReportFeatures(logicalAddress, + ReportFeaturesMessage.build(logicalAddress, cecVersion, Collections.emptyList(), Constants.RC_PROFILE_SOURCE, Collections.emptyList(), - Collections.emptyList())); + DeviceFeatures.NO_FEATURES_SUPPORTED)); - assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1); + synchronized (mHdmiCecNetwork.mLock) { + assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1); + } HdmiDeviceInfo cecDeviceInfo = mHdmiCecNetwork.getCecDeviceInfo(logicalAddress); assertThat(cecDeviceInfo.getLogicalAddress()).isEqualTo(logicalAddress); @@ -531,12 +535,14 @@ public class HdmiCecNetworkTest { int logicalAddress = Constants.ADDR_PLAYBACK_1; int cecVersion = HdmiControlManager.HDMI_CEC_VERSION_2_0; mHdmiCecNetwork.handleCecMessage( - HdmiCecMessageBuilder.buildReportFeatures(logicalAddress, + ReportFeaturesMessage.build(logicalAddress, cecVersion, Collections.emptyList(), Constants.RC_PROFILE_SOURCE, Collections.emptyList(), - Collections.emptyList())); + DeviceFeatures.NO_FEATURES_SUPPORTED)); - assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1); + synchronized (mHdmiCecNetwork.mLock) { + assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1); + } HdmiDeviceInfo cecDeviceInfo = mHdmiCecNetwork.getCecDeviceInfo(logicalAddress); assertThat(cecDeviceInfo.getLogicalAddress()).isEqualTo(logicalAddress); @@ -544,10 +550,33 @@ public class HdmiCecNetworkTest { } @Test - public void getSafeCecDevicesLocked_addDevice_sizeOne() { - HdmiDeviceInfo cecDeviceInfo = new HdmiDeviceInfo(); + public void cecDevices_tracking_reportFeatures_updatesDeviceFeatures() { + // Features should be set correctly with the initial <Report Features> + int logicalAddress = Constants.ADDR_PLAYBACK_1; + int cecVersion = HdmiControlManager.HDMI_CEC_VERSION_2_0; + DeviceFeatures deviceFeatures = DeviceFeatures.NO_FEATURES_SUPPORTED; + mHdmiCecNetwork.handleCecMessage( + ReportFeaturesMessage.build(logicalAddress, + cecVersion, Collections.emptyList(), + Constants.RC_PROFILE_SOURCE, Collections.emptyList(), deviceFeatures)); - mHdmiCecNetwork.addCecDevice(cecDeviceInfo); + HdmiDeviceInfo cecDeviceInfo = mHdmiCecNetwork.getCecDeviceInfo(logicalAddress); + assertThat(cecDeviceInfo.getDeviceFeatures()).isEqualTo(deviceFeatures); + + // New information from <Report Features> should override old information + DeviceFeatures updatedFeatures = DeviceFeatures.NO_FEATURES_SUPPORTED.toBuilder() + .setSetAudioVolumeLevelSupport(FEATURE_SUPPORTED).build(); + mHdmiCecNetwork.handleCecMessage( + ReportFeaturesMessage.build(logicalAddress, + cecVersion, Collections.emptyList(), + Constants.RC_PROFILE_SOURCE, Collections.emptyList(), updatedFeatures)); + cecDeviceInfo = mHdmiCecNetwork.getCecDeviceInfo(logicalAddress); + assertThat(cecDeviceInfo.getDeviceFeatures()).isEqualTo(updatedFeatures); + } + + @Test + public void getSafeCecDevicesLocked_addDevice_sizeOne() { + mHdmiCecNetwork.addCecDevice(HdmiDeviceInfo.INACTIVE_DEVICE); assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1); } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java index bff12968e8cd..7a68285bc003 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java @@ -98,7 +98,6 @@ public class HdmiCecPowerStatusControllerTest { mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter()); mHdmiControlService.setCecController(hdmiCecController); mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); - mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); mLocalDevices.add(mHdmiCecLocalDevicePlayback); HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1]; hdmiPortInfos[0] = diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java index a44bd8e98b57..7751ef564138 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java @@ -111,8 +111,6 @@ public class HdmiControlServiceTest { mHdmiControlServiceSpy.setCecController(mHdmiCecController); mHdmiControlServiceSpy.setHdmiMhlController(HdmiMhlControllerStub.create( mHdmiControlServiceSpy)); - mHdmiControlServiceSpy.setMessageValidator(new HdmiCecMessageValidator( - mHdmiControlServiceSpy)); mLocalDevices.add(mAudioSystemDeviceSpy); mLocalDevices.add(mPlaybackDeviceSpy); @@ -487,7 +485,7 @@ public class HdmiControlServiceTest { Constants.ADDR_PLAYBACK_1)); mTestLooper.dispatchAll(); - HdmiCecMessage reportFeatures = HdmiCecMessageBuilder.buildReportFeatures( + HdmiCecMessage reportFeatures = ReportFeaturesMessage.build( Constants.ADDR_PLAYBACK_1, HdmiControlManager.HDMI_CEC_VERSION_2_0, Arrays.asList(DEVICE_PLAYBACK, DEVICE_AUDIO_SYSTEM), mPlaybackDeviceSpy.getRcProfile(), mPlaybackDeviceSpy.getRcFeatures(), @@ -505,7 +503,7 @@ public class HdmiControlServiceTest { mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mTestLooper.dispatchAll(); - HdmiCecMessage reportFeatures = HdmiCecMessageBuilder.buildReportFeatures( + HdmiCecMessage reportFeatures = ReportFeaturesMessage.build( Constants.ADDR_PLAYBACK_1, HdmiControlManager.HDMI_CEC_VERSION_2_0, Arrays.asList(DEVICE_PLAYBACK, DEVICE_AUDIO_SYSTEM), mPlaybackDeviceSpy.getRcProfile(), mPlaybackDeviceSpy.getRcFeatures(), @@ -522,7 +520,7 @@ public class HdmiControlServiceTest { mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mTestLooper.dispatchAll(); - HdmiCecMessage reportFeatures = HdmiCecMessageBuilder.buildReportFeatures( + HdmiCecMessage reportFeatures = ReportFeaturesMessage.build( Constants.ADDR_PLAYBACK_1, HdmiControlManager.HDMI_CEC_VERSION_2_0, Arrays.asList(DEVICE_PLAYBACK, DEVICE_AUDIO_SYSTEM), mPlaybackDeviceSpy.getRcProfile(), mPlaybackDeviceSpy.getRcFeatures(), diff --git a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java index 22ad956fcc10..561e6a5fec41 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java @@ -57,10 +57,16 @@ public class OneTouchPlayActionTest { new byte[]{HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON}; private static final int PORT_1 = 1; - private static final HdmiDeviceInfo INFO_TV = new HdmiDeviceInfo( - ADDR_TV, 0x0000, PORT_1, HdmiDeviceInfo.DEVICE_TV, - 0x1234, "TV", - HdmiControlManager.POWER_STATUS_ON, HdmiControlManager.HDMI_CEC_VERSION_1_4_B); + private static final HdmiDeviceInfo INFO_TV = HdmiDeviceInfo.cecDeviceBuilder() + .setLogicalAddress(ADDR_TV) + .setPhysicalAddress(0x0000) + .setPortId(PORT_1) + .setDeviceType(HdmiDeviceInfo.DEVICE_TV) + .setVendorId(0x1234) + .setDisplayName("TV") + .setDevicePowerStatus(HdmiControlManager.POWER_STATUS_ON) + .setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_1_4_B) + .build(); private Context mContextSpy; private HdmiControlService mHdmiControlService; @@ -113,7 +119,6 @@ public class OneTouchPlayActionTest { this.mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter()); mHdmiControlService.setCecController(hdmiCecController); mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); - mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); mHdmiControlService.initService(); mPowerManager = new FakePowerManagerWrapper(mContextSpy); mHdmiControlService.setPowerManager(mPowerManager); @@ -165,7 +170,7 @@ public class OneTouchPlayActionTest { mNativeWrapper.clearResultMessages(); assertThat(actionTimer.getState()).isEqualTo(STATE_WAITING_FOR_REPORT_POWER_STATUS); HdmiCecMessage reportPowerStatusOn = - new HdmiCecMessage( + HdmiCecMessage.build( ADDR_TV, playbackDevice.getDeviceInfo().getLogicalAddress(), Constants.MESSAGE_REPORT_POWER_STATUS, @@ -218,7 +223,7 @@ public class OneTouchPlayActionTest { mNativeWrapper.clearResultMessages(); assertThat(actionTimer.getState()).isEqualTo(STATE_WAITING_FOR_REPORT_POWER_STATUS); HdmiCecMessage reportPowerStatusOn = - new HdmiCecMessage( + HdmiCecMessage.build( ADDR_TV, playbackDevice.getDeviceInfo().getLogicalAddress(), Constants.MESSAGE_REPORT_POWER_STATUS, @@ -271,7 +276,7 @@ public class OneTouchPlayActionTest { mNativeWrapper.clearResultMessages(); assertThat(actionTimer.getState()).isEqualTo(STATE_WAITING_FOR_REPORT_POWER_STATUS); HdmiCecMessage reportPowerStatusTransientToOn = - new HdmiCecMessage( + HdmiCecMessage.build( ADDR_TV, playbackDevice.getDeviceInfo().getLogicalAddress(), Constants.MESSAGE_REPORT_POWER_STATUS, @@ -284,7 +289,7 @@ public class OneTouchPlayActionTest { mNativeWrapper.clearResultMessages(); HdmiCecMessage reportPowerStatusOn = - new HdmiCecMessage( + HdmiCecMessage.build( ADDR_TV, playbackDevice.getDeviceInfo().getLogicalAddress(), Constants.MESSAGE_REPORT_POWER_STATUS, @@ -428,7 +433,7 @@ public class OneTouchPlayActionTest { mNativeWrapper.clearResultMessages(); assertThat(actionTimer.getState()).isEqualTo(STATE_WAITING_FOR_REPORT_POWER_STATUS); HdmiCecMessage reportPowerStatusOn = - new HdmiCecMessage( + HdmiCecMessage.build( ADDR_TV, playbackDevice.getDeviceInfo().getLogicalAddress(), Constants.MESSAGE_REPORT_POWER_STATUS, @@ -482,7 +487,7 @@ public class OneTouchPlayActionTest { mNativeWrapper.clearResultMessages(); assertThat(actionTimer.getState()).isEqualTo(STATE_WAITING_FOR_REPORT_POWER_STATUS); HdmiCecMessage reportPowerStatusOn = - new HdmiCecMessage( + HdmiCecMessage.build( ADDR_TV, playbackDevice.getDeviceInfo().getLogicalAddress(), Constants.MESSAGE_REPORT_POWER_STATUS, diff --git a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java index 2f22bce18558..c878f99f7912 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java @@ -99,7 +99,6 @@ public class PowerStatusMonitorActionTest { this.mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter()); mHdmiControlService.setCecController(hdmiCecController); mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); - mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); mTvDevice = new HdmiCecLocalDeviceTv(mHdmiControlService); mTvDevice.init(); mLocalDevices.add(mTvDevice); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ReportFeaturesMessageTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ReportFeaturesMessageTest.java new file mode 100644 index 000000000000..22f1f431db0a --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/hdmi/ReportFeaturesMessageTest.java @@ -0,0 +1,176 @@ +/* + * 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.hdmi; + +import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1; +import static com.android.server.hdmi.Constants.ADDR_TV; +import static com.android.server.hdmi.HdmiCecMessageValidator.ERROR_DESTINATION; +import static com.android.server.hdmi.HdmiCecMessageValidator.ERROR_PARAMETER_SHORT; +import static com.android.server.hdmi.HdmiCecMessageValidator.ERROR_SOURCE; +import static com.android.server.hdmi.HdmiCecMessageValidator.OK; +import static com.android.server.hdmi.HdmiUtils.buildMessage; + +import static com.google.common.truth.Truth.assertThat; + +import android.hardware.hdmi.DeviceFeatures; +import android.hardware.hdmi.HdmiControlManager; +import android.hardware.hdmi.HdmiDeviceInfo; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import com.google.android.collect.Lists; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@SmallTest +@Presubmit +@RunWith(JUnit4.class) +public class ReportFeaturesMessageTest { + @Test + public void build_invalidMessages() { + assertThat(HdmiUtils.buildMessage("FF:A6:05:80:00:00") + .getValidationResult()).isEqualTo(ERROR_SOURCE); + assertThat(HdmiUtils.buildMessage("04:A6:05:80:00:00") + .getValidationResult()).isEqualTo(ERROR_DESTINATION); + assertThat(HdmiUtils.buildMessage("0F:A6") + .getValidationResult()).isEqualTo(ERROR_PARAMETER_SHORT); + assertThat(HdmiUtils.buildMessage("4F:A6:06:00:80:80:00") + .getValidationResult()).isEqualTo(ERROR_PARAMETER_SHORT); + } + + @Test + public void build_longMessage() { + HdmiCecMessage longMessage = HdmiUtils.buildMessage("4F:A6:05:00:80:80:00:81:80:00"); + assertThat(longMessage).isInstanceOf(ReportFeaturesMessage.class); + ReportFeaturesMessage longReportFeaturesMessage = (ReportFeaturesMessage) longMessage; + + HdmiCecMessage shortMessage = HdmiUtils.buildMessage("4F:A6:05:00:00:01"); + assertThat(shortMessage).isInstanceOf(ReportFeaturesMessage.class); + ReportFeaturesMessage shortReportFeaturesMessage = (ReportFeaturesMessage) shortMessage; + + assertThat(longReportFeaturesMessage.getDeviceFeatures()).isEqualTo( + shortReportFeaturesMessage.getDeviceFeatures()); + assertThat(longReportFeaturesMessage.getCecVersion()).isEqualTo( + shortReportFeaturesMessage.getCecVersion()); + } + + @Test + public void build_basicTv_1_4() { + HdmiCecMessage message = ReportFeaturesMessage.build(ADDR_TV, + HdmiControlManager.HDMI_CEC_VERSION_1_4_B, + Lists.newArrayList(HdmiDeviceInfo.DEVICE_TV), Constants.RC_PROFILE_TV, + Lists.newArrayList(Constants.RC_PROFILE_TV_NONE), + DeviceFeatures.NO_FEATURES_SUPPORTED); + + assertThat(message.getValidationResult()).isEqualTo(OK); + assertThat(message).isEqualTo(buildMessage("0F:A6:05:80:00:00")); + } + + @Test + public void build_basicPlayback_1_4() { + HdmiCecMessage message = ReportFeaturesMessage.build(ADDR_PLAYBACK_1, + HdmiControlManager.HDMI_CEC_VERSION_1_4_B, + Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK), Constants.RC_PROFILE_TV, + Lists.newArrayList(Constants.RC_PROFILE_TV_NONE), + DeviceFeatures.NO_FEATURES_SUPPORTED); + + assertThat(message.getValidationResult()).isEqualTo(OK); + assertThat(message).isEqualTo(buildMessage("4F:A6:05:10:00:00")); + } + + @Test + public void build_basicPlaybackAudioSystem_1_4() { + HdmiCecMessage message = ReportFeaturesMessage.build(ADDR_PLAYBACK_1, + HdmiControlManager.HDMI_CEC_VERSION_1_4_B, + Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK, + HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM), Constants.RC_PROFILE_TV, + Lists.newArrayList(Constants.RC_PROFILE_TV_NONE), + DeviceFeatures.NO_FEATURES_SUPPORTED); + + assertThat(message.getValidationResult()).isEqualTo(OK); + assertThat(message).isEqualTo(buildMessage("4F:A6:05:18:00:00")); + } + + @Test + public void build_basicTv_2_0() { + HdmiCecMessage message = ReportFeaturesMessage.build(ADDR_TV, + HdmiControlManager.HDMI_CEC_VERSION_2_0, + Lists.newArrayList(HdmiDeviceInfo.DEVICE_TV), Constants.RC_PROFILE_TV, + Lists.newArrayList(Constants.RC_PROFILE_TV_NONE), + DeviceFeatures.NO_FEATURES_SUPPORTED); + + assertThat(message.getValidationResult()).isEqualTo(OK); + assertThat(message).isEqualTo(buildMessage("0F:A6:06:80:00:00")); + } + + @Test + public void build_remoteControlTv_2_0() { + HdmiCecMessage message = ReportFeaturesMessage.build(ADDR_TV, + HdmiControlManager.HDMI_CEC_VERSION_2_0, + Lists.newArrayList(HdmiDeviceInfo.DEVICE_TV), Constants.RC_PROFILE_TV, + Lists.newArrayList(Constants.RC_PROFILE_TV_ONE), + DeviceFeatures.NO_FEATURES_SUPPORTED); + + assertThat(message.getValidationResult()).isEqualTo(OK); + assertThat(message).isEqualTo(buildMessage("0F:A6:06:80:02:00")); + } + + @Test + public void build_remoteControlPlayback_2_0() { + HdmiCecMessage message = ReportFeaturesMessage.build(ADDR_TV, + HdmiControlManager.HDMI_CEC_VERSION_2_0, + Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK), Constants.RC_PROFILE_SOURCE, + Lists.newArrayList(Constants.RC_PROFILE_SOURCE_HANDLES_TOP_MENU, + Constants.RC_PROFILE_SOURCE_HANDLES_SETUP_MENU), + DeviceFeatures.NO_FEATURES_SUPPORTED); + + assertThat(message.getValidationResult()).isEqualTo(OK); + assertThat(message).isEqualTo(buildMessage("0F:A6:06:10:4A:00")); + } + + @Test + public void build_deviceFeaturesTv_2_0() { + HdmiCecMessage message = ReportFeaturesMessage.build(ADDR_TV, + HdmiControlManager.HDMI_CEC_VERSION_2_0, + Lists.newArrayList(HdmiDeviceInfo.DEVICE_TV), Constants.RC_PROFILE_TV, + Lists.newArrayList(Constants.RC_PROFILE_TV_NONE), + DeviceFeatures.NO_FEATURES_SUPPORTED.toBuilder() + .setRecordTvScreenSupport(DeviceFeatures.FEATURE_SUPPORTED) + .build()); + + assertThat(message.getValidationResult()).isEqualTo(OK); + assertThat(message).isEqualTo(buildMessage("0F:A6:06:80:00:40")); + } + + @Test + public void build_deviceFeaturesPlayback_2_0() { + HdmiCecMessage message = ReportFeaturesMessage.build(ADDR_TV, + HdmiControlManager.HDMI_CEC_VERSION_2_0, + Lists.newArrayList(HdmiDeviceInfo.DEVICE_PLAYBACK), Constants.RC_PROFILE_SOURCE, + Lists.newArrayList(Constants.RC_PROFILE_SOURCE_HANDLES_TOP_MENU, + Constants.RC_PROFILE_SOURCE_HANDLES_SETUP_MENU), + DeviceFeatures.NO_FEATURES_SUPPORTED.toBuilder() + .setDeckControlSupport(DeviceFeatures.FEATURE_SUPPORTED) + .build()); + + assertThat(message.getValidationResult()).isEqualTo(OK); + assertThat(message).isEqualTo(buildMessage("0F:A6:06:10:4A:10")); + } +} diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java index 2d81fc94e274..6184c2116e1d 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java @@ -123,7 +123,6 @@ public class RequestSadActionTest { mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter()); mHdmiControlService.setCecController(mHdmiCecController); mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); - mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); mLocalDevices.add(mHdmiCecLocalDeviceTv); mHdmiControlService.initService(); mPowerManager = new FakePowerManagerWrapper(context); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java index 302c0ac399d0..0587864eeb20 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java @@ -101,19 +101,29 @@ public class RoutingControlActionTest { private static final byte[] PLAYER_PARAM = new byte[]{(PHYSICAL_ADDRESS_PLAYER >> 8) & 0xFF, PHYSICAL_ADDRESS_PLAYER & 0xFF}; - private static final HdmiDeviceInfo DEVICE_INFO_AVR = - new HdmiDeviceInfo(ADDR_AUDIO_SYSTEM, PHYSICAL_ADDRESS_AVR, PORT_1, - HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM, VENDOR_ID_AVR, "Audio"); - private static final HdmiDeviceInfo DEVICE_INFO_PLAYER = - new HdmiDeviceInfo(ADDR_PLAYBACK_1, PHYSICAL_ADDRESS_PLAYER, PORT_1, - HdmiDeviceInfo.DEVICE_PLAYBACK, VENDOR_ID_AVR, "Player"); - private static final HdmiCecMessage ROUTING_INFORMATION_TUNER = new HdmiCecMessage( + private static final HdmiDeviceInfo DEVICE_INFO_AVR = HdmiDeviceInfo.cecDeviceBuilder() + .setLogicalAddress(ADDR_AUDIO_SYSTEM) + .setPhysicalAddress(PHYSICAL_ADDRESS_AVR) + .setPortId(PORT_1) + .setDeviceType(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) + .setVendorId(VENDOR_ID_AVR) + .setDisplayName("Audio") + .build(); + private static final HdmiDeviceInfo DEVICE_INFO_PLAYER = HdmiDeviceInfo.cecDeviceBuilder() + .setLogicalAddress(ADDR_PLAYBACK_1) + .setPhysicalAddress(PHYSICAL_ADDRESS_PLAYER) + .setPortId(PORT_1) + .setDeviceType(HdmiDeviceInfo.DEVICE_PLAYBACK) + .setVendorId(VENDOR_ID_AVR) + .setDisplayName("Player") + .build(); + private static final HdmiCecMessage ROUTING_INFORMATION_TUNER = HdmiCecMessage.build( ADDR_UNREGISTERED, ADDR_BROADCAST, MESSAGE_ROUTING_INFORMATION, TUNER_PARAM); - private static final HdmiCecMessage ROUTING_INFORMATION_PLAYER = new HdmiCecMessage( + private static final HdmiCecMessage ROUTING_INFORMATION_PLAYER = HdmiCecMessage.build( ADDR_UNREGISTERED, ADDR_BROADCAST, MESSAGE_ROUTING_INFORMATION, PLAYER_PARAM); - private static final HdmiCecMessage ACTIVE_SOURCE_TUNER = new HdmiCecMessage( + private static final HdmiCecMessage ACTIVE_SOURCE_TUNER = HdmiCecMessage.build( ADDR_TUNER_1, ADDR_BROADCAST, MESSAGE_ACTIVE_SOURCE, TUNER_PARAM); - private static final HdmiCecMessage ACTIVE_SOURCE_PLAYER = new HdmiCecMessage( + private static final HdmiCecMessage ACTIVE_SOURCE_PLAYER = HdmiCecMessage.build( ADDR_PLAYBACK_1, ADDR_BROADCAST, MESSAGE_ACTIVE_SOURCE, PLAYER_PARAM); private HdmiControlService mHdmiControlService; @@ -169,7 +179,6 @@ public class RoutingControlActionTest { mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter()); mHdmiControlService.setCecController(mHdmiCecController); mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); - mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); mLocalDevices.add(mHdmiCecLocalDeviceTv); HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1]; hdmiPortInfos[0] = diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java new file mode 100644 index 000000000000..a34b55c00308 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java @@ -0,0 +1,218 @@ +/* + * 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.hdmi; + +import static android.hardware.hdmi.DeviceFeatures.FEATURE_NOT_SUPPORTED; +import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORTED; +import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORT_UNKNOWN; + +import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.spy; + +import android.content.Context; +import android.content.ContextWrapper; +import android.hardware.hdmi.DeviceFeatures; +import android.hardware.hdmi.HdmiControlManager; +import android.hardware.hdmi.HdmiDeviceInfo; +import android.hardware.hdmi.IHdmiControlCallback; +import android.hardware.tv.cec.V1_0.SendMessageResult; +import android.os.Looper; +import android.os.RemoteException; +import android.os.test.TestLooper; +import android.platform.test.annotations.Presubmit; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; + +import com.android.server.SystemService; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.ArrayList; +import java.util.Collections; + +@SmallTest +@Presubmit +@RunWith(JUnit4.class) +public class SetAudioVolumeLevelDiscoveryActionTest { + private HdmiControlService mHdmiControlServiceSpy; + private HdmiCecController mHdmiCecController; + private HdmiCecLocalDevicePlayback mPlaybackDevice; + private FakeNativeWrapper mNativeWrapper; + private FakePowerManagerWrapper mPowerManager; + private Looper mLooper; + private Context mContextSpy; + private TestLooper mTestLooper = new TestLooper(); + private int mPhysicalAddress = 0x1100; + private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>(); + private int mPlaybackLogicalAddress; + + private TestCallback mTestCallback; + private SetAudioVolumeLevelDiscoveryAction mAction; + + /** + * Setup: Local Playback device attempts to determine whether a connected TV supports + * <Set Audio Volume Level>. + */ + @Before + public void setUp() throws RemoteException { + mContextSpy = spy(new ContextWrapper( + InstrumentationRegistry.getInstrumentation().getTargetContext())); + + mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, Collections.emptyList())); + doNothing().when(mHdmiControlServiceSpy) + .writeStringSystemProperty(anyString(), anyString()); + + mLooper = mTestLooper.getLooper(); + mHdmiControlServiceSpy.setIoLooper(mLooper); + mHdmiControlServiceSpy.setHdmiCecConfig(new FakeHdmiCecConfig(mContextSpy)); + + mNativeWrapper = new FakeNativeWrapper(); + mNativeWrapper.setPhysicalAddress(mPhysicalAddress); + + mHdmiCecController = HdmiCecController.createWithNativeWrapper( + mHdmiControlServiceSpy, mNativeWrapper, mHdmiControlServiceSpy.getAtomWriter()); + mHdmiControlServiceSpy.setCecController(mHdmiCecController); + mHdmiControlServiceSpy.setHdmiMhlController( + HdmiMhlControllerStub.create(mHdmiControlServiceSpy)); + mHdmiControlServiceSpy.initService(); + mPowerManager = new FakePowerManagerWrapper(mContextSpy); + mHdmiControlServiceSpy.setPowerManager(mPowerManager); + + mPlaybackDevice = new HdmiCecLocalDevicePlayback(mHdmiControlServiceSpy); + mPlaybackDevice.init(); + mLocalDevices.add(mPlaybackDevice); + + mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + mHdmiControlServiceSpy.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); + mTestLooper.dispatchAll(); + + synchronized (mPlaybackDevice.mLock) { + mPlaybackLogicalAddress = mPlaybackDevice.getDeviceInfo().getLogicalAddress(); + } + + // Setup specific to these tests + mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( + Constants.ADDR_TV, 0x0000, HdmiDeviceInfo.DEVICE_TV)); + mTestLooper.dispatchAll(); + + mTestCallback = new TestCallback(); + mAction = new SetAudioVolumeLevelDiscoveryAction(mPlaybackDevice, + Constants.ADDR_TV, mTestCallback); + } + + @Test + public void sendsSetAudioVolumeLevel() { + mPlaybackDevice.addAndStartAction(mAction); + mTestLooper.dispatchAll(); + + HdmiCecMessage setAudioVolumeLevel = SetAudioVolumeLevelMessage.build( + mPlaybackLogicalAddress, Constants.ADDR_TV, + Constants.AUDIO_VOLUME_STATUS_UNKNOWN); + assertThat(mNativeWrapper.getResultMessages()).contains(setAudioVolumeLevel); + } + + @Test + public void noMatchingFeatureAbortReceived_actionSucceedsAndSetsFeatureSupported() { + mPlaybackDevice.addAndStartAction(mAction); + mTestLooper.dispatchAll(); + + // Wrong opcode + mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildFeatureAbortCommand( + Constants.ADDR_TV, + mPlaybackLogicalAddress, + Constants.MESSAGE_GIVE_DECK_STATUS, + Constants.ABORT_UNRECOGNIZED_OPCODE)); + // Wrong source + mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildFeatureAbortCommand( + Constants.ADDR_AUDIO_SYSTEM, + mPlaybackLogicalAddress, + Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL, + Constants.ABORT_UNRECOGNIZED_OPCODE)); + mTestLooper.dispatchAll(); + + mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); + mTestLooper.dispatchAll(); + + @DeviceFeatures.FeatureSupportStatus int avcSupport = + mHdmiControlServiceSpy.getHdmiCecNetwork().getCecDeviceInfo(Constants.ADDR_TV) + .getDeviceFeatures().getSetAudioVolumeLevelSupport(); + + assertThat(avcSupport).isEqualTo(FEATURE_SUPPORTED); + assertThat(mTestCallback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS); + } + + @Test + public void matchingFeatureAbortReceived_actionSucceedsAndSetsFeatureNotSupported() { + mPlaybackDevice.addAndStartAction(mAction); + mTestLooper.dispatchAll(); + + mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildFeatureAbortCommand( + Constants.ADDR_TV, + mPlaybackLogicalAddress, + Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL, + Constants.ABORT_UNRECOGNIZED_OPCODE)); + mTestLooper.dispatchAll(); + + @DeviceFeatures.FeatureSupportStatus int avcSupport = + mHdmiControlServiceSpy.getHdmiCecNetwork().getCecDeviceInfo(Constants.ADDR_TV) + .getDeviceFeatures().getSetAudioVolumeLevelSupport(); + + assertThat(avcSupport).isEqualTo(FEATURE_NOT_SUPPORTED); + assertThat(mTestCallback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS); + } + + @Test + public void messageFailedToSend_actionFailsAndDoesNotUpdateFeatureSupport() { + mNativeWrapper.setMessageSendResult(Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL, + SendMessageResult.FAIL); + mTestLooper.dispatchAll(); + + mPlaybackDevice.addAndStartAction(mAction); + mTestLooper.dispatchAll(); + + @DeviceFeatures.FeatureSupportStatus int avcSupport = + mHdmiControlServiceSpy.getHdmiCecNetwork().getCecDeviceInfo(Constants.ADDR_TV) + .getDeviceFeatures().getSetAudioVolumeLevelSupport(); + + assertThat(avcSupport).isEqualTo(FEATURE_SUPPORT_UNKNOWN); + assertThat(mTestCallback.getResult()).isEqualTo( + HdmiControlManager.RESULT_COMMUNICATION_FAILED); + } + + private static class TestCallback extends IHdmiControlCallback.Stub { + private final ArrayList<Integer> mCallbackResult = new ArrayList<Integer>(); + + @Override + public void onComplete(int result) { + mCallbackResult.add(result); + } + + private int getResult() { + assertThat(mCallbackResult.size()).isEqualTo(1); + return mCallbackResult.get(0); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelMessageTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelMessageTest.java new file mode 100644 index 000000000000..0201c68361f8 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelMessageTest.java @@ -0,0 +1,68 @@ +/* + * 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.hdmi; + +import static com.android.server.hdmi.HdmiCecMessageValidator.ERROR_DESTINATION; +import static com.android.server.hdmi.HdmiCecMessageValidator.ERROR_PARAMETER_SHORT; +import static com.android.server.hdmi.HdmiCecMessageValidator.ERROR_SOURCE; +import static com.android.server.hdmi.HdmiUtils.buildMessage; + +import static com.google.common.truth.Truth.assertThat; + +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@SmallTest +@Presubmit +@RunWith(JUnit4.class) +public class SetAudioVolumeLevelMessageTest { + @Test + public void build_maxVolume() { + HdmiCecMessage message = SetAudioVolumeLevelMessage.build( + Constants.ADDR_TV, Constants.ADDR_PLAYBACK_1, 100); + assertThat(message.getValidationResult()).isEqualTo(HdmiCecMessageValidator.OK); + assertThat(message).isEqualTo(buildMessage("04:73:64")); + } + + @Test + public void build_noVolumeChange() { + HdmiCecMessage message = SetAudioVolumeLevelMessage.build( + Constants.ADDR_TV, Constants.ADDR_AUDIO_SYSTEM, 0x7F); + assertThat(message.getValidationResult()).isEqualTo(HdmiCecMessageValidator.OK); + assertThat(message).isEqualTo(buildMessage("05:73:7F")); + } + + @Test + public void build_invalid() { + assertThat(SetAudioVolumeLevelMessage + .build(Constants.ADDR_UNREGISTERED, Constants.ADDR_AUDIO_SYSTEM, 50) + .getValidationResult()) + .isEqualTo(ERROR_SOURCE); + assertThat(SetAudioVolumeLevelMessage + .build(Constants.ADDR_TV, Constants.ADDR_BROADCAST, 50) + .getValidationResult()) + .isEqualTo(ERROR_DESTINATION); + assertThat(HdmiUtils.buildMessage("04:73") + .getValidationResult()) + .isEqualTo(ERROR_PARAMETER_SHORT); + } +} diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java index b34b853b4ca3..9d143418fd4c 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java @@ -100,7 +100,6 @@ public class SystemAudioAutoInitiationActionTest { mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter()); mHdmiControlService.setCecController(hdmiCecController); mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); - mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); mLocalDevices.add(mHdmiCecLocalDeviceTv); HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[2]; hdmiPortInfos[0] = diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java index b40650e767fd..095c69c776a2 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java @@ -64,7 +64,7 @@ public class SystemAudioInitiationActionFromAvrTest { @Before public void SetUp() { - mDeviceInfoForTests = new HdmiDeviceInfo(1001, 1234); + mDeviceInfoForTests = HdmiDeviceInfo.hardwarePort(1001, 1234); Context context = InstrumentationRegistry.getTargetContext(); diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java index 658f8d52d74b..ee2bb0ad429a 100644 --- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java @@ -30,6 +30,7 @@ import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -58,6 +59,7 @@ import org.mockito.Mock; @RunWith(AndroidJUnit4.class) public class LocaleManagerServiceTest { private static final String DEFAULT_PACKAGE_NAME = "com.android.myapp"; + private static final String DEFAULT_INSTALLER_PACKAGE_NAME = "com.android.myapp.installer"; private static final int DEFAULT_USER_ID = 0; private static final int DEFAULT_UID = Binder.getCallingUid() + 100; private static final int INVALID_UID = -1; @@ -66,7 +68,8 @@ public class LocaleManagerServiceTest { LocaleList.forLanguageTags(DEFAULT_LOCALE_TAGS); private static final InstallSourceInfo DEFAULT_INSTALL_SOURCE_INFO = new InstallSourceInfo( /* initiatingPackageName = */ null, /* initiatingPackageSigningInfo = */ null, - /* originatingPackageName = */ null, /* installingPackageName = */ null); + /* originatingPackageName = */ null, + /* installingPackageName = */ DEFAULT_INSTALLER_PACKAGE_NAME); private LocaleManagerService mLocaleManagerService; private LocaleManagerBackupHelper mMockBackupHelper; @@ -89,7 +92,7 @@ public class LocaleManagerServiceTest { mMockActivityManager = mock(ActivityManagerInternal.class); mMockPackageManagerInternal = mock(PackageManagerInternal.class); - // For unit tests, set the default (null) installer info + // For unit tests, set the default installer info PackageManager mockPackageManager = mock(PackageManager.class); doReturn(DEFAULT_INSTALL_SOURCE_INFO).when(mockPackageManager) .getInstallSourceInfo(anyString()); @@ -275,6 +278,23 @@ public class LocaleManagerServiceTest { assertEquals(DEFAULT_LOCALES, locales); } + @Test + public void testGetApplicationLocales_callerIsInstaller_returnsLocales() + throws Exception { + doReturn(DEFAULT_UID).when(mMockPackageManagerInternal) + .getPackageUid(eq(DEFAULT_PACKAGE_NAME), anyLong(), anyInt()); + doReturn(Binder.getCallingUid()).when(mMockPackageManagerInternal) + .getPackageUid(eq(DEFAULT_INSTALLER_PACKAGE_NAME), anyLong(), anyInt()); + doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES)) + .when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt()); + + LocaleList locales = + mLocaleManagerService.getApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID); + + verify(mMockContext, never()).enforceCallingOrSelfPermission(any(), any()); + assertEquals(DEFAULT_LOCALES, locales); + } + private static void assertNoLocalesStored(LocaleList locales) { assertNull(locales); } 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 66df0fecad60..81c98717d2e7 100644 --- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java @@ -49,12 +49,8 @@ import static android.net.NetworkPolicyManager.blockedReasonsToString; import static android.net.NetworkPolicyManager.uidPoliciesToString; import static android.net.NetworkPolicyManager.uidRulesToString; import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK; -import static android.net.NetworkStats.IFACE_ALL; import static android.net.NetworkStats.METERED_NO; import static android.net.NetworkStats.METERED_YES; -import static android.net.NetworkStats.SET_ALL; -import static android.net.NetworkStats.TAG_ALL; -import static android.net.NetworkStats.TAG_NONE; import static android.net.NetworkTemplate.buildTemplateCarrierMetered; import static android.net.NetworkTemplate.buildTemplateWifi; import static android.net.TrafficStats.MB_IN_BYTES; @@ -75,6 +71,7 @@ import static com.android.server.net.NetworkPolicyManagerService.TYPE_LIMIT_SNOO import static com.android.server.net.NetworkPolicyManagerService.TYPE_RAPID; import static com.android.server.net.NetworkPolicyManagerService.TYPE_WARNING; import static com.android.server.net.NetworkPolicyManagerService.UidBlockedState.getEffectiveBlockedReasons; +import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_UPDATED; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -108,6 +105,8 @@ import android.app.IActivityManager; import android.app.IUidObserver; import android.app.Notification; import android.app.NotificationManager; +import android.app.usage.NetworkStats; +import android.app.usage.NetworkStatsManager; import android.app.usage.UsageStatsManagerInternal; import android.content.Context; import android.content.Intent; @@ -125,8 +124,6 @@ import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkPolicy; import android.net.NetworkStateSnapshot; -import android.net.NetworkStats; -import android.net.NetworkStatsHistory; import android.net.NetworkTemplate; import android.net.TelephonyNetworkSpecifier; import android.net.wifi.WifiInfo; @@ -138,7 +135,6 @@ import android.os.PowerManagerInternal; import android.os.PowerSaveState; import android.os.RemoteException; import android.os.SimpleClock; -import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; import android.platform.test.annotations.Presubmit; @@ -263,12 +259,13 @@ public class NetworkPolicyManagerServiceTest { private @Mock CarrierConfigManager mCarrierConfigManager; private @Mock TelephonyManager mTelephonyManager; private @Mock UserManager mUserManager; + private @Mock NetworkStatsManager mStatsManager; + private TestDependencies mDeps; private ArgumentCaptor<ConnectivityManager.NetworkCallback> mNetworkCallbackCaptor = ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class); private ActivityManagerInternal mActivityManagerInternal; - private NetworkStatsManagerInternal mStatsService; private IUidObserver mUidObserver; private INetworkManagementEventObserver mNetworkObserver; @@ -335,8 +332,47 @@ public class NetworkPolicyManagerServiceTest { .setBatterySaverEnabled(false).build(); final PowerManagerInternal pmInternal = addLocalServiceMock(PowerManagerInternal.class); when(pmInternal.getLowPowerState(anyInt())).thenReturn(state); + } + + private class TestDependencies extends NetworkPolicyManagerService.Dependencies { + private final SparseArray<NetworkStats.Bucket> mMockedStats = new SparseArray<>(); - mStatsService = addLocalServiceMock(NetworkStatsManagerInternal.class); + TestDependencies(Context context) { + super(context); + } + + @Override + long getNetworkTotalBytes(NetworkTemplate template, long start, long end) { + int total = 0; + for (int i = 0; i < mMockedStats.size(); i++) { + NetworkStats.Bucket bucket = mMockedStats.valueAt(i); + total += bucket.getRxBytes() + bucket.getTxBytes(); + } + return total; + } + + @Override + List<NetworkStats.Bucket> getNetworkUidBytes(NetworkTemplate template, long start, + long end) { + final List<NetworkStats.Bucket> ret = new ArrayList<>(); + for (int i = 0; i < mMockedStats.size(); i++) { + ret.add(mMockedStats.valueAt(i)); + } + return ret; + } + + private void setMockedTotalBytes(int uid, long rxBytes, long txBytes) { + final NetworkStats.Bucket bucket = mock(NetworkStats.Bucket.class); + when(bucket.getUid()).thenReturn(uid); + when(bucket.getRxBytes()).thenReturn(rxBytes); + when(bucket.getTxBytes()).thenReturn(txBytes); + mMockedStats.set(uid, bucket); + } + + private void increaseMockedTotalBytes(int uid, long rxBytes, long txBytes) { + final NetworkStats.Bucket bucket = mMockedStats.get(uid); + setMockedTotalBytes(uid, bucket.getRxBytes() + rxBytes, bucket.getTxBytes() + txBytes); + } } @Before @@ -376,6 +412,8 @@ public class NetworkPolicyManagerServiceTest { return mConnManager; case Context.USER_SERVICE: return mUserManager; + case Context.NETWORK_STATS_SERVICE: + return mStatsManager; default: return super.getSystemService(name); } @@ -400,8 +438,9 @@ public class NetworkPolicyManagerServiceTest { }).when(mActivityManager).registerUidObserver(any(), anyInt(), anyInt(), any(String.class)); mFutureIntent = newRestrictBackgroundChangedFuture(); + mDeps = new TestDependencies(mServiceContext); mService = new NetworkPolicyManagerService(mServiceContext, mActivityManager, - mNetworkManager, mIpm, mClock, mPolicyDir, true); + mNetworkManager, mIpm, mClock, mPolicyDir, true, mDeps); mService.bindConnectivityManager(); mPolicyListener = new NetworkPolicyListenerAnswer(mService); @@ -456,6 +495,9 @@ public class NetworkPolicyManagerServiceTest { verify(mNetworkManager).registerObserver(networkObserver.capture()); mNetworkObserver = networkObserver.getValue(); + // Simulate NetworkStatsService broadcast stats updated to signal its readiness. + mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_UPDATED)); + NetworkPolicy defaultPolicy = mService.buildDefaultCarrierPolicy(0, ""); mDefaultWarningBytes = defaultPolicy.warningBytes; mDefaultLimitBytes = defaultPolicy.limitBytes; @@ -479,7 +521,6 @@ public class NetworkPolicyManagerServiceTest { LocalServices.removeServiceForTest(DeviceIdleInternal.class); LocalServices.removeServiceForTest(AppStandbyInternal.class); LocalServices.removeServiceForTest(UsageStatsManagerInternal.class); - LocalServices.removeServiceForTest(NetworkStatsManagerInternal.class); } @After @@ -1108,10 +1149,7 @@ public class NetworkPolicyManagerServiceTest { when(mConnManager.getAllNetworkStateSnapshots()).thenReturn(snapshots); // pretend that 512 bytes total have happened - stats = new NetworkStats(getElapsedRealtime(), 1) - .insertEntry(TEST_IFACE, 256L, 2L, 256L, 2L); - when(mStatsService.getNetworkTotalBytes(sTemplateWifi, CYCLE_START, CYCLE_END)) - .thenReturn(stats.getTotalBytes()); + mDeps.setMockedTotalBytes(UID_A, 256L, 256L); mPolicyListener.expect().onMeteredIfacesChanged(any()); setNetworkPolicies(new NetworkPolicy( @@ -1124,26 +1162,6 @@ public class NetworkPolicyManagerServiceTest { @Test public void testNotificationWarningLimitSnooze() throws Exception { - // Create a place to store fake usage - final NetworkStatsHistory history = new NetworkStatsHistory(TimeUnit.HOURS.toMillis(1)); - final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0); - when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong())) - .thenAnswer(new Answer<Long>() { - @Override - public Long answer(InvocationOnMock invocation) throws Throwable { - final NetworkStatsHistory.Entry entry = history.getValues( - invocation.getArgument(1), invocation.getArgument(2), null); - return entry.rxBytes + entry.txBytes; - } - }); - when(mStatsService.getNetworkUidBytes(any(), anyLong(), anyLong())) - .thenAnswer(new Answer<NetworkStats>() { - @Override - public NetworkStats answer(InvocationOnMock invocation) throws Throwable { - return stats; - } - }); - // Get active mobile network in place expectMobileDefaults(); mService.updateNetworks(); @@ -1161,9 +1179,7 @@ public class NetworkPolicyManagerServiceTest { // Normal usage means no notification { - history.clear(); - history.recordData(start, end, - new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(360), 0L, 0L, 0L, 0)); + mDeps.setMockedTotalBytes(UID_A, DataUnit.MEGABYTES.toBytes(360), 0); reset(mTelephonyManager, mNetworkManager, mNotifManager); TelephonyManager tmSub = expectMobileDefaults(); @@ -1178,9 +1194,7 @@ public class NetworkPolicyManagerServiceTest { // Push over warning { - history.clear(); - history.recordData(start, end, - new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(1799), 0L, 0L, 0L, 0)); + mDeps.setMockedTotalBytes(UID_A, DataUnit.MEGABYTES.toBytes(1799), 0); reset(mTelephonyManager, mNetworkManager, mNotifManager); TelephonyManager tmSub = expectMobileDefaults(); @@ -1196,9 +1210,7 @@ public class NetworkPolicyManagerServiceTest { // Push over warning, but with a config that isn't from an identified carrier { - history.clear(); - history.recordData(start, end, - new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(1799), 0L, 0L, 0L, 0)); + mDeps.setMockedTotalBytes(UID_A, DataUnit.MEGABYTES.toBytes(1799), 0); reset(mTelephonyManager, mNetworkManager, mNotifManager); TelephonyManager tmSub = expectMobileDefaults(); @@ -1215,9 +1227,7 @@ public class NetworkPolicyManagerServiceTest { // Push over limit { - history.clear(); - history.recordData(start, end, - new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(1810), 0L, 0L, 0L, 0)); + mDeps.setMockedTotalBytes(UID_A, DataUnit.MEGABYTES.toBytes(1810), 0); reset(mTelephonyManager, mNetworkManager, mNotifManager); TelephonyManager tmSub = expectMobileDefaults(); @@ -1248,26 +1258,6 @@ public class NetworkPolicyManagerServiceTest { @Test public void testNotificationRapid() throws Exception { - // Create a place to store fake usage - final NetworkStatsHistory history = new NetworkStatsHistory(TimeUnit.HOURS.toMillis(1)); - final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0); - when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong())) - .thenAnswer(new Answer<Long>() { - @Override - public Long answer(InvocationOnMock invocation) throws Throwable { - final NetworkStatsHistory.Entry entry = history.getValues( - invocation.getArgument(1), invocation.getArgument(2), null); - return entry.rxBytes + entry.txBytes; - } - }); - when(mStatsService.getNetworkUidBytes(any(), anyLong(), anyLong())) - .thenAnswer(new Answer<NetworkStats>() { - @Override - public NetworkStats answer(InvocationOnMock invocation) throws Throwable { - return stats; - } - }); - // Get active mobile network in place expectMobileDefaults(); mService.updateNetworks(); @@ -1285,9 +1275,7 @@ public class NetworkPolicyManagerServiceTest { // Using 20% data in 20% time is normal { - history.clear(); - history.recordData(start, end, - new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(360), 0L, 0L, 0L, 0)); + mDeps.setMockedTotalBytes(UID_A, DataUnit.MEGABYTES.toBytes(360), 0); reset(mNotifManager); mService.updateNetworks(); @@ -1297,16 +1285,9 @@ public class NetworkPolicyManagerServiceTest { // Using 80% data in 20% time is alarming; but spread equally among // three UIDs means we get generic alert { - history.clear(); - history.recordData(start, end, - new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(1440), 0L, 0L, 0L, 0)); - stats.clear(); - stats.insertEntry(IFACE_ALL, UID_A, SET_ALL, TAG_ALL, - DataUnit.MEGABYTES.toBytes(480), 0, 0, 0, 0); - stats.insertEntry(IFACE_ALL, UID_B, SET_ALL, TAG_ALL, - DataUnit.MEGABYTES.toBytes(480), 0, 0, 0, 0); - stats.insertEntry(IFACE_ALL, UID_C, SET_ALL, TAG_ALL, - DataUnit.MEGABYTES.toBytes(480), 0, 0, 0, 0); + mDeps.setMockedTotalBytes(UID_A, DataUnit.MEGABYTES.toBytes(480), 0); + mDeps.setMockedTotalBytes(UID_B, DataUnit.MEGABYTES.toBytes(480), 0); + mDeps.setMockedTotalBytes(UID_C, DataUnit.MEGABYTES.toBytes(480), 0); reset(mNotifManager); mService.updateNetworks(); @@ -1325,14 +1306,9 @@ public class NetworkPolicyManagerServiceTest { // Using 80% data in 20% time is alarming; but mostly done by one UID // means we get specific alert { - history.clear(); - history.recordData(start, end, - new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(1440), 0L, 0L, 0L, 0)); - stats.clear(); - stats.insertEntry(IFACE_ALL, UID_A, SET_ALL, TAG_ALL, - DataUnit.MEGABYTES.toBytes(960), 0, 0, 0, 0); - stats.insertEntry(IFACE_ALL, UID_B, SET_ALL, TAG_ALL, - DataUnit.MEGABYTES.toBytes(480), 0, 0, 0, 0); + mDeps.setMockedTotalBytes(UID_A, DataUnit.MEGABYTES.toBytes(960), 0); + mDeps.setMockedTotalBytes(UID_B, DataUnit.MEGABYTES.toBytes(480), 0); + mDeps.setMockedTotalBytes(UID_C, 0, 0); reset(mNotifManager); mService.updateNetworks(); @@ -1362,13 +1338,10 @@ public class NetworkPolicyManagerServiceTest { // bring up wifi network with metered policy snapshots = List.of(buildWifi()); - stats = new NetworkStats(getElapsedRealtime(), 1) - .insertEntry(TEST_IFACE, 0L, 0L, 0L, 0L); + mDeps.setMockedTotalBytes(UID_A, 0L, 0L); { when(mConnManager.getAllNetworkStateSnapshots()).thenReturn(snapshots); - when(mStatsService.getNetworkTotalBytes(sTemplateWifi, TIME_FEB_15, - currentTimeMillis())).thenReturn(stats.getTotalBytes()); mPolicyListener.expect().onMeteredIfacesChanged(any()); setNetworkPolicies(new NetworkPolicy( @@ -1647,18 +1620,6 @@ public class NetworkPolicyManagerServiceTest { final NetworkPolicyManagerInternal internal = LocalServices .getService(NetworkPolicyManagerInternal.class); - // Create a place to store fake usage - final NetworkStatsHistory history = new NetworkStatsHistory(TimeUnit.HOURS.toMillis(1)); - final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0); - when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong())) - .thenAnswer(invocation -> { - final NetworkStatsHistory.Entry entry = history.getValues( - invocation.getArgument(1), invocation.getArgument(2), null); - return entry.rxBytes + entry.txBytes; - }); - when(mStatsService.getNetworkUidBytes(any(), anyLong(), anyLong())) - .thenReturn(stats); - // Get active mobile network in place expectMobileDefaults(); mService.updateNetworks(); @@ -1669,9 +1630,7 @@ public class NetworkPolicyManagerServiceTest { setCurrentTimeMillis(end); // Get some data usage in place - history.clear(); - history.recordData(start, end, - new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(360), 0L, 0L, 0L, 0)); + mDeps.setMockedTotalBytes(UID_A, DataUnit.MEGABYTES.toBytes(360), 0); // No data plan { @@ -1786,20 +1745,11 @@ public class NetworkPolicyManagerServiceTest { true); } - private void increaseMockedTotalBytes(NetworkStats stats, long rxBytes, long txBytes) { - stats.insertEntry(TEST_IFACE, UID_A, SET_ALL, TAG_NONE, - rxBytes, 1, txBytes, 1, 0); - when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong())) - .thenReturn(stats.getTotalBytes()); - when(mStatsService.getNetworkUidBytes(any(), anyLong(), anyLong())) - .thenReturn(stats); - } - private void triggerOnStatsProviderWarningOrLimitReached() throws InterruptedException { - mService.onStatsProviderWarningOrLimitReached(); + mService.notifyStatsProviderWarningOrLimitReached(); // Wait for processing of MSG_STATS_PROVIDER_WARNING_OR_LIMIT_REACHED. postMsgAndWaitForCompletion(); - verify(mStatsService).forceUpdate(); + verify(mStatsManager).forceUpdate(); // Wait for processing of MSG_*_INTERFACE_QUOTAS. postMsgAndWaitForCompletion(); } @@ -1812,13 +1762,12 @@ public class NetworkPolicyManagerServiceTest { public void testStatsProviderWarningAndLimitReached() throws Exception { final int CYCLE_DAY = 15; - final NetworkStats stats = new NetworkStats(0L, 1); - increaseMockedTotalBytes(stats, 2999, 2000); + mDeps.setMockedTotalBytes(UID_A, 2999, 2000); // Get active mobile network in place expectMobileDefaults(); mService.updateNetworks(); - verify(mStatsService).setStatsProviderWarningAndLimitAsync(TEST_IFACE, Long.MAX_VALUE, + verify(mStatsManager).setStatsProviderWarningAndLimitAsync(TEST_IFACE, Long.MAX_VALUE, Long.MAX_VALUE); // Set warning to 7KB and limit to 10KB. @@ -1828,32 +1777,32 @@ public class NetworkPolicyManagerServiceTest { postMsgAndWaitForCompletion(); // Verifies that remaining quotas are set to providers. - verify(mStatsService).setStatsProviderWarningAndLimitAsync(TEST_IFACE, 2001L, 5001L); - reset(mStatsService); + verify(mStatsManager).setStatsProviderWarningAndLimitAsync(TEST_IFACE, 2001L, 5001L); + reset(mStatsManager); // Increase the usage and simulates that limit reached fires earlier by provider, // but actually the quota is not yet reached. Verifies that the limit reached leads to // a force update and new quotas should be set. - increaseMockedTotalBytes(stats, 1000, 999); + mDeps.increaseMockedTotalBytes(UID_A, 1000, 999); triggerOnStatsProviderWarningOrLimitReached(); - verify(mStatsService).setStatsProviderWarningAndLimitAsync(TEST_IFACE, 2L, 3002L); - reset(mStatsService); + verify(mStatsManager).setStatsProviderWarningAndLimitAsync(TEST_IFACE, 2L, 3002L); + reset(mStatsManager); // Increase the usage and simulate warning reached, the new warning should be unlimited // since service will disable warning quota to stop lower layer from keep triggering // warning reached event. - increaseMockedTotalBytes(stats, 1000L, 1000); + mDeps.increaseMockedTotalBytes(UID_A, 1000L, 1000); triggerOnStatsProviderWarningOrLimitReached(); - verify(mStatsService).setStatsProviderWarningAndLimitAsync( + verify(mStatsManager).setStatsProviderWarningAndLimitAsync( TEST_IFACE, Long.MAX_VALUE, 1002L); - reset(mStatsService); + reset(mStatsManager); // Increase the usage that over the warning and limit, the new limit should set to 1 to // block the network traffic. - increaseMockedTotalBytes(stats, 1000L, 1000); + mDeps.increaseMockedTotalBytes(UID_A, 1000L, 1000); triggerOnStatsProviderWarningOrLimitReached(); - verify(mStatsService).setStatsProviderWarningAndLimitAsync(TEST_IFACE, Long.MAX_VALUE, 1L); - reset(mStatsService); + verify(mStatsManager).setStatsProviderWarningAndLimitAsync(TEST_IFACE, Long.MAX_VALUE, 1L); + reset(mStatsManager); } private void enableRestrictedMode(boolean enable) throws Exception { @@ -2097,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() @@ -2143,7 +2092,7 @@ public class NetworkPolicyManagerServiceTest { } private void verifyAdvisePersistThreshold() throws Exception { - verify(mStatsService).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/ApexManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java index 2f5993d1d989..7f7c716bc1f0 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java @@ -61,7 +61,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.util.List; +import java.util.Map; @SmallTest @Presubmit @@ -136,10 +136,9 @@ public class ApexManagerTest { mApexManager.scanApexPackagesTraced(mPackageParser2, ParallelPackageParser.makeExecutorService()); - List<ApexSystemServiceInfo> services = mApexManager.getApexSystemServices(); + Map<String, String> services = mApexManager.getApexSystemServices(); assertThat(services).hasSize(1); - assertThat(services.stream().map(ApexSystemServiceInfo::getName).findFirst().orElse(null)) - .matches("com.android.apex.test.ApexSystemService"); + assertThat(services).containsKey("com.android.apex.test.ApexSystemService"); } @Test diff --git a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java index 0f6dfda4bf63..13a8f69358b6 100644 --- a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java @@ -33,12 +33,12 @@ import android.content.pm.ApplicationInfo; import android.content.pm.Signature; import android.content.pm.SigningDetails; import android.content.pm.UserInfo; -import android.content.pm.parsing.ParsingPackage; -import android.content.pm.parsing.component.ParsedActivity; -import android.content.pm.parsing.component.ParsedActivityImpl; -import android.content.pm.parsing.component.ParsedInstrumentationImpl; -import android.content.pm.parsing.component.ParsedIntentInfoImpl; -import android.content.pm.parsing.component.ParsedProviderImpl; +import com.android.server.pm.pkg.parsing.ParsingPackage; +import com.android.server.pm.pkg.component.ParsedActivity; +import com.android.server.pm.pkg.component.ParsedActivityImpl; +import com.android.server.pm.pkg.component.ParsedInstrumentationImpl; +import com.android.server.pm.pkg.component.ParsedIntentInfoImpl; +import com.android.server.pm.pkg.component.ParsedProviderImpl; import android.os.Build; import android.os.Process; import android.os.UserHandle; diff --git a/services/tests/servicestests/src/com/android/server/pm/CompatibilityModeTest.java b/services/tests/servicestests/src/com/android/server/pm/CompatibilityModeTest.java index 54ab133d760e..e137c374b4cc 100644 --- a/services/tests/servicestests/src/com/android/server/pm/CompatibilityModeTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/CompatibilityModeTest.java @@ -29,8 +29,8 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.content.pm.ApplicationInfo; -import android.content.pm.parsing.PackageInfoWithoutStateUtils; -import android.content.pm.parsing.ParsingPackageUtils; +import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import android.os.Build; import android.platform.test.annotations.Presubmit; diff --git a/services/tests/servicestests/src/com/android/server/pm/KeySetManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/KeySetManagerServiceTest.java index 6b6d84af5f60..d7e3825bf9d0 100644 --- a/services/tests/servicestests/src/com/android/server/pm/KeySetManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/KeySetManagerServiceTest.java @@ -17,7 +17,7 @@ package com.android.server.pm; -import static android.content.pm.parsing.ParsingPackageUtils.parsePublicKey; +import static android.content.pm.parsing.FrameworkParsingPackageUtils.parsePublicKey; import android.content.pm.Signature; import android.platform.test.annotations.Presubmit; diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java index 9d672405603d..6c9a60ac47fb 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java @@ -22,7 +22,7 @@ import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; import static android.content.pm.SuspendDialogInfo.BUTTON_ACTION_MORE_DETAILS; import static android.content.pm.SuspendDialogInfo.BUTTON_ACTION_UNSUSPEND; -import static android.content.pm.parsing.ParsingPackageUtils.parsePublicKey; +import static android.content.pm.parsing.FrameworkParsingPackageUtils.parsePublicKey; import static android.content.res.Resources.ID_NULL; import static org.hamcrest.CoreMatchers.equalTo; @@ -43,6 +43,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.SuspendDialogInfo; import android.content.pm.UserInfo; +import android.content.pm.parsing.FrameworkParsingPackageUtils; import android.os.BaseBundle; import android.os.PersistableBundle; import android.os.Process; diff --git a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerTests.java index c2519ca0f238..b621a4408f40 100644 --- a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerTests.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006 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 android.content.pm; +package com.android.server.pm; import static android.system.OsConstants.S_IFDIR; import static android.system.OsConstants.S_IFMT; @@ -32,10 +32,16 @@ import android.content.IIntentSender; import android.content.Intent; import android.content.IntentFilter; import android.content.IntentSender; +import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageDeleteObserver; +import android.content.pm.KeySet; +import android.content.pm.PackageInfo; +import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.SessionParams; +import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; -import android.content.pm.parsing.ParsingPackage; -import android.content.pm.parsing.ParsingPackageUtils; +import android.content.pm.PermissionInfo; +import android.content.pm.VerifierDeviceIdentity; import android.content.pm.parsing.result.ParseResult; import android.content.res.Resources; import android.content.res.Resources.NotFoundException; @@ -63,8 +69,10 @@ import androidx.test.filters.LargeTest; import androidx.test.filters.SmallTest; import androidx.test.filters.Suppress; -import com.android.frameworks.coretests.R; +import com.android.frameworks.servicestests.R; import com.android.internal.content.PackageHelper; +import com.android.server.pm.pkg.parsing.ParsingPackage; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import dalvik.system.VMRuntime; diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java index c888524a6d00..d8ecf20b98c4 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java @@ -15,7 +15,7 @@ */ package com.android.server.pm; -import static android.content.pm.permission.CompatibilityPermissionInfo.COMPAT_PERMS; +import static com.android.server.pm.permission.CompatibilityPermissionInfo.COMPAT_PERMS; import static com.google.common.truth.Truth.assertWithMessage; @@ -43,27 +43,6 @@ import android.content.pm.PackageManager.Property; import android.content.pm.ServiceInfo; import android.content.pm.Signature; import android.content.pm.SigningDetails; -import android.content.pm.parsing.ParsingPackage; -import android.content.pm.parsing.component.ParsedActivity; -import android.content.pm.parsing.component.ParsedActivityImpl; -import android.content.pm.parsing.component.ParsedApexSystemService; -import android.content.pm.parsing.component.ParsedComponent; -import android.content.pm.parsing.component.ParsedInstrumentation; -import android.content.pm.parsing.component.ParsedInstrumentationImpl; -import android.content.pm.parsing.component.ParsedIntentInfo; -import android.content.pm.parsing.component.ParsedIntentInfoImpl; -import android.content.pm.parsing.component.ParsedPermission; -import android.content.pm.parsing.component.ParsedPermissionGroup; -import android.content.pm.parsing.component.ParsedPermissionGroupImpl; -import android.content.pm.parsing.component.ParsedPermissionImpl; -import android.content.pm.parsing.component.ParsedPermissionUtils; -import android.content.pm.parsing.component.ParsedProvider; -import android.content.pm.parsing.component.ParsedProviderImpl; -import android.content.pm.parsing.component.ParsedService; -import android.content.pm.parsing.component.ParsedServiceImpl; -import android.content.pm.parsing.component.ParsedUsesPermission; -import android.content.pm.parsing.component.ParsedUsesPermissionImpl; -import android.content.pm.permission.CompatibilityPermissionInfo; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -85,7 +64,28 @@ import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.parsing.pkg.AndroidPackageUtils; import com.android.server.pm.parsing.pkg.PackageImpl; import com.android.server.pm.parsing.pkg.ParsedPackage; +import com.android.server.pm.permission.CompatibilityPermissionInfo; import com.android.server.pm.pkg.PackageUserState; +import com.android.server.pm.pkg.component.ParsedActivity; +import com.android.server.pm.pkg.component.ParsedActivityImpl; +import com.android.server.pm.pkg.component.ParsedApexSystemService; +import com.android.server.pm.pkg.component.ParsedComponent; +import com.android.server.pm.pkg.component.ParsedInstrumentation; +import com.android.server.pm.pkg.component.ParsedInstrumentationImpl; +import com.android.server.pm.pkg.component.ParsedIntentInfo; +import com.android.server.pm.pkg.component.ParsedIntentInfoImpl; +import com.android.server.pm.pkg.component.ParsedPermission; +import com.android.server.pm.pkg.component.ParsedPermissionGroup; +import com.android.server.pm.pkg.component.ParsedPermissionGroupImpl; +import com.android.server.pm.pkg.component.ParsedPermissionImpl; +import com.android.server.pm.pkg.component.ParsedPermissionUtils; +import com.android.server.pm.pkg.component.ParsedProvider; +import com.android.server.pm.pkg.component.ParsedProviderImpl; +import com.android.server.pm.pkg.component.ParsedService; +import com.android.server.pm.pkg.component.ParsedServiceImpl; +import com.android.server.pm.pkg.component.ParsedUsesPermission; +import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl; +import com.android.server.pm.pkg.parsing.ParsingPackage; import org.junit.Before; import org.junit.Rule; diff --git a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java index 28f24f2b55ee..7ff8eec70a10 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java @@ -43,8 +43,8 @@ import android.Manifest; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.SharedLibraryInfo; -import android.content.pm.parsing.ParsingPackage; -import android.content.pm.parsing.component.ParsedUsesPermissionImpl; +import com.android.server.pm.pkg.parsing.ParsingPackage; +import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl; import android.content.res.TypedArray; import android.os.Environment; import android.os.UserHandle; 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/dex/DexoptUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java index 1dcb0b7eb159..7c8bbec458ee 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java @@ -23,7 +23,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import android.content.pm.SharedLibraryInfo; -import android.content.pm.parsing.ParsingPackage; +import com.android.server.pm.pkg.parsing.ParsingPackage; import android.platform.test.annotations.Presubmit; import android.util.SparseArray; diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageInfoFlagBehaviorTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageInfoFlagBehaviorTest.kt deleted file mode 100644 index 4059a496e8ea..000000000000 --- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageInfoFlagBehaviorTest.kt +++ /dev/null @@ -1,147 +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 com.android.server.pm.parsing - -import android.Manifest -import android.content.pm.ApplicationInfo -import android.content.pm.PackageInfo -import android.content.pm.PackageManager -import android.content.pm.PackageParser -import android.platform.test.annotations.Postsubmit -import com.android.internal.util.ArrayUtils -import com.android.server.pm.parsing.AndroidPackageInfoFlagBehaviorTest.Companion.Param.Companion.appInfo -import com.android.server.pm.parsing.AndroidPackageInfoFlagBehaviorTest.Companion.Param.Companion.pkgInfo -import com.android.server.pm.parsing.pkg.AndroidPackage -import com.google.common.truth.Truth.assertThat -import com.google.common.truth.Truth.assertWithMessage -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.Parameterized - -/** - * Verifies that missing/adding [PackageManager] flags adds/remove the appropriate fields from the - * [PackageInfo] or [ApplicationInfo] results. - * - * This test has to be updated manually whenever the info generation behavior changes, since - * there's no single place where flag -> field is defined besides this test. - */ -@Postsubmit -@RunWith(Parameterized::class) -class AndroidPackageInfoFlagBehaviorTest : AndroidPackageParsingTestBase() { - - companion object { - - data class Param<T> constructor( - val flag: Int, - val logTag: String, - val oldPkgFunction: (pkg: PackageParser.Package, flags: Int) -> T?, - val newPkgFunction: (pkg: AndroidPackage, flags: Int) -> T?, - val fieldFunction: (T) -> List<Any?> - ) { - companion object { - fun pkgInfo(flag: Int, fieldFunction: (PackageInfo) -> List<Any?>) = Param( - flag, PackageInfo::class.java.simpleName, - ::oldPackageInfo, ::newPackageInfo, fieldFunction - ) - - fun appInfo(flag: Int, fieldFunction: (ApplicationInfo) -> List<Any?>) = Param( - flag, ApplicationInfo::class.java.simpleName, - { pkg, flags -> oldAppInfo(pkg, flags) }, - { pkg, flags -> newAppInfo(pkg, flags) }, - fieldFunction - ) - } - - override fun toString(): String { - val hex = Integer.toHexString(flag) - val fromRight = Integer.toBinaryString(flag).reversed().indexOf('1') - return "$logTag $hex | 1 shl $fromRight" - } - } - - @JvmStatic - @Parameterized.Parameters(name = "{0}") - fun parameters() = arrayOf( - pkgInfo(PackageManager.GET_ACTIVITIES) { listOf(it.activities) }, - pkgInfo(PackageManager.GET_GIDS) { listOf(it.gids) }, - pkgInfo(PackageManager.GET_INSTRUMENTATION) { listOf(it.instrumentation) }, - pkgInfo(PackageManager.GET_META_DATA) { listOf(it.applicationInfo.metaData) }, - pkgInfo(PackageManager.GET_PROVIDERS) { listOf(it.providers) }, - pkgInfo(PackageManager.GET_RECEIVERS) { listOf(it.receivers) }, - pkgInfo(PackageManager.GET_SERVICES) { listOf(it.services) }, - pkgInfo(PackageManager.GET_SIGNATURES) { listOf(it.signatures) }, - pkgInfo(PackageManager.GET_SIGNING_CERTIFICATES) { listOf(it.signingInfo) }, - pkgInfo(PackageManager.GET_SHARED_LIBRARY_FILES) { - it.applicationInfo.run { listOf(sharedLibraryFiles, sharedLibraryFiles) } - }, - pkgInfo(PackageManager.GET_CONFIGURATIONS) { - listOf(it.configPreferences, it.reqFeatures, it.featureGroups) - }, - pkgInfo(PackageManager.GET_PERMISSIONS) { - listOf( - it.permissions, - // Strip compatibility permission added in T - it.requestedPermissions?.filter { x -> - x != Manifest.permission.POST_NOTIFICATIONS - }?.ifEmpty { null }?.toTypedArray(), - // Strip the flag from compatibility permission added in T - it.requestedPermissionsFlags?.filterIndexed { index, _ -> - index != ArrayUtils.indexOf(it.requestedPermissions, - Manifest.permission.POST_NOTIFICATIONS) - }?.ifEmpty { null }?.toTypedArray()) - }, - appInfo(PackageManager.GET_META_DATA) { listOf(it.metaData) }, - appInfo(PackageManager.GET_SHARED_LIBRARY_FILES) { - listOf(it.sharedLibraryFiles, it.sharedLibraryFiles) - } - ) - } - - @Parameterized.Parameter(0) - lateinit var param: Param<Any> - - @Test - fun fieldPresence() { - oldPackages.asSequence().zip(newPackages.asSequence()) - .forEach { (old, new) -> - val oldWithFlag = param.oldPkgFunction(old, param.flag) - val newWithFlag = param.newPkgFunction(new, param.flag) - val oldFieldList = oldWithFlag?.let(param.fieldFunction).orEmpty() - val newFieldList = newWithFlag?.let(param.fieldFunction).orEmpty() - - oldFieldList.zip(newFieldList).forEach { - assertWithMessage(new.packageName).that(it.second).apply { - // Assert same null-ness as old logic - if (it.first == null) { - isNull() - } else { - isNotNull() - } - } - } - } - } - - @Test - fun fieldAbsence() { - newPackages.forEach { - val newWithoutFlag = param.newPkgFunction(it, 0) - val newFieldListWithoutFlag = newWithoutFlag?.let(param.fieldFunction).orEmpty() - assertThat(newFieldListWithoutFlag.filterNotNull()).isEmpty() - } - } -} diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingEquivalenceTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingEquivalenceTest.kt deleted file mode 100644 index 574921cdbd05..000000000000 --- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingEquivalenceTest.kt +++ /dev/null @@ -1,158 +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 com.android.server.pm.parsing - -import android.content.pm.PackageManager -import android.platform.test.annotations.Postsubmit -import androidx.test.filters.LargeTest -import com.google.common.truth.Expect -import org.junit.Rule -import org.junit.Test - -/** - * Collects APKs from the device and verifies that the new parsing behavior outputs - * the same exposed Info object as the old parsing logic. - */ -@Postsubmit -class AndroidPackageParsingEquivalenceTest : AndroidPackageParsingTestBase() { - - @get:Rule - val expect = Expect.create() - - @Test - fun applicationInfoEquality() { - val flags = PackageManager.GET_META_DATA or PackageManager.GET_SHARED_LIBRARY_FILES - val oldAppInfo = oldPackages.asSequence().map { oldAppInfo(it, flags) } - val newAppInfo = newPackages.asSequence().map { newAppInfo(it, flags) } - oldAppInfo.zip(newAppInfo).forEach { - val firstName = it.first?.packageName - val secondName = it.second?.packageName - val packageName = if (firstName == secondName) { - "$firstName" - } else { - "$firstName | $secondName" - } - expect.withMessage("${it.first?.sourceDir} $packageName") - .that(it.first?.dumpToString()) - .isEqualTo(it.second?.dumpToString()) - } - } - - @LargeTest - @Test - fun packageInfoEquality() { - val flags = PackageManager.GET_ACTIVITIES or - PackageManager.GET_CONFIGURATIONS or - PackageManager.GET_GIDS or - PackageManager.GET_INSTRUMENTATION or - PackageManager.GET_META_DATA or - PackageManager.GET_PERMISSIONS or - PackageManager.GET_PROVIDERS or - PackageManager.GET_RECEIVERS or - PackageManager.GET_SERVICES or - PackageManager.GET_SHARED_LIBRARY_FILES or - PackageManager.GET_SIGNATURES or - PackageManager.GET_SIGNING_CERTIFICATES or - PackageManager.MATCH_DIRECT_BOOT_UNAWARE or - PackageManager.MATCH_DIRECT_BOOT_AWARE - val oldPackageInfo = oldPackages.asSequence().map { oldPackageInfo(it, flags) } - val newPackageInfo = newPackages.asSequence().map { newPackageInfo(it, flags) } - - oldPackageInfo.zip(newPackageInfo).forEach { - val firstName = it.first?.packageName - val secondName = it.second?.packageName - val packageName = if (firstName == secondName) { - "$firstName" - } else { - "$firstName | $secondName" - } - - // Main components are asserted independently to separate the failures. Otherwise the - // comparison would include every component in one massive string. - - val prefix = "${it.first?.applicationInfo?.sourceDir} $packageName" - - expect.withMessage("$prefix PackageInfo") - .that(it.second?.dumpToString()) - .isEqualTo(it.first?.dumpToString()) - - expect.withMessage("$prefix ApplicationInfo") - .that(it.second?.applicationInfo?.dumpToString()) - .isEqualTo(it.first?.applicationInfo?.dumpToString()) - - val firstActivityNames = it.first?.activities?.map { it.name } ?: emptyList() - val secondActivityNames = it.second?.activities?.map { it.name } ?: emptyList() - expect.withMessage("$prefix activities") - .that(secondActivityNames) - .containsExactlyElementsIn(firstActivityNames) - .inOrder() - - if (!it.first?.activities.isNullOrEmpty() && !it.second?.activities.isNullOrEmpty()) { - it.first?.activities?.zip(it.second?.activities!!)?.forEach { - expect.withMessage("$prefix ${it.first.name}") - .that(it.second.dumpToString()) - .isEqualTo(it.first.dumpToString()) - } - } - - val firstReceiverNames = it.first?.receivers?.map { it.name } ?: emptyList() - val secondReceiverNames = it.second?.receivers?.map { it.name } ?: emptyList() - expect.withMessage("$prefix receivers") - .that(secondReceiverNames) - .containsExactlyElementsIn(firstReceiverNames) - .inOrder() - - if (!it.first?.receivers.isNullOrEmpty() && !it.second?.receivers.isNullOrEmpty()) { - it.first?.receivers?.zip(it.second?.receivers!!)?.forEach { - expect.withMessage("$prefix ${it.first.name}") - .that(it.second.dumpToString()) - .isEqualTo(it.first.dumpToString()) - } - } - - val firstProviderNames = it.first?.providers?.map { it.name } ?: emptyList() - val secondProviderNames = it.second?.providers?.map { it.name } ?: emptyList() - expect.withMessage("$prefix providers") - .that(secondProviderNames) - .containsExactlyElementsIn(firstProviderNames) - .inOrder() - - if (!it.first?.providers.isNullOrEmpty() && !it.second?.providers.isNullOrEmpty()) { - it.first?.providers?.zip(it.second?.providers!!)?.forEach { - expect.withMessage("$prefix ${it.first.name}") - .that(it.second.dumpToString()) - .isEqualTo(it.first.dumpToString()) - } - } - - val firstServiceNames = it.first?.services?.map { it.name } ?: emptyList() - val secondServiceNames = it.second?.services?.map { it.name } ?: emptyList() - expect.withMessage("$prefix services") - .that(secondServiceNames) - .containsExactlyElementsIn(firstServiceNames) - .inOrder() - - if (!it.first?.services.isNullOrEmpty() && !it.second?.services.isNullOrEmpty()) { - it.first?.services?.zip(it.second?.services!!)?.forEach { - expect.withMessage("$prefix ${it.first.name}") - .that(it.second.dumpToString()) - .isEqualTo(it.first.dumpToString()) - } - } - } - } -} diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt deleted file mode 100644 index 122661ea93da..000000000000 --- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt +++ /dev/null @@ -1,557 +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 com.android.server.pm.parsing - -import android.Manifest -import android.content.Context -import android.content.pm.ActivityInfo -import android.content.pm.ApplicationInfo -import android.content.pm.ConfigurationInfo -import android.content.pm.FeatureInfo -import android.content.pm.InstrumentationInfo -import android.content.pm.PackageInfo -import android.content.pm.PackageParser -import android.content.pm.PermissionInfo -import android.content.pm.ProviderInfo -import android.content.pm.ServiceInfo -import android.content.pm.parsing.ParsingPackageUtils -import android.os.Bundle -import android.os.Debug -import android.os.Environment -import android.os.Process -import android.util.SparseArray -import androidx.test.platform.app.InstrumentationRegistry -import com.android.internal.util.ArrayUtils -import com.android.server.pm.PackageManagerService -import com.android.server.pm.parsing.pkg.AndroidPackage -import com.android.server.pm.pkg.PackageStateInternal -import com.android.server.pm.pkg.PackageStateUnserialized -import com.android.server.pm.pkg.PackageUserStateImpl -import com.android.server.testutils.mockThrowOnUnmocked -import com.android.server.testutils.whenever -import org.junit.BeforeClass -import org.mockito.Mockito.anyInt -import java.io.File - -open class AndroidPackageParsingTestBase { - - companion object { - - private const val VERIFY_ALL_APKS = true - - // For auditing memory usage differences to /sdcard/AndroidPackageParsingTestBase.hprof - private const val DUMP_HPROF_TO_EXTERNAL = false - - val context: Context = InstrumentationRegistry.getInstrumentation().getContext() - protected val packageParser = PackageParser().apply { - setOnlyCoreApps(false) - setDisplayMetrics(context.resources.displayMetrics) - setCallback { false /* hasFeature */ } - } - - protected val packageParser2 = PackageParser2.forParsingFileWithDefaults() - - /** - * It would be difficult to mock all possibilities, so just use the APKs on device. - * Unfortunately, this means the device must be bootable to verify potentially - * boot-breaking behavior. - */ - private val apks = mutableListOf(File(Environment.getRootDirectory(), "framework")) - .apply { - @Suppress("ConstantConditionIf") - if (VERIFY_ALL_APKS) { - this += (PackageManagerService.SYSTEM_PARTITIONS) - .flatMap { - listOfNotNull(it.privAppFolder, it.appFolder, it.overlayFolder) - } - } - } - .flatMap { - it.walkTopDown() - .filter { file -> file.name.endsWith(".apk") } - .toList() - } - .distinct() - - private val dummyUserState = - PackageUserStateImpl() - - val oldPackages = mutableListOf<PackageParser.Package>() - - val newPackages = mutableListOf<AndroidPackage>() - - @Suppress("ConstantConditionIf") - @JvmStatic - @BeforeClass - fun setUpPackages() { - var uid = Process.FIRST_APPLICATION_UID - apks.mapNotNull { - try { - packageParser.parsePackage(it, PackageParser.PARSE_IS_SYSTEM_DIR, false) to - packageParser2.parsePackage(it, ParsingPackageUtils.PARSE_IS_SYSTEM_DIR, - false) - } catch (ignored: Exception) { - // It is intentional that a failure of either call here will result in failing - // both. Having null on one side would mean nothing to compare. Due to the - // nature of presubmit, this may not be caused by the change being tested, so - // it's unhelpful to consider it a failure. Actual parsing issues will be - // reported by SystemPartitionParseTest in postsubmit. - null - } - }.forEach { (old, new) -> - // Assign an arbitrary UID. This is normally done after parsing completes, inside - // PackageManagerService, but since that code isn't run here, need to mock it. This - // is equivalent to what the system would assign. - old.applicationInfo.uid = uid - new.uid = uid - uid++ - - oldPackages += old - newPackages += new.hideAsFinal() - } - - if (DUMP_HPROF_TO_EXTERNAL) { - System.gc() - Environment.getExternalStorageDirectory() - .resolve( - "${AndroidPackageParsingTestBase::class.java.simpleName}.hprof") - .absolutePath - .run(Debug::dumpHprofData) - } - } - - fun oldAppInfo( - pkg: PackageParser.Package, - flags: Int = 0, - userId: Int = 0 - ): ApplicationInfo? { - return PackageParser.generateApplicationInfo(pkg, flags, dummyUserState, userId) - } - - fun newAppInfo( - pkg: AndroidPackage, - flags: Int = 0, - userId: Int = 0 - ): ApplicationInfo? { - return PackageInfoUtils.generateApplicationInfo(pkg, flags.toLong(), dummyUserState, - userId, mockPkgSetting(pkg)) - } - - fun newAppInfoWithoutState( - pkg: AndroidPackage, - flags: Int = 0, - userId: Int = 0 - ): ApplicationInfo? { - return PackageInfoUtils.generateApplicationInfo(pkg, flags.toLong(), dummyUserState, - userId, mockPkgSetting(pkg)) - } - - fun oldPackageInfo(pkg: PackageParser.Package, flags: Int = 0): PackageInfo? { - return PackageParser.generatePackageInfo(pkg, intArrayOf(), flags, 5, 6, emptySet(), - dummyUserState) - } - - fun newPackageInfo(pkg: AndroidPackage, flags: Int = 0): PackageInfo? { - return PackageInfoUtils.generate(pkg, intArrayOf(), flags.toLong(), 5, 6, emptySet(), - dummyUserState, 0, mockPkgSetting(pkg)) - } - - private fun mockPkgSetting(aPkg: AndroidPackage) = - mockThrowOnUnmocked<PackageStateInternal> { - whenever(pkg) { aPkg } - whenever(appId) { aPkg.uid } - whenever(transientState) { PackageStateUnserialized() } - whenever(getUserStateOrDefault(anyInt())) { dummyUserState } - whenever(categoryOverride) { ApplicationInfo.CATEGORY_UNDEFINED } - whenever(primaryCpuAbi) { null } - whenever(secondaryCpuAbi) { null } - } - } - - // The following methods dump an exact set of fields from the object to compare, because - // 1. comprehensive equals/toStrings do not exist on all of the Info objects, and - // 2. the test must only verify fields that [PackageParser.Package] can actually fill, as - // no new functionality will be added to it. - - // The following methods prepend "this." because @hide APIs can cause an IDE to auto-import - // the R.attr constant instead of referencing the field in an attempt to fix the error. - - // It's difficult to comment out a line in a triple quoted string, so this is used instead - // to ignore specific fields. A comment is required to explain why a field was ignored. - private fun Any?.ignored(comment: String): String = "IGNORED" - - protected fun ApplicationInfo.dumpToString() = """ - appComponentFactory=${this.appComponentFactory} - backupAgentName=${this.backupAgentName} - banner=${this.banner} - category=${this.category} - classLoaderName=${this.classLoaderName} - className=${this.className} - compatibleWidthLimitDp=${this.compatibleWidthLimitDp} - compileSdkVersion=${this.compileSdkVersion} - compileSdkVersionCodename=${this.compileSdkVersionCodename} - credentialProtectedDataDir=${this.credentialProtectedDataDir - .ignored("Deferred pre-R, but assigned immediately in R")} - crossProfile=${this.crossProfile.ignored("Added in R")} - dataDir=${this.dataDir.ignored("Deferred pre-R, but assigned immediately in R")} - descriptionRes=${this.descriptionRes} - deviceProtectedDataDir=${this.deviceProtectedDataDir - .ignored("Deferred pre-R, but assigned immediately in R")} - enabled=${this.enabled} - enabledSetting=${this.enabledSetting} - flags=${Integer.toBinaryString(this.flags)} - fullBackupContent=${this.fullBackupContent} - gwpAsanMode=${this.gwpAsanMode.ignored("Added in R")} - hiddenUntilInstalled=${this.hiddenUntilInstalled} - icon=${this.icon} - iconRes=${this.iconRes} - installLocation=${this.installLocation} - labelRes=${this.labelRes} - largestWidthLimitDp=${this.largestWidthLimitDp} - logo=${this.logo} - longVersionCode=${this.longVersionCode} - ${"".ignored("mHiddenApiPolicy is a private field")} - manageSpaceActivityName=${this.manageSpaceActivityName} - maxAspectRatio=${this.maxAspectRatio} - metaData=${this.metaData.dumpToString()} - minAspectRatio=${this.minAspectRatio} - minSdkVersion=${this.minSdkVersion} - name=${this.name} - nativeLibraryDir=${this.nativeLibraryDir} - nativeLibraryRootDir=${this.nativeLibraryRootDir} - nativeLibraryRootRequiresIsa=${this.nativeLibraryRootRequiresIsa} - networkSecurityConfigRes=${this.networkSecurityConfigRes} - nonLocalizedLabel=${ - // Per b/184574333, v1 mistakenly trimmed the label. v2 fixed this, but for test - // comparison, trim both so they can be matched. - this.nonLocalizedLabel?.trim() - } - packageName=${this.packageName} - permission=${this.permission} - primaryCpuAbi=${this.primaryCpuAbi} - privateFlags=${Integer.toBinaryString(this.privateFlags)} - processName=${this.processName.ignored("Deferred pre-R, but assigned immediately in R")} - publicSourceDir=${this.publicSourceDir - .ignored("Deferred pre-R, but assigned immediately in R")} - requiresSmallestWidthDp=${this.requiresSmallestWidthDp} - resourceDirs=${this.resourceDirs?.contentToString()} - overlayPaths=${this.overlayPaths?.contentToString()} - roundIconRes=${this.roundIconRes} - scanPublicSourceDir=${this.scanPublicSourceDir - .ignored("Deferred pre-R, but assigned immediately in R")} - scanSourceDir=${this.scanSourceDir - .ignored("Deferred pre-R, but assigned immediately in R")} - seInfo=${this.seInfo} - seInfoUser=${this.seInfoUser} - secondaryCpuAbi=${this.secondaryCpuAbi} - secondaryNativeLibraryDir=${this.secondaryNativeLibraryDir} - sharedLibraryFiles=${this.sharedLibraryFiles?.contentToString()} - sharedLibraryInfos=${this.sharedLibraryInfos} - showUserIcon=${this.showUserIcon} - sourceDir=${this.sourceDir - .ignored("Deferred pre-R, but assigned immediately in R")} - splitClassLoaderNames=${this.splitClassLoaderNames?.contentToString()} - splitDependencies=${this.splitDependencies.dumpToString()} - splitNames=${this.splitNames?.contentToString()} - splitPublicSourceDirs=${this.splitPublicSourceDirs?.contentToString()} - splitSourceDirs=${this.splitSourceDirs?.contentToString()} - storageUuid=${this.storageUuid} - targetSandboxVersion=${this.targetSandboxVersion} - targetSdkVersion=${this.targetSdkVersion} - taskAffinity=${this.taskAffinity} - theme=${this.theme} - uiOptions=${this.uiOptions} - uid=${this.uid} - versionCode=${this.versionCode} - volumeUuid=${this.volumeUuid} - zygotePreloadName=${this.zygotePreloadName} - """.trimIndent() - - protected fun FeatureInfo.dumpToString() = """ - flags=${Integer.toBinaryString(this.flags)} - name=${this.name} - reqGlEsVersion=${this.reqGlEsVersion} - version=${this.version} - """.trimIndent() - - protected fun InstrumentationInfo.dumpToString() = """ - banner=${this.banner} - credentialProtectedDataDir=${this.credentialProtectedDataDir} - dataDir=${this.dataDir} - deviceProtectedDataDir=${this.deviceProtectedDataDir} - functionalTest=${this.functionalTest} - handleProfiling=${this.handleProfiling} - icon=${this.icon} - labelRes=${this.labelRes} - logo=${this.logo} - metaData=${this.metaData} - name=${this.name} - nativeLibraryDir=${this.nativeLibraryDir} - nonLocalizedLabel=${ - // Per b/184574333, v1 mistakenly trimmed the label. v2 fixed this, but for test - // comparison, trim both so they can be matched. - this.nonLocalizedLabel?.trim() - } - packageName=${this.packageName} - primaryCpuAbi=${this.primaryCpuAbi} - publicSourceDir=${this.publicSourceDir} - secondaryCpuAbi=${this.secondaryCpuAbi} - secondaryNativeLibraryDir=${this.secondaryNativeLibraryDir} - showUserIcon=${this.showUserIcon} - sourceDir=${this.sourceDir} - splitDependencies=${this.splitDependencies.dumpToString()} - splitNames=${this.splitNames?.contentToString()} - splitPublicSourceDirs=${this.splitPublicSourceDirs?.contentToString()} - splitSourceDirs=${this.splitSourceDirs?.contentToString()} - targetPackage=${this.targetPackage} - targetProcesses=${this.targetProcesses} - """.trimIndent() - - protected fun ActivityInfo.dumpToString() = """ - banner=${this.banner} - colorMode=${this.colorMode} - configChanges=${this.configChanges} - descriptionRes=${this.descriptionRes} - directBootAware=${this.directBootAware} - documentLaunchMode=${this.documentLaunchMode - .ignored("Update for fixing b/128526493 and the testing is no longer valid")} - enabled=${this.enabled} - exported=${this.exported} - flags=${Integer.toBinaryString( - // Strip flag added in T - this.flags and (ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES.inv())) - } - icon=${this.icon} - labelRes=${this.labelRes} - launchMode=${this.launchMode} - launchToken=${this.launchToken} - lockTaskLaunchMode=${this.lockTaskLaunchMode} - logo=${this.logo} - maxRecents=${this.maxRecents} - metaData=${this.metaData.dumpToString()} - name=${this.name} - nonLocalizedLabel=${ - // Per b/184574333, v1 mistakenly trimmed the label. v2 fixed this, but for test - // comparison, trim both so they can be matched. - this.nonLocalizedLabel?.trim() - } - packageName=${this.packageName} - parentActivityName=${this.parentActivityName} - permission=${this.permission} - persistableMode=${this.persistableMode.ignored("Could be dropped pre-R, fixed in R")} - privateFlags=${ - // Strip flag added in S - this.privateFlags and (ActivityInfo.PRIVATE_FLAG_HOME_TRANSITION_SOUND.inv()) - } - processName=${this.processName.ignored("Deferred pre-R, but assigned immediately in R")} - requestedVrComponent=${this.requestedVrComponent} - resizeMode=${this.resizeMode} - rotationAnimation=${this.rotationAnimation} - screenOrientation=${this.screenOrientation} - showUserIcon=${this.showUserIcon} - softInputMode=${this.softInputMode} - splitName=${this.splitName} - targetActivity=${this.targetActivity} - taskAffinity=${this.taskAffinity} - theme=${this.theme} - uiOptions=${this.uiOptions} - windowLayout=${this.windowLayout?.dumpToString()} - """.trimIndent() - - protected fun ActivityInfo.WindowLayout.dumpToString() = """ - gravity=${this.gravity} - height=${this.height} - heightFraction=${this.heightFraction} - minHeight=${this.minHeight} - minWidth=${this.minWidth} - width=${this.width} - widthFraction=${this.widthFraction} - """.trimIndent() - - protected fun PermissionInfo.dumpToString() = """ - backgroundPermission=${this.backgroundPermission} - banner=${this.banner} - descriptionRes=${this.descriptionRes} - flags=${Integer.toBinaryString(this.flags)} - group=${this.group} - icon=${this.icon} - labelRes=${this.labelRes} - logo=${this.logo} - metaData=${this.metaData.dumpToString()} - name=${this.name} - nonLocalizedDescription=${this.nonLocalizedDescription} - nonLocalizedLabel=${ - // Per b/184574333, v1 mistakenly trimmed the label. v2 fixed this, but for test - // comparison, trim both so they can be matched. - this.nonLocalizedLabel?.trim() - } - packageName=${this.packageName} - protectionLevel=${this.protectionLevel} - requestRes=${this.requestRes} - showUserIcon=${this.showUserIcon} - """.trimIndent() - - protected fun ProviderInfo.dumpToString() = """ - applicationInfo=${this.applicationInfo.ignored("Already checked")} - authority=${this.authority} - banner=${this.banner} - descriptionRes=${this.descriptionRes} - directBootAware=${this.directBootAware} - enabled=${this.enabled} - exported=${this.exported} - flags=${Integer.toBinaryString(this.flags)} - forceUriPermissions=${this.forceUriPermissions} - grantUriPermissions=${this.grantUriPermissions} - icon=${this.icon} - initOrder=${this.initOrder} - isSyncable=${this.isSyncable} - labelRes=${this.labelRes} - logo=${this.logo} - metaData=${this.metaData.dumpToString()} - multiprocess=${this.multiprocess} - name=${this.name} - nonLocalizedLabel=${ - // Per b/184574333, v1 mistakenly trimmed the label. v2 fixed this, but for test - // comparison, trim both so they can be matched. - this.nonLocalizedLabel?.trim() - } - packageName=${this.packageName} - pathPermissions=${this.pathPermissions?.joinToString { - "readPermission=${it.readPermission}\nwritePermission=${it.writePermission}" - }} - processName=${this.processName.ignored("Deferred pre-R, but assigned immediately in R")} - readPermission=${this.readPermission} - showUserIcon=${this.showUserIcon} - splitName=${this.splitName} - uriPermissionPatterns=${this.uriPermissionPatterns?.contentToString()} - writePermission=${this.writePermission} - """.trimIndent() - - protected fun ServiceInfo.dumpToString() = """ - applicationInfo=${this.applicationInfo.ignored("Already checked")} - banner=${this.banner} - descriptionRes=${this.descriptionRes} - directBootAware=${this.directBootAware} - enabled=${this.enabled} - exported=${this.exported} - flags=${Integer.toBinaryString(this.flags)} - icon=${this.icon} - labelRes=${this.labelRes} - logo=${this.logo} - mForegroundServiceType"${this.mForegroundServiceType} - metaData=${this.metaData.dumpToString()} - name=${this.name} - nonLocalizedLabel=${ - // Per b/184574333, v1 mistakenly trimmed the label. v2 fixed this, but for test - // comparison, trim both so they can be matched. - this.nonLocalizedLabel?.trim() - } - packageName=${this.packageName} - permission=${this.permission} - processName=${this.processName.ignored("Deferred pre-R, but assigned immediately in R")} - showUserIcon=${this.showUserIcon} - splitName=${this.splitName} - """.trimIndent() - - protected fun ConfigurationInfo.dumpToString() = """ - reqGlEsVersion=${this.reqGlEsVersion} - reqInputFeatures=${this.reqInputFeatures} - reqKeyboardType=${this.reqKeyboardType} - reqNavigation=${this.reqNavigation} - reqTouchScreen=${this.reqTouchScreen} - """.trimIndent() - - protected fun PackageInfo.dumpToString() = """ - activities=${this.activities?.joinToString { it.dumpToString() } - .ignored("Checked separately in test")} - applicationInfo=${this.applicationInfo.dumpToString() - .ignored("Checked separately in test")} - baseRevisionCode=${this.baseRevisionCode} - compileSdkVersion=${this.compileSdkVersion} - compileSdkVersionCodename=${this.compileSdkVersionCodename} - configPreferences=${this.configPreferences?.joinToString { it.dumpToString() }} - coreApp=${this.coreApp} - featureGroups=${this.featureGroups?.joinToString { - it.features?.joinToString { featureInfo -> featureInfo.dumpToString() }.orEmpty() - }} - firstInstallTime=${this.firstInstallTime} - gids=${gids?.contentToString()} - installLocation=${this.installLocation} - instrumentation=${instrumentation?.joinToString { it.dumpToString() }} - isApex=${this.isApex} - isStub=${this.isStub} - lastUpdateTime=${this.lastUpdateTime} - mOverlayIsStatic=${this.mOverlayIsStatic} - overlayCategory=${this.overlayCategory} - overlayPriority=${this.overlayPriority} - overlayTarget=${this.overlayTarget} - packageName=${this.packageName} - permissions=${this.permissions?.joinToString { it.dumpToString() }} - providers=${this.providers?.joinToString { it.dumpToString() } - .ignored("Checked separately in test")} - receivers=${this.receivers?.joinToString { it.dumpToString() } - .ignored("Checked separately in test")} - reqFeatures=${this.reqFeatures?.joinToString { it.dumpToString() }} - requestedPermissions=${ - // Strip compatibility permission added in T - this.requestedPermissions?.filter { x -> - x != Manifest.permission.POST_NOTIFICATIONS - }?.ifEmpty { null }?.joinToString() - } - requestedPermissionsFlags=${ - // Strip the flag from compatibility permission added in T - this.requestedPermissionsFlags?.filterIndexed { index, _ -> - index != ArrayUtils.indexOf(requestedPermissions, - Manifest.permission.POST_NOTIFICATIONS) - }?.map { - // Newer flags are stripped - it and (PackageInfo.REQUESTED_PERMISSION_REQUIRED - or PackageInfo.REQUESTED_PERMISSION_GRANTED) - }?.ifEmpty { null }?.joinToString() - } - requiredAccountType=${this.requiredAccountType} - requiredForAllUsers=${this.requiredForAllUsers} - restrictedAccountType=${this.restrictedAccountType} - services=${this.services?.joinToString { it.dumpToString() } - .ignored("Checked separately in test")} - sharedUserId=${this.sharedUserId} - sharedUserLabel=${this.sharedUserLabel} - signatures=${this.signatures?.joinToString { it.toCharsString() }} - signingInfo=${this.signingInfo?.signingCertificateHistory - ?.joinToString { it.toCharsString() }.orEmpty()} - splitNames=${this.splitNames?.contentToString()} - splitRevisionCodes=${this.splitRevisionCodes?.contentToString()} - targetOverlayableName=${this.targetOverlayableName} - versionCode=${this.versionCode} - versionCodeMajor=${this.versionCodeMajor} - versionName=${this.versionName} - """.trimIndent() - - private fun Bundle?.dumpToString() = this?.keySet()?.associateWith { get(it) }?.toString() - - private fun <T> SparseArray<T>?.dumpToString(): String { - if (this == null) { - return "EMPTY" - } - - val list = mutableListOf<Pair<Int, T>>() - for (index in (0 until size())) { - list += keyAt(index) to valueAt(index) - } - return list.toString() - } -} diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageInfoUserFieldsTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageInfoUserFieldsTest.kt deleted file mode 100644 index 67b5d683de9a..000000000000 --- a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageInfoUserFieldsTest.kt +++ /dev/null @@ -1,145 +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.server.pm.parsing - -import android.content.pm.ApplicationInfo -import android.content.pm.PackageParser -import android.os.Environment -import android.os.UserHandle -import android.platform.test.annotations.Presubmit -import com.google.common.truth.Truth.assertWithMessage -import org.junit.Test - -/** - * As a performance optimization, the new parsing code builds the user data directories manually - * using string concatenation. This tries to mirror the logic that [Environment] uses, but it is - * still fragile to changes and potentially different device configurations. - * - * This compares the resultant values against the old [PackageParser] outputs as well as - * [ApplicationInfo]'s own [ApplicationInfo.initForUser]. - */ -@Presubmit -class PackageInfoUserFieldsTest : AndroidPackageParsingTestBase() { - - @Test - fun userEnvironmentValues() { - // Specifically use a large user ID to test assumptions about single character IDs - val userId = 110 - - oldPackages.zip(newPackages) - .map { (old, new) -> - (old to oldAppInfo(pkg = old, userId = userId)!!) to - (new to newAppInfo(pkg = new, userId = userId)!!) - } - .forEach { (oldPair, newPair) -> - val (oldPkg, oldInfo) = oldPair - val (newPkg, newInfo) = newPair - - val oldValuesActual = extractActual(oldInfo) - val newValuesActual = extractActual(newInfo) - val oldValuesExpected: Values - val newValuesExpected: Values - - val packageName = oldPkg.packageName - if (packageName == "android") { - val systemDataDir = Environment.getDataSystemDirectory().absolutePath - oldValuesExpected = Values( - uid = UserHandle.getUid(userId, - UserHandle.getAppId(oldPkg.applicationInfo.uid)), - userDe = null, - userCe = null, - dataDir = systemDataDir - ) - newValuesExpected = Values( - uid = UserHandle.getUid(userId, UserHandle.getAppId(newPkg.uid)), - userDe = null, - userCe = null, - dataDir = systemDataDir - ) - } else { - oldValuesExpected = extractExpected(oldInfo, oldInfo.uid, userId) - newValuesExpected = extractExpected(newInfo, newPkg.uid, userId) - } - - // Calls the internal ApplicationInfo logic to compare against. This must be - // done after saving the original values, since this will overwrite them. - oldInfo.initForUser(userId) - newInfo.initForUser(userId) - - val oldInitValues = extractActual(oldInfo) - val newInitValues = extractActual(newInfo) - - // The optimization is also done for the no state API that isn't used by the - // system. This API is still exposed publicly, so for this test we should - // verify it. - val newNoStateValues = extractActual( - newAppInfoWithoutState(newPkg, 0, userId)!!) - - assertAllEquals(packageName, - oldValuesActual, oldValuesExpected, oldInitValues, - newValuesActual, newValuesExpected, newInitValues, newNoStateValues) - } - } - - private fun assertAllEquals(packageName: String, vararg values: Values) { - // Local function to avoid accidentally calling wrong type - fun assertAllEquals(message: String, vararg values: Any?) { - values.forEachIndexed { index, value -> - if (index == 0) return@forEachIndexed - assertWithMessage("$message $index").that(values[0]).isEqualTo(value) - } - } - - assertAllEquals("$packageName mismatched uid", values.map { it.uid }) - assertAllEquals("$packageName mismatched userDe", values.map { it.userDe }) - assertAllEquals("$packageName mismatched userCe", values.map { it.userCe }) - assertAllEquals("$packageName mismatched dataDir", values.map { it.dataDir }) - } - - private fun extractActual(appInfo: ApplicationInfo) = Values( - uid = appInfo.uid, - userDe = appInfo.deviceProtectedDataDir, - userCe = appInfo.credentialProtectedDataDir, - dataDir = appInfo.dataDir - ) - - private fun extractExpected(appInfo: ApplicationInfo, appIdUid: Int, userId: Int): Values { - val userDe = Environment.getDataUserDePackageDirectory(appInfo.volumeUuid, userId, - appInfo.packageName).absolutePath - val userCe = Environment.getDataUserCePackageDirectory(appInfo.volumeUuid, userId, - appInfo.packageName).absolutePath - val dataDir = if (appInfo.isDefaultToDeviceProtectedStorage) { - appInfo.deviceProtectedDataDir - } else { - appInfo.credentialProtectedDataDir - } - - return Values( - uid = UserHandle.getUid(userId, UserHandle.getAppId(appIdUid)), - userDe = userDe, - userCe = userCe, - dataDir = dataDir - ) - } - - data class Values( - val uid: Int, - val userDe: String?, - val userCe: String?, - val dataDir: String? - ) -} diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java index c99034201d69..004d7bc2707c 100644 --- a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java @@ -18,7 +18,6 @@ package com.android.server.pm.parsing; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import android.apex.ApexInfo; @@ -27,16 +26,9 @@ import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; -import android.content.pm.PackageParser; import android.content.pm.PermissionInfo; import android.content.pm.SigningDetails; -import android.content.pm.parsing.PackageInfoWithoutStateUtils; -import android.content.pm.parsing.ParsingPackage; -import android.content.pm.parsing.ParsingPackageUtils; -import android.content.pm.parsing.component.ParsedComponent; -import android.content.pm.parsing.component.ParsedIntentInfo; -import android.content.pm.parsing.component.ParsedPermission; -import android.content.pm.parsing.component.ParsedPermissionUtils; +import android.content.pm.parsing.FrameworkParsingPackageUtils; import android.content.pm.parsing.result.ParseResult; import android.content.pm.parsing.result.ParseTypeImpl; import android.os.Build; @@ -55,6 +47,14 @@ import com.android.internal.util.ArrayUtils; import com.android.server.pm.PackageManagerException; import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.parsing.pkg.ParsedPackage; +import com.android.server.pm.pkg.component.ParsedActivityUtils; +import com.android.server.pm.pkg.component.ParsedComponent; +import com.android.server.pm.pkg.component.ParsedIntentInfo; +import com.android.server.pm.pkg.component.ParsedPermission; +import com.android.server.pm.pkg.component.ParsedPermissionUtils; +import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils; +import com.android.server.pm.pkg.parsing.ParsingPackage; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import com.google.common.truth.Expect; @@ -105,20 +105,19 @@ public class PackageParserLegacyCoreTest { private void verifyComputeMinSdkVersion(int minSdkVersion, String minSdkCodename, boolean isPlatformReleased, int expectedMinSdk) { - final String[] outError = new String[1]; - final int result = PackageParser.computeMinSdkVersion( + final ParseTypeImpl input = ParseTypeImpl.forParsingWithoutPlatformCompat(); + final ParseResult<Integer> result = FrameworkParsingPackageUtils.computeMinSdkVersion( minSdkVersion, minSdkCodename, PLATFORM_VERSION, isPlatformReleased ? CODENAMES_RELEASED : CODENAMES_PRE_RELEASE, - outError); - - assertEquals("Error msg: " + outError[0], expectedMinSdk, result); + input); if (expectedMinSdk == -1) { - assertNotNull(outError[0]); + assertTrue(result.isError()); } else { - assertNull(outError[0]); + assertTrue(result.isSuccess()); + assertEquals(expectedMinSdk, (int) result.getResult()); } } @@ -201,19 +200,18 @@ public class PackageParserLegacyCoreTest { private void verifyComputeTargetSdkVersion(int targetSdkVersion, String targetSdkCodename, boolean isPlatformReleased, int expectedTargetSdk) { - final String[] outError = new String[1]; - final int result = PackageParser.computeTargetSdkVersion( + final ParseTypeImpl input = ParseTypeImpl.forParsingWithoutPlatformCompat(); + final ParseResult<Integer> result = FrameworkParsingPackageUtils.computeTargetSdkVersion( targetSdkVersion, targetSdkCodename, isPlatformReleased ? CODENAMES_RELEASED : CODENAMES_PRE_RELEASE, - outError); - - assertEquals(result, expectedTargetSdk); + input); if (expectedTargetSdk == -1) { - assertNotNull(outError[0]); + assertTrue(result.isError()); } else { - assertNull(outError[0]); + assertTrue(result.isSuccess()); + assertEquals(expectedTargetSdk, (int) result.getResult()); } } @@ -306,34 +304,34 @@ public class PackageParserLegacyCoreTest { // Not set in either configChanges or recreateOnConfigChanges. int configChanges = 0x0000; // 00000000. int recreateOnConfigChanges = 0x0000; // 00000000. - int finalConfigChanges = - PackageParser.getActivityConfigChanges(configChanges, recreateOnConfigChanges); + int finalConfigChanges = ParsedActivityUtils.getActivityConfigChanges(configChanges, + recreateOnConfigChanges); assertEquals(0x0003, finalConfigChanges); // Should be 00000011. // Not set in configChanges, but set in recreateOnConfigChanges. configChanges = 0x0000; // 00000000. recreateOnConfigChanges = 0x0003; // 00000011. - finalConfigChanges = - PackageParser.getActivityConfigChanges(configChanges, recreateOnConfigChanges); + finalConfigChanges = ParsedActivityUtils.getActivityConfigChanges(configChanges, + recreateOnConfigChanges); assertEquals(0x0000, finalConfigChanges); // Should be 00000000. // Set in configChanges. configChanges = 0x0003; // 00000011. recreateOnConfigChanges = 0X0000; // 00000000. - finalConfigChanges = - PackageParser.getActivityConfigChanges(configChanges, recreateOnConfigChanges); + finalConfigChanges = ParsedActivityUtils.getActivityConfigChanges(configChanges, + recreateOnConfigChanges); assertEquals(0x0003, finalConfigChanges); // Should be 00000011. recreateOnConfigChanges = 0x0003; // 00000011. - finalConfigChanges = - PackageParser.getActivityConfigChanges(configChanges, recreateOnConfigChanges); + finalConfigChanges = ParsedActivityUtils.getActivityConfigChanges(configChanges, + recreateOnConfigChanges); assertEquals(0x0003, finalConfigChanges); // Should still be 00000011. // Other bit set in configChanges. configChanges = 0x0080; // 10000000, orientation. recreateOnConfigChanges = 0x0000; // 00000000. - finalConfigChanges = - PackageParser.getActivityConfigChanges(configChanges, recreateOnConfigChanges); + finalConfigChanges = ParsedActivityUtils.getActivityConfigChanges(configChanges, + recreateOnConfigChanges); assertEquals(0x0083, finalConfigChanges); // Should be 10000011. } diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt index f53042183af8..bb094ba897e6 100644 --- a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt +++ b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt @@ -18,8 +18,8 @@ package com.android.server.pm.parsing import android.annotation.RawRes import android.content.Context -import android.content.pm.parsing.ParsingPackage -import android.content.pm.parsing.ParsingPackageUtils +import com.android.server.pm.pkg.parsing.ParsingPackage +import com.android.server.pm.pkg.parsing.ParsingPackageUtils import android.content.pm.parsing.result.ParseResult import android.platform.test.annotations.Presubmit import androidx.test.InstrumentationRegistry diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt index ffa19575718b..1f57b6c9f95f 100644 --- a/services/tests/servicestests/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt +++ b/services/tests/servicestests/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt @@ -17,7 +17,7 @@ package com.android.server.pm.parsing import android.content.pm.PackageManager -import android.content.pm.parsing.ParsingPackageUtils +import com.android.server.pm.pkg.parsing.ParsingPackageUtils import android.platform.test.annotations.Postsubmit import com.android.server.pm.PackageManagerException import com.android.server.pm.PackageManagerService diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java b/services/tests/servicestests/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java index 5bcd0f6bb029..b28446b337a6 100644 --- a/services/tests/servicestests/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java @@ -23,7 +23,7 @@ import static com.android.server.pm.parsing.library.SharedLibraryNames.ORG_APACH import static com.google.common.truth.Truth.assertThat; -import android.content.pm.parsing.ParsingPackage; +import com.android.server.pm.pkg.parsing.ParsingPackage; import android.os.Build; import android.platform.test.annotations.Presubmit; 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/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/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java index b0bdaf084b1a..ab86e295e28a 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java @@ -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/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/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 8a057f7b7065..0dcf7992e02b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -103,6 +103,7 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.never; @@ -136,6 +137,7 @@ import android.view.IWindowManager; import android.view.IWindowSession; import android.view.InsetsSource; import android.view.InsetsState; +import android.view.InsetsVisibilities; import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationTarget; import android.view.Surface; @@ -152,6 +154,7 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.invocation.InvocationOnMock; import java.util.ArrayList; @@ -3065,6 +3068,50 @@ public class ActivityRecordTests extends WindowTestsBase { assertEquals(state.getSource(ITYPE_IME).getFrame(), imeSource.getFrame()); } + @UseTestDisplay(addWindows = {W_ACTIVITY, W_INPUT_METHOD}) + @Test + public void testImeInsetsFrozenFlag_noDispatchVisibleInsetsWhenAppNotRequest() + throws RemoteException { + final WindowState app1 = createWindow(null, TYPE_APPLICATION, "app1"); + final WindowState app2 = createWindow(null, TYPE_APPLICATION, "app2"); + + mDisplayContent.getInsetsStateController().getSourceProvider(ITYPE_IME).setWindow( + mImeWindow, null, null); + mImeWindow.getControllableInsetProvider().setServerVisible(true); + + // Simulate app2 is closing and let app1 is visible to be IME targets. + makeWindowVisibleAndDrawn(app1, mImeWindow); + mDisplayContent.setImeLayeringTarget(app1); + mDisplayContent.updateImeInputAndControlTarget(app1); + app2.mActivityRecord.commitVisibility(false, false); + + // app1 requests IME visible. + final InsetsVisibilities requestedVisibilities = new InsetsVisibilities(); + requestedVisibilities.setVisibility(ITYPE_IME, true); + app1.setRequestedVisibilities(requestedVisibilities); + mDisplayContent.getInsetsStateController().onInsetsModified(app1); + + // Verify app1's IME insets is visible and app2's IME insets frozen flag set. + assertTrue(app1.getInsetsState().peekSource(ITYPE_IME).isVisible()); + assertTrue(app2.mActivityRecord.mImeInsetsFrozenUntilStartInput); + + // Simulate switching to app2 to make it visible to be IME targets. + makeWindowVisibleAndDrawn(app2); + spyOn(app2); + spyOn(app2.mClient); + ArgumentCaptor<InsetsState> insetsStateCaptor = ArgumentCaptor.forClass(InsetsState.class); + doReturn(true).when(app2).isReadyToDispatchInsetsState(); + mDisplayContent.setImeLayeringTarget(app2); + mDisplayContent.updateImeInputAndControlTarget(app2); + + // Verify after unfreezing app2's IME insets state, we won't dispatch visible IME insets + // to client if the app didn't request IME visible. + assertFalse(app2.mActivityRecord.mImeInsetsFrozenUntilStartInput); + verify(app2.mClient, atLeastOnce()).insetsChanged(insetsStateCaptor.capture(), anyBoolean(), + anyBoolean()); + assertFalse(insetsStateCaptor.getAllValues().get(0).peekSource(ITYPE_IME).isVisible()); + } + @Test public void testInClosingAnimation_doNotHideSurface() { final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java index e2f0658f3da3..fdc898207e27 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -31,7 +31,6 @@ import static android.app.ActivityManager.START_TASK_TO_FRONT; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; @@ -51,7 +50,6 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; -import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS; import static com.android.server.wm.WindowContainer.POSITION_BOTTOM; import static com.android.server.wm.WindowContainer.POSITION_TOP; @@ -457,7 +455,7 @@ public class ActivityStarterTests extends WindowTestsBase { final ActivityRecord splitSecondReusableActivity = activities.second; final ActivityRecord splitSecondTopActivity = new ActivityBuilder(mAtm).setCreateTask(true) .setParentTask(splitSecondReusableActivity.getRootTask()).build(); - assertTrue(splitSecondTopActivity.inSplitScreenSecondaryWindowingMode()); + assertTrue(splitSecondTopActivity.inMultiWindowMode()); // Let primary stack has focus. splitPrimaryFocusActivity.moveFocusableActivityToTop("testSplitScreenTaskToFront"); @@ -476,13 +474,16 @@ public class ActivityStarterTests extends WindowTestsBase { final TestSplitOrganizer splitOrg = new TestSplitOrganizer(mAtm); // The fullscreen windowing mode activity will be moved to split-secondary by // TestSplitOrganizer when a split-primary task appears. - final ActivityRecord splitSecondActivity = - new ActivityBuilder(mAtm).setCreateTask(true).build(); final ActivityRecord splitPrimaryActivity = new TaskBuilder(mSupervisor) .setParentTaskFragment(splitOrg.mPrimary) .setCreateActivity(true) .build() .getTopMostActivity(); + final ActivityRecord splitSecondActivity = new TaskBuilder(mSupervisor) + .setParentTaskFragment(splitOrg.mSecondary) + .setCreateActivity(true) + .build() + .getTopMostActivity(); splitPrimaryActivity.mVisibleRequested = splitSecondActivity.mVisibleRequested = true; assertEquals(splitOrg.mPrimary, splitPrimaryActivity.getRootTask()); @@ -1090,7 +1091,7 @@ public class ActivityStarterTests extends WindowTestsBase { starter.setActivityOptions(options.toBundle()) .setReason("testWindowingModeOptionsLaunchAdjacent") .setOutActivity(outActivity).execute(); - assertThat(outActivity[0].inSplitScreenSecondaryWindowingMode()).isTrue(); + assertThat(outActivity[0].inMultiWindowMode()).isTrue(); } @Test @@ -1108,50 +1109,6 @@ public class ActivityStarterTests extends WindowTestsBase { } @Test - public void testStartActivityInner_allSplitScreenPrimaryActivitiesVisible() { - // Given - final ActivityStarter starter = prepareStarter(0, false); - - starter.setReason("testAllSplitScreenPrimaryActivitiesAreResumed"); - - final ActivityRecord targetRecord = new ActivityBuilder(mAtm).build(); - targetRecord.setFocusable(false); - targetRecord.setVisibility(false); - final ActivityRecord sourceRecord = new ActivityBuilder(mAtm).build(); - - final Task stack = spy( - mRootWindowContainer.getDefaultTaskDisplayArea() - .createRootTask(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, - /* onTop */true)); - - stack.addChild(targetRecord); - - doReturn(stack).when(mRootWindowContainer).getLaunchRootTask(any(), any(), any(), any(), - anyBoolean(), any(), anyInt(), anyInt(), anyInt()); - - starter.mStartActivity = new ActivityBuilder(mAtm).build(); - - // When - starter.startActivityInner( - /* r */targetRecord, - /* sourceRecord */ sourceRecord, - /* voiceSession */null, - /* voiceInteractor */ null, - /* startFlags */ 0, - /* doResume */true, - /* options */null, - /* inTask */null, - /* inTaskFragment */ null, - /* restrictedBgActivity */false, - /* intentGrants */null); - - // Then - verify(stack).ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS); - verify(targetRecord).makeVisibleIfNeeded(null, true); - assertTrue(targetRecord.mVisibleRequested); - } - - @Test public void testStartActivityInner_inTaskFragment() { final ActivityStarter starter = prepareStarter(0, false); final ActivityRecord targetRecord = new ActivityBuilder(mAtm).build(); 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..18fa2df47f8b 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; @@ -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/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java index ea5bf52af905..d3282b97a6b8 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java @@ -19,7 +19,6 @@ package com.android.server.wm; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_STATUS_BAR; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; @@ -82,19 +81,6 @@ public class InsetsPolicyTest extends WindowTestsBase { } @Test - public void testControlsForDispatch_dockedTaskVisible() { - addWindow(TYPE_STATUS_BAR, "statusBar"); - addWindow(TYPE_NAVIGATION_BAR, "navBar"); - - final WindowState win = createWindow(null, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, - ACTIVITY_TYPE_STANDARD, TYPE_APPLICATION, mDisplayContent, "app"); - final InsetsSourceControl[] controls = addWindowAndGetControlsForDispatch(win); - - // The app must not control any system bars. - assertNull(controls); - } - - @Test public void testControlsForDispatch_multiWindowTaskVisible() { addWindow(TYPE_STATUS_BAR, "statusBar"); addWindow(TYPE_NAVIGATION_BAR, "navBar"); diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java index b4c449a94a40..632a59d3484d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java @@ -117,8 +117,7 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_RECENTS, mFinishedCallback); - // Remove the app window so that the animation target can not be created - activity.removeImmediately(); + // The activity doesn't contain window so the animation target cannot be created. mController.startAnimation(); // Verify that the finish callback to reparent the leash is called 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 bb49cd25ac6b..3cb0bed32493 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java @@ -22,6 +22,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; 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; @@ -240,7 +241,7 @@ public class RootTaskTests extends WindowTestsBase { final WindowConfiguration windowConfiguration = task.getResolvedOverrideConfiguration().windowConfiguration; spyOn(windowConfiguration); - doReturn(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) + doReturn(WINDOWING_MODE_MULTI_WINDOW) .when(windowConfiguration).getWindowingMode(); // Prevent adjust task dimensions @@ -323,72 +324,12 @@ public class RootTaskTests extends WindowTestsBase { } @Test - public void testPrimarySplitScreenMoveToBack() { - TestSplitOrganizer organizer = new TestSplitOrganizer(mAtm); - // We're testing an edge case here where we have primary + fullscreen rather than secondary. - organizer.setMoveToSecondaryOnEnter(false); - - // Create primary splitscreen root task. - final Task primarySplitScreen = new TaskBuilder(mAtm.mTaskSupervisor) - .setParentTaskFragment(organizer.mPrimary) - .setOnTop(true) - .build(); - - // Assert windowing mode. - assertEquals(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, primarySplitScreen.getWindowingMode()); - - // Move primary to back. - primarySplitScreen.moveToBack("testPrimarySplitScreenToFullscreenWhenMovedToBack", - null /* task */); - - // Assert that root task is at the bottom. - assertEquals(0, getTaskIndexOf(mDefaultTaskDisplayArea, primarySplitScreen)); - - // Ensure no longer in splitscreen. - assertEquals(WINDOWING_MODE_FULLSCREEN, primarySplitScreen.getWindowingMode()); - - // Ensure that the override mode is restored to undefined - assertEquals(WINDOWING_MODE_UNDEFINED, - primarySplitScreen.getRequestedOverrideWindowingMode()); - } - - @Test - public void testMoveToPrimarySplitScreenThenMoveToBack() { - TestSplitOrganizer organizer = new TestSplitOrganizer(mAtm); - // This time, start with a fullscreen activity root task. - final Task primarySplitScreen = mDefaultTaskDisplayArea.createRootTask( - WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, true /* onTop */); - - primarySplitScreen.reparent(organizer.mPrimary, POSITION_TOP, - false /*moveParents*/, "test"); - - // Assert windowing mode. - assertEquals(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, primarySplitScreen.getWindowingMode()); - - // Move primary to back. - primarySplitScreen.moveToBack("testPrimarySplitScreenToFullscreenWhenMovedToBack", - null /* task */); - - // Assert that root task is at the bottom. - assertEquals(primarySplitScreen, organizer.mSecondary.getChildAt(0)); - - // Ensure that the override mode is restored to what it was (fullscreen) - assertEquals(WINDOWING_MODE_UNDEFINED, - primarySplitScreen.getRequestedOverrideWindowingMode()); - } - - @Test public void testSplitScreenMoveToBack() { TestSplitOrganizer organizer = new TestSplitOrganizer(mAtm); - // Explicitly reparent task to primary split root to enter split mode, in which implies - // primary on top and secondary containing the home task below another root task. - final Task primaryTask = mDefaultTaskDisplayArea.createRootTask( - WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, true /* onTop */); - final Task secondaryTask = mDefaultTaskDisplayArea.createRootTask( - WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, true /* onTop */); + final Task primaryTask = organizer.createTaskToPrimary(true /* onTop */); + final Task secondaryTask = organizer.createTaskToSecondary(true /* onTop */); final Task homeRoot = mDefaultTaskDisplayArea.getRootTask( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME); - primaryTask.reparent(organizer.mPrimary, POSITION_TOP); mDefaultTaskDisplayArea.positionChildAt(POSITION_TOP, organizer.mPrimary, false /* includingParents */); @@ -397,21 +338,26 @@ public class RootTaskTests extends WindowTestsBase { // Assert that the primaryTask is now below home in its parent but primary is left alone. assertEquals(0, organizer.mPrimary.getChildCount()); - assertEquals(primaryTask, organizer.mSecondary.getChildAt(0)); + // Assert that root task is at the bottom. + assertEquals(0, getTaskIndexOf(mDefaultTaskDisplayArea, primaryTask)); assertEquals(1, organizer.mPrimary.compareTo(organizer.mSecondary)); assertEquals(1, homeRoot.compareTo(primaryTask)); assertEquals(homeRoot.getParent(), primaryTask.getParent()); // Make sure windowing modes are correct - assertEquals(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, organizer.mPrimary.getWindowingMode()); - assertEquals(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, primaryTask.getWindowingMode()); + assertEquals(WINDOWING_MODE_MULTI_WINDOW, organizer.mPrimary.getWindowingMode()); + assertEquals(WINDOWING_MODE_MULTI_WINDOW, secondaryTask.getWindowingMode()); + // Ensure no longer in splitscreen. + assertEquals(WINDOWING_MODE_FULLSCREEN, primaryTask.getWindowingMode()); + // Ensure that the override mode is restored to undefined + assertEquals(WINDOWING_MODE_UNDEFINED, primaryTask.getRequestedOverrideWindowingMode()); // Move secondary to back via parent (should be equivalent) organizer.mSecondary.moveToBack("test", secondaryTask); - // Assert that it is now in back but still in secondary split + // Assert that it is now in back and left in secondary split + assertEquals(0, organizer.mSecondary.getChildCount()); assertEquals(1, homeRoot.compareTo(primaryTask)); - assertEquals(secondaryTask, organizer.mSecondary.getChildAt(0)); assertEquals(1, primaryTask.compareTo(secondaryTask)); assertEquals(homeRoot.getParent(), secondaryTask.getParent()); } @@ -423,7 +369,7 @@ public class RootTaskTests extends WindowTestsBase { .setTask(rootHomeTask) .build(); final Task secondaryRootTask = mAtm.mTaskOrganizerController.createRootTask( - rootHomeTask.getDisplayContent(), WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, null); + rootHomeTask.getDisplayContent(), WINDOWING_MODE_MULTI_WINDOW, null); rootHomeTask.reparent(secondaryRootTask, POSITION_TOP); assertEquals(secondaryRootTask, rootHomeTask.getParent()); @@ -581,6 +527,7 @@ public class RootTaskTests extends WindowTestsBase { assertTrue(pinnedRootTask.shouldBeVisible(null /* starting */)); } + // TODO(b/199236198): check this is unnecessary or need to migrate after remove legacy split. @Test public void testShouldBeVisible_SplitScreen() { // task not supporting split should be fullscreen for this test. @@ -700,30 +647,23 @@ public class RootTaskTests extends WindowTestsBase { @Test public void testGetVisibility_MultiLevel() { - final Task homeRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, - WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, true /* onTop */); + TestSplitOrganizer organizer = new TestSplitOrganizer(mAtm); final Task splitPrimary = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, - WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED, true /* onTop */); - // Creating as two-level tasks so home task can be reparented to split-secondary root task. + WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_UNDEFINED, true /* onTop */); final Task splitSecondary = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, - WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_UNDEFINED, true /* onTop */, - true /* twoLevelTask */); + WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_UNDEFINED, true /* onTop */); - doReturn(false).when(homeRootTask).isTranslucent(any()); doReturn(false).when(splitPrimary).isTranslucent(any()); doReturn(false).when(splitSecondary).isTranslucent(any()); - // Re-parent home to split secondary. - homeRootTask.reparent(splitSecondary, POSITION_TOP); - // Current tasks should be visible. + // Re-parent tasks to split. + organizer.putTaskToPrimary(splitPrimary, true /* onTop */); + organizer.putTaskToSecondary(splitSecondary, true /* onTop */); + // Reparented tasks should be visible. assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE, splitPrimary.getVisibility(null /* starting */)); assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE, splitSecondary.getVisibility(null /* starting */)); - // Home task should still be visible even though it is a child of another visible task. - assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE, - homeRootTask.getVisibility(null /* starting */)); - // Add fullscreen translucent task that partially occludes split tasks final Task translucentRootTask = createStandardRootTaskForVisibilityTest( @@ -736,19 +676,12 @@ public class RootTaskTests extends WindowTestsBase { splitPrimary.getVisibility(null /* starting */)); assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT, splitSecondary.getVisibility(null /* starting */)); - // Home task should be visible behind translucent since its parent is visible behind - // translucent. - assertEquals(TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT, - homeRootTask.getVisibility(null /* starting */)); - // Hide split-secondary - splitSecondary.setForceHidden(FLAG_FORCE_HIDDEN_FOR_TASK_ORG, true /* set */); + organizer.mSecondary.setForceHidden(FLAG_FORCE_HIDDEN_FOR_TASK_ORG, true /* set */); // Home split secondary and home task should be invisible. assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE, splitSecondary.getVisibility(null /* starting */)); - assertEquals(TASK_FRAGMENT_VISIBILITY_INVISIBLE, - homeRootTask.getVisibility(null /* starting */)); } @Test @@ -1094,36 +1027,6 @@ public class RootTaskTests extends WindowTestsBase { assertEquals(pinnedRootTask, getRootTaskAbove(alwaysOnTopRootTask2)); } - @Test - public void testSplitScreenMoveToFront() { - final Task splitScreenPrimary = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, - WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); - final Task splitScreenSecondary = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, - WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); - final Task assistantRootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, true /* onTop */); - - doReturn(false).when(splitScreenPrimary).isTranslucent(any()); - doReturn(false).when(splitScreenSecondary).isTranslucent(any()); - doReturn(false).when(assistantRootTask).isTranslucent(any()); - - assertFalse(splitScreenPrimary.shouldBeVisible(null /* starting */)); - assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */)); - assertTrue(assistantRootTask.shouldBeVisible(null /* starting */)); - - splitScreenSecondary.moveToFront("testSplitScreenMoveToFront"); - - if (isAssistantOnTop()) { - assertFalse(splitScreenPrimary.shouldBeVisible(null /* starting */)); - assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */)); - assertTrue(assistantRootTask.shouldBeVisible(null /* starting */)); - } else { - assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */)); - assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */)); - assertFalse(assistantRootTask.shouldBeVisible(null /* starting */)); - } - } - private Task createStandardRootTaskForVisibilityTest(int windowingMode, boolean translucent) { final Task rootTask = createTaskForShouldBeVisibleTest(mDefaultTaskDisplayArea, 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 645d804b3cfc..b815c38b7a8a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -16,7 +16,7 @@ package com.android.server.wm; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; @@ -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; @@ -937,8 +938,8 @@ public class SizeCompatTests extends WindowTestsBase { mTask.reparent(organizer.mPrimary, POSITION_TOP, false /*moveParents*/, "test"); organizer.mPrimary.setBounds(0, 0, 1000, 1400); - assertEquals(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, mTask.getWindowingMode()); - assertEquals(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, activity.getWindowingMode()); + assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode()); + assertEquals(WINDOWING_MODE_MULTI_WINDOW, activity.getWindowingMode()); // Resizable activity is sandboxed due to config being enabled. assertActivityMaxBoundsSandboxed(activity); @@ -1828,8 +1829,8 @@ public class SizeCompatTests extends WindowTestsBase { mTask.reparent(organizer.mPrimary, POSITION_TOP, false /*moveParents*/, "test"); organizer.mPrimary.setBounds(0, 0, 1000, 1400); - assertEquals(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, mTask.getWindowingMode()); - assertEquals(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, mActivity.getWindowingMode()); + assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode()); + assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode()); // Non-resizable activity in size compat mode assertScaled(); @@ -1868,8 +1869,8 @@ public class SizeCompatTests extends WindowTestsBase { mTask.reparent(organizer.mPrimary, POSITION_TOP, false /*moveParents*/, "test"); organizer.mPrimary.setBounds(0, 0, 1000, 1400); - assertEquals(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, mTask.getWindowingMode()); - assertEquals(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, mActivity.getWindowingMode()); + assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode()); + assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode()); // Non-resizable activity in size compat mode assertScaled(); @@ -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/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java index bec53d71b312..8b14e981f046 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java @@ -22,6 +22,7 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE; @@ -77,6 +78,7 @@ import androidx.test.filters.SmallTest; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mockito; import java.util.ArrayList; @@ -271,6 +273,22 @@ public class WindowContainerTests extends WindowTestsBase { } @Test + public void testRemoveImmediatelyClearsLeash() { + final AnimationAdapter animAdapter = mock(AnimationAdapter.class); + final WindowToken token = createTestWindowToken(TYPE_APPLICATION_OVERLAY, mDisplayContent); + final SurfaceControl.Transaction t = token.getPendingTransaction(); + token.startAnimation(t, animAdapter, false /* hidden */, + SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION); + final ArgumentCaptor<SurfaceControl> leashCaptor = + ArgumentCaptor.forClass(SurfaceControl.class); + verify(animAdapter).startAnimation(leashCaptor.capture(), eq(t), anyInt(), any()); + assertTrue(token.mSurfaceAnimator.hasLeash()); + token.removeImmediately(); + assertFalse(token.mSurfaceAnimator.hasLeash()); + verify(t).remove(eq(leashCaptor.getValue())); + } + + @Test public void testAddChildByIndex() { final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(mWm); final TestWindowContainer root = builder.setLayer(0).build(); 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..d4364d9c12be 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -83,6 +83,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; @@ -986,4 +987,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/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index 59a2068e7b11..62c1067ec707 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -17,14 +17,10 @@ package com.android.server.wm; import static android.app.AppOpsManager.OP_NONE; -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.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.ROTATION_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -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.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; @@ -66,15 +62,14 @@ import static org.mockito.Mockito.mock; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.IApplicationThread; -import android.app.WindowConfiguration; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; +import android.graphics.Rect; import android.hardware.HardwareBuffer; import android.hardware.display.DisplayManager; import android.os.Build; @@ -1515,64 +1510,55 @@ class WindowTestsBase extends SystemServiceTestsBase { static class TestSplitOrganizer extends WindowOrganizerTests.StubOrganizer { final ActivityTaskManagerService mService; + final TaskDisplayArea mDefaultTDA; Task mPrimary; Task mSecondary; - boolean mInSplit = false; - // moves everything to secondary. Most tests expect this since sysui usually does it. - boolean mMoveToSecondaryOnEnter = true; int mDisplayId; - private static final int[] CONTROLLED_ACTIVITY_TYPES = { - ACTIVITY_TYPE_STANDARD, - ACTIVITY_TYPE_HOME, - ACTIVITY_TYPE_RECENTS, - ACTIVITY_TYPE_UNDEFINED - }; - private static final int[] CONTROLLED_WINDOWING_MODES = { - WINDOWING_MODE_FULLSCREEN, - WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, - WINDOWING_MODE_UNDEFINED - }; + TestSplitOrganizer(ActivityTaskManagerService service, DisplayContent display) { mService = service; + mDefaultTDA = display.getDefaultTaskDisplayArea(); mDisplayId = display.mDisplayId; mService.mTaskOrganizerController.registerTaskOrganizer(this); mPrimary = mService.mTaskOrganizerController.createRootTask( - display, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, null); + display, WINDOWING_MODE_MULTI_WINDOW, null); mSecondary = mService.mTaskOrganizerController.createRootTask( - display, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, null);; + display, WINDOWING_MODE_MULTI_WINDOW, null); + + mPrimary.setAdjacentTaskFragment(mSecondary, true); + display.getDefaultTaskDisplayArea().setLaunchAdjacentFlagRootTask(mSecondary); + + final Rect primaryBounds = new Rect(); + final Rect secondaryBounds = new Rect(); + display.getBounds().splitVertically(primaryBounds, secondaryBounds); + mPrimary.setBounds(primaryBounds); + mSecondary.setBounds(secondaryBounds); } + TestSplitOrganizer(ActivityTaskManagerService service) { this(service, service.mTaskSupervisor.mRootWindowContainer.getDefaultDisplay()); } - public void setMoveToSecondaryOnEnter(boolean move) { - mMoveToSecondaryOnEnter = move; + + public Task createTaskToPrimary(boolean onTop) { + final Task primaryTask = mDefaultTDA.createRootTask( + WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, onTop); + putTaskToPrimary(primaryTask, onTop); + return primaryTask; } - @Override - public void onTaskInfoChanged(ActivityManager.RunningTaskInfo info) { - if (mInSplit) { - return; - } - if (info.topActivityType == ACTIVITY_TYPE_UNDEFINED) { - // Not populated - return; - } - if (info.configuration.windowConfiguration.getWindowingMode() - != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { - return; - } - mInSplit = true; - if (!mMoveToSecondaryOnEnter) { - return; - } - DisplayContent dc = mService.mRootWindowContainer.getDisplayContent(mDisplayId); - dc.getDefaultTaskDisplayArea().setLaunchRootTask( - mSecondary, CONTROLLED_WINDOWING_MODES, CONTROLLED_ACTIVITY_TYPES); - dc.forAllRootTasks(rootTask -> { - if (!WindowConfiguration.isSplitScreenWindowingMode(rootTask.getWindowingMode())) { - rootTask.reparent(mSecondary, POSITION_BOTTOM); - } - }); + public Task createTaskToSecondary(boolean onTop) { + final Task secondaryTask = mDefaultTDA.createRootTask( + WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, onTop); + putTaskToSecondary(secondaryTask, onTop); + return secondaryTask; + } + + public void putTaskToPrimary(Task task, boolean onTop) { + task.reparent(mPrimary, onTop ? POSITION_TOP : POSITION_BOTTOM); + } + + public void putTaskToSecondary(Task task, boolean onTop) { + task.reparent(mSecondary, onTop ? POSITION_TOP : POSITION_BOTTOM); } } 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/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/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java index 0aa62c53e269..1c72eb8db708 100644 --- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java +++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java @@ -257,10 +257,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..9ac270f17fc4 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. @@ -245,6 +255,7 @@ public class UsbHostManager { setUsbDeviceConnectionHandler(ComponentName.unflattenFromString( deviceConnectionHandler)); } + mHasMidiFeature = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MIDI); } public void setCurrentUserSettings(UsbProfileGroupSettingsManager settings) { @@ -413,6 +424,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 +469,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..98173adfd0eb 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,133 @@ 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 + * 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 +539,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 +551,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 +590,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 +730,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 +758,9 @@ public class UsbPortManager { portInfo.supportsEnableContaminantPresenceProtection, portInfo.contaminantProtectionStatus, portInfo.supportsEnableContaminantPresenceDetection, - portInfo.contaminantDetectionStatus, pw); + portInfo.contaminantDetectionStatus, + portInfo.usbDataEnabled, + portInfo.powerTransferLimited, pw); } } else { for (RawPortInfo currentPortInfo : newPortInfo) { @@ -881,7 +772,9 @@ public class UsbPortManager { currentPortInfo.supportsEnableContaminantPresenceProtection, currentPortInfo.contaminantProtectionStatus, currentPortInfo.supportsEnableContaminantPresenceDetection, - currentPortInfo.contaminantDetectionStatus, pw); + currentPortInfo.contaminantDetectionStatus, + currentPortInfo.usbDataEnabled, + currentPortInfo.powerTransferLimited, pw); } } @@ -917,6 +810,8 @@ public class UsbPortManager { int contaminantProtectionStatus, boolean supportsEnableContaminantPresenceDetection, int contaminantDetectionStatus, + boolean usbDataEnabled, + boolean powerTransferLimited, IndentingPrintWriter pw) { // Only allow mode switch capability for dual role ports. // Validate that the current mode matches the supported modes we expect. @@ -975,7 +870,8 @@ public class UsbPortManager { currentPowerRole, canChangePowerRole, currentDataRole, canChangeDataRole, supportedRoleCombinations, contaminantProtectionStatus, - contaminantDetectionStatus); + contaminantDetectionStatus, usbDataEnabled, + powerTransferLimited); mPorts.put(portId, portInfo); } else { // Validate that ports aren't changing definition out from under us. @@ -1012,7 +908,8 @@ public class UsbPortManager { currentPowerRole, canChangePowerRole, currentDataRole, canChangeDataRole, supportedRoleCombinations, contaminantProtectionStatus, - contaminantDetectionStatus)) { + contaminantDetectionStatus, usbDataEnabled, + powerTransferLimited)) { portInfo.mDisposition = PortInfo.DISPOSITION_CHANGED; } else { portInfo.mDisposition = PortInfo.DISPOSITION_READY; @@ -1034,6 +931,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 +988,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 +1052,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 +1090,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 +1135,7 @@ 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, true, false); dispositionChanged = true; } @@ -1243,7 +1154,8 @@ public class UsbPortManager { int currentPowerRole, boolean canChangePowerRole, int currentDataRole, boolean canChangeDataRole, int supportedRoleCombinations, int contaminantProtectionStatus, - int contaminantDetectionStatus) { + int contaminantDetectionStatus, boolean usbDataEnabled, + boolean powerTransferLimited) { boolean dispositionChanged = false; mCanChangeMode = canChangeMode; @@ -1258,10 +1170,15 @@ public class UsbPortManager { || mUsbPortStatus.getContaminantProtectionStatus() != contaminantProtectionStatus || mUsbPortStatus.getContaminantDetectionStatus() - != contaminantDetectionStatus) { + != contaminantDetectionStatus + || mUsbPortStatus.getUsbDataStatus() + != usbDataEnabled + || mUsbPortStatus.isPowerTransferLimited() + != powerTransferLimited) { mUsbPortStatus = new UsbPortStatus(currentMode, currentPowerRole, currentDataRole, supportedRoleCombinations, contaminantProtectionStatus, - contaminantDetectionStatus); + contaminantDetectionStatus, usbDataEnabled, + powerTransferLimited); dispositionChanged = true; } @@ -1290,7 +1207,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 +1220,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..51643e7d7d3c 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,19 +787,30 @@ 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) { - return mPortManager.enableUsbDataSignal(enable); + wait = mPortManager.enableUsbData(portId, enable, operationId, callback, null); } else { - return false; + 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 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..3412a6f80cc7 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 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_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 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*/); } /** 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..8dfc85995272 --- /dev/null +++ b/services/usb/java/com/android/server/usb/hal/port/RawPortInfo.java @@ -0,0 +1,143 @@ +/* + * 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 boolean usbDataEnabled; + public boolean powerTransferLimited; + + 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.usbDataEnabled = true; + this.powerTransferLimited = false; + } + + 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, + boolean usbDataEnabled, + boolean powerTransferLimited) { + 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.usbDataEnabled = usbDataEnabled; + this.powerTransferLimited = powerTransferLimited; + } + + @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.writeBoolean(usbDataEnabled); + dest.writeBoolean(powerTransferLimited); + } + + 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(); + boolean usbDataEnabled = in.readBoolean(); + boolean powerTransferLimited = in.readBoolean(); + return new RawPortInfo(id, supportedModes, + supportedContaminantProtectionModes, currentMode, canChangeMode, + currentPowerRole, canChangePowerRole, + currentDataRole, canChangeDataRole, + supportsEnableContaminantPresenceProtection, + contaminantProtectionStatus, + supportsEnableContaminantPresenceDetection, + contaminantDetectionStatus, usbDataEnabled, + powerTransferLimited); + } + + @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..24602426930d --- /dev/null +++ b/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java @@ -0,0 +1,533 @@ +/* + * 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); + } + } + } + + 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; + } + + @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, + current.usbDataEnabled, + current.powerTransferLimited); + 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); + } + } + } +} 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..90c89090ef16 --- /dev/null +++ b/services/usb/java/com/android/server/usb/hal/port/UsbPortHal.java @@ -0,0 +1,184 @@ +/* + * 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 UsbOperationCallbackInternal} must therefore call + * {@link UsbOperationCallbackInternal#waitForOperationComplete} for processing + * the result. + * False when the operation is synchronous. Caller can proceed reading the result + * through {@link UsbOperationCallbackInternal#getStatus} + */ + public boolean enableUsbData(String portName, boolean enable, 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..8a0370ad4909 --- /dev/null +++ b/services/usb/java/com/android/server/usb/hal/port/UsbPortHidl.java @@ -0,0 +1,485 @@ +/* + * 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_ROLE_SINK; +import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SOURCE; + +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.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 boolean sUsbDataEnabled = true; + + 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 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) { + sUsbDataEnabled = enable; + } + + 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, sUsbDataEnabled, + false); + 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, sUsbDataEnabled, + false); + 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, + sUsbDataEnabled, + false); + 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/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/Connection.java b/telecomm/java/android/telecom/Connection.java index d63cdc004a3d..30d495942ece 100644 --- a/telecomm/java/android/telecom/Connection.java +++ b/telecomm/java/android/telecom/Connection.java @@ -3546,9 +3546,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..0f034ad6a45e 100644 --- a/telecomm/java/android/telecom/DisconnectCause.java +++ b/telecomm/java/android/telecom/DisconnectCause.java @@ -287,7 +287,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/ParcelableCall.java b/telecomm/java/android/telecom/ParcelableCall.java index 320308c9e926..f412a1825e2a 100644 --- a/telecomm/java/android/telecom/ParcelableCall.java +++ b/telecomm/java/android/telecom/ParcelableCall.java @@ -623,9 +623,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,23 +633,23 @@ 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(); 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/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/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl index a75f79caeee0..b9936ce2e1b2 100644 --- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl +++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl @@ -342,6 +342,8 @@ interface ITelecomService { void cleanupStuckCalls(); + int cleanupOrphanPhoneAccounts(); + void resetCarMode(); void setTestDefaultCallRedirectionApp(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 6d8edc52e2e3..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"; @@ -2486,6 +2498,10 @@ public class CarrierConfigManager { * * Note: If {@code *} is specified for the original code, any ImsReasonInfo with the matching * {@code MESSAGE} will be remapped to {@code NEW_CODE}. + * If {@code *} is specified for the message, any ImsReasonInfo with the matching + * {@code ORIGINAL_CODE} will be remapped to {@code NEW_CODE}. + * The wildcard for {@code ORIGINAL_CODE} takes precedence to the wildcard for {@code MESSAGE}. + * A mapping with both wildcards has no effect. * * Example: "501|call completion elsewhere|1014" * When the {@link ImsReasonInfo#getCode()} is {@link ImsReasonInfo#CODE_USER_TERMINATED} and @@ -5169,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() { @@ -5205,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); @@ -7569,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}) @@ -7718,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; } @@ -8646,10 +8765,10 @@ public class CarrierConfigManager { /* Default value is 2 seconds. */ sDefaults.putLong(KEY_OPPORTUNISTIC_NETWORK_5G_DATA_SWITCH_EXIT_HYSTERESIS_TIME_LONG, 2000); sDefaults.putBoolean(KEY_ENABLE_4G_OPPORTUNISTIC_NETWORK_SCAN_BOOL, true); - sDefaults.putInt(KEY_TIME_TO_SWITCH_BACK_TO_PRIMARY_IF_OPPORTUNISTIC_OOS_LONG, 60000); - sDefaults.putInt( + sDefaults.putLong(KEY_TIME_TO_SWITCH_BACK_TO_PRIMARY_IF_OPPORTUNISTIC_OOS_LONG, 60000L); + sDefaults.putLong( KEY_OPPORTUNISTIC_TIME_TO_SCAN_AFTER_CAPABILITY_SWITCH_TO_PRIMARY_LONG, - 120000); + 120000L); sDefaults.putAll(ImsServiceEntitlement.getDefaults()); sDefaults.putAll(Gps.getDefaults()); sDefaults.putIntArray(KEY_CDMA_ENHANCED_ROAMING_INDICATOR_FOR_HOME_NETWORK_INT_ARRAY, 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/SmsManager.java b/telephony/java/android/telephony/SmsManager.java index 54fb65cea617..cbd03c7f653a 100644 --- a/telephony/java/android/telephony/SmsManager.java +++ b/telephony/java/android/telephony/SmsManager.java @@ -2548,6 +2548,11 @@ public final class SmsManager { */ public static final int RESULT_RIL_BLOCKED_DUE_TO_CALL = 123; + /** + * A RIL error occurred during the SMS send. + */ + public static final int RESULT_RIL_GENERIC_ERROR = 124; + // SMS receiving results sent as a "result" extra in {@link Intents.SMS_REJECTED_ACTION} /** 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/UiccSlotMapping.java b/telephony/java/android/telephony/UiccSlotMapping.java index 87e7acdc792d..08de7fdc7029 100644 --- a/telephony/java/android/telephony/UiccSlotMapping.java +++ b/telephony/java/android/telephony/UiccSlotMapping.java @@ -94,7 +94,6 @@ public final class UiccSlotMapping implements Parcelable { * @param physicalSlotIndex is unique index referring to a physical SIM slot. * @param logicalSlotIndex is unique index referring to a logical SIM slot. * - * @hide */ public UiccSlotMapping(int portIndex, int physicalSlotIndex, int logicalSlotIndex) { this.mPortIndex = portIndex; 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 977fe33988d6..acbd64b57773 100644 --- a/telephony/java/android/telephony/data/ApnSetting.java +++ b/telephony/java/android/telephony/data/ApnSetting.java @@ -118,11 +118,9 @@ public class ApnSetting implements Parcelable { public static final int TYPE_VSIM = 1 << 12; // TODO: Refer to ApnTypes.VSIM /** APN type for BIP. */ public static final int TYPE_BIP = 1 << 13; // TODO: Refer to ApnTypes.BIP - /** - * APN type for ENTERPRISE. - * @hide - */ - public static final int TYPE_ENTERPRISE = TYPE_BIP << 1; + /** APN type for ENTERPRISE. */ + public static final int TYPE_ENTERPRISE = 1 << 14; //TODO: In future should be referenced from + // hardware.interfaces.radio.data.ApnTypes /** @hide */ @IntDef(flag = true, prefix = {"TYPE_"}, value = { @@ -355,6 +353,7 @@ public class ApnSetting implements Parcelable { * modem components or carriers. Non-system apps should use the integer variants instead. * @hide */ + @SystemApi public static final String TYPE_ENTERPRISE_STRING = "enterprise"; @@ -1629,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/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java index 2e5402c53d73..a49a61b592ba 100644 --- a/telephony/java/android/telephony/euicc/EuiccManager.java +++ b/telephony/java/android/telephony/euicc/EuiccManager.java @@ -805,6 +805,13 @@ public class EuiccManager { */ public static final int ERROR_OPERATION_BUSY = 10016; + /** + * Failure due to target port is not supported. + * @see #switchToSubscription(int, int, PendingIntent) + */ + public static final int ERROR_INVALID_PORT = 10017; + + private final Context mContext; private int mCardId; @@ -1120,6 +1127,15 @@ public class EuiccManager { * intent to prompt the user to accept the download. The caller should also be authorized to * manage the subscription to be enabled. * + * <p> From Android T, devices might support MEP(Multiple Enabled Profile), the subscription + * can be installed on different port from the eUICC. Calling apps with carrier privilege + * (see {@link TelephonyManager#hasCarrierPrivileges}) over the currently active subscriptions + * can use {@link #switchToSubscription(int, int, PendingIntent)} to specify which port to + * enable the subscription. Otherwise, use this API to enable the subscription on the eUICC + * and the platform will internally resolve a port. If there is no available port, + * an {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR} might be returned in the callback + * intent to prompt the user to disable an already-active subscription. + * * @param subscriptionId the ID of the subscription to enable. May be * {@link android.telephony.SubscriptionManager#INVALID_SUBSCRIPTION_ID} to deactivate the * current profile without activating another profile to replace it. If it's a disable @@ -1127,12 +1143,7 @@ public class EuiccManager { * permission, or the calling app must be authorized to manage the active subscription on * the target eUICC. * @param callbackIntent a PendingIntent to launch when the operation completes. - * - * @deprecated From T, callers should use - * {@link #switchToSubscription(int, int, PendingIntent)} instead to specify a port - * index on the card to switch to. */ - @Deprecated @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void switchToSubscription(int subscriptionId, PendingIntent callbackIntent) { if (!isEnabled()) { @@ -1150,20 +1161,19 @@ public class EuiccManager { /** * Switch to (enable) the given subscription. * - * <p>Requires the {@code android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission, - * or the calling app must be authorized to manage both the currently-active subscription and - * the subscription to be enabled according to the subscription metadata. Without the former, - * an {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR} will be returned in the callback - * intent to prompt the user to accept the download. + * <p> Requires the {@code android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission, + * or the caller must be having both the carrier privileges + * (see {@link TelephonyManager#hasCarrierPrivileges}) over any currently active subscriptions + * and the subscription to be enabled according to the subscription metadata. + * Without the former permissions, an SecurityException is thrown. * - * <p>On a multi-active SIM device, requires the - * {@code android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission, or a calling app - * only if the targeted eUICC does not currently have an active subscription or the calling app - * is authorized to manage the active subscription on the target eUICC, and the calling app is - * authorized to manage any active subscription on any SIM. Without it, an - * {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR} will be returned in the callback - * intent to prompt the user to accept the download. The caller should also be authorized to - * manage the subscription to be enabled. + * <p> If the caller is passing invalid port index, + * an {@link #EMBEDDED_SUBSCRIPTION_RESULT_ERROR} with detailed error code + * {@link #ERROR_INVALID_PORT} will be returned. + * + * <p> Depending on the target port and permission check, + * an {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR} might be returned to the callback + * intent to prompt the user to authorize before the switch. * * @param subscriptionId the ID of the subscription to enable. May be * {@link android.telephony.SubscriptionManager#INVALID_SUBSCRIPTION_ID} to deactivate the 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/ImsService.java b/telephony/java/android/telephony/ims/ImsService.java index 53dff545b0ce..be233b82c426 100644 --- a/telephony/java/android/telephony/ims/ImsService.java +++ b/telephony/java/android/telephony/ims/ImsService.java @@ -42,6 +42,7 @@ import android.telephony.ims.stub.ImsRegistrationImplBase; import android.telephony.ims.stub.SipTransportImplBase; import android.util.Log; import android.util.SparseArray; +import android.util.SparseBooleanArray; import com.android.ims.internal.IImsFeatureStatusCallback; import com.android.internal.annotations.VisibleForTesting; @@ -180,6 +181,12 @@ public class ImsService extends Service { // call ImsFeature#onFeatureRemoved. private final SparseArray<SparseArray<ImsFeature>> mFeaturesBySlot = new SparseArray<>(); + // A map of slot id -> boolean array, where each entry in the boolean array corresponds to an + // ImsFeature that was created for a slot id and not a sub id for backwards compatibility + // purposes. + private final SparseArray<SparseBooleanArray> mCreateImsFeatureWithSlotIdFlagMap = + new SparseArray<>(); + private IImsServiceControllerListener mListener; private Executor mExecutor; @@ -222,15 +229,36 @@ public class ImsService extends Service { } @Override - public IImsMmTelFeature createMmTelFeature(int slotId) { - return executeMethodAsyncForResult(() -> createMmTelFeatureInternal(slotId), - "createMmTelFeature"); + public IImsMmTelFeature createMmTelFeature(int slotId, int subId) { + MmTelFeature f = (MmTelFeature) getImsFeature(slotId, ImsFeature.FEATURE_MMTEL); + if (f == null) { + return executeMethodAsyncForResult(() -> createMmTelFeatureInternal(slotId, subId), + "createMmTelFeature"); + } else { + return f.getBinder(); + } + } + + @Override + public IImsMmTelFeature createEmergencyOnlyMmTelFeature(int slotId) { + MmTelFeature f = (MmTelFeature) getImsFeature(slotId, ImsFeature.FEATURE_MMTEL); + if (f == null) { + return executeMethodAsyncForResult(() -> createEmergencyOnlyMmTelFeatureInternal( + slotId), "createEmergencyOnlyMmTelFeature"); + } else { + return f.getBinder(); + } } @Override - public IImsRcsFeature createRcsFeature(int slotId) { - return executeMethodAsyncForResult(() -> createRcsFeatureInternal(slotId), - "createRcsFeature"); + public IImsRcsFeature createRcsFeature(int slotId, int subId) { + RcsFeature f = (RcsFeature) getImsFeature(slotId, ImsFeature.FEATURE_RCS); + if (f == null) { + return executeMethodAsyncForResult(() -> + createRcsFeatureInternal(slotId, subId), "createRcsFeature"); + } else { + return f.getBinder(); + } } @Override @@ -248,9 +276,14 @@ public class ImsService extends Service { } @Override - public void removeImsFeature(int slotId, int featureType) { + public void removeImsFeature(int slotId, int featureType, boolean changeSubId) { + if (changeSubId && isImsFeatureCreatedForSlot(slotId, featureType)) { + Log.w(LOG_TAG, "Do not remove Ims feature for compatibility"); + return; + } executeMethodAsync(() -> ImsService.this.removeImsFeature(slotId, featureType), "removeImsFeature"); + setImsFeatureCreatedForSlot(slotId, featureType, false); } @Override @@ -279,9 +312,10 @@ public class ImsService extends Service { } @Override - public IImsConfig getConfig(int slotId) { + public IImsConfig getConfig(int slotId, int subId) { return executeMethodAsyncForResult(() -> { - ImsConfigImplBase c = ImsService.this.getConfig(slotId); + ImsConfigImplBase c = + ImsService.this.getConfigForSubscription(slotId, subId); if (c != null) { c.setDefaultExecutor(mExecutor); return c.getIImsConfig(); @@ -292,9 +326,10 @@ public class ImsService extends Service { } @Override - public IImsRegistration getRegistration(int slotId) { + public IImsRegistration getRegistration(int slotId, int subId) { return executeMethodAsyncForResult(() -> { - ImsRegistrationImplBase r = ImsService.this.getRegistration(slotId); + ImsRegistrationImplBase r = + ImsService.this.getRegistrationForSubscription(slotId, subId); if (r != null) { r.setDefaultExecutor(mExecutor); return r.getBinder(); @@ -318,13 +353,15 @@ public class ImsService extends Service { } @Override - public void enableIms(int slotId) { - executeMethodAsync(() -> ImsService.this.enableIms(slotId), "enableIms"); + public void enableIms(int slotId, int subId) { + executeMethodAsync(() -> + ImsService.this.enableImsForSubscription(slotId, subId), "enableIms"); } @Override - public void disableIms(int slotId) { - executeMethodAsync(() -> ImsService.this.disableIms(slotId), "disableIms"); + public void disableIms(int slotId, int subId) { + executeMethodAsync(() -> + ImsService.this.disableImsForSubscription(slotId, subId), "disableIms"); } // Call the methods with a clean calling identity on the executor and wait indefinitely for @@ -364,28 +401,32 @@ public class ImsService extends Service { return null; } - /** - * @hide - */ - @VisibleForTesting - public SparseArray<ImsFeature> getFeatures(int slotId) { - return mFeaturesBySlot.get(slotId); + private IImsMmTelFeature createMmTelFeatureInternal(int slotId, int subscriptionId) { + MmTelFeature f = createMmTelFeatureForSubscription(slotId, subscriptionId); + if (f != null) { + setupFeature(f, slotId, ImsFeature.FEATURE_MMTEL); + f.setDefaultExecutor(mExecutor); + return f.getBinder(); + } else { + Log.e(LOG_TAG, "createMmTelFeatureInternal: null feature returned."); + return null; + } } - private IImsMmTelFeature createMmTelFeatureInternal(int slotId) { - MmTelFeature f = createMmTelFeature(slotId); + private IImsMmTelFeature createEmergencyOnlyMmTelFeatureInternal(int slotId) { + MmTelFeature f = createEmergencyOnlyMmTelFeature(slotId); if (f != null) { setupFeature(f, slotId, ImsFeature.FEATURE_MMTEL); f.setDefaultExecutor(mExecutor); return f.getBinder(); } else { - Log.e(LOG_TAG, "createMmTelFeatureInternal: null feature returned."); + Log.e(LOG_TAG, "createEmergencyOnlyMmTelFeatureInternal: null feature returned."); return null; } } - private IImsRcsFeature createRcsFeatureInternal(int slotId) { - RcsFeature f = createRcsFeature(slotId); + private IImsRcsFeature createRcsFeatureInternal(int slotId, int subI) { + RcsFeature f = createRcsFeatureForSubscription(slotId, subI); if (f != null) { f.setDefaultExecutor(mExecutor); setupFeature(f, slotId, ImsFeature.FEATURE_RCS); @@ -466,6 +507,49 @@ public class ImsService extends Service { f.onFeatureRemoved(); features.remove(featureType); } + + } + + /** + * @hide + */ + @VisibleForTesting + public ImsFeature getImsFeature(int slotId, int featureType) { + synchronized (mFeaturesBySlot) { + // Get SparseArray for Features, by querying slot Id + SparseArray<ImsFeature> features = mFeaturesBySlot.get(slotId); + if (features == null) { + return null; + } + return features.get(featureType); + } + } + + private void setImsFeatureCreatedForSlot(int slotId, + @ImsFeature.FeatureType int featureType, boolean createdForSlot) { + synchronized (mCreateImsFeatureWithSlotIdFlagMap) { + getImsFeatureCreatedForSlot(slotId).put(featureType, createdForSlot); + } + } + + /** + * @hide + */ + @VisibleForTesting + public boolean isImsFeatureCreatedForSlot(int slotId, + @ImsFeature.FeatureType int featureType) { + synchronized (mCreateImsFeatureWithSlotIdFlagMap) { + return getImsFeatureCreatedForSlot(slotId).get(featureType); + } + } + + private SparseBooleanArray getImsFeatureCreatedForSlot(int slotId) { + SparseBooleanArray createFlag = mCreateImsFeatureWithSlotIdFlagMap.get(slotId); + if (createFlag == null) { + createFlag = new SparseBooleanArray(); + mCreateImsFeatureWithSlotIdFlagMap.put(slotId, createFlag); + } + return createFlag; } /** @@ -524,27 +608,95 @@ public class ImsService extends Service { } /** + * The framework has enabled IMS for the subscription specified, the ImsService should register + * for IMS and perform all appropriate initialization to bring up all ImsFeatures. + * + * @param slotId The slot ID that IMS will be enabled for. + * @param subscriptionId The subscription ID that IMS will be enabled for. + */ + public void enableImsForSubscription(int slotId, int subscriptionId) { + enableIms(slotId); + } + + /** + * The framework has disabled IMS for the subscription specified. The ImsService must deregister + * for IMS and set capability status to false for all ImsFeatures. + * @param slotId The slot ID that IMS will be disabled for. + * @param subscriptionId The subscription ID that IMS will be disabled for. + */ + public void disableImsForSubscription(int slotId, int subscriptionId) { + disableIms(slotId); + } + + /** * The framework has enabled IMS for the slot specified, the ImsService should register for IMS * and perform all appropriate initialization to bring up all ImsFeatures. + * @deprecated Use {@link #enableImsForSubscription} instead. */ + @Deprecated public void enableIms(int slotId) { } /** * The framework has disabled IMS for the slot specified. The ImsService must deregister for IMS * and set capability status to false for all ImsFeatures. + * @deprecated Use {@link #disableImsForSubscription} instead. */ + @Deprecated public void disableIms(int slotId) { } /** * When called, the framework is requesting that a new {@link MmTelFeature} is created for the + * specified subscription. + * + * @param subscriptionId The subscription ID that the MMTEL Feature is being created for. + * @return The newly created {@link MmTelFeature} associated with the subscription or null if + * the feature is not supported. + */ + public @Nullable MmTelFeature createMmTelFeatureForSubscription(int slotId, + int subscriptionId) { + setImsFeatureCreatedForSlot(slotId, ImsFeature.FEATURE_MMTEL, true); + return createMmTelFeature(slotId); + } + + /** + * When called, the framework is requesting that a new {@link RcsFeature} is created for the + * specified subscription. + * + * @param subscriptionId The subscription ID that the RCS Feature is being created for. + * @return The newly created {@link RcsFeature} associated with the subscription or null if the + * feature is not supported. + */ + public @Nullable RcsFeature createRcsFeatureForSubscription(int slotId, int subscriptionId) { + setImsFeatureCreatedForSlot(slotId, ImsFeature.FEATURE_RCS, true); + return createRcsFeature(slotId); + } + + /** + * When called, the framework is requesting that a new emergency-only {@link MmTelFeature} is + * created for the specified slot. For emergency calls, there is no known Subscription Id. + * + * @param slotId The slot ID that the MMTEL Feature is being created for. + * @return An MmTelFeature instance to be used for the slot ID when there is not + * subscription inserted. Only requested when there is no subscription active on + * the specified slot. + */ + public @Nullable MmTelFeature createEmergencyOnlyMmTelFeature(int slotId) { + setImsFeatureCreatedForSlot(slotId, ImsFeature.FEATURE_MMTEL, true); + return createMmTelFeature(slotId); + } + + /** + * When called, the framework is requesting that a new {@link MmTelFeature} is created for the * specified slot. + * @deprecated Use {@link #createMmTelFeatureForSubscription} instead * * @param slotId The slot ID that the MMTEL Feature is being created for. * @return The newly created {@link MmTelFeature} associated with the slot or null if the * feature is not supported. */ + @Deprecated public MmTelFeature createMmTelFeature(int slotId) { return null; } @@ -552,32 +704,62 @@ public class ImsService extends Service { /** * When called, the framework is requesting that a new {@link RcsFeature} is created for the * specified slot. + * @deprecated Use {@link #createRcsFeatureForSubscription} instead * * @param slotId The slot ID that the RCS Feature is being created for. * @return The newly created {@link RcsFeature} associated with the slot or null if the feature * is not supported. */ + @Deprecated public RcsFeature createRcsFeature(int slotId) { return null; } /** + * Return the {@link ImsConfigImplBase} implementation associated with the provided + * subscription. This will be used by the platform to get/set specific IMS related + * configurations. + * + * @param subscriptionId The subscription ID that the IMS configuration is associated with. + * @return ImsConfig implementation that is associated with the specified subscription. + */ + public @NonNull ImsConfigImplBase getConfigForSubscription(int slotId, int subscriptionId) { + return getConfig(slotId); + } + + /** + * Return the {@link ImsRegistrationImplBase} implementation associated with the provided + * subscription. + * + * @param subscriptionId The subscription ID that is associated with the IMS Registration. + * @return the ImsRegistration implementation associated with the subscription. + */ + public @NonNull ImsRegistrationImplBase getRegistrationForSubscription(int slotId, + int subscriptionId) { + return getRegistration(slotId); + } + + /** * Return the {@link ImsConfigImplBase} implementation associated with the provided slot. This * will be used by the platform to get/set specific IMS related configurations. + * @deprecated use {@link #getConfigForSubscription} instead. * * @param slotId The slot that the IMS configuration is associated with. * @return ImsConfig implementation that is associated with the specified slot. */ + @Deprecated public ImsConfigImplBase getConfig(int slotId) { return new ImsConfigImplBase(); } /** * Return the {@link ImsRegistrationImplBase} implementation associated with the provided slot. + * @deprecated use {@link #getRegistrationForSubscription} instead. * * @param slotId The slot that is associated with the IMS Registration. * @return the ImsRegistration implementation associated with the slot. */ + @Deprecated public ImsRegistrationImplBase getRegistration(int slotId) { return new ImsRegistrationImplBase(); } 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/aidl/IImsServiceController.aidl b/telephony/java/android/telephony/ims/aidl/IImsServiceController.aidl index c6966b3cf53e..ae6166fea02a 100644 --- a/telephony/java/android/telephony/ims/aidl/IImsServiceController.aidl +++ b/telephony/java/android/telephony/ims/aidl/IImsServiceController.aidl @@ -32,18 +32,19 @@ import com.android.ims.internal.IImsFeatureStatusCallback; */ interface IImsServiceController { void setListener(IImsServiceControllerListener l); - IImsMmTelFeature createMmTelFeature(int slotId); - IImsRcsFeature createRcsFeature(int slotId); + IImsMmTelFeature createMmTelFeature(int slotId, int subId); + IImsMmTelFeature createEmergencyOnlyMmTelFeature(int slotId); + IImsRcsFeature createRcsFeature(int slotId, int subId); ImsFeatureConfiguration querySupportedImsFeatures(); long getImsServiceCapabilities(); void addFeatureStatusCallback(int slotId, int featureType, in IImsFeatureStatusCallback c); void removeFeatureStatusCallback(int slotId, int featureType, in IImsFeatureStatusCallback c); // Synchronous call to ensure the ImsService is ready before continuing with feature creation. void notifyImsServiceReadyForFeatureCreation(); - void removeImsFeature(int slotId, int featureType); - IImsConfig getConfig(int slotId); - IImsRegistration getRegistration(int slotId); + void removeImsFeature(int slotId, int featureType, boolean changeSubId); + IImsConfig getConfig(int slotId, int subId); + IImsRegistration getRegistration(int slotId, int subId); ISipTransport getSipTransport(int slotId); - oneway void enableIms(int slotId); - oneway void disableIms(int slotId); + oneway void enableIms(int slotId, int subId); + oneway void disableIms(int slotId, int subId); } 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..af7373b376d5 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() { } @@ -448,7 +488,9 @@ public class RcsFeature extends ImsFeature { * has already been created in the framework. * @param listener A {@link CapabilityExchangeEventListener} to send the capability exchange * event to the framework. + * @hide */ + @SystemApi private void initRcsCapabilityExchangeImplBase( @NonNull CapabilityExchangeEventListener listener) { synchronized (mLock) { 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/ims/internal/IImsServiceFeatureCallback.aidl b/telephony/java/com/android/ims/internal/IImsServiceFeatureCallback.aidl index f5f67bd36ec3..416096b1f6ec 100644 --- a/telephony/java/com/android/ims/internal/IImsServiceFeatureCallback.aidl +++ b/telephony/java/com/android/ims/internal/IImsServiceFeatureCallback.aidl @@ -23,11 +23,11 @@ import com.android.ims.ImsFeatureContainer; * {@hide} */ oneway interface IImsServiceFeatureCallback { - void imsFeatureCreated(in ImsFeatureContainer feature); + void imsFeatureCreated(in ImsFeatureContainer feature, int subId); // Reason defined in FeatureConnector.UnavailableReason void imsFeatureRemoved(int reason); // Status defined in ImsFeature.ImsState. - void imsStatusChanged(int status); + void imsStatusChanged(int status, int subId); //Capabilities defined in ImsService.ImsServiceCapability void updateCapabilities(long capabilities); }
\ No newline at end of file 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/test-base/Android.bp b/test-base/Android.bp index 97ebba689d8b..8be732452228 100644 --- a/test-base/Android.bp +++ b/test-base/Android.bp @@ -26,7 +26,19 @@ package { // to get the below license kinds: // SPDX-license-identifier-Apache-2.0 // SPDX-license-identifier-CPL-1.0 - default_applicable_licenses: ["frameworks_base_license"], + default_applicable_licenses: ["frameworks_base_test-base_license"], +} + +license { + name: "frameworks_base_test-base_license", + visibility: [":__subpackages__"], + license_kinds: [ + "SPDX-license-identifier-Apache-2.0", + "SPDX-license-identifier-CPL-1.0", + ], + license_text: [ + "src/junit/cpl-v10.html", + ], } java_sdk_library { diff --git a/test-runner/Android.bp b/test-runner/Android.bp index 0f56bb3819f1..2a19af9f8cd2 100644 --- a/test-runner/Android.bp +++ b/test-runner/Android.bp @@ -18,12 +18,19 @@ // ===================================== package { // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "frameworks_base_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - // SPDX-license-identifier-CPL-1.0 - default_applicable_licenses: ["frameworks_base_license"], + default_applicable_licenses: ["frameworks_base_test-runner_license"], +} + +license { + name: "frameworks_base_test-runner_license", + visibility: [":__subpackages__"], + license_kinds: [ + "SPDX-license-identifier-Apache-2.0", + "SPDX-license-identifier-CPL-1.0", + ], + license_text: [ + "src/junit/cpl-v10.html", + ], } java_sdk_library { 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/DozeTest/src/com/android/dreams/dozetest/DozeTestDream.java b/tests/DozeTest/src/com/android/dreams/dozetest/DozeTestDream.java index 6985702c24e6..9a88abdd7b37 100644 --- a/tests/DozeTest/src/com/android/dreams/dozetest/DozeTestDream.java +++ b/tests/DozeTest/src/com/android/dreams/dozetest/DozeTestDream.java @@ -81,7 +81,8 @@ public class DozeTestDream extends DreamService { intent.setPackage(getPackageName()); IntentFilter filter = new IntentFilter(); filter.addAction(intent.getAction()); - registerReceiver(mAlarmReceiver, filter); + registerReceiver(mAlarmReceiver, filter, + Context.RECEIVER_EXPORTED_UNAUDITED); mAlarmIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED); 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..6d67ee736604 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,29 @@ 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) + + // Tapping outside of the dialog to dismiss + if (dialog != null) { + val dialogBounds = dialog.visibleBounds + uiDevice.click(dialogBounds.left, dialogBounds.top - 300) + wmHelper.waitForAppTransitionIdle() + } + } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt index 56879c90006e..c87d8e1b98e1 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt @@ -31,7 +31,6 @@ import com.android.server.wm.flicker.annotation.Group2 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.entireScreenCovered import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.navBarLayerIsVisible import com.android.server.wm.flicker.navBarLayerRotatesAndScales import com.android.server.wm.flicker.navBarWindowIsVisible @@ -39,7 +38,6 @@ 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.FlickerComponentName -import org.junit.Assume.assumeFalse import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -157,11 +155,7 @@ class CloseImeAutoOpenWindowToAppTest(private val testSpec: FlickerTestParameter @FlakyTest(bugId = 206753786) @Test - fun statusBarLayerRotatesScales() { - // This test doesn't work in shell transitions because of b/206753786 - assumeFalse(isShellTransitionsEnabled) - testSpec.statusBarLayerRotatesScales() - } + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @Presubmit @Test 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 c28466c485e9..f2696d8a71ff 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 @@ -34,12 +34,10 @@ import com.android.server.wm.flicker.navBarLayerIsVisible import com.android.server.wm.flicker.navBarLayerRotatesAndScales import com.android.server.wm.flicker.navBarWindowIsVisible import com.android.server.wm.flicker.entireScreenCovered -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.FlickerComponentName -import org.junit.Assume.assumeFalse import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -117,11 +115,7 @@ class CloseImeAutoOpenWindowToHomeTest(private val testSpec: FlickerTestParamete @Presubmit @Test - fun entireScreenCovered() { - // This test doesn't work in shell transitions because of b/206086894 - assumeFalse(isShellTransitionsEnabled) - testSpec.entireScreenCovered() - } + fun entireScreenCovered() = testSpec.entireScreenCovered() @Presubmit @Test @@ -159,11 +153,7 @@ class CloseImeAutoOpenWindowToHomeTest(private val testSpec: FlickerTestParamete @FlakyTest(bugId = 206753786) @Test - fun statusBarLayerRotatesScales() { - // This test doesn't work in shell transitions because of b/206753786 - assumeFalse(isShellTransitionsEnabled) - testSpec.statusBarLayerRotatesScales() - } + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @Presubmit @Test diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt index c7f1b99329fb..24b1598f899c 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt @@ -32,7 +32,6 @@ import com.android.server.wm.flicker.navBarLayerIsVisible import com.android.server.wm.flicker.navBarLayerRotatesAndScales import com.android.server.wm.flicker.navBarWindowIsVisible import com.android.server.wm.flicker.entireScreenCovered -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.server.wm.flicker.statusBarWindowIsVisible import com.android.server.wm.traces.common.FlickerComponentName @@ -134,11 +133,7 @@ class CloseImeWindowToAppTest(private val testSpec: FlickerTestParameter) { @FlakyTest(bugId = 206753786) @Test - fun statusBarLayerRotatesScales() { - // This test doesn't work in shell transitions because of b/206753786 - assumeFalse(isShellTransitionsEnabled) - testSpec.statusBarLayerRotatesScales() - } + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @Presubmit @Test diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt index 46ed0ad88d31..e5d82a11c389 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt @@ -34,11 +34,9 @@ import com.android.server.wm.flicker.navBarLayerIsVisible import com.android.server.wm.flicker.navBarLayerRotatesAndScales import com.android.server.wm.flicker.navBarWindowIsVisible import com.android.server.wm.flicker.entireScreenCovered -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.server.wm.flicker.statusBarWindowIsVisible import com.android.server.wm.traces.common.FlickerComponentName -import org.junit.Assume.assumeFalse import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -126,11 +124,7 @@ class CloseImeWindowToHomeTest(private val testSpec: FlickerTestParameter) { @Presubmit @Test - fun entireScreenCovered() { - // This test doesn't work in shell transitions because of b/206086894 - assumeFalse(isShellTransitionsEnabled) - testSpec.entireScreenCovered() - } + fun entireScreenCovered() = testSpec.entireScreenCovered() @Presubmit @Test @@ -152,11 +146,7 @@ class CloseImeWindowToHomeTest(private val testSpec: FlickerTestParameter) { @FlakyTest(bugId = 206753786) @Test - fun statusBarLayerRotatesScales() { - // This test doesn't work in shell transitions because of b/206753786 - assumeFalse(isShellTransitionsEnabled) - testSpec.statusBarLayerRotatesScales() - } + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @Presubmit @Test 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/OpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt index ebe4be2b437f..87f8ef28cfda 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt @@ -34,11 +34,9 @@ import com.android.server.wm.flicker.navBarLayerRotatesAndScales import com.android.server.wm.flicker.navBarWindowIsVisible import com.android.server.wm.flicker.entireScreenCovered import com.android.server.wm.flicker.dsl.FlickerBuilder -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 org.junit.Assume.assumeFalse import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -130,11 +128,7 @@ class OpenImeWindowTest(private val testSpec: FlickerTestParameter) { @FlakyTest(bugId = 206753786) @Test - fun statusBarLayerRotatesScales() { - // This test doesn't work in shell transitions because of b/206753786 - assumeFalse(isShellTransitionsEnabled) - testSpec.statusBarLayerRotatesScales() - } + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @Presubmit @Test 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 f6e5adc2309b..0ad0a033a4fe 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 @@ -101,8 +101,6 @@ class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { @Presubmit @Test fun visibleWindowsShownMoreThanOneConsecutiveEntry() { - // This test doesn't work in shell transitions because of b/204570898 - assumeFalse(isShellTransitionsEnabled) val component = FlickerComponentName("", "RecentTaskScreenshotSurface") testSpec.assertWm { this.visibleWindowsShownMoreThanOneConsecutiveEntry( @@ -116,8 +114,6 @@ class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { @Presubmit @Test fun launcherWindowBecomesInvisible() { - // This test doesn't work in shell transitions because of b/204574221 - assumeFalse(isShellTransitionsEnabled) testSpec.assertWm { this.isAppWindowVisible(LAUNCHER_COMPONENT) .then() @@ -127,11 +123,7 @@ class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { @Presubmit @Test - fun imeWindowIsAlwaysVisible() { - // This test doesn't work in shell transitions because of b/204570898 - assumeFalse(isShellTransitionsEnabled) - testSpec.imeWindowIsAlwaysVisible(!isShellTransitionsEnabled) - } + fun imeWindowIsAlwaysVisible() = testSpec.imeWindowIsAlwaysVisible(!isShellTransitionsEnabled) @Presubmit @Test @@ -202,8 +194,6 @@ class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { @Presubmit @Test fun appLayerReplacesLauncher() { - // This test doesn't work in shell transitions because of b/204574221 - assumeFalse(isShellTransitionsEnabled) testSpec.assertLayers { this.isVisible(LAUNCHER_COMPONENT) .then() @@ -219,11 +209,7 @@ class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { @FlakyTest(bugId = 206753786) @Test - fun statusBarLayerRotatesScales() { - // This test doesn't work in shell transitions because of b/206753786 - assumeFalse(isShellTransitionsEnabled) - testSpec.statusBarLayerRotatesScales() - } + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() @Presubmit @Test 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/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt index 0879b98031bb..3f0de7f3cd7d 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt @@ -25,13 +25,11 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group3 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.SimpleAppHelper -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.rules.WMFlickerServiceRuleForTestSpec 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.FlickerComponentName -import org.junit.Assume.assumeFalse import org.junit.FixMethodOrder import org.junit.Rule import org.junit.Test @@ -167,11 +165,7 @@ class ChangeAppRotationTest( */ @FlakyTest(bugId = 206753786) @Test - fun statusBarLayerRotatesScales() { - // This test doesn't work in shell transitions because of b/206753786 - assumeFalse(isShellTransitionsEnabled) - testSpec.statusBarLayerRotatesScales() - } + fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() /** {@inheritDoc} */ @FlakyTest 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/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt index e44bee644ceb..3ae484b5e5d2 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt @@ -26,10 +26,8 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group3 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.SeamlessRotationAppHelper -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.testapp.ActivityOptions import com.android.server.wm.traces.common.FlickerComponentName -import org.junit.Assume.assumeFalse import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -102,8 +100,6 @@ class SeamlessAppRotationTest( @Presubmit @Test fun appWindowFullScreen() { - // This test doesn't work in shell transitions because of b/206101151 - assumeFalse(isShellTransitionsEnabled) testSpec.assertWm { this.invoke("isFullScreen") { val appWindow = it.windowState(testApp.`package`) @@ -139,8 +135,6 @@ class SeamlessAppRotationTest( @Presubmit @Test fun appLayerAlwaysVisible() { - // This test doesn't work in shell transitions because of b/206101151 - assumeFalse(isShellTransitionsEnabled) testSpec.assertLayers { isVisible(testApp.component) } @@ -152,8 +146,6 @@ class SeamlessAppRotationTest( @Presubmit @Test fun appLayerRotates() { - // This test doesn't work in shell transitions because of b/206101151 - assumeFalse(isShellTransitionsEnabled) testSpec.assertLayers { this.invoke("entireScreenCovered") { entry -> entry.entry.displays.map { display -> @@ -193,8 +185,6 @@ class SeamlessAppRotationTest( @Presubmit @Test fun focusDoesNotChange() { - // This test doesn't work in shell transitions because of b/206101151 - assumeFalse(isShellTransitionsEnabled) testSpec.assertEventLog { this.focusDoesNotChange() } 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..9a4913668192 100644 --- a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml @@ -27,4 +27,14 @@ <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> + </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/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/tools/sdkparcelables/Android.bp b/tools/sdkparcelables/Android.bp index 9d773e44faea..ec2bffdfaf57 100644 --- a/tools/sdkparcelables/Android.bp +++ b/tools/sdkparcelables/Android.bp @@ -14,7 +14,7 @@ java_binary_host { "src/**/*.kt", ], static_libs: [ - "asm-6.0", + "asm-7.0", ], } diff --git a/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt b/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt index 22e8d781335b..0fb062f280e3 100644 --- a/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt +++ b/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt @@ -39,7 +39,7 @@ fun main(args: Array<String>) { kotlin.system.exitProcess(2) } - val ancestorCollector = AncestorCollector(Opcodes.ASM6, null) + val ancestorCollector = AncestorCollector(Opcodes.ASM7, null) for (entry in zipFile.entries()) { if (entry.name.endsWith(".class")) { |