diff options
1043 files changed, 31242 insertions, 11566 deletions
diff --git a/Android.bp b/Android.bp index a10f8de13c8d..8c8563139083 100644 --- a/Android.bp +++ b/Android.bp @@ -262,12 +262,13 @@ filegroup { name: "framework-updatable-sources", srcs: [ ":framework-appsearch-sources", - ":framework-sdkext-sources", + ":framework-sdkextensions-sources", ":framework-statsd-sources", ":framework-tethering-srcs", ":updatable-media-srcs", ":framework-mediaprovider-sources", ":framework-wifi-updatable-sources", + ":ike-srcs", ] } @@ -415,6 +416,13 @@ filegroup { } filegroup { + name: "graphicsstats_proto", + srcs: [ + "libs/hwui/protos/graphicsstats.proto", + ], +} + +filegroup { name: "libvibrator_aidl", srcs: [ "core/java/android/os/IExternalVibrationController.aidl", @@ -432,6 +440,7 @@ java_library { // TODO(b/146167933): Use framework-statsd-stubs "framework-statsd", "framework-wifi-stubs", + "ike-stubs", ], installable: true, javac_shard_size: 150, @@ -440,8 +449,6 @@ java_library { "libcore-platform-compat-config", "services-platform-compat-config", "media-provider-platform-compat-config", - "services-devicepolicy-platform-compat-config", - "services-core-platform-compat-config", ], static_libs: [ // If MimeMap ever becomes its own APEX, then this dependency would need to be removed @@ -481,12 +488,14 @@ java_library { "updatable_media_stubs", "framework_mediaprovider_stubs", "framework-appsearch", // TODO(b/146218515): should be framework-appsearch-stubs - "framework-sdkext-stubs-systemapi", + "framework-sdkextensions-stubs-systemapi", // TODO(b/146167933): Use framework-statsd-stubs instead. "framework-statsd", // TODO(b/140299412): should be framework-wifi-stubs "framework-wifi", - // TODO(jiyong): add more stubs for APEXes here + "ike-stubs", + // TODO(b/147200698): should be the stub of framework-tethering + "framework-tethering", ], sdk_version: "core_platform", apex_available: ["//apex_available:platform"], @@ -616,6 +625,15 @@ java_library { } filegroup { + name: "framework-ike-shared-srcs", + visibility: ["//frameworks/opt/net/ike"], + srcs: [ + "core/java/android/net/annotations/PolicyDirection.java", + "telephony/java/android/telephony/Annotation.java", + ], +} + +filegroup { name: "framework-networkstack-shared-srcs", srcs: [ // TODO: remove these annotations as soon as we can use andoid.support.annotations.* @@ -642,13 +660,14 @@ filegroup { name: "framework-tethering-shared-srcs", srcs: [ "core/java/android/util/LocalLog.java", - "core/java/com/android/internal/util/BitUtils.java", "core/java/com/android/internal/util/IndentingPrintWriter.java", "core/java/com/android/internal/util/IState.java", "core/java/com/android/internal/util/MessageUtils.java", "core/java/com/android/internal/util/Preconditions.java", "core/java/com/android/internal/util/State.java", "core/java/com/android/internal/util/StateMachine.java", + "core/java/com/android/internal/util/TrafficStatsConstants.java", + "core/java/android/net/shared/Inet4AddressUtils.java", ], } @@ -1134,6 +1153,7 @@ filegroup { srcs: [ ":framework-annotations", "core/java/android/net/InterfaceConfiguration.java", + "core/java/android/os/BasicShellCommandHandler.java", "core/java/android/os/HandlerExecutor.java", "core/java/android/util/BackupUtils.java", "core/java/android/util/LocalLog.java", diff --git a/apex/appsearch/framework/Android.bp b/apex/appsearch/framework/Android.bp index 3dc5a2c10b6b..1f30dda21ef7 100644 --- a/apex/appsearch/framework/Android.bp +++ b/apex/appsearch/framework/Android.bp @@ -26,9 +26,16 @@ java_library { installable: true, sdk_version: "core_platform", // TODO(b/146218515) should be core_current srcs: [":framework-appsearch-sources"], + hostdex: true, // for hiddenapi check libs: [ "framework-minus-apex", // TODO(b/146218515) should be framework-system-stubs ], + visibility: [ + "//frameworks/base/apex/appsearch:__subpackages__", + // TODO(b/146218515) remove this when framework is built with the stub of appsearch + "//frameworks/base", + ], + apex_available: ["com.android.appsearch"], } metalava_appsearch_docs_args = diff --git a/apex/blobstore/OWNERS b/apex/blobstore/OWNERS new file mode 100644 index 000000000000..8e04399196e2 --- /dev/null +++ b/apex/blobstore/OWNERS @@ -0,0 +1,4 @@ +set noparent + +sudheersai@google.com +yamasani@google.com diff --git a/apex/blobstore/TEST_MAPPING b/apex/blobstore/TEST_MAPPING new file mode 100644 index 000000000000..4dc0c49380c8 --- /dev/null +++ b/apex/blobstore/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit": [ + { + "name": "CtsBlobStoreTestCases" + } + ] +}
\ No newline at end of file diff --git a/apex/jobscheduler/framework/Android.bp b/apex/jobscheduler/framework/Android.bp index 98bbe8243183..ec074262fb13 100644 --- a/apex/jobscheduler/framework/Android.bp +++ b/apex/jobscheduler/framework/Android.bp @@ -25,5 +25,6 @@ java_library { }, libs: [ "framework-minus-apex", + "unsupportedappusage", ], } diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java index 8b3b3a28f2bc..0bb07caf0b00 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java @@ -29,7 +29,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.ClipData; import android.content.ComponentName; import android.net.NetworkRequest; diff --git a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java index 42cf17b1264e..ef1351e6d597 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java @@ -18,8 +18,7 @@ package android.app.job; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.UnsupportedAppUsage; -import android.app.job.IJobCallback; +import android.compat.annotation.UnsupportedAppUsage; import android.content.ClipData; import android.net.Network; import android.net.Uri; diff --git a/apex/jobscheduler/framework/java/android/app/job/JobWorkItem.java b/apex/jobscheduler/framework/java/android/app/job/JobWorkItem.java index c6631fa76494..0c45cbf6dc11 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobWorkItem.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobWorkItem.java @@ -19,7 +19,7 @@ package android.app.job; import static android.app.job.JobInfo.NETWORK_BYTES_UNKNOWN; import android.annotation.BytesLong; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.Intent; import android.os.Build; import android.os.Parcel; diff --git a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java index 041825c235d0..6109b713de24 100644 --- a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java +++ b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java @@ -1,5 +1,6 @@ package com.android.server.usage; +import android.annotation.NonNull; import android.annotation.UserIdInt; import android.app.usage.AppStandbyInfo; import android.app.usage.UsageEvents; @@ -99,8 +100,18 @@ public interface AppStandbyInternal { List<AppStandbyInfo> getAppStandbyBuckets(int userId); - void setAppStandbyBucket(String packageName, int userId, @StandbyBuckets int newBucket, - int reason, long elapsedRealtime, boolean resetTimeout); + /** + * Changes an app's standby bucket to the provided value. The caller can only set the standby + * bucket for a different app than itself. + */ + void setAppStandbyBucket(@NonNull String packageName, int bucket, int userId, int callingUid, + int callingPid); + + /** + * Changes the app standby bucket for multiple apps at once. + */ + void setAppStandbyBuckets(@NonNull List<AppStandbyInfo> appBuckets, int userId, int callingUid, + int callingPid); void addActiveDeviceAdmin(String adminPkg, int userId); 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 2f8b5130edb0..58eb58961ac4 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java @@ -47,6 +47,7 @@ import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET; import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY; +import android.annotation.NonNull; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.AppGlobals; @@ -101,7 +102,6 @@ import com.android.internal.util.ConcurrentUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.server.LocalServices; import com.android.server.usage.AppIdleHistory.AppUsageHistory; -import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener; import java.io.File; import java.io.PrintWriter; @@ -109,6 +109,7 @@ import java.time.Duration; import java.time.format.DateTimeParseException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Set; import java.util.concurrent.CountDownLatch; @@ -1014,14 +1015,57 @@ public class AppStandbyController implements AppStandbyInternal { } } + @Override + public void setAppStandbyBucket(@NonNull String packageName, int bucket, int userId, + int callingUid, int callingPid) { + setAppStandbyBuckets( + Collections.singletonList(new AppStandbyInfo(packageName, bucket)), + userId, callingUid, callingPid); + } + + @Override + public void setAppStandbyBuckets(@NonNull List<AppStandbyInfo> appBuckets, int userId, + int callingUid, int callingPid) { + userId = ActivityManager.handleIncomingUser( + callingPid, callingUid, userId, false, true, "setAppStandbyBucket", null); + final boolean shellCaller = callingUid == Process.ROOT_UID + || callingUid == Process.SHELL_UID; + final boolean systemCaller = UserHandle.isCore(callingUid); + final int reason = systemCaller ? REASON_MAIN_FORCED : REASON_MAIN_PREDICTED; + final int packageFlags = PackageManager.MATCH_ANY_USER + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE + | PackageManager.MATCH_DIRECT_BOOT_AWARE; + final int numApps = appBuckets.size(); + final long elapsedRealtime = mInjector.elapsedRealtime(); + for (int i = 0; i < numApps; ++i) { + final AppStandbyInfo bucketInfo = appBuckets.get(i); + final String packageName = bucketInfo.mPackageName; + final int bucket = bucketInfo.mStandbyBucket; + if (bucket < STANDBY_BUCKET_ACTIVE || bucket > STANDBY_BUCKET_NEVER) { + throw new IllegalArgumentException("Cannot set the standby bucket to " + bucket); + } + final int packageUid = mInjector.getPackageManagerInternal() + .getPackageUid(packageName, packageFlags, userId); + // Caller cannot set their own standby state + if (packageUid == callingUid) { + throw new IllegalArgumentException("Cannot set your own standby bucket"); + } + if (packageUid < 0) { + throw new IllegalArgumentException( + "Cannot set standby bucket for non existent package (" + packageName + ")"); + } + setAppStandbyBucket(packageName, userId, bucket, reason, elapsedRealtime, shellCaller); + } + } + @VisibleForTesting void setAppStandbyBucket(String packageName, int userId, @StandbyBuckets int newBucket, - int reason, long elapsedRealtime) { - setAppStandbyBucket(packageName, userId, newBucket, reason, elapsedRealtime, false); + int reason) { + setAppStandbyBucket( + packageName, userId, newBucket, reason, mInjector.elapsedRealtime(), false); } - @Override - public void setAppStandbyBucket(String packageName, int userId, @StandbyBuckets int newBucket, + private void setAppStandbyBucket(String packageName, int userId, @StandbyBuckets int newBucket, int reason, long elapsedRealtime, boolean resetTimeout) { synchronized (mAppIdleLock) { // If the package is not installed, don't allow the bucket to be set. @@ -1444,6 +1488,10 @@ public class AppStandbyController implements AppStandbyInternal { mBatteryStats.noteEvent(event, packageName, uid); } + PackageManagerInternal getPackageManagerInternal() { + return mPackageManagerInternal; + } + boolean isPackageEphemeral(int userId, String packageName) { return mPackageManagerInternal.isPackageEphemeral(userId, packageName); } diff --git a/apex/media/framework/Android.bp b/apex/media/framework/Android.bp index 6bd0086681a7..18382a488428 100644 --- a/apex/media/framework/Android.bp +++ b/apex/media/framework/Android.bp @@ -55,6 +55,13 @@ java_library { jarjar_rules: "jarjar_rules.txt", plugins: ["java_api_finder"], + + hostdex: true, // for hiddenapi check + visibility: ["//frameworks/av/apex:__subpackages__"], + apex_available: [ + "com.android.media", + "test_com.android.media", + ], } filegroup { diff --git a/apex/media/framework/java/android/media/MediaController2.java b/apex/media/framework/java/android/media/MediaController2.java index c3dd3fe4451c..d059c670ccb6 100644 --- a/apex/media/framework/java/android/media/MediaController2.java +++ b/apex/media/framework/java/android/media/MediaController2.java @@ -141,6 +141,9 @@ public class MediaController2 implements AutoCloseable { // Note: unbindService() throws IllegalArgumentException when it's called twice. return; } + if (DEBUG) { + Log.d(TAG, "closing " + this); + } mClosed = true; if (mServiceConnection != null) { // Note: This should be called even when the bindService() has returned false. diff --git a/apex/sdkext/TEST_MAPPING b/apex/sdkext/TEST_MAPPING deleted file mode 100644 index 91947f39980a..000000000000 --- a/apex/sdkext/TEST_MAPPING +++ /dev/null @@ -1,7 +0,0 @@ -{ - "presubmit": [ - { - "name": "CtsSdkExtTestCases" - } - ] -} diff --git a/apex/sdkext/Android.bp b/apex/sdkextensions/Android.bp index f62f167cdcfa..4c5c2b2cfd4f 100644 --- a/apex/sdkext/Android.bp +++ b/apex/sdkextensions/Android.bp @@ -18,21 +18,26 @@ package { apex { name: "com.android.sdkext", - manifest: "manifest.json", + defaults: [ "com.android.sdkext-defaults" ], binaries: [ "derive_sdk" ], - java_libs: [ "framework-sdkext" ], + prebuilts: [ "cur_sdkinfo" ], + manifest: "manifest.json", +} + +apex_defaults { + name: "com.android.sdkext-defaults", + java_libs: [ "framework-sdkextensions" ], prebuilts: [ - "com.android.sdkext.ldconfig", - "cur_sdkinfo", - "derive_sdk.rc", + "com.android.sdkext.ldconfig", + "derive_sdk.rc", ], key: "com.android.sdkext.key", certificate: ":com.android.sdkext.certificate", } sdk { - name: "sdkext-sdk", - java_header_libs: [ "framework-sdkext-stubs-systemapi" ], + name: "sdkextensions-sdk", + java_header_libs: [ "framework-sdkextensions-stubs-systemapi" ], } apex_key { diff --git a/apex/sdkext/OWNERS b/apex/sdkextensions/OWNERS index feb274262bef..feb274262bef 100644 --- a/apex/sdkext/OWNERS +++ b/apex/sdkextensions/OWNERS diff --git a/apex/sdkextensions/TEST_MAPPING b/apex/sdkextensions/TEST_MAPPING new file mode 100644 index 000000000000..4e1883382e2c --- /dev/null +++ b/apex/sdkextensions/TEST_MAPPING @@ -0,0 +1,10 @@ +{ + "presubmit": [ + { + "name": "CtsSdkExtensionsTestCases" + }, + { + "name": "apiextensions_e2e_tests" + } + ] +} diff --git a/apex/sdkext/com.android.sdkext.avbpubkey b/apex/sdkextensions/com.android.sdkext.avbpubkey Binary files differindex 8f47741ed3b8..8f47741ed3b8 100644 --- a/apex/sdkext/com.android.sdkext.avbpubkey +++ b/apex/sdkextensions/com.android.sdkext.avbpubkey diff --git a/apex/sdkext/com.android.sdkext.pem b/apex/sdkextensions/com.android.sdkext.pem index 816460183aa3..816460183aa3 100644 --- a/apex/sdkext/com.android.sdkext.pem +++ b/apex/sdkextensions/com.android.sdkext.pem diff --git a/apex/sdkext/com.android.sdkext.pk8 b/apex/sdkextensions/com.android.sdkext.pk8 Binary files differindex ccc0bf438cd1..ccc0bf438cd1 100644 --- a/apex/sdkext/com.android.sdkext.pk8 +++ b/apex/sdkextensions/com.android.sdkext.pk8 diff --git a/apex/sdkext/com.android.sdkext.x509.pem b/apex/sdkextensions/com.android.sdkext.x509.pem index 45d2ade354d4..45d2ade354d4 100644 --- a/apex/sdkext/com.android.sdkext.x509.pem +++ b/apex/sdkextensions/com.android.sdkext.x509.pem diff --git a/apex/sdkext/derive_sdk/Android.bp b/apex/sdkextensions/derive_sdk/Android.bp index c4e3c296f210..cf49902d9978 100644 --- a/apex/sdkext/derive_sdk/Android.bp +++ b/apex/sdkextensions/derive_sdk/Android.bp @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -cc_binary { - name: "derive_sdk", +cc_defaults { + name: "derive_sdk-defaults", srcs: [ "derive_sdk.cpp", "sdk.proto", @@ -30,6 +30,24 @@ cc_binary { ], } +cc_binary { + name: "derive_sdk", + defaults: [ "derive_sdk-defaults" ], + apex_available: [ "com.android.sdkext" ], + visibility: [ "//frameworks/base/apex/sdkextensions" ] +} + +// Work around testing using a 64-bit test suite on 32-bit test device by +// using a prefer32 version of derive_sdk in testing. +cc_binary { + name: "derive_sdk_prefer32", + defaults: [ "derive_sdk-defaults" ], + compile_multilib: "prefer32", + stem: "derive_sdk", + apex_available: [ "test_com.android.sdkext" ], + visibility: [ "//frameworks/base/apex/sdkextensions/testing" ] +} + prebuilt_etc { name: "derive_sdk.rc", src: "derive_sdk.rc", diff --git a/apex/sdkext/derive_sdk/derive_sdk.cpp b/apex/sdkextensions/derive_sdk/derive_sdk.cpp index 0a9711677015..6fb7ef43416e 100644 --- a/apex/sdkext/derive_sdk/derive_sdk.cpp +++ b/apex/sdkextensions/derive_sdk/derive_sdk.cpp @@ -26,7 +26,7 @@ #include <android-base/logging.h> #include <android-base/properties.h> -#include "frameworks/base/apex/sdkext/derive_sdk/sdk.pb.h" +#include "frameworks/base/apex/sdkextensions/derive_sdk/sdk.pb.h" using com::android::sdkext::proto::SdkVersion; diff --git a/apex/sdkext/derive_sdk/derive_sdk.rc b/apex/sdkextensions/derive_sdk/derive_sdk.rc index 1b667949eeaa..1b667949eeaa 100644 --- a/apex/sdkext/derive_sdk/derive_sdk.rc +++ b/apex/sdkextensions/derive_sdk/derive_sdk.rc diff --git a/apex/sdkext/derive_sdk/sdk.proto b/apex/sdkextensions/derive_sdk/sdk.proto index d15b93552ff4..d15b93552ff4 100644 --- a/apex/sdkext/derive_sdk/sdk.proto +++ b/apex/sdkextensions/derive_sdk/sdk.proto diff --git a/apex/sdkext/framework/Android.bp b/apex/sdkextensions/framework/Android.bp index a50dc3d4f349..dd174734df6d 100644 --- a/apex/sdkext/framework/Android.bp +++ b/apex/sdkextensions/framework/Android.bp @@ -17,55 +17,63 @@ package { } filegroup { - name: "framework-sdkext-sources", + name: "framework-sdkextensions-sources", srcs: [ "java/**/*.java", ], path: "java", - visibility: [ "//frameworks/base:__pkg__" ] // For the "global" stubs. + visibility: [ "//frameworks/base" ] // For the "global" stubs. } java_library { - name: "framework-sdkext", - srcs: [ ":framework-sdkext-sources" ], + name: "framework-sdkextensions", + srcs: [ ":framework-sdkextensions-sources" ], sdk_version: "system_current", libs: [ "framework-annotations-lib" ], permitted_packages: [ "android.os.ext" ], installable: true, - visibility: [ "//frameworks/base/apex/sdkext:__pkg__" ], + visibility: [ + "//frameworks/base/apex/sdkextensions", + "//frameworks/base/apex/sdkextensions/testing", + ], + hostdex: true, // for hiddenapi check + apex_available: [ + "com.android.sdkext", + "test_com.android.sdkext", + ], } droidstubs { - name: "framework-sdkext-droidstubs-publicapi", + name: "framework-sdkextensions-droidstubs-publicapi", defaults: [ - "framework-sdkext-stubs-defaults", + "framework-sdkextensions-stubs-defaults", "framework-module-stubs-defaults-publicapi", ] } droidstubs { - name: "framework-sdkext-droidstubs-systemapi", + name: "framework-sdkextensions-droidstubs-systemapi", defaults: [ - "framework-sdkext-stubs-defaults", + "framework-sdkextensions-stubs-defaults", "framework-module-stubs-defaults-systemapi", ] } stubs_defaults { - name: "framework-sdkext-stubs-defaults", + name: "framework-sdkextensions-stubs-defaults", srcs: [ - ":framework-sdkext-sources", + ":framework-sdkextensions-sources", ":framework-annotations", ], sdk_version: "system_current", } java_library { - name: "framework-sdkext-stubs-systemapi", - srcs: [":framework-sdkext-droidstubs-systemapi"], + name: "framework-sdkextensions-stubs-systemapi", + srcs: [":framework-sdkextensions-droidstubs-systemapi"], sdk_version: "system_current", visibility: [ - "//frameworks/base:__pkg__", // Framework - "//frameworks/base/apex/sdkext:__pkg__", // sdkext SDK + "//frameworks/base", // Framework + "//frameworks/base/apex/sdkextensions", // sdkextensions SDK ] } diff --git a/apex/sdkext/framework/java/android/os/ext/SdkExtensions.java b/apex/sdkextensions/framework/java/android/os/ext/SdkExtensions.java index a8a7effa9b6c..a8a7effa9b6c 100644 --- a/apex/sdkext/framework/java/android/os/ext/SdkExtensions.java +++ b/apex/sdkextensions/framework/java/android/os/ext/SdkExtensions.java diff --git a/apex/sdkext/framework/java/android/os/ext/package.html b/apex/sdkextensions/framework/java/android/os/ext/package.html index 34c1697c01fd..34c1697c01fd 100644 --- a/apex/sdkext/framework/java/android/os/ext/package.html +++ b/apex/sdkextensions/framework/java/android/os/ext/package.html diff --git a/apex/sdkext/gen_sdkinfo.py b/apex/sdkextensions/gen_sdkinfo.py index 5af478ba7fe6..5af478ba7fe6 100644 --- a/apex/sdkext/gen_sdkinfo.py +++ b/apex/sdkextensions/gen_sdkinfo.py diff --git a/apex/sdkext/ld.config.txt b/apex/sdkextensions/ld.config.txt index b4470685f4fc..dcc69b892760 100644 --- a/apex/sdkext/ld.config.txt +++ b/apex/sdkextensions/ld.config.txt @@ -1,10 +1,10 @@ # Copyright (C) 2019 The Android Open Source Project # -# Bionic loader config file for the sdkext apex. +# Bionic loader config file for the sdkextensions apex. -dir.sdkext = /apex/com.android.sdkext/bin/ +dir.sdkextensions = /apex/com.android.sdkext/bin/ -[sdkext] +[sdkextensions] additional.namespaces = platform namespace.default.isolated = true diff --git a/apex/sdkext/manifest.json b/apex/sdkextensions/manifest.json index 048f5c4f177b..048f5c4f177b 100644 --- a/apex/sdkext/manifest.json +++ b/apex/sdkextensions/manifest.json diff --git a/apex/sdkext/sdk.proto b/apex/sdkextensions/sdk.proto index d15b93552ff4..d15b93552ff4 100644 --- a/apex/sdkext/sdk.proto +++ b/apex/sdkextensions/sdk.proto diff --git a/apex/sdkextensions/testing/Android.bp b/apex/sdkextensions/testing/Android.bp new file mode 100644 index 000000000000..e6451cc29bc2 --- /dev/null +++ b/apex/sdkextensions/testing/Android.bp @@ -0,0 +1,46 @@ +// 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. + +apex { + name: "test_com.android.sdkext", + visibility: [ "//system/apex/tests" ], + defaults: ["com.android.sdkext-defaults"], + manifest: "test_manifest.json", + prebuilts: [ "sdkinfo_45" ], + file_contexts: ":com.android.sdkext-file_contexts", + installable: false, // Should never be installed on the systemimage + multilib: { + prefer32: { + binaries: ["derive_sdk_prefer32"], + }, + }, + // The automated test infra ends up building this apex for 64+32-bit and + // then installs it on a 32-bit-only device. Work around this weirdness + // by preferring 32-bit. + compile_multilib: "prefer32", +} + +genrule { + name: "sdkinfo_45_src", + out: [ "sdkinfo.binarypb" ], + tools: [ "gen_sdkinfo" ], + cmd: "$(location) -v 45 -o $(out)", +} + +prebuilt_etc { + name: "sdkinfo_45", + src: ":sdkinfo_45_src", + filename: "sdkinfo.binarypb", + installable: false, +} diff --git a/apex/sdkextensions/testing/test_manifest.json b/apex/sdkextensions/testing/test_manifest.json new file mode 100644 index 000000000000..1b4a2b0c6e60 --- /dev/null +++ b/apex/sdkextensions/testing/test_manifest.json @@ -0,0 +1,4 @@ +{ + "name": "com.android.sdkext", + "version": 2147483647 +} diff --git a/apex/statsd/aidl/android/os/IStatsCompanionService.aidl b/apex/statsd/aidl/android/os/IStatsCompanionService.aidl index 21b7767e932d..99b9d398e30c 100644 --- a/apex/statsd/aidl/android/os/IStatsCompanionService.aidl +++ b/apex/statsd/aidl/android/os/IStatsCompanionService.aidl @@ -67,11 +67,4 @@ interface IStatsCompanionService { /** Tells StatsCompaionService to grab the uid map snapshot and send it to statsd. */ oneway void triggerUidSnapshot(); - - /** Tells StatsCompanionService to tell statsd to register a puller for the given atom id */ - oneway void registerPullAtomCallback(int atomTag, long coolDownNs, long timeoutNs, - in int[] additiveFields, IPullAtomCallback pullerCallback); - - /** Tells StatsCompanionService to tell statsd to unregister a puller for the given atom id */ - oneway void unregisterPullAtomCallback(int atomTag); } diff --git a/apex/statsd/aidl/android/os/IStatsManagerService.aidl b/apex/statsd/aidl/android/os/IStatsManagerService.aidl index 055836777678..4a259f50d2f6 100644 --- a/apex/statsd/aidl/android/os/IStatsManagerService.aidl +++ b/apex/statsd/aidl/android/os/IStatsManagerService.aidl @@ -17,6 +17,7 @@ package android.os; import android.app.PendingIntent; +import android.os.IPullAtomCallback; /** * Binder interface to communicate with the Java-based statistics service helper. @@ -85,4 +86,51 @@ interface IStatsManagerService { * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS. */ void unsetBroadcastSubscriber(long configKey, long subscriberId, in String packageName); -}
\ No newline at end of file + + /** + * Returns the most recently registered experiment IDs. + * + * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS. + */ + long[] getRegisteredExperimentIds(); + + /** + * Fetches metadata across statsd. Returns byte array representing wire-encoded proto. + * + * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS. + */ + byte[] getMetadata(in String packageName); + + /** + * Fetches data for the specified configuration key. Returns a byte array representing proto + * wire-encoded of ConfigMetricsReportList. + * + * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS. + */ + byte[] getData(in long key, in String packageName); + + /** + * Sets a configuration with the specified config id and subscribes to updates for this + * configuration id. Broadcasts will be sent if this configuration needs to be collected. + * The configuration must be a wire-encoded StatsdConfig. The receiver for this data is + * registered in a separate function. + * + * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS. + */ + void addConfiguration(in long configId, in byte[] config, in String packageName); + + /** + * Removes the configuration with the matching config id. No-op if this config id does not + * exist. + * + * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS. + */ + void removeConfiguration(in long configId, in String packageName); + + /** Tell StatsManagerService to register a puller for the given atom tag with statsd. */ + oneway void registerPullAtomCallback(int atomTag, long coolDownNs, long timeoutNs, + in int[] additiveFields, IPullAtomCallback pullerCallback); + + /** Tell StatsManagerService to unregister the pulller for the given atom tag from statsd. */ + oneway void unregisterPullAtomCallback(int atomTag); +} diff --git a/apex/statsd/aidl/android/os/IStatsd.aidl b/apex/statsd/aidl/android/os/IStatsd.aidl index 935843921feb..c409f516de1b 100644 --- a/apex/statsd/aidl/android/os/IStatsd.aidl +++ b/apex/statsd/aidl/android/os/IStatsd.aidl @@ -89,24 +89,24 @@ interface IStatsd { * * Requires Manifest.permission.DUMP. */ - byte[] getData(in long key, in String packageName); + byte[] getData(in long key, int callingUid); /** * Fetches metadata across statsd. Returns byte array representing wire-encoded proto. * * Requires Manifest.permission.DUMP. */ - byte[] getMetadata(in String packageName); + byte[] getMetadata(); /** - * Sets a configuration with the specified config key and subscribes to updates for this + * Sets a configuration with the specified config id and subscribes to updates for this * configuration key. Broadcasts will be sent if this configuration needs to be collected. * The configuration must be a wire-encoded StatsdConfig. The receiver for this data is * registered in a separate function. * * Requires Manifest.permission.DUMP. */ - void addConfiguration(in long configKey, in byte[] config, in String packageName); + void addConfiguration(in long configId, in byte[] config, in int callingUid); /** * Registers the given pending intent for this config key. This intent is invoked when the @@ -143,12 +143,12 @@ interface IStatsd { void removeActiveConfigsChangedOperation(int callingUid); /** - * Removes the configuration with the matching config key. No-op if this config key does not + * Removes the configuration with the matching config id. No-op if this config id does not * exist. * * Requires Manifest.permission.DUMP. */ - void removeConfiguration(in long configKey, in String packageName); + void removeConfiguration(in long configId, in int callingUid); /** * Set the PendingIntentRef to be used when broadcasting subscriber diff --git a/apex/statsd/framework/Android.bp b/apex/statsd/framework/Android.bp index a2b0577fe001..0b46645ad06f 100644 --- a/apex/statsd/framework/Android.bp +++ b/apex/statsd/framework/Android.bp @@ -37,7 +37,16 @@ java_library { // TODO(b/146230220): Use framework-system-stubs instead. "android_system_stubs_current", ], - // TODO:(b/146210774): Add apex_available field. + hostdex: true, // for hiddenapi check + visibility: [ + "//frameworks/base/apex/statsd:__subpackages__", + //TODO(b/146167933) remove this when framework is built with framework-statsd-stubs + "//frameworks/base", + ], + apex_available: [ + "com.android.os.statsd", + "test_com.android.os.statsd", + ], } droidstubs { diff --git a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java index d57afeeb7157..a9081786866c 100644 --- a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java +++ b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java @@ -75,7 +75,6 @@ import android.os.FileUtils; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; -import android.os.IPullAtomCallback; import android.os.IStatsCompanionService; import android.os.IStatsd; import android.os.IStoraged; @@ -263,71 +262,6 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { private StatsManagerService mStatsManagerService; - private static final class PullerKey { - private final int mUid; - private final int mAtomTag; - - PullerKey(int uid, int atom) { - mUid = uid; - mAtomTag = atom; - } - - public int getUid() { - return mUid; - } - - public int getAtom() { - return mAtomTag; - } - - @Override - public int hashCode() { - return Objects.hash(mUid, mAtomTag); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof PullerKey) { - PullerKey other = (PullerKey) obj; - return this.mUid == other.getUid() && this.mAtomTag == other.getAtom(); - } - return false; - } - } - - private static final class PullerValue { - private final long mCoolDownNs; - private final long mTimeoutNs; - private int[] mAdditiveFields; - private IPullAtomCallback mCallback; - - PullerValue(long coolDownNs, long timeoutNs, int[] additiveFields, - IPullAtomCallback callback) { - mCoolDownNs = coolDownNs; - mTimeoutNs = timeoutNs; - mAdditiveFields = additiveFields; - mCallback = callback; - } - - public long getCoolDownNs() { - return mCoolDownNs; - } - - public long getTimeoutNs() { - return mTimeoutNs; - } - - public int[] getAdditiveFields() { - return mAdditiveFields; - } - - public IPullAtomCallback getCallback() { - return mCallback; - } - } - - private final HashMap<PullerKey, PullerValue> mPullers = new HashMap<>(); - private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader(); private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats(); private WifiManager mWifiManager = null; @@ -2634,57 +2568,6 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } } - @Override - public void registerPullAtomCallback(int atomTag, long coolDownNs, long timeoutNs, - int[] additiveFields, IPullAtomCallback pullerCallback) { - synchronized (sStatsdLock) { - // Always cache the puller in SCS. - // If statsd is down, we will register it when it comes back up. - int callingUid = Binder.getCallingUid(); - final long token = Binder.clearCallingIdentity(); - PullerKey key = new PullerKey(callingUid, atomTag); - PullerValue val = new PullerValue( - coolDownNs, timeoutNs, additiveFields, pullerCallback); - mPullers.put(key, val); - - if (sStatsd == null) { - Slog.w(TAG, "Could not access statsd for registering puller for atom " + atomTag); - return; - } - try { - sStatsd.registerPullAtomCallback( - callingUid, atomTag, coolDownNs, timeoutNs, additiveFields, pullerCallback); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to access statsd to register puller for atom " + atomTag); - } finally { - Binder.restoreCallingIdentity(token); - } - } - } - - @Override - public void unregisterPullAtomCallback(int atomTag) { - synchronized (sStatsdLock) { - // Always remove the puller in SCS. - // If statsd is down, we will not register it when it comes back up. - int callingUid = Binder.getCallingUid(); - final long token = Binder.clearCallingIdentity(); - PullerKey key = new PullerKey(callingUid, atomTag); - mPullers.remove(key); - - if (sStatsd == null) { - Slog.w(TAG, "Could not access statsd for registering puller for atom " + atomTag); - return; - } - try { - sStatsd.unregisterPullAtomCallback(callingUid, atomTag); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to access statsd to register puller for atom " + atomTag); - } finally { - Binder.restoreCallingIdentity(token); - } - } - } // Statsd related code @@ -2763,8 +2646,6 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { // Pull the latest state of UID->app name, version mapping when // statsd starts. informAllUidsLocked(mContext); - // Register all pullers. If SCS has just started, this should be empty. - registerAllPullersLocked(); } finally { restoreCallingIdentity(token); } @@ -2776,17 +2657,6 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } } - @GuardedBy("sStatsdLock") - private void registerAllPullersLocked() throws RemoteException { - // TODO: pass in one call, using a file descriptor (similar to uidmap). - for (Map.Entry<PullerKey, PullerValue> entry : mPullers.entrySet()) { - PullerKey key = entry.getKey(); - PullerValue val = entry.getValue(); - sStatsd.registerPullAtomCallback(key.getUid(), key.getAtom(), val.getCoolDownNs(), - val.getTimeoutNs(), val.getAdditiveFields(), val.getCallback()); - } - } - private class StatsdDeathRecipient implements IBinder.DeathRecipient { @Override public void binderDied() { diff --git a/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java b/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java index 1d03e3b702c4..04d8b006f51d 100644 --- a/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java +++ b/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java @@ -19,10 +19,12 @@ package com.android.server.stats; import static com.android.server.stats.StatsCompanion.PendingIntentRef; import android.Manifest; +import android.annotation.Nullable; import android.app.AppOpsManager; import android.app.PendingIntent; import android.content.Context; import android.os.Binder; +import android.os.IPullAtomCallback; import android.os.IStatsManagerService; import android.os.IStatsd; import android.os.Process; @@ -59,8 +61,7 @@ public class StatsManagerService extends IStatsManagerService.Stub { @GuardedBy("mLock") private ArrayMap<ConfigKey, PendingIntentRef> mDataFetchPirMap = new ArrayMap<>(); @GuardedBy("mLock") - private ArrayMap<Integer, PendingIntentRef> mActiveConfigsPirMap = - new ArrayMap<>(); + private ArrayMap<Integer, PendingIntentRef> mActiveConfigsPirMap = new ArrayMap<>(); @GuardedBy("mLock") private ArrayMap<ConfigKey, ArrayMap<Long, PendingIntentRef>> mBroadcastSubscriberPirMap = new ArrayMap<>(); @@ -71,8 +72,8 @@ public class StatsManagerService extends IStatsManagerService.Stub { } private static class ConfigKey { - private int mUid; - private long mConfigId; + private final int mUid; + private final long mConfigId; ConfigKey(int uid, long configId) { mUid = uid; @@ -102,6 +103,126 @@ public class StatsManagerService extends IStatsManagerService.Stub { } } + private static class PullerKey { + private final int mUid; + private final int mAtomTag; + + PullerKey(int uid, int atom) { + mUid = uid; + mAtomTag = atom; + } + + public int getUid() { + return mUid; + } + + public int getAtom() { + return mAtomTag; + } + + @Override + public int hashCode() { + return Objects.hash(mUid, mAtomTag); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof PullerKey) { + PullerKey other = (PullerKey) obj; + return this.mUid == other.getUid() && this.mAtomTag == other.getAtom(); + } + return false; + } + } + + private static class PullerValue { + private final long mCoolDownNs; + private final long mTimeoutNs; + private final int[] mAdditiveFields; + private final IPullAtomCallback mCallback; + + PullerValue(long coolDownNs, long timeoutNs, int[] additiveFields, + IPullAtomCallback callback) { + mCoolDownNs = coolDownNs; + mTimeoutNs = timeoutNs; + mAdditiveFields = additiveFields; + mCallback = callback; + } + + public long getCoolDownNs() { + return mCoolDownNs; + } + + public long getTimeoutNs() { + return mTimeoutNs; + } + + public int[] getAdditiveFields() { + return mAdditiveFields; + } + + public IPullAtomCallback getCallback() { + return mCallback; + } + } + + private final ArrayMap<PullerKey, PullerValue> mPullers = new ArrayMap<>(); + + @Override + public void registerPullAtomCallback(int atomTag, long coolDownNs, long timeoutNs, + int[] additiveFields, IPullAtomCallback pullerCallback) { + int callingUid = Binder.getCallingUid(); + final long token = Binder.clearCallingIdentity(); + PullerKey key = new PullerKey(callingUid, atomTag); + PullerValue val = new PullerValue(coolDownNs, timeoutNs, additiveFields, pullerCallback); + + // Always cache the puller in StatsManagerService. If statsd is down, we will register the + // puller when statsd comes back up. + synchronized (mLock) { + mPullers.put(key, val); + } + + IStatsd statsd = getStatsdNonblocking(); + if (statsd == null) { + return; + } + + try { + statsd.registerPullAtomCallback( + callingUid, atomTag, coolDownNs, timeoutNs, additiveFields, pullerCallback); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to access statsd to register puller for atom " + atomTag); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void unregisterPullAtomCallback(int atomTag) { + int callingUid = Binder.getCallingUid(); + final long token = Binder.clearCallingIdentity(); + PullerKey key = new PullerKey(callingUid, atomTag); + + // Always remove the puller from StatsManagerService even if statsd is down. When statsd + // comes back up, we will not re-register the removed puller. + synchronized (mLock) { + mPullers.remove(key); + } + + IStatsd statsd = getStatsdNonblocking(); + if (statsd == null) { + return; + } + + try { + statsd.unregisterPullAtomCallback(callingUid, atomTag); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to access statsd to unregister puller for atom " + atomTag); + } finally { + Binder.restoreCallingIdentity(token); + } + } + @Override public void setDataFetchOperation(long configId, PendingIntent pendingIntent, String packageName) { @@ -245,20 +366,127 @@ public class StatsManagerService extends IStatsManagerService.Stub { } } + @Override + public long[] getRegisteredExperimentIds() throws IllegalStateException { + enforceDumpAndUsageStatsPermission(null); + final long token = Binder.clearCallingIdentity(); + try { + IStatsd statsd = waitForStatsd(); + if (statsd != null) { + return statsd.getRegisteredExperimentIds(); + } + } catch (RemoteException e) { + Slog.e(TAG, "Failed to getRegisteredExperimentIds with statsd"); + throw new IllegalStateException(e.getMessage(), e); + } finally { + Binder.restoreCallingIdentity(token); + } + throw new IllegalStateException("Failed to connect to statsd to registerExperimentIds"); + } + + @Override + public byte[] getMetadata(String packageName) throws IllegalStateException { + enforceDumpAndUsageStatsPermission(packageName); + final long token = Binder.clearCallingIdentity(); + try { + IStatsd statsd = waitForStatsd(); + if (statsd != null) { + return statsd.getMetadata(); + } + } catch (RemoteException e) { + Slog.e(TAG, "Failed to getMetadata with statsd"); + throw new IllegalStateException(e.getMessage(), e); + } finally { + Binder.restoreCallingIdentity(token); + } + throw new IllegalStateException("Failed to connect to statsd to getMetadata"); + } + + @Override + public byte[] getData(long key, String packageName) throws IllegalStateException { + enforceDumpAndUsageStatsPermission(packageName); + int callingUid = Binder.getCallingUid(); + final long token = Binder.clearCallingIdentity(); + try { + IStatsd statsd = waitForStatsd(); + if (statsd != null) { + return statsd.getData(key, callingUid); + } + } catch (RemoteException e) { + Slog.e(TAG, "Failed to getData with statsd"); + throw new IllegalStateException(e.getMessage(), e); + } finally { + Binder.restoreCallingIdentity(token); + } + throw new IllegalStateException("Failed to connect to statsd to getData"); + } + + @Override + public void addConfiguration(long configId, byte[] config, String packageName) + throws IllegalStateException { + enforceDumpAndUsageStatsPermission(packageName); + int callingUid = Binder.getCallingUid(); + final long token = Binder.clearCallingIdentity(); + try { + IStatsd statsd = waitForStatsd(); + if (statsd != null) { + statsd.addConfiguration(configId, config, callingUid); + return; + } + } catch (RemoteException e) { + Slog.e(TAG, "Failed to addConfiguration with statsd"); + throw new IllegalStateException(e.getMessage(), e); + } finally { + Binder.restoreCallingIdentity(token); + } + throw new IllegalStateException("Failed to connect to statsd to addConfig"); + } + + @Override + public void removeConfiguration(long configId, String packageName) + throws IllegalStateException { + enforceDumpAndUsageStatsPermission(packageName); + int callingUid = Binder.getCallingUid(); + final long token = Binder.clearCallingIdentity(); + try { + IStatsd statsd = waitForStatsd(); + if (statsd != null) { + statsd.removeConfiguration(configId, callingUid); + return; + } + } catch (RemoteException e) { + Slog.e(TAG, "Failed to removeConfiguration with statsd"); + throw new IllegalStateException(e.getMessage(), e); + } finally { + Binder.restoreCallingIdentity(token); + } + throw new IllegalStateException("Failed to connect to statsd to removeConfig"); + } + void setStatsCompanionService(StatsCompanionService statsCompanionService) { mStatsCompanionService = statsCompanionService; } - private void enforceDumpAndUsageStatsPermission(String packageName) { + /** + * Checks that the caller has both DUMP and PACKAGE_USAGE_STATS permissions. Also checks that + * the caller has USAGE_STATS_PERMISSION_OPS for the specified packageName if it is not null. + * + * @param packageName The packageName to check USAGE_STATS_PERMISSION_OPS. + */ + private void enforceDumpAndUsageStatsPermission(@Nullable String packageName) { int callingUid = Binder.getCallingUid(); int callingPid = Binder.getCallingPid(); if (callingPid == Process.myPid()) { return; } + mContext.enforceCallingPermission(Manifest.permission.DUMP, null); mContext.enforceCallingPermission(Manifest.permission.PACKAGE_USAGE_STATS, null); + if (packageName == null) { + return; + } AppOpsManager appOpsManager = (AppOpsManager) mContext .getSystemService(Context.APP_OPS_SERVICE); switch (appOpsManager.noteOp(USAGE_STATS_PERMISSION_OPS, @@ -333,46 +561,85 @@ public class StatsManagerService extends IStatsManagerService.Stub { if (statsd == null) { return; } - // Since we do not want to make an IPC with the a lock held, we first create local deep - // copies of the data with the lock held before iterating through the maps. + + final long token = Binder.clearCallingIdentity(); + try { + registerAllPullers(statsd); + registerAllDataFetchOperations(statsd); + registerAllActiveConfigsChangedOperations(statsd); + registerAllBroadcastSubscribers(statsd); + } catch (RemoteException e) { + Slog.e(TAG, "StatsManager failed to (re-)register data with statsd"); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + // Pre-condition: the Binder calling identity has already been cleared + private void registerAllPullers(IStatsd statsd) throws RemoteException { + // Since we do not want to make an IPC with the lock held, we first create a copy of the + // data with the lock held before iterating through the map. + ArrayMap<PullerKey, PullerValue> pullersCopy; + synchronized (mLock) { + pullersCopy = new ArrayMap<>(mPullers); + } + + for (Map.Entry<PullerKey, PullerValue> entry : pullersCopy.entrySet()) { + PullerKey key = entry.getKey(); + PullerValue value = entry.getValue(); + statsd.registerPullAtomCallback(key.getUid(), key.getAtom(), value.getCoolDownNs(), + value.getTimeoutNs(), value.getAdditiveFields(), value.getCallback()); + } + } + + // Pre-condition: the Binder calling identity has already been cleared + private void registerAllDataFetchOperations(IStatsd statsd) throws RemoteException { + // Since we do not want to make an IPC with the lock held, we first create a copy of the + // data with the lock held before iterating through the map. ArrayMap<ConfigKey, PendingIntentRef> dataFetchCopy; - ArrayMap<Integer, PendingIntentRef> activeConfigsChangedCopy; - ArrayMap<ConfigKey, ArrayMap<Long, PendingIntentRef>> broadcastSubscriberCopy; synchronized (mLock) { dataFetchCopy = new ArrayMap<>(mDataFetchPirMap); - activeConfigsChangedCopy = new ArrayMap<>(mActiveConfigsPirMap); - broadcastSubscriberCopy = new ArrayMap<>(); - for (Map.Entry<ConfigKey, ArrayMap<Long, PendingIntentRef>> entry - : mBroadcastSubscriberPirMap.entrySet()) { - broadcastSubscriberCopy.put(entry.getKey(), new ArrayMap<>(entry.getValue())); - } } + for (Map.Entry<ConfigKey, PendingIntentRef> entry : dataFetchCopy.entrySet()) { ConfigKey key = entry.getKey(); - try { - statsd.setDataFetchOperation(key.getConfigId(), entry.getValue(), key.getUid()); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to setDataFetchOperation from pirMap"); - } + statsd.setDataFetchOperation(key.getConfigId(), entry.getValue(), key.getUid()); } - for (Map.Entry<Integer, PendingIntentRef> entry - : activeConfigsChangedCopy.entrySet()) { - try { - statsd.setActiveConfigsChangedOperation(entry.getValue(), entry.getKey()); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to setActiveConfigsChangedOperation from pirMap"); + } + + // Pre-condition: the Binder calling identity has already been cleared + private void registerAllActiveConfigsChangedOperations(IStatsd statsd) throws RemoteException { + // Since we do not want to make an IPC with the lock held, we first create a copy of the + // data with the lock held before iterating through the map. + ArrayMap<Integer, PendingIntentRef> activeConfigsChangedCopy; + synchronized (mLock) { + activeConfigsChangedCopy = new ArrayMap<>(mActiveConfigsPirMap); + } + + for (Map.Entry<Integer, PendingIntentRef> entry : activeConfigsChangedCopy.entrySet()) { + statsd.setActiveConfigsChangedOperation(entry.getValue(), entry.getKey()); + } + } + + // Pre-condition: the Binder calling identity has already been cleared + private void registerAllBroadcastSubscribers(IStatsd statsd) throws RemoteException { + // Since we do not want to make an IPC with the lock held, we first create a deep copy of + // the data with the lock held before iterating through the map. + ArrayMap<ConfigKey, ArrayMap<Long, PendingIntentRef>> broadcastSubscriberCopy = + new ArrayMap<>(); + synchronized (mLock) { + for (Map.Entry<ConfigKey, ArrayMap<Long, PendingIntentRef>> entry : + mBroadcastSubscriberPirMap.entrySet()) { + broadcastSubscriberCopy.put(entry.getKey(), new ArrayMap(entry.getValue())); } } - for (Map.Entry<ConfigKey, ArrayMap<Long, PendingIntentRef>> entry - : broadcastSubscriberCopy.entrySet()) { + + for (Map.Entry<ConfigKey, ArrayMap<Long, PendingIntentRef>> entry : + mBroadcastSubscriberPirMap.entrySet()) { + ConfigKey configKey = entry.getKey(); for (Map.Entry<Long, PendingIntentRef> subscriberEntry : entry.getValue().entrySet()) { - ConfigKey configKey = entry.getKey(); - try { - statsd.setBroadcastSubscriber(configKey.getConfigId(), subscriberEntry.getKey(), - subscriberEntry.getValue(), configKey.getUid()); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to setBroadcastSubscriber from pirMap"); - } + statsd.setBroadcastSubscriber(configKey.getConfigId(), subscriberEntry.getKey(), + subscriberEntry.getValue(), configKey.getUid()); } } } diff --git a/api/current.txt b/api/current.txt index ac69f6fa6b60..4ff7ef98cca2 100644 --- a/api/current.txt +++ b/api/current.txt @@ -5837,6 +5837,7 @@ package android.app { method public void enableLights(boolean); method public void enableVibration(boolean); method public android.media.AudioAttributes getAudioAttributes(); + method @Nullable public String getConversationId(); method public String getDescription(); method public String getGroup(); method public String getId(); @@ -5844,12 +5845,14 @@ package android.app { method public int getLightColor(); method public int getLockscreenVisibility(); method public CharSequence getName(); + method @Nullable public String getParentChannelId(); method public android.net.Uri getSound(); method public long[] getVibrationPattern(); method public boolean hasUserSetImportance(); method public boolean hasUserSetSound(); method public void setAllowBubbles(boolean); method public void setBypassDnd(boolean); + method public void setConversationId(@Nullable String, @Nullable String); method public void setDescription(String); method public void setGroup(String); method public void setImportance(int); @@ -5862,6 +5865,7 @@ package android.app { method public boolean shouldShowLights(); method public boolean shouldVibrate(); method public void writeToParcel(android.os.Parcel, int); + field public static final String CONVERSATION_CHANNEL_ID_FORMAT = "%1$s : %2$s"; field @NonNull public static final android.os.Parcelable.Creator<android.app.NotificationChannel> CREATOR; field public static final String DEFAULT_CHANNEL_ID = "miscellaneous"; } @@ -5903,6 +5907,7 @@ package android.app { method public final int getCurrentInterruptionFilter(); method public int getImportance(); method public android.app.NotificationChannel getNotificationChannel(String); + method @Nullable public android.app.NotificationChannel getNotificationChannel(@NonNull String, @NonNull String); method public android.app.NotificationChannelGroup getNotificationChannelGroup(String); method public java.util.List<android.app.NotificationChannelGroup> getNotificationChannelGroups(); method public java.util.List<android.app.NotificationChannel> getNotificationChannels(); @@ -6790,6 +6795,7 @@ package android.app.admin { method @Nullable public java.util.List<java.lang.String> getPermittedAccessibilityServices(@NonNull android.content.ComponentName); method @Nullable public java.util.List<java.lang.String> getPermittedCrossProfileNotificationListeners(@NonNull android.content.ComponentName); method @Nullable public java.util.List<java.lang.String> getPermittedInputMethods(@NonNull android.content.ComponentName); + method @NonNull public java.util.List<java.lang.String> getProtectedPackages(@NonNull android.content.ComponentName); method public long getRequiredStrongAuthTimeout(@Nullable android.content.ComponentName); method public boolean getScreenCaptureDisabled(@Nullable android.content.ComponentName); method public java.util.List<android.os.UserHandle> getSecondaryUsers(@NonNull android.content.ComponentName); @@ -6911,6 +6917,7 @@ package android.app.admin { method public boolean setPermittedInputMethods(@NonNull android.content.ComponentName, java.util.List<java.lang.String>); method public void setProfileEnabled(@NonNull android.content.ComponentName); method public void setProfileName(@NonNull android.content.ComponentName, String); + method public void setProtectedPackages(@NonNull android.content.ComponentName, @NonNull java.util.List<java.lang.String>); method public void setRecommendedGlobalProxy(@NonNull android.content.ComponentName, @Nullable android.net.ProxyInfo); method public void setRequiredStrongAuthTimeout(@NonNull android.content.ComponentName, long); method public boolean setResetPasswordToken(android.content.ComponentName, byte[]); @@ -9502,6 +9509,7 @@ package android.content { method public void dump(java.io.FileDescriptor, java.io.PrintWriter, String[]); method @Nullable public final String getCallingFeatureId(); method @Nullable public final String getCallingPackage(); + method @Nullable public final String getCallingPackageUnchecked(); method @Nullable public final android.content.Context getContext(); method @Nullable public final android.content.pm.PathPermission[] getPathPermissions(); method @Nullable public final String getReadPermission(); @@ -9511,6 +9519,7 @@ package android.content { method @Nullable public abstract android.net.Uri insert(@NonNull android.net.Uri, @Nullable android.content.ContentValues); method @Nullable public android.net.Uri insert(@NonNull android.net.Uri, @Nullable android.content.ContentValues, @Nullable android.os.Bundle); method protected boolean isTemporary(); + method public void onCallingPackageChanged(); method public void onConfigurationChanged(android.content.res.Configuration); method public abstract boolean onCreate(); method public void onLowMemory(); @@ -9625,13 +9634,13 @@ package android.content { ctor public ContentProviderResult(@NonNull android.net.Uri); ctor public ContentProviderResult(int); ctor public ContentProviderResult(@NonNull android.os.Bundle); - ctor public ContentProviderResult(@NonNull Exception); + ctor public ContentProviderResult(@NonNull Throwable); ctor public ContentProviderResult(android.os.Parcel); method public int describeContents(); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.content.ContentProviderResult> CREATOR; field @Nullable public final Integer count; - field @Nullable public final Exception exception; + field @Nullable public final Throwable exception; field @Nullable public final android.os.Bundle extras; field @Nullable public final android.net.Uri uri; } @@ -9819,6 +9828,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 @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 @CheckResult(suggest="#enforceCallingPermission(String,String)") public abstract int checkCallingPermission(@NonNull String); @@ -11351,6 +11361,9 @@ package android.content.pm { } public class CrossProfileApps { + method public boolean canInteractAcrossProfiles(); + method public boolean canRequestInteractAcrossProfiles(); + method @Nullable public android.content.Intent createRequestInteractAcrossProfilesIntent(); method @NonNull public android.graphics.drawable.Drawable getProfileSwitchingIconDrawable(@NonNull android.os.UserHandle); method @NonNull public CharSequence getProfileSwitchingLabel(@NonNull android.os.UserHandle); method @NonNull public java.util.List<android.os.UserHandle> getTargetUserProfiles(); @@ -11923,6 +11936,7 @@ package android.content.pm { field public static final String FEATURE_STRONGBOX_KEYSTORE = "android.hardware.strongbox_keystore"; field public static final String FEATURE_TELEPHONY = "android.hardware.telephony"; field public static final String FEATURE_TELEPHONY_CDMA = "android.hardware.telephony.cdma"; + field public static final String FEATURE_TELEPHONY_DATA = "android.hardware.telephony.data"; field public static final String FEATURE_TELEPHONY_EUICC = "android.hardware.telephony.euicc"; field public static final String FEATURE_TELEPHONY_GSM = "android.hardware.telephony.gsm"; field public static final String FEATURE_TELEPHONY_IMS = "android.hardware.telephony.ims"; @@ -13321,6 +13335,7 @@ package android.database.sqlite { method @Deprecated public String buildUnionSubQuery(String, String[], java.util.Set<java.lang.String>, int, String, String, String[], String, String); method public int delete(@NonNull android.database.sqlite.SQLiteDatabase, @Nullable String, @Nullable String[]); method @Nullable public android.database.sqlite.SQLiteDatabase.CursorFactory getCursorFactory(); + method @Nullable public java.util.Collection<java.util.regex.Pattern> getProjectionGreylist(); method @Nullable public java.util.Map<java.lang.String,java.lang.String> getProjectionMap(); method @Nullable public String getTables(); method public long insert(@NonNull android.database.sqlite.SQLiteDatabase, @NonNull android.content.ContentValues); @@ -13333,6 +13348,7 @@ package android.database.sqlite { method public android.database.Cursor query(android.database.sqlite.SQLiteDatabase, String[], String, String[], String, String, String, String, android.os.CancellationSignal); method public void setCursorFactory(@Nullable android.database.sqlite.SQLiteDatabase.CursorFactory); method public void setDistinct(boolean); + method public void setProjectionGreylist(@Nullable java.util.Collection<java.util.regex.Pattern>); method public void setProjectionMap(@Nullable java.util.Map<java.lang.String,java.lang.String>); method public void setStrict(boolean); method public void setStrictColumns(boolean); @@ -25779,8 +25795,8 @@ package android.media { method @Nullable public android.graphics.Bitmap getImageAtIndex(int); method @Nullable public android.graphics.Bitmap getPrimaryImage(@NonNull android.media.MediaMetadataRetriever.BitmapParams); method @Nullable public android.graphics.Bitmap getPrimaryImage(); - method @Nullable public android.graphics.Bitmap getScaledFrameAtTime(long, int, int, int); - method @Nullable public android.graphics.Bitmap getScaledFrameAtTime(long, int, int, int, @NonNull android.media.MediaMetadataRetriever.BitmapParams); + method @Nullable public android.graphics.Bitmap getScaledFrameAtTime(long, int, @IntRange(from=1) int, @IntRange(from=1) int); + method @Nullable public android.graphics.Bitmap getScaledFrameAtTime(long, int, @IntRange(from=1) int, @IntRange(from=1) int, @NonNull android.media.MediaMetadataRetriever.BitmapParams); method public void release(); method public void setDataSource(String) throws java.lang.IllegalArgumentException; method public void setDataSource(String, java.util.Map<java.lang.String,java.lang.String>) throws java.lang.IllegalArgumentException; @@ -28679,7 +28695,10 @@ package android.media.tv { method public int getVideoHeight(); method public float getVideoPixelAspectRatio(); method public int getVideoWidth(); + method public boolean isAudioDescription(); method public boolean isEncrypted(); + method public boolean isHardOfHearing(); + method public boolean isSpokenSubtitle(); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.TvTrackInfo> CREATOR; field public static final int TYPE_AUDIO = 0; // 0x0 @@ -28691,11 +28710,14 @@ package android.media.tv { ctor public TvTrackInfo.Builder(int, @NonNull String); method public android.media.tv.TvTrackInfo build(); method public android.media.tv.TvTrackInfo.Builder setAudioChannelCount(int); + method @NonNull public android.media.tv.TvTrackInfo.Builder setAudioDescription(boolean); method public android.media.tv.TvTrackInfo.Builder setAudioSampleRate(int); method public android.media.tv.TvTrackInfo.Builder setDescription(CharSequence); method @NonNull public android.media.tv.TvTrackInfo.Builder setEncrypted(boolean); method public android.media.tv.TvTrackInfo.Builder setExtra(android.os.Bundle); + method @NonNull public android.media.tv.TvTrackInfo.Builder setHardOfHearing(boolean); method public android.media.tv.TvTrackInfo.Builder setLanguage(String); + method @NonNull public android.media.tv.TvTrackInfo.Builder setSpokenSubtitle(boolean); method public android.media.tv.TvTrackInfo.Builder setVideoActiveFormatDescription(byte); method public android.media.tv.TvTrackInfo.Builder setVideoFrameRate(float); method public android.media.tv.TvTrackInfo.Builder setVideoHeight(int); @@ -29007,6 +29029,37 @@ package android.net { field @NonNull public static final android.os.Parcelable.Creator<android.net.CaptivePortal> CREATOR; } + public class ConnectivityDiagnosticsManager { + method public void registerConnectivityDiagnosticsCallback(@NonNull android.net.NetworkRequest, @NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback); + method public void unregisterConnectivityDiagnosticsCallback(@NonNull android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback); + field public static final int DETECTION_METHOD_DNS_EVENTS = 1; // 0x1 + field public static final int DETECTION_METHOD_TCP_METRICS = 2; // 0x2 + } + + public abstract static class ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback { + ctor public ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback(); + method public void onConnectivityReport(@NonNull android.net.ConnectivityDiagnosticsManager.ConnectivityReport); + method public void onDataStallSuspected(@NonNull android.net.ConnectivityDiagnosticsManager.DataStallReport); + method public void onNetworkConnectivityReported(@NonNull android.net.Network, boolean); + } + + public static class ConnectivityDiagnosticsManager.ConnectivityReport { + ctor public ConnectivityDiagnosticsManager.ConnectivityReport(@NonNull android.net.Network, long, @NonNull android.net.LinkProperties, @NonNull android.net.NetworkCapabilities, @NonNull android.os.PersistableBundle); + field @NonNull public final android.os.PersistableBundle additionalInfo; + field @NonNull public final android.net.LinkProperties linkProperties; + field @NonNull public final android.net.Network network; + field @NonNull public final android.net.NetworkCapabilities networkCapabilities; + field public final long reportTimestamp; + } + + public static class ConnectivityDiagnosticsManager.DataStallReport { + ctor public ConnectivityDiagnosticsManager.DataStallReport(@NonNull android.net.Network, long, int, @NonNull android.os.PersistableBundle); + field public final int detectionMethod; + field @NonNull public final android.net.Network network; + field public final long reportTimestamp; + field @NonNull public final android.os.PersistableBundle stallDetails; + } + public class ConnectivityManager { method public void addDefaultNetworkActiveListener(android.net.ConnectivityManager.OnNetworkActiveListener); method public boolean bindProcessToNetwork(@Nullable android.net.Network); @@ -29111,6 +29164,7 @@ package android.net { ctor public DhcpInfo(); method public int describeContents(); method public void writeToParcel(android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.DhcpInfo> CREATOR; field public int dns1; field public int dns2; field public int gateway; @@ -29477,6 +29531,7 @@ package android.net { method public android.net.NetworkRequest.Builder addCapability(int); method public android.net.NetworkRequest.Builder addTransportType(int); method public android.net.NetworkRequest build(); + method @NonNull public android.net.NetworkRequest.Builder clearCapabilities(); method public android.net.NetworkRequest.Builder removeCapability(int); method public android.net.NetworkRequest.Builder removeTransportType(int); method public android.net.NetworkRequest.Builder setNetworkSpecifier(String); @@ -30275,6 +30330,7 @@ package android.net.wifi { @Deprecated public static class WifiConfiguration.AuthAlgorithm { field @Deprecated public static final int LEAP = 2; // 0x2 field @Deprecated public static final int OPEN = 0; // 0x0 + field @Deprecated public static final int SAE = 3; // 0x3 field @Deprecated public static final int SHARED = 1; // 0x1 field @Deprecated public static final String[] strings; field @Deprecated public static final String varName = "auth_alg"; @@ -30352,6 +30408,7 @@ package android.net.wifi { method public String getPlmn(); method public String getRealm(); method @Deprecated public String getSubjectMatch(); + method public boolean isAuthenticationSimBased(); method public void setAltSubjectMatch(String); method public void setAnonymousIdentity(String); method public void setCaCertificate(@Nullable java.security.cert.X509Certificate); @@ -35676,7 +35733,9 @@ package android.os { method public int describeContents(); method @Nullable public android.os.PersistableBundle getPersistableBundle(@Nullable String); method public void putPersistableBundle(@Nullable String, @Nullable android.os.PersistableBundle); + method @NonNull public static android.os.PersistableBundle readFromStream(@NonNull java.io.InputStream) throws java.io.IOException; method public void writeToParcel(android.os.Parcel, int); + method public void writeToStream(@NonNull java.io.OutputStream) throws java.io.IOException; field @NonNull public static final android.os.Parcelable.Creator<android.os.PersistableBundle> CREATOR; field public static final android.os.PersistableBundle EMPTY; } @@ -35778,6 +35837,7 @@ package android.os { field public static final int THREAD_PRIORITY_URGENT_AUDIO = -19; // 0xffffffed field public static final int THREAD_PRIORITY_URGENT_DISPLAY = -8; // 0xfffffff8 field public static final int THREAD_PRIORITY_VIDEO = -10; // 0xfffffff6 + field public static final int WIFI_UID = 1010; // 0x3f2 } public abstract class ProxyFileDescriptorCallback { @@ -36012,6 +36072,7 @@ package android.os { method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public android.os.Bundle getUserRestrictions(android.os.UserHandle); method public boolean hasUserRestriction(String); method public boolean isDemoUser(); + method public boolean isManagedProfile(); method public boolean isQuietModeEnabled(android.os.UserHandle); method public boolean isSystemUser(); method public boolean isUserAGoat(); @@ -36344,15 +36405,22 @@ package android.os.storage { method public boolean isObbMounted(String); method public boolean mountObb(String, String, android.os.storage.OnObbStateChangeListener); method @NonNull public android.os.ParcelFileDescriptor openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback, android.os.Handler) throws java.io.IOException; + method public void registerStorageVolumeCallback(@NonNull java.util.concurrent.Executor, @NonNull android.os.storage.StorageManager.StorageVolumeCallback); method public void setCacheBehaviorGroup(java.io.File, boolean) throws java.io.IOException; method public void setCacheBehaviorTombstone(java.io.File, boolean) throws java.io.IOException; method public boolean unmountObb(String, boolean, android.os.storage.OnObbStateChangeListener); + method public void unregisterStorageVolumeCallback(@NonNull android.os.storage.StorageManager.StorageVolumeCallback); field public static final String ACTION_MANAGE_STORAGE = "android.os.storage.action.MANAGE_STORAGE"; field public static final String EXTRA_REQUESTED_BYTES = "android.os.storage.extra.REQUESTED_BYTES"; field public static final String EXTRA_UUID = "android.os.storage.extra.UUID"; field public static final java.util.UUID UUID_DEFAULT; } + public static class StorageManager.StorageVolumeCallback { + ctor public StorageManager.StorageVolumeCallback(); + method public void onStateChanged(@NonNull android.os.storage.StorageVolume); + } + public final class StorageVolume implements android.os.Parcelable { method @Deprecated @Nullable public android.content.Intent createAccessIntent(String); method @NonNull public android.content.Intent createOpenDocumentTreeIntent(); @@ -39468,7 +39536,9 @@ package android.provider { field public static final String ACTION_LOCALE_SETTINGS = "android.settings.LOCALE_SETTINGS"; field public static final String ACTION_LOCATION_SOURCE_SETTINGS = "android.settings.LOCATION_SOURCE_SETTINGS"; field public static final String ACTION_MANAGE_ALL_APPLICATIONS_SETTINGS = "android.settings.MANAGE_ALL_APPLICATIONS_SETTINGS"; + field public static final String ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION = "android.settings.MANAGE_ALL_FILES_ACCESS_PERMISSION"; field public static final String ACTION_MANAGE_APPLICATIONS_SETTINGS = "android.settings.MANAGE_APPLICATIONS_SETTINGS"; + field public static final String ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION = "android.settings.MANAGE_APP_ALL_FILES_ACCESS_PERMISSION"; field public static final String ACTION_MANAGE_DEFAULT_APPS_SETTINGS = "android.settings.MANAGE_DEFAULT_APPS_SETTINGS"; field public static final String ACTION_MANAGE_OVERLAY_PERMISSION = "android.settings.action.MANAGE_OVERLAY_PERMISSION"; field public static final String ACTION_MANAGE_UNKNOWN_APP_SOURCES = "android.settings.MANAGE_UNKNOWN_APP_SOURCES"; @@ -42612,9 +42682,13 @@ package android.service.voice { method public android.content.Intent createEnrollIntent(); method public android.content.Intent createReEnrollIntent(); method public android.content.Intent createUnEnrollIntent(); + method public int getParameter(int); method public int getSupportedRecognitionModes(); + method @Nullable public android.service.voice.AlwaysOnHotwordDetector.ModelParamRange queryParameter(int); + method public int setParameter(int, int); method public boolean startRecognition(int); method public boolean stopRecognition(); + field public static final int MODEL_PARAM_THRESHOLD_FACTOR = 0; // 0x0 field public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 2; // 0x2 field public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 1; // 0x1 field public static final int RECOGNITION_MODE_USER_IDENTIFICATION = 2; // 0x2 @@ -42639,6 +42713,11 @@ package android.service.voice { method @Nullable public byte[] getTriggerAudio(); } + public static final class AlwaysOnHotwordDetector.ModelParamRange { + method public int end(); + method public int start(); + } + public class VoiceInteractionService extends android.app.Service { ctor public VoiceInteractionService(); method public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(String, java.util.Locale, android.service.voice.AlwaysOnHotwordDetector.Callback); @@ -44954,6 +45033,7 @@ package android.telephony { field public static final String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED"; field public static final int DATA_CYCLE_THRESHOLD_DISABLED = -2; // 0xfffffffe field public static final int DATA_CYCLE_USE_PLATFORM_DEFAULT = -1; // 0xffffffff + field public static final String ENABLE_EAP_METHOD_PREFIX_BOOL = "enable_eap_method_prefix_bool"; field public static final String EXTRA_SLOT_INDEX = "android.telephony.extra.SLOT_INDEX"; field public static final String EXTRA_SUBSCRIPTION_INDEX = "android.telephony.extra.SUBSCRIPTION_INDEX"; field public static final String KEY_5G_NR_SSRSRP_THRESHOLDS_INT_ARRAY = "5g_nr_ssrsrp_thresholds_int_array"; @@ -45151,6 +45231,7 @@ package android.telephony { public static final class CarrierConfigManager.Ims { field public static final String KEY_PREFIX = "ims."; + field public static final String KEY_WIFI_OFF_DEFERRING_TIME_INT = "ims.wifi_off_deferring_time_int"; } public abstract class CellIdentity implements android.os.Parcelable { @@ -45655,6 +45736,7 @@ package android.telephony { method public String getOperatorNumeric(); method public boolean getRoaming(); method public int getState(); + method public boolean isSearching(); method public void setIsManualSelection(boolean); method public void setOperatorName(String, String, String); method public void setRoaming(boolean); @@ -45913,6 +45995,7 @@ package android.telephony { public class SubscriptionManager { method public void addOnOpportunisticSubscriptionsChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.SubscriptionManager.OnOpportunisticSubscriptionsChangedListener); method public void addOnSubscriptionsChangedListener(android.telephony.SubscriptionManager.OnSubscriptionsChangedListener); + method public void addOnSubscriptionsChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.SubscriptionManager.OnSubscriptionsChangedListener); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void addSubscriptionsIntoGroup(@NonNull java.util.List<java.lang.Integer>, @NonNull android.os.ParcelUuid); method public boolean canManageSubscription(android.telephony.SubscriptionInfo); method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.os.ParcelUuid createSubscriptionGroup(@NonNull java.util.List<java.lang.Integer>); @@ -46071,12 +46154,12 @@ package android.telephony { method public android.net.Uri getVoicemailRingtoneUri(android.telecom.PhoneAccountHandle); method public boolean hasCarrierPrivileges(); method public boolean hasIccCard(); - method public boolean iccCloseLogicalChannel(int); - method public byte[] iccExchangeSimIO(int, int, int, int, int, String); + method @Deprecated public boolean iccCloseLogicalChannel(int); + method @Deprecated public byte[] iccExchangeSimIO(int, int, int, int, int, String); method @Deprecated public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(String); - method public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(String, int); - method public String iccTransmitApduBasicChannel(int, int, int, int, int, String); - method public String iccTransmitApduLogicalChannel(int, int, int, int, int, int, String); + method @Deprecated public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(String, int); + method @Deprecated public String iccTransmitApduBasicChannel(int, int, int, int, int, String); + method @Deprecated public String iccTransmitApduLogicalChannel(int, int, int, int, int, int, String); method public boolean isConcurrentVoiceAndDataSupported(); method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean isDataEnabled(); method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isDataRoamingEnabled(); @@ -46094,7 +46177,7 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void requestCellInfoUpdate(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback); method @RequiresPermission(allOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public android.telephony.NetworkScan requestNetworkScan(android.telephony.NetworkScanRequest, java.util.concurrent.Executor, android.telephony.TelephonyScanManager.NetworkScanCallback); method public void sendDialerSpecialCode(String); - method public String sendEnvelopeWithStatus(String); + method @Deprecated public String sendEnvelopeWithStatus(String); method @RequiresPermission(android.Manifest.permission.CALL_PHONE) public void sendUssdRequest(String, android.telephony.TelephonyManager.UssdResponseCallback, android.os.Handler); method public void sendVisualVoicemailSms(String, int, String, android.app.PendingIntent); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabled(boolean); @@ -51536,9 +51619,10 @@ package android.view { method public boolean dispatchUnhandledMove(android.view.View, int); method protected void dispatchVisibilityChanged(@NonNull android.view.View, int); method public void dispatchWindowFocusChanged(boolean); - method public void dispatchWindowInsetsAnimationFinished(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation); + method public void dispatchWindowInsetsAnimationFinish(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation); + method public void dispatchWindowInsetsAnimationPrepare(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation); method @NonNull public android.view.WindowInsets dispatchWindowInsetsAnimationProgress(@NonNull android.view.WindowInsets); - method @NonNull public android.view.WindowInsetsAnimationCallback.AnimationBounds dispatchWindowInsetsAnimationStarted(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation, @NonNull android.view.WindowInsetsAnimationCallback.AnimationBounds); + method @NonNull public android.view.WindowInsetsAnimationCallback.AnimationBounds dispatchWindowInsetsAnimationStart(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation, @NonNull android.view.WindowInsetsAnimationCallback.AnimationBounds); method public void dispatchWindowSystemUiVisiblityChanged(int); method public void dispatchWindowVisibilityChanged(int); method @CallSuper public void draw(android.graphics.Canvas); @@ -53218,9 +53302,10 @@ package android.view { } public interface WindowInsetsAnimationCallback { - method public default void onFinished(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation); + method public default void onFinish(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation); + method public default void onPrepare(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation); method @NonNull public android.view.WindowInsets onProgress(@NonNull android.view.WindowInsets); - method @NonNull public default android.view.WindowInsetsAnimationCallback.AnimationBounds onStarted(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation, @NonNull android.view.WindowInsetsAnimationCallback.AnimationBounds); + method @NonNull public default android.view.WindowInsetsAnimationCallback.AnimationBounds onStart(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation, @NonNull android.view.WindowInsetsAnimationCallback.AnimationBounds); } public static final class WindowInsetsAnimationCallback.AnimationBounds { diff --git a/api/system-current.txt b/api/system-current.txt index 82a9682617e1..803b4f04d2cf 100755 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -152,6 +152,7 @@ package android { field public static final String PROVIDE_RESOLVER_RANKER_SERVICE = "android.permission.PROVIDE_RESOLVER_RANKER_SERVICE"; field public static final String PROVIDE_TRUST_AGENT = "android.permission.PROVIDE_TRUST_AGENT"; field public static final String QUERY_TIME_ZONE_RULES = "android.permission.QUERY_TIME_ZONE_RULES"; + field public static final String RADIO_SCAN_WITHOUT_LOCATION = "android.permission.RADIO_SCAN_WITHOUT_LOCATION"; field public static final String READ_ACTIVE_EMERGENCY_SESSION = "android.permission.READ_ACTIVE_EMERGENCY_SESSION"; field public static final String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS"; field public static final String READ_CONTENT_RATING_SYSTEMS = "android.permission.READ_CONTENT_RATING_SYSTEMS"; @@ -413,6 +414,16 @@ package android.app { field public static final int UID_STATE_TOP = 200; // 0xc8 } + public static final class AppOpsManager.HistoricalFeatureOps implements android.os.Parcelable { + method public int describeContents(); + method @Nullable public String getFeatureId(); + method @Nullable public android.app.AppOpsManager.HistoricalOp getOp(@NonNull String); + method @NonNull public android.app.AppOpsManager.HistoricalOp getOpAt(@IntRange(from=0) int); + method @IntRange(from=0) public int getOpCount(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalFeatureOps> CREATOR; + } + public static final class AppOpsManager.HistoricalOp implements android.os.Parcelable { method public int describeContents(); method public long getAccessCount(int, int, int); @@ -446,6 +457,7 @@ package android.app { public static final class AppOpsManager.HistoricalOpsRequest.Builder { ctor public AppOpsManager.HistoricalOpsRequest.Builder(long, long); method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest build(); + method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setFeatureId(@Nullable String); method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setFlags(int); method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setOpNames(@Nullable java.util.List<java.lang.String>); method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setPackageName(@Nullable String); @@ -454,6 +466,9 @@ package android.app { public static final class AppOpsManager.HistoricalPackageOps implements android.os.Parcelable { method public int describeContents(); + method @IntRange(from=0) public int getFeatureCount(); + method @Nullable public android.app.AppOpsManager.HistoricalFeatureOps getFeatureOps(@NonNull String); + method @NonNull public android.app.AppOpsManager.HistoricalFeatureOps getFeatureOpsAt(@IntRange(from=0) int); method @Nullable public android.app.AppOpsManager.HistoricalOp getOp(@NonNull String); method @NonNull public android.app.AppOpsManager.HistoricalOp getOpAt(@IntRange(from=0) int); method @IntRange(from=0) public int getOpCount(); @@ -553,6 +568,7 @@ package android.app { } public class DownloadManager { + method @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE) public void onMediaStoreDownloadsDeleted(@NonNull android.util.LongSparseArray<java.lang.String>); field public static final String ACTION_DOWNLOAD_COMPLETED = "android.intent.action.DOWNLOAD_COMPLETED"; } @@ -663,6 +679,7 @@ package android.app { public class StatusBarManager { method @NonNull @RequiresPermission(android.Manifest.permission.STATUS_BAR) public android.app.StatusBarManager.DisableInfo getDisableInfo(); method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setDisabledForSetup(boolean); + method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setDisabledForSimNetworkLock(boolean); } public static final class StatusBarManager.DisableInfo { @@ -785,6 +802,7 @@ package android.app.admin { method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioned(); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioningConfigApplied(); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isManagedKiosk(); + method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isOrganizationOwnedDeviceWithManagedProfile(); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isUnattendedManagedKiosk(); method @RequiresPermission("android.permission.NOTIFY_PENDING_SYSTEM_UPDATE") public void notifyPendingSystemUpdate(long); method @RequiresPermission("android.permission.NOTIFY_PENDING_SYSTEM_UPDATE") public void notifyPendingSystemUpdate(long, boolean); @@ -1518,12 +1536,32 @@ package android.bluetooth { method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int); } + public final class BluetoothHidDevice implements android.bluetooth.BluetoothProfile { + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int); + } + + public final class BluetoothHidHost implements android.bluetooth.BluetoothProfile { + method @NonNull public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices(); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice); + method public int getConnectionState(@Nullable android.bluetooth.BluetoothDevice); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice, int); + field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED"; + } + + public final class BluetoothMap implements android.bluetooth.BluetoothProfile { + method @NonNull public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices(); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice, int); + field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.map.profile.action.CONNECTION_STATE_CHANGED"; + } + public final class BluetoothPan implements android.bluetooth.BluetoothProfile { method protected void finalize(); method @NonNull public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices(); method public int getConnectionState(@Nullable android.bluetooth.BluetoothDevice); method public boolean isTetheringOn(); method public void setBluetoothTethering(boolean); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int); field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED"; field public static final String EXTRA_LOCAL_ROLE = "android.bluetooth.pan.extra.LOCAL_ROLE"; field public static final int LOCAL_NAP_ROLE = 1; // 0x1 @@ -1535,6 +1573,7 @@ package android.bluetooth { public class BluetoothPbap implements android.bluetooth.BluetoothProfile { method public int getConnectionState(@Nullable android.bluetooth.BluetoothDevice); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int); field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED"; } @@ -1629,17 +1668,22 @@ package android.content { method @NonNull public final android.os.UserHandle getSendingUser(); } + public abstract class ContentProvider implements android.content.ComponentCallbacks2 { + method public int checkUriPermission(@NonNull android.net.Uri, int, int); + } + public class ContentProviderClient implements java.lang.AutoCloseable { method @RequiresPermission(android.Manifest.permission.REMOVE_TASKS) public void setDetectNotResponding(long); } public abstract class ContentResolver { + method @NonNull public static android.net.Uri decodeFromFile(@NonNull java.io.File); + method @NonNull public static java.io.File encodeToFile(@NonNull android.net.Uri); method @Nullable @RequiresPermission("android.permission.CACHE_CONTENT") public android.os.Bundle getCache(@NonNull android.net.Uri); method @RequiresPermission("android.permission.CACHE_CONTENT") public void putCache(@NonNull android.net.Uri, @Nullable android.os.Bundle); } public abstract class Context { - method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public boolean bindServiceAsUser(@RequiresPermission android.content.Intent, android.content.ServiceConnection, int, android.os.UserHandle); method @NonNull public android.content.Context createContextAsUser(@NonNull android.os.UserHandle, int); method public abstract android.content.Context createCredentialProtectedStorageContext(); method @NonNull public android.content.Context createPackageContextAsUser(@NonNull String, int, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException; @@ -1660,6 +1704,7 @@ package android.content { field public static final String EUICC_CARD_SERVICE = "euicc_card"; field public static final String HDMI_CONTROL_SERVICE = "hdmi_control"; field public static final String NETD_SERVICE = "netd"; + field public static final String NETWORK_POLICY_SERVICE = "netpolicy"; field public static final String NETWORK_SCORE_SERVICE = "network_score"; field public static final String OEM_LOCK_SERVICE = "oem_lock"; field public static final String PERMISSION_SERVICE = "permission"; @@ -1673,6 +1718,7 @@ package android.content { field public static final String TELEPHONY_REGISTRY_SERVICE = "telephony_registry"; field public static final String TETHERING_SERVICE = "tethering"; field public static final String VR_SERVICE = "vrmanager"; + field public static final String WIFI_COND_SERVICE = "wificond"; field @Deprecated public static final String WIFI_RTT_SERVICE = "rttmanager"; field public static final String WIFI_SCANNING_SERVICE = "wifiscanner"; } @@ -1746,6 +1792,7 @@ package android.content { field @Deprecated public static final String EXTRA_SIM_STATE = "ss"; field public static final String EXTRA_UNKNOWN_INSTANT_APP = "android.intent.extra.UNKNOWN_INSTANT_APP"; field public static final String EXTRA_VERIFICATION_BUNDLE = "android.intent.extra.VERIFICATION_BUNDLE"; + field public static final int FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT = 67108864; // 0x4000000 field public static final String METADATA_SETUP_VERSION = "android.SETUP_VERSION"; field @Deprecated public static final String SIM_ABSENT_ON_PERM_DISABLED = "PERM_DISABLED"; field @Deprecated public static final String SIM_LOCKED_NETWORK = "NETWORK"; @@ -2117,6 +2164,7 @@ package android.content.pm { field public static final String EXTRA_REQUEST_PERMISSIONS_NAMES = "android.content.pm.extra.REQUEST_PERMISSIONS_NAMES"; field public static final String EXTRA_REQUEST_PERMISSIONS_RESULTS = "android.content.pm.extra.REQUEST_PERMISSIONS_RESULTS"; field public static final String FEATURE_BROADCAST_RADIO = "android.hardware.broadcastradio"; + field public static final String FEATURE_REBOOT_ESCROW = "android.hardware.reboot_escrow"; field public static final String FEATURE_TELEPHONY_CARRIERLOCK = "android.hardware.telephony.carrierlock"; field public static final int FLAG_PERMISSION_APPLY_RESTRICTION = 16384; // 0x4000 field public static final int FLAG_PERMISSION_GRANTED_BY_DEFAULT = 32; // 0x20 @@ -3510,7 +3558,6 @@ package android.hardware.soundtrigger { } public static final class SoundTrigger.ModelParamRange implements android.os.Parcelable { - method public int describeContents(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.ModelParamRange> CREATOR; field public final int end; @@ -3531,6 +3578,7 @@ package android.hardware.soundtrigger { field public final int powerConsumptionMw; field public final int recognitionModes; field public final boolean returnsTriggerInEvent; + field @NonNull public final String supportedModelArch; field public final boolean supportsCaptureTransition; field public final boolean supportsConcurrentCapture; field @NonNull public final java.util.UUID uuid; @@ -3553,9 +3601,17 @@ package android.hardware.usb { } public class UsbManager { + method @RequiresPermission(android.Manifest.permission.MANAGE_USB) public long getCurrentFunctions(); method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_USB) public java.util.List<android.hardware.usb.UsbPort> getPorts(); method @RequiresPermission(android.Manifest.permission.MANAGE_USB) public void grantPermission(android.hardware.usb.UsbDevice, String); + method @RequiresPermission(android.Manifest.permission.MANAGE_USB) public void setCurrentFunctions(long); field @RequiresPermission(android.Manifest.permission.MANAGE_USB) public static final String ACTION_USB_PORT_CHANGED = "android.hardware.usb.action.USB_PORT_CHANGED"; + field public static final String ACTION_USB_STATE = "android.hardware.usb.action.USB_STATE"; + field public static final long FUNCTION_NONE = 0L; // 0x0L + field public static final long FUNCTION_RNDIS = 32L; // 0x20L + field public static final String USB_CONFIGURED = "configured"; + field public static final String USB_CONNECTED = "connected"; + field public static final String USB_FUNCTION_RNDIS = "rndis"; } public final class UsbPort { @@ -4141,6 +4197,14 @@ package android.media { } +package android.media.audiofx { + + public class AudioEffect { + ctor @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public AudioEffect(@NonNull java.util.UUID, @NonNull android.media.AudioDeviceAddress); + } + +} + package android.media.audiopolicy { public class AudioMix { @@ -4260,7 +4324,7 @@ package android.media.session { } public static interface MediaSessionManager.OnMediaKeyEventDispatchedListener { - method public default void onMediaKeyEventDispatched(@NonNull android.view.KeyEvent, @NonNull String, @NonNull android.media.session.MediaSession.Token); + method public default void onMediaKeyEventDispatched(@NonNull android.view.KeyEvent, @NonNull String, @Nullable android.media.session.MediaSession.Token); } public static interface MediaSessionManager.OnMediaKeyEventSessionChangedListener { @@ -4316,9 +4380,9 @@ package android.media.soundtrigger { method public int getDetectionServiceOperationsTimeout(); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.media.soundtrigger.SoundTriggerManager.Model getModel(java.util.UUID); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.hardware.soundtrigger.SoundTrigger.ModuleProperties getModuleProperties(); - method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int getParameter(@NonNull java.util.UUID, int) throws java.lang.IllegalArgumentException, java.lang.UnsupportedOperationException; + method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int getParameter(@NonNull java.util.UUID, int); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.hardware.soundtrigger.SoundTrigger.ModelParamRange queryParameter(@Nullable java.util.UUID, int); - method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int setParameter(@Nullable java.util.UUID, int, int) throws java.lang.IllegalArgumentException, java.lang.UnsupportedOperationException; + method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int setParameter(@Nullable java.util.UUID, int, int); method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public void updateModel(android.media.soundtrigger.SoundTriggerManager.Model); } @@ -4552,6 +4616,36 @@ package android.media.tv.tuner { public class Tuner.Descrambler { } + public class Tuner.Filter { + } + + public static interface Tuner.FilterCallback { + method public void onFilterEvent(@NonNull android.media.tv.tuner.Tuner.Filter, @NonNull android.media.tv.tuner.filter.FilterEvent[]); + method public void onFilterStatusChanged(@NonNull android.media.tv.tuner.Tuner.Filter, int); + } + + public final class TunerConstants { + field public static final int FILTER_STATUS_DATA_READY = 1; // 0x1 + field public static final int FILTER_STATUS_HIGH_WATER = 4; // 0x4 + field public static final int FILTER_STATUS_LOW_WATER = 2; // 0x2 + field public static final int FILTER_STATUS_OVERFLOW = 8; // 0x8 + } + +} + +package android.media.tv.tuner.filter { + + public abstract class FilterEvent { + ctor public FilterEvent(); + } + + public class SectionEvent extends android.media.tv.tuner.filter.FilterEvent { + method public int getDataLength(); + method public int getSectionNumber(); + method public int getTableId(); + method public int getVersion(); + } + } package android.metrics { @@ -4614,6 +4708,7 @@ package android.net { method @Deprecated @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public String getCaptivePortalServerUrl(); method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void getLatestTetheringEntitlementResult(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEntitlementResultListener); method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public boolean isTetheringSupported(); + method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public int registerNetworkProvider(@NonNull android.net.NetworkProvider); method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEventCallback); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_AIRPLANE_MODE, android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void setAirplaneMode(boolean); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public boolean shouldAvoidBadWifi(); @@ -4621,6 +4716,7 @@ package android.net { method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback); method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback, android.os.Handler); method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void stopTethering(int); + method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void unregisterNetworkProvider(@NonNull android.net.NetworkProvider); method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void unregisterTetheringEventCallback(@NonNull android.net.ConnectivityManager.OnTetheringEventCallback); field public static final String EXTRA_CAPTIVE_PORTAL_PROBE_SPEC = "android.net.extra.CAPTIVE_PORTAL_PROBE_SPEC"; field public static final String EXTRA_CAPTIVE_PORTAL_USER_AGENT = "android.net.extra.CAPTIVE_PORTAL_USER_AGENT"; @@ -4760,6 +4856,7 @@ package android.net { public final class MatchAllNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable { ctor public MatchAllNetworkSpecifier(); method public int describeContents(); + method public boolean satisfiedBy(android.net.NetworkSpecifier); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.net.MatchAllNetworkSpecifier> CREATOR; } @@ -4771,6 +4868,7 @@ package android.net { } public final class NetworkCapabilities implements android.os.Parcelable { + method public boolean deduceRestrictedCapability(); method @NonNull public int[] getTransportTypes(); method public boolean satisfiedByNetworkCapabilities(@Nullable android.net.NetworkCapabilities); method @NonNull public android.net.NetworkCapabilities setSSID(@Nullable String); @@ -4790,6 +4888,17 @@ package android.net { field public final android.net.WifiKey wifiKey; } + public class NetworkProvider { + ctor public NetworkProvider(@NonNull android.content.Context, @NonNull android.os.Looper, @NonNull String); + method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void declareNetworkRequestUnfulfillable(@NonNull android.net.NetworkRequest); + method @Nullable public android.os.Messenger getMessenger(); + method @NonNull public String getName(); + method public int getProviderId(); + method public void onNetworkRequested(@NonNull android.net.NetworkRequest, int, int); + method public void onRequestWithdrawn(@NonNull android.net.NetworkRequest); + field public static final int ID_NONE = -1; // 0xffffffff + } + public abstract class NetworkRecommendationProvider { ctor public NetworkRecommendationProvider(android.content.Context, java.util.concurrent.Executor); method public final android.os.IBinder getBinder(); @@ -4823,10 +4932,43 @@ package android.net { method public void updateScores(@NonNull java.util.List<android.net.ScoredNetwork>); } + public abstract class NetworkSpecifier { + method public void assertValidFromUid(int); + method @Nullable public android.net.NetworkSpecifier redact(); + method public abstract boolean satisfiedBy(@Nullable android.net.NetworkSpecifier); + } + public class NetworkStack { field public static final String PERMISSION_MAINLINE_NETWORK_STACK = "android.permission.MAINLINE_NETWORK_STACK"; } + public final class NetworkStats implements 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 addValues(@NonNull android.net.NetworkStats.Entry); + method public int describeContents(); + 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; + field public static final int DEFAULT_NETWORK_NO = 0; // 0x0 + field public static final int DEFAULT_NETWORK_YES = 1; // 0x1 + field @Nullable public static final String IFACE_ALL; + field public static final String IFACE_VT = "vt_data0"; + field public static final int METERED_NO = 0; // 0x0 + field public static final int METERED_YES = 1; // 0x1 + field public static final int ROAMING_NO = 0; // 0x0 + field public static final int ROAMING_YES = 1; // 0x1 + field public static final int SET_DEFAULT = 0; // 0x0 + field public static final int SET_FOREGROUND = 1; // 0x1 + field public static final int TAG_NONE = 0; // 0x0 + field public static final int UID_ALL = -1; // 0xffffffff + field public static final int UID_TETHERING = -5; // 0xfffffffb + } + + public static class NetworkStats.Entry { + ctor public NetworkStats.Entry(@Nullable String, int, int, int, int, int, int, long, long, long, long, long); + } + public final class RouteInfo implements android.os.Parcelable { ctor public RouteInfo(@Nullable android.net.IpPrefix, @Nullable java.net.InetAddress, @Nullable String, int); method public int getType(); @@ -4893,6 +5035,7 @@ package android.net { public final class StringNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable { ctor public StringNetworkSpecifier(@NonNull String); method public int describeContents(); + method public boolean satisfiedBy(android.net.NetworkSpecifier); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.net.StringNetworkSpecifier> CREATOR; field @NonNull public final String specifier; @@ -5417,6 +5560,25 @@ package android.net.metrics { } +package android.net.netstats.provider { + + public abstract class AbstractNetworkStatsProvider { + ctor public AbstractNetworkStatsProvider(); + method public abstract void requestStatsUpdate(int); + method public abstract void setAlert(long); + method public abstract void setLimit(@NonNull String, long); + field public static final int QUOTA_UNLIMITED = -1; // 0xffffffff + } + + public class NetworkStatsProviderCallback { + method public void onAlertReached(); + method public void onLimitReached(); + method public void onStatsUpdated(int, @NonNull android.net.NetworkStats, @NonNull android.net.NetworkStats); + method public void unregister(); + } + +} + package android.net.util { public final class SocketUtils { @@ -5676,6 +5838,7 @@ package android.net.wifi { field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.SoftApCapability> CREATOR; field public static final int SOFTAP_FEATURE_ACS_OFFLOAD = 1; // 0x1 field public static final int SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT = 2; // 0x2 + field public static final int SOFTAP_FEATURE_WPA3_SAE = 4; // 0x4 } public final class SoftApConfiguration implements android.os.Parcelable { @@ -5684,9 +5847,10 @@ package android.net.wifi { method @Nullable public android.net.MacAddress getBssid(); method public int getChannel(); method public int getMaxNumberOfClients(); + method @Nullable public String getPassphrase(); method public int getSecurityType(); + method public int getShutdownTimeoutMillis(); method @Nullable public String getSsid(); - method @Nullable public String getWpa2Passphrase(); method public boolean isHiddenSsid(); method public void writeToParcel(@NonNull android.os.Parcel, int); field public static final int BAND_2GHZ = 1; // 0x1 @@ -5696,6 +5860,8 @@ package android.net.wifi { field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.SoftApConfiguration> CREATOR; field public static final int SECURITY_TYPE_OPEN = 0; // 0x0 field public static final int SECURITY_TYPE_WPA2_PSK = 1; // 0x1 + field public static final int SECURITY_TYPE_WPA3_SAE = 3; // 0x3 + field public static final int SECURITY_TYPE_WPA3_SAE_TRANSITION = 2; // 0x2 } public static final class SoftApConfiguration.Builder { @@ -5707,8 +5873,9 @@ package android.net.wifi { method @NonNull public android.net.wifi.SoftApConfiguration.Builder setChannel(int, int); method @NonNull public android.net.wifi.SoftApConfiguration.Builder setHiddenSsid(boolean); method @NonNull public android.net.wifi.SoftApConfiguration.Builder setMaxNumberOfClients(int); + method @NonNull public android.net.wifi.SoftApConfiguration.Builder setPassphrase(@Nullable String, int); + method @NonNull public android.net.wifi.SoftApConfiguration.Builder setShutdownTimeoutMillis(int); method @NonNull public android.net.wifi.SoftApConfiguration.Builder setSsid(@Nullable String); - method @NonNull public android.net.wifi.SoftApConfiguration.Builder setWpa2Passphrase(@Nullable String); } public final class SoftApInfo implements android.os.Parcelable { @@ -5851,6 +6018,7 @@ package android.net.wifi { public class WifiManager { method @RequiresPermission(android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE) public void addOnWifiUsabilityStatsListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.OnWifiUsabilityStatsListener); method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void allowAutojoin(int, boolean); + method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void allowAutojoinPasspoint(@NonNull String, boolean); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void connect(@NonNull android.net.wifi.WifiConfiguration, @Nullable android.net.wifi.WifiManager.ActionListener); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void connect(int, @Nullable android.net.wifi.WifiManager.ActionListener); method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void disable(int, @Nullable android.net.wifi.WifiManager.ActionListener); @@ -5870,6 +6038,7 @@ package android.net.wifi { method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public void getWifiActivityEnergyInfoAsync(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.OnWifiActivityEnergyInfoListener); method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public android.net.wifi.WifiConfiguration getWifiApConfiguration(); method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public int getWifiApState(); + method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.List<android.net.wifi.WifiConfiguration> getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(@NonNull java.util.List<android.net.wifi.ScanResult>); method public boolean isApMacRandomizationSupported(); method public boolean isConnectedMacRandomizationSupported(); method @Deprecated public boolean isDeviceToDeviceRttSupported(); @@ -5927,6 +6096,7 @@ package android.net.wifi { field public static final String EXTRA_OSU_NETWORK = "android.net.wifi.extra.OSU_NETWORK"; field public static final String EXTRA_PREVIOUS_WIFI_AP_STATE = "previous_wifi_state"; field public static final String EXTRA_URL = "android.net.wifi.extra.URL"; + field public static final String EXTRA_WIFI_AP_FAILURE_REASON = "android.net.wifi.extra.WIFI_AP_FAILURE_REASON"; field public static final String EXTRA_WIFI_AP_INTERFACE_NAME = "android.net.wifi.extra.WIFI_AP_INTERFACE_NAME"; field public static final String EXTRA_WIFI_AP_MODE = "android.net.wifi.extra.WIFI_AP_MODE"; field public static final String EXTRA_WIFI_AP_STATE = "wifi_state"; @@ -6005,6 +6175,10 @@ package android.net.wifi { field public int numUsage; } + public final class WifiNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable { + method public boolean satisfiedBy(android.net.NetworkSpecifier); + } + public static final class WifiNetworkSuggestion.Builder { method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_CARRIER_PROVISIONING) public android.net.wifi.WifiNetworkSuggestion.Builder setCarrierId(int); } @@ -6191,6 +6365,10 @@ package android.net.wifi.aware { method @Deprecated public android.net.NetworkSpecifier createNetworkSpecifierPmk(@NonNull android.net.wifi.aware.PeerHandle, @NonNull byte[]); } + public final class WifiAwareNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable { + method public boolean satisfiedBy(android.net.NetworkSpecifier); + } + public class WifiAwareSession implements java.lang.AutoCloseable { method public android.net.NetworkSpecifier createNetworkSpecifierPmk(int, @NonNull byte[], @NonNull byte[]); } @@ -6207,6 +6385,10 @@ package android.net.wifi.hotspot2 { field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.OsuProvider> CREATOR; } + public final class PasspointConfiguration implements android.os.Parcelable { + method public boolean isAutoJoinEnabled(); + } + public abstract class ProvisioningCallback { ctor public ProvisioningCallback(); method public abstract void onProvisioningComplete(); @@ -6873,6 +7055,10 @@ package android.os { method public boolean hasSingleFileDescriptor(); } + public class ParcelFileDescriptor implements java.io.Closeable android.os.Parcelable { + method @NonNull public static android.os.ParcelFileDescriptor wrap(@NonNull android.os.ParcelFileDescriptor, @NonNull android.os.Handler, @NonNull android.os.ParcelFileDescriptor.OnCloseListener) throws java.io.IOException; + } + public final class PowerManager { method @RequiresPermission(allOf={android.Manifest.permission.READ_DREAM_STATE, android.Manifest.permission.WRITE_DREAM_STATE}) public void dream(long); method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public boolean forceSuspend(); @@ -6905,9 +7091,12 @@ package android.os { public class RecoverySystem { method @RequiresPermission(android.Manifest.permission.RECOVERY) public static void cancelScheduledUpdate(android.content.Context) throws java.io.IOException; + method @RequiresPermission(android.Manifest.permission.RECOVERY) public static boolean clearPrepareForUnattendedUpdate(@NonNull android.content.Context) throws java.io.IOException; method @RequiresPermission(android.Manifest.permission.RECOVERY) public static void installPackage(android.content.Context, java.io.File, boolean) throws java.io.IOException; + method @RequiresPermission(android.Manifest.permission.RECOVERY) public static void prepareForUnattendedUpdate(@NonNull android.content.Context, @NonNull String, @Nullable android.content.IntentSender) throws java.io.IOException; method @RequiresPermission(android.Manifest.permission.RECOVERY) public static void processPackage(android.content.Context, java.io.File, android.os.RecoverySystem.ProgressListener, android.os.Handler) throws java.io.IOException; method @RequiresPermission(android.Manifest.permission.RECOVERY) public static void processPackage(android.content.Context, java.io.File, android.os.RecoverySystem.ProgressListener) throws java.io.IOException; + method @RequiresPermission(android.Manifest.permission.RECOVERY) public static boolean rebootAndApply(@NonNull android.content.Context, @NonNull String, @NonNull String) throws java.io.IOException; method @RequiresPermission(allOf={android.Manifest.permission.RECOVERY, android.Manifest.permission.REBOOT}) public static void rebootWipeAb(android.content.Context, java.io.File, String) throws java.io.IOException; method @RequiresPermission(android.Manifest.permission.RECOVERY) public static void scheduleUpdateOnBoot(android.content.Context, java.io.File) throws java.io.IOException; method public static boolean verifyPackageCompatibility(java.io.File) throws java.io.IOException; @@ -6978,7 +7167,22 @@ package android.os { } public class TelephonyServiceManager { + method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getCarrierConfigServiceRegisterer(); + method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getEuiccCardControllerServiceRegisterer(); + method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getEuiccControllerService(); + method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getIccPhoneBookServiceRegisterer(); + method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getNetworkPolicyServiceRegisterer(); + method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getOpportunisticNetworkServiceRegisterer(); + method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getPackageManagerServiceRegisterer(); + method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getPermissionManagerServiceRegisterer(); + method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getPhoneSubServiceRegisterer(); + method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getSmsServiceRegisterer(); + method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getSubscriptionServiceRegisterer(); + method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getTelephonyImsServiceRegisterer(); + method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getTelephonyRcsMessageServiceRegisterer(); + method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getTelephonyRegistryServiceRegisterer(); method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getTelephonyServiceRegisterer(); + method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getWindowServiceRegisterer(); } public static class TelephonyServiceManager.ServiceNotFoundException extends java.lang.Exception { @@ -6994,11 +7198,13 @@ package android.os { public class UpdateEngine { ctor public UpdateEngine(); + method @NonNull public android.os.UpdateEngine.AllocateSpaceResult allocateSpace(@NonNull String, @NonNull String[]); method public void applyPayload(String, long, long, String[]); - method public void applyPayload(@NonNull android.os.ParcelFileDescriptor, long, long, @NonNull String[]); + method public void applyPayload(@NonNull android.content.res.AssetFileDescriptor, @NonNull String[]); method public boolean bind(android.os.UpdateEngineCallback, android.os.Handler); method public boolean bind(android.os.UpdateEngineCallback); method public void cancel(); + method public int cleanupAppliedPayload(); method public void resetStatus(); method public void resume(); method public void suspend(); @@ -7006,14 +7212,21 @@ package android.os { method public boolean verifyPayloadMetadata(String); } + public static final class UpdateEngine.AllocateSpaceResult { + method public int errorCode(); + method public long freeSpaceRequired(); + } + public static final class UpdateEngine.ErrorCodeConstants { ctor public UpdateEngine.ErrorCodeConstants(); + field public static final int DEVICE_CORRUPTED = 61; // 0x3d field public static final int DOWNLOAD_PAYLOAD_VERIFICATION_ERROR = 12; // 0xc field public static final int DOWNLOAD_TRANSFER_ERROR = 9; // 0x9 field public static final int ERROR = 1; // 0x1 field public static final int FILESYSTEM_COPIER_ERROR = 4; // 0x4 field public static final int INSTALL_DEVICE_OPEN_ERROR = 7; // 0x7 field public static final int KERNEL_DEVICE_OPEN_ERROR = 8; // 0x8 + field public static final int NOT_ENOUGH_SPACE = 60; // 0x3c field public static final int PAYLOAD_HASH_MISMATCH_ERROR = 10; // 0xa field public static final int PAYLOAD_MISMATCHED_TYPE_ERROR = 6; // 0x6 field public static final int PAYLOAD_SIZE_MISMATCH_ERROR = 11; // 0xb @@ -7072,7 +7285,6 @@ package android.os { method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean hasUserRestrictionForUser(@NonNull String, @NonNull android.os.UserHandle); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isAdminUser(); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isGuestUser(); - method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isManagedProfile(); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isManagedProfile(int); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isPrimaryUser(); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isRestrictedProfile(); @@ -7249,6 +7461,10 @@ package android.os.storage { field @RequiresPermission(android.Manifest.permission.ALLOCATE_AGGRESSIVE) public static final int FLAG_ALLOCATE_AGGRESSIVE = 1; // 0x1 } + public final class StorageVolume implements android.os.Parcelable { + method @NonNull public String getId(); + } + } package android.permission { @@ -7658,6 +7874,7 @@ package android.provider { } public final class Settings { + method public static boolean checkAndNoteWriteSettingsOperation(@NonNull android.content.Context, int, @NonNull String, boolean); field public static final String ACTION_ACCESSIBILITY_DETAILS_SETTINGS = "android.settings.ACCESSIBILITY_DETAILS_SETTINGS"; field public static final String ACTION_BUGREPORT_HANDLER_SETTINGS = "android.settings.BUGREPORT_HANDLER_SETTINGS"; field public static final String ACTION_ENTERPRISE_PRIVACY_SETTINGS = "android.settings.ENTERPRISE_PRIVACY_SETTINGS"; @@ -7668,6 +7885,7 @@ package android.provider { field public static final String ACTION_NOTIFICATION_POLICY_ACCESS_DETAIL_SETTINGS = "android.settings.NOTIFICATION_POLICY_ACCESS_DETAIL_SETTINGS"; field public static final String ACTION_REQUEST_ENABLE_CONTENT_CAPTURE = "android.settings.REQUEST_ENABLE_CONTENT_CAPTURE"; field public static final String ACTION_SHOW_ADMIN_SUPPORT_DETAILS = "android.settings.SHOW_ADMIN_SUPPORT_DETAILS"; + field public static final String ACTION_TETHER_PROVISIONING_UI = "android.settings.TETHER_PROVISIONING_UI"; } public static final class Settings.Global extends android.provider.Settings.NameValueTable { @@ -7687,6 +7905,7 @@ package android.provider { field public static final String INSTALL_CARRIER_APP_NOTIFICATION_SLEEP_MILLIS = "install_carrier_app_notification_sleep_millis"; field public static final String OTA_DISABLE_AUTOMATIC_UPDATE = "ota_disable_automatic_update"; field public static final String REQUIRE_PASSWORD_TO_DECRYPT = "require_password_to_decrypt"; + field public static final String TETHER_OFFLOAD_DISABLED = "tether_offload_disabled"; field public static final String TETHER_SUPPORTED = "tether_supported"; field public static final String THEATER_MODE_ON = "theater_mode_on"; field public static final String WEBVIEW_MULTIPROCESS = "webview_multiprocess"; @@ -7807,7 +8026,7 @@ package android.provider { field public static final String SERIAL_NUMBER = "serial_number"; field public static final String SERVICE_CATEGORY = "service_category"; field public static final String SLOT_INDEX = "slot_index"; - field public static final String SUB_ID = "sub_id"; + field public static final String SUBSCRIPTION_ID = "sub_id"; } public static final class Telephony.CellBroadcasts.Preference { @@ -7826,7 +8045,71 @@ package android.provider { } public static final class Telephony.SimInfo { + field public static final String ACCESS_RULES = "access_rules"; + field public static final String ACCESS_RULES_FROM_CARRIER_CONFIGS = "access_rules_from_carrier_configs"; + field public static final String CARD_ID = "card_id"; + field public static final String CARRIER_ID = "carrier_id"; + field public static final String CARRIER_NAME = "carrier_name"; + field public static final String CB_ALERT_REMINDER_INTERVAL = "alert_reminder_interval"; + field public static final String CB_ALERT_SOUND_DURATION = "alert_sound_duration"; + field public static final String CB_ALERT_SPEECH = "enable_alert_speech"; + field public static final String CB_ALERT_VIBRATE = "enable_alert_vibrate"; + field public static final String CB_AMBER_ALERT = "enable_cmas_amber_alerts"; + field public static final String CB_CHANNEL_50_ALERT = "enable_channel_50_alerts"; + field public static final String CB_CMAS_TEST_ALERT = "enable_cmas_test_alerts"; + field public static final String CB_EMERGENCY_ALERT = "enable_emergency_alerts"; + field public static final String CB_ETWS_TEST_ALERT = "enable_etws_test_alerts"; + field public static final String CB_EXTREME_THREAT_ALERT = "enable_cmas_extreme_threat_alerts"; + field public static final String CB_OPT_OUT_DIALOG = "show_cmas_opt_out_dialog"; + field public static final String CB_SEVERE_THREAT_ALERT = "enable_cmas_severe_threat_alerts"; + field public static final String COLOR = "color"; field @NonNull public static final android.net.Uri CONTENT_URI; + field public static final String DATA_ENABLED_OVERRIDE_RULES = "data_enabled_override_rules"; + field public static final String DATA_ROAMING = "data_roaming"; + field public static final int DATA_ROAMING_DEFAULT = 0; // 0x0 + field public static final int DATA_ROAMING_DISABLE = 0; // 0x0 + field public static final int DATA_ROAMING_ENABLE = 1; // 0x1 + field public static final String DISPLAY_NAME = "display_name"; + field public static final String EHPLMNS = "ehplmns"; + field public static final String ENHANCED_4G_MODE_ENABLED = "volte_vt_enabled"; + field public static final String GROUP_OWNER = "group_owner"; + field public static final String GROUP_UUID = "group_uuid"; + field public static final String HPLMNS = "hplmns"; + field public static final String ICC_ID = "icc_id"; + field public static final String IMSI = "imsi"; + field public static final String ISO_COUNTRY_CODE = "iso_country_code"; + field public static final String IS_EMBEDDED = "is_embedded"; + field public static final String IS_OPPORTUNISTIC = "is_opportunistic"; + field public static final String IS_REMOVABLE = "is_removable"; + field public static final String MCC = "mcc"; + field public static final String MCC_STRING = "mcc_string"; + field public static final String MNC = "mnc"; + field public static final String MNC_STRING = "mnc_string"; + field public static final String NAME_SOURCE = "name_source"; + field public static final int NAME_SOURCE_CARRIER = 3; // 0x3 + field public static final int NAME_SOURCE_DEFAULT = 0; // 0x0 + field public static final int NAME_SOURCE_SIM_PNN = 4; // 0x4 + field public static final int NAME_SOURCE_SIM_SPN = 1; // 0x1 + field public static final int NAME_SOURCE_USER_INPUT = 2; // 0x2 + field public static final String NUMBER = "number"; + field public static final String PROFILE_CLASS = "profile_class"; + field public static final int PROFILE_CLASS_DEFAULT = -1; // 0xffffffff + field public static final int PROFILE_CLASS_OPERATIONAL = 2; // 0x2 + field public static final int PROFILE_CLASS_PROVISIONING = 1; // 0x1 + field public static final int PROFILE_CLASS_TESTING = 0; // 0x0 + field public static final int PROFILE_CLASS_UNSET = -1; // 0xffffffff + field public static final int SIM_NOT_INSERTED = -1; // 0xffffffff + field public static final String SIM_SLOT_INDEX = "sim_id"; + field public static final String SUBSCRIPTION_TYPE = "subscription_type"; + field public static final int SUBSCRIPTION_TYPE_LOCAL_SIM = 0; // 0x0 + field public static final int SUBSCRIPTION_TYPE_REMOTE_SIM = 1; // 0x1 + field public static final String UICC_APPLICATIONS_ENABLED = "uicc_applications_enabled"; + field public static final String UNIQUE_KEY_SUBSCRIPTION_ID = "_id"; + field public static final String VT_IMS_ENABLED = "vt_ims_enabled"; + field public static final String WFC_IMS_ENABLED = "wfc_ims_enabled"; + field public static final String WFC_IMS_MODE = "wfc_ims_mode"; + field public static final String WFC_IMS_ROAMING_ENABLED = "wfc_ims_roaming_enabled"; + field public static final String WFC_IMS_ROAMING_MODE = "wfc_ims_roaming_mode"; } public static final class Telephony.Sms.Intents { @@ -8390,7 +8673,10 @@ package android.service.notification { method @Nullable public android.service.notification.Adjustment onNotificationEnqueued(@NonNull android.service.notification.StatusBarNotification, @NonNull android.app.NotificationChannel); method public void onNotificationExpansionChanged(@NonNull String, boolean, boolean); method public abstract void onNotificationSnoozedUntilContext(@NonNull android.service.notification.StatusBarNotification, @NonNull String); + method public void onNotificationVisibilityChanged(@NonNull String, boolean); method public void onNotificationsSeen(@NonNull java.util.List<java.lang.String>); + method public void onPanelHidden(); + method public void onPanelRevealed(int); method public void onSuggestedReplySent(@NonNull String, @NonNull CharSequence, int); method public final void unsnoozeNotification(@NonNull String); field public static final String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService"; @@ -9093,30 +9379,37 @@ package android.telephony { public abstract class CellIdentity implements android.os.Parcelable { method @NonNull public abstract android.telephony.CellLocation asCellLocation(); + method @NonNull public abstract android.telephony.CellIdentity sanitizeLocationInfo(); } public final class CellIdentityCdma extends android.telephony.CellIdentity { method @NonNull public android.telephony.cdma.CdmaCellLocation asCellLocation(); + method @NonNull public android.telephony.CellIdentityCdma sanitizeLocationInfo(); } public final class CellIdentityGsm extends android.telephony.CellIdentity { method @NonNull public android.telephony.gsm.GsmCellLocation asCellLocation(); + method @NonNull public android.telephony.CellIdentityGsm sanitizeLocationInfo(); } public final class CellIdentityLte extends android.telephony.CellIdentity { method @NonNull public android.telephony.gsm.GsmCellLocation asCellLocation(); + method @NonNull public android.telephony.CellIdentityLte sanitizeLocationInfo(); } public final class CellIdentityNr extends android.telephony.CellIdentity { method @NonNull public android.telephony.CellLocation asCellLocation(); + method @NonNull public android.telephony.CellIdentityNr sanitizeLocationInfo(); } public final class CellIdentityTdscdma extends android.telephony.CellIdentity { method @NonNull public android.telephony.gsm.GsmCellLocation asCellLocation(); + method @NonNull public android.telephony.CellIdentityTdscdma sanitizeLocationInfo(); } public final class CellIdentityWcdma extends android.telephony.CellIdentity { method @NonNull public android.telephony.gsm.GsmCellLocation asCellLocation(); + method @NonNull public android.telephony.CellIdentityWcdma sanitizeLocationInfo(); } public final class DataFailCause { @@ -9516,6 +9809,7 @@ package android.telephony { field public static final int IMS_ACCESS_BLOCKED = 60; // 0x3c field public static final int IMS_MERGED_SUCCESSFULLY = 45; // 0x2d field public static final int IMS_SIP_ALTERNATE_EMERGENCY_CALL = 71; // 0x47 + field public static final int INCOMING_AUTO_REJECTED = 81; // 0x51 field public static final int INCOMING_MISSED = 1; // 0x1 field public static final int INCOMING_REJECTED = 16; // 0x10 field public static final int INVALID_CREDENTIALS = 10; // 0xa @@ -10008,6 +10302,7 @@ package android.telephony { method public boolean canManageSubscription(@Nullable android.telephony.SubscriptionInfo, @Nullable String); method @NonNull public int[] getActiveAndHiddenSubscriptionIdList(); method @NonNull public int[] getActiveSubscriptionIdList(); + method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.SubscriptionInfo getActiveSubscriptionInfoForIcc(@NonNull String); method public java.util.List<android.telephony.SubscriptionInfo> getAvailableSubscriptionInfoList(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getEnabledSubscriptionId(int); method @NonNull public static android.content.res.Resources getResourcesForSubId(@NonNull android.content.Context, int); @@ -10122,10 +10417,10 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getVoiceActivationState(); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean handlePinMmi(String); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean handlePinMmiForSubscriber(int, String); - method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean iccCloseLogicalChannelBySlot(int, int); + method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean iccCloseLogicalChannelBySlot(int, int); method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannelBySlot(int, @Nullable String, int); - method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String iccTransmitApduBasicChannelBySlot(int, int, int, int, int, int, @Nullable String); - method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String iccTransmitApduLogicalChannelBySlot(int, int, int, int, int, int, int, @Nullable String); + method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String iccTransmitApduBasicChannelBySlot(int, int, int, int, int, int, @Nullable String); + method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String iccTransmitApduLogicalChannelBySlot(int, int, int, int, int, int, int, @Nullable String); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isAnyRadioPoweredOn(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isApnMetered(int); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isApplicationOnUicc(int); @@ -10292,6 +10587,20 @@ package android.telephony { method public static final void setSmsFilterSettings(android.content.Context, android.telecom.PhoneAccountHandle, android.telephony.VisualVoicemailSmsFilterSettings); } + public final class WapPushManagerConnector { + ctor public WapPushManagerConnector(@NonNull android.content.Context); + method public boolean bindToWapPushManagerService(); + method @Nullable public String getConnectedWapPushManagerServicePackage(); + method public int processMessage(@NonNull String, @NonNull String, @NonNull android.content.Intent); + method public void unbindWapPushManagerService(); + field public static final int RESULT_APP_QUERY_FAILED = 2; // 0x2 + field public static final int RESULT_EXCEPTION_CAUGHT = 16; // 0x10 + field public static final int RESULT_FURTHER_PROCESSING = 32768; // 0x8000 + field public static final int RESULT_INVALID_RECEIVER_NAME = 8; // 0x8 + field public static final int RESULT_MESSAGE_HANDLED = 1; // 0x1 + field public static final int RESULT_SIGNATURE_NO_MATCH = 4; // 0x4 + } + } package android.telephony.cdma { @@ -10672,6 +10981,7 @@ package android.telephony.ims { field public static final int DIALSTRING_USSD = 2; // 0x2 field public static final String EXTRA_ADDITIONAL_CALL_INFO = "AdditionalCallInfo"; field public static final String EXTRA_ADDITIONAL_SIP_INVITE_FIELDS = "android.telephony.ims.extra.ADDITIONAL_SIP_INVITE_FIELDS"; + field public static final String EXTRA_CALL_DISCONNECT_CAUSE = "android.telephony.ims.extra.CALL_DISCONNECT_CAUSE"; field public static final String EXTRA_CALL_RAT_TYPE = "CallRadioTech"; field public static final String EXTRA_CHILD_NUMBER = "ChildNum"; field public static final String EXTRA_CNA = "cna"; @@ -11541,6 +11851,8 @@ package android.view.accessibility { public final class AccessibilityManager { method public int getAccessibilityWindowId(@Nullable android.os.IBinder); method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public void performAccessibilityShortcut(); + method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public void registerSystemAction(@NonNull android.app.RemoteAction, int); + method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public void unregisterSystemAction(int); } } diff --git a/api/test-current.txt b/api/test-current.txt index f35da6951bb1..00a2c29cd06f 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -244,6 +244,16 @@ package android.app { field public static final int UID_STATE_TOP = 200; // 0xc8 } + public static final class AppOpsManager.HistoricalFeatureOps implements android.os.Parcelable { + method public int describeContents(); + method @Nullable public String getFeatureId(); + method @Nullable public android.app.AppOpsManager.HistoricalOp getOp(@NonNull String); + method @NonNull public android.app.AppOpsManager.HistoricalOp getOpAt(@IntRange(from=0) int); + method @IntRange(from=0) public int getOpCount(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalFeatureOps> CREATOR; + } + public static final class AppOpsManager.HistoricalOp implements android.os.Parcelable { method public int describeContents(); method public long getAccessCount(int, int, int); @@ -268,9 +278,9 @@ package android.app { method @IntRange(from=0) public int getUidCount(); method @Nullable public android.app.AppOpsManager.HistoricalUidOps getUidOps(int); method @NonNull public android.app.AppOpsManager.HistoricalUidOps getUidOpsAt(@IntRange(from=0) int); - method public void increaseAccessCount(int, int, @NonNull String, int, int, long); - method public void increaseAccessDuration(int, int, @NonNull String, int, int, long); - method public void increaseRejectCount(int, int, @NonNull String, int, int, long); + method public void increaseAccessCount(int, int, @NonNull String, @Nullable String, int, int, long); + method public void increaseAccessDuration(int, int, @NonNull String, @Nullable String, int, int, long); + method public void increaseRejectCount(int, int, @NonNull String, @Nullable String, int, int, long); method public void offsetBeginAndEndTime(long); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalOps> CREATOR; @@ -282,6 +292,7 @@ package android.app { public static final class AppOpsManager.HistoricalOpsRequest.Builder { ctor public AppOpsManager.HistoricalOpsRequest.Builder(long, long); method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest build(); + method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setFeatureId(@Nullable String); method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setFlags(int); method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setOpNames(@Nullable java.util.List<java.lang.String>); method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setPackageName(@Nullable String); @@ -290,6 +301,9 @@ package android.app { public static final class AppOpsManager.HistoricalPackageOps implements android.os.Parcelable { method public int describeContents(); + method @IntRange(from=0) public int getFeatureCount(); + method @Nullable public android.app.AppOpsManager.HistoricalFeatureOps getFeatureOps(@NonNull String); + method @NonNull public android.app.AppOpsManager.HistoricalFeatureOps getFeatureOpsAt(@IntRange(from=0) int); method @Nullable public android.app.AppOpsManager.HistoricalOp getOp(@NonNull String); method @NonNull public android.app.AppOpsManager.HistoricalOp getOpAt(@IntRange(from=0) int); method @IntRange(from=0) public int getOpCount(); @@ -421,8 +435,11 @@ package android.app { } public class StatusBarManager { + method public void collapsePanels(); + method public void expandNotificationsPanel(); method @NonNull @RequiresPermission(android.Manifest.permission.STATUS_BAR) public android.app.StatusBarManager.DisableInfo getDisableInfo(); method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setDisabledForSetup(boolean); + method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setDisabledForSimNetworkLock(boolean); } public static final class StatusBarManager.DisableInfo { @@ -2537,6 +2554,7 @@ package android.provider { field public static final String ACTION_ENTERPRISE_PRIVACY_SETTINGS = "android.settings.ENTERPRISE_PRIVACY_SETTINGS"; field public static final String ACTION_MANAGE_APP_OVERLAY_PERMISSION = "android.settings.MANAGE_APP_OVERLAY_PERMISSION"; field public static final String ACTION_REQUEST_ENABLE_CONTENT_CAPTURE = "android.settings.REQUEST_ENABLE_CONTENT_CAPTURE"; + field public static final String ACTION_TETHER_PROVISIONING_UI = "android.settings.TETHER_PROVISIONING_UI"; field public static final int RESET_MODE_PACKAGE_DEFAULTS = 1; // 0x1 } @@ -2555,6 +2573,7 @@ package android.provider { field public static final String LOW_POWER_MODE_STICKY = "low_power_sticky"; field public static final String NOTIFICATION_BUBBLES = "notification_bubbles"; field public static final String OVERLAY_DISPLAY_DEVICES = "overlay_display_devices"; + field public static final String TETHER_OFFLOAD_DISABLED = "tether_offload_disabled"; field public static final String USE_OPEN_WIFI_PACKAGE = "use_open_wifi_package"; } @@ -2610,7 +2629,7 @@ package android.provider { field public static final String SERIAL_NUMBER = "serial_number"; field public static final String SERVICE_CATEGORY = "service_category"; field public static final String SLOT_INDEX = "slot_index"; - field public static final String SUB_ID = "sub_id"; + field public static final String SUBSCRIPTION_ID = "sub_id"; } public static final class Telephony.Sms.Intents { @@ -2912,7 +2931,10 @@ package android.service.notification { method @Nullable public android.service.notification.Adjustment onNotificationEnqueued(@NonNull android.service.notification.StatusBarNotification, @NonNull android.app.NotificationChannel); method public void onNotificationExpansionChanged(@NonNull String, boolean, boolean); method public abstract void onNotificationSnoozedUntilContext(@NonNull android.service.notification.StatusBarNotification, @NonNull String); + method public void onNotificationVisibilityChanged(@NonNull String, boolean); method public void onNotificationsSeen(@NonNull java.util.List<java.lang.String>); + method public void onPanelHidden(); + method public void onPanelRevealed(int); method public void onSuggestedReplySent(@NonNull String, @NonNull CharSequence, int); method public final void unsnoozeNotification(@NonNull String); field public static final String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService"; @@ -3380,6 +3402,7 @@ package android.telephony.ims { field public static final int DIALSTRING_USSD = 2; // 0x2 field public static final String EXTRA_ADDITIONAL_CALL_INFO = "AdditionalCallInfo"; field public static final String EXTRA_ADDITIONAL_SIP_INVITE_FIELDS = "android.telephony.ims.extra.ADDITIONAL_SIP_INVITE_FIELDS"; + field public static final String EXTRA_CALL_DISCONNECT_CAUSE = "android.telephony.ims.extra.CALL_DISCONNECT_CAUSE"; field public static final String EXTRA_CALL_RAT_TYPE = "CallRadioTech"; field public static final String EXTRA_CHILD_NUMBER = "ChildNum"; field public static final String EXTRA_CNA = "cna"; diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp index 7b96ce92e307..1c6867c39790 100644 --- a/cmds/statsd/Android.bp +++ b/cmds/statsd/Android.bp @@ -78,7 +78,6 @@ cc_defaults { "src/external/StatsPuller.cpp", "src/external/StatsPullerManager.cpp", "src/external/SubsystemSleepStatePuller.cpp", - "src/external/SurfaceflingerStatsPuller.cpp", "src/external/TrainInfoPuller.cpp", "src/FieldValue.cpp", "src/guardrail/StatsdStats.cpp", @@ -126,30 +125,28 @@ cc_defaults { ], static_libs: [ - "libhealthhalutils", - "libplatformprotos", - ], - - shared_libs: [ "android.frameworks.stats@1.0", - "android.hardware.health@2.0", "android.hardware.power.stats@1.0", "android.hardware.power@1.0", "android.hardware.power@1.1", "libbase", - "libbinder", "libcutils", - "libgraphicsenv", - "libhidlbase", - "libincident", + "libhealthhalutils", "liblog", + "libplatformprotos", "libprotoutil", - "libservices", "libstatslog", - "libstatsmetadata", "libstatssocket", "libsysutils", - "libtimestats_proto", + ], + shared_libs: [ + "android.hardware.health@2.0", + "libbinder", + "libgraphicsenv", + "libhidlbase", + "libincident", + "libservices", + "libstatsmetadata", "libutils", ], } @@ -298,7 +295,6 @@ cc_test { "tests/external/puller_util_test.cpp", "tests/external/StatsCallbackPuller_test.cpp", "tests/external/StatsPuller_test.cpp", - "tests/external/SurfaceflingerStatsPuller_test.cpp", "tests/FieldValue_test.cpp", "tests/guardrail/StatsdStats_test.cpp", "tests/indexed_priority_queue_test.cpp", diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp index 5ff0f97865b8..1ca19c3417c2 100644 --- a/cmds/statsd/src/StatsService.cpp +++ b/cmds/statsd/src/StatsService.cpp @@ -1135,12 +1135,11 @@ void StatsService::OnLogEvent(LogEvent* event) { } } -Status StatsService::getData(int64_t key, const String16& packageName, vector<uint8_t>* output) { - ENFORCE_DUMP_AND_USAGE_STATS(packageName); +Status StatsService::getData(int64_t key, const int32_t callingUid, vector<uint8_t>* output) { + ENFORCE_UID(AID_SYSTEM); - IPCThreadState* ipc = IPCThreadState::self(); - VLOG("StatsService::getData with Pid %i, Uid %i", ipc->getCallingPid(), ipc->getCallingUid()); - ConfigKey configKey(ipc->getCallingUid(), key); + VLOG("StatsService::getData with Uid %i", callingUid); + ConfigKey configKey(callingUid, key); // The dump latency does not matter here since we do not include the current bucket, we do not // need to pull any new data anyhow. mProcessor->onDumpReport(configKey, getElapsedRealtimeNs(), false /* include_current_bucket*/, @@ -1148,22 +1147,18 @@ Status StatsService::getData(int64_t key, const String16& packageName, vector<ui return Status::ok(); } -Status StatsService::getMetadata(const String16& packageName, vector<uint8_t>* output) { - ENFORCE_DUMP_AND_USAGE_STATS(packageName); +Status StatsService::getMetadata(vector<uint8_t>* output) { + ENFORCE_UID(AID_SYSTEM); - IPCThreadState* ipc = IPCThreadState::self(); - VLOG("StatsService::getMetadata with Pid %i, Uid %i", ipc->getCallingPid(), - ipc->getCallingUid()); StatsdStats::getInstance().dumpStats(output, false); // Don't reset the counters. return Status::ok(); } Status StatsService::addConfiguration(int64_t key, const vector <uint8_t>& config, - const String16& packageName) { - ENFORCE_DUMP_AND_USAGE_STATS(packageName); + const int32_t callingUid) { + ENFORCE_UID(AID_SYSTEM); - IPCThreadState* ipc = IPCThreadState::self(); - if (addConfigurationChecked(ipc->getCallingUid(), key, config)) { + if (addConfigurationChecked(callingUid, key, config)) { return Status::ok(); } else { ALOGE("Could not parse malformatted StatsdConfig"); @@ -1228,13 +1223,11 @@ Status StatsService::removeActiveConfigsChangedOperation(const int32_t callingUi return Status::ok(); } -Status StatsService::removeConfiguration(int64_t key, const String16& packageName) { - ENFORCE_DUMP_AND_USAGE_STATS(packageName); +Status StatsService::removeConfiguration(int64_t key, const int32_t callingUid) { + ENFORCE_UID(AID_SYSTEM); - IPCThreadState* ipc = IPCThreadState::self(); - ConfigKey configKey(ipc->getCallingUid(), key); + ConfigKey configKey(callingUid, key); mConfigManager->RemoveConfig(configKey); - SubscriberReporter::getInstance().removeConfig(configKey); return Status::ok(); } @@ -1477,17 +1470,7 @@ Status StatsService::sendWatchdogRollbackOccurredAtom(const int32_t rollbackType Status StatsService::getRegisteredExperimentIds(std::vector<int64_t>* experimentIdsOut) { - uid_t uid = IPCThreadState::self()->getCallingUid(); - - // Caller must be granted these permissions - if (!checkCallingPermission(String16(kPermissionDump))) { - return exception(binder::Status::EX_SECURITY, - StringPrintf("UID %d lacks permission %s", uid, kPermissionDump)); - } - if (!checkCallingPermission(String16(kPermissionUsage))) { - return exception(binder::Status::EX_SECURITY, - StringPrintf("UID %d lacks permission %s", uid, kPermissionUsage)); - } + ENFORCE_UID(AID_SYSTEM); // TODO: add verifier permission // Read the latest train info diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h index 0565f3ce2484..c9a9072ecb92 100644 --- a/cmds/statsd/src/StatsService.h +++ b/cmds/statsd/src/StatsService.h @@ -99,15 +99,14 @@ public: * Binder call for clients to request data for this configuration key. */ virtual Status getData(int64_t key, - const String16& packageName, + const int32_t callingUid, vector<uint8_t>* output) override; /** * Binder call for clients to get metadata across all configs in statsd. */ - virtual Status getMetadata(const String16& packageName, - vector<uint8_t>* output) override; + virtual Status getMetadata(vector<uint8_t>* output) override; /** @@ -116,7 +115,7 @@ public: */ virtual Status addConfiguration(int64_t key, const vector<uint8_t>& config, - const String16& packageName) override; + const int32_t callingUid) override; /** * Binder call to let clients register the data fetch operation for a configuration. @@ -146,7 +145,7 @@ public: * Binder call to allow clients to remove the specified configuration. */ virtual Status removeConfiguration(int64_t key, - const String16& packageName) override; + const int32_t callingUid) override; /** * Binder call to associate the given config's subscriberId with the given pendingIntentRef. diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 19b9709e1d41..2bacfbc57395 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -333,10 +333,11 @@ message Atom { MediaProviderSchemaChange media_provider_schema_change = 236 [(module) = "mediaprovider"]; MediaProviderIdleMaintenance media_provider_idle_maintenance = 237 [(module) = "mediaprovider"]; + RebootEscrowRecoveryReported reboot_escrow_recovery_reported = 238; } // Pulled events will start at field 10000. - // Next: 10068 + // Next: 10069 oneof pulled { WifiBytesTransfer wifi_bytes_transfer = 10000; WifiBytesTransferByFgBg wifi_bytes_transfer_by_fg_bg = 10001; @@ -405,6 +406,7 @@ message Atom { VmsClientStats vms_client_stats = 10065; NotificationRemoteViews notification_remote_views = 10066; DangerousPermissionStateSampled dangerous_permission_state_sampled = 10067; + GraphicsStats graphics_stats = 10068; } // DO NOT USE field numbers above 100,000 in AOSP. @@ -7338,6 +7340,17 @@ message UpdateEngineSuccessfulUpdateReported { } /** + * Reported when the RebootEscrow HAL has attempted to recover the escrowed + * key to indicate whether it was successful or not. + * + * Logged from: + * frameworks/base/services/core/java/com/android/server/locksettings/RebootEscrowManager.java + */ +message RebootEscrowRecoveryReported { + optional bool successful = 1; +} + +/** * Global display pipeline metrics reported by SurfaceFlinger. * Pulled from: * frameworks/native/services/surfaceflinger/TimeStats/TimeStats.cpp @@ -7553,3 +7566,69 @@ message DangerousPermissionStateSampled { optional int32 permission_flags = 4; } +/** + * HWUI renders pipeline type: GL (0) or Vulkan (1). + */ +enum PipelineType { + GL = 0; + VULKAN = 1; +} + +/** + * HWUI stats for a given app. + */ +message GraphicsStats { + // The package name of the app + optional string package_name = 1; + + // The version code of the app + optional int64 version_code = 2; + + // The start & end timestamps in UTC as + // milliseconds since January 1, 1970 + // Compatible with java.util.Date#setTime() + optional int64 stats_start = 3; + + optional int64 stats_end = 4; + + // HWUI renders pipeline type: GL or Vulkan. + optional PipelineType pipeline = 5; + + // Distinct frame count. + optional int32 total_frames = 6; + + // Number of "missed vsync" events. + optional int32 missed_vsync_count = 7; + + // Number of frames in triple-buffering scenario (high input latency) + optional int32 high_input_latency_count = 8; + + // Number of "slow UI thread" events. + optional int32 slow_ui_thread_count = 9; + + // Number of "slow bitmap upload" events. + optional int32 slow_bitmap_upload_count = 10; + + // Number of "slow draw" events. + optional int32 slow_draw_count = 11; + + // Number of frames that missed their deadline (aka, visibly janked) + optional int32 missed_deadline_count = 12; + + // The frame time histogram for the package + optional FrameTimingHistogram cpu_histogram = 13 + [(android.os.statsd.log_mode) = MODE_BYTES]; + + // The gpu frame time histogram for the package + optional FrameTimingHistogram gpu_histogram = 14 + [(android.os.statsd.log_mode) = MODE_BYTES]; + + // UI mainline module version. + optional int64 version_ui_module = 15; + + // If true, these are HWUI stats for up to a 24h period for a given app from today. + // If false, these are HWUI stats for a 24h period for a given app from the last complete + // day (yesterday). Stats from yesterday stay constant, while stats from today may change as + // more apps are running / rendering. + optional bool is_today = 16; +} diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp index 55d73c1ae342..972adf7d4d05 100644 --- a/cmds/statsd/src/config/ConfigManager.cpp +++ b/cmds/statsd/src/config/ConfigManager.cpp @@ -189,25 +189,11 @@ void ConfigManager::RemoveConfig(const ConfigKey& key) { // Remove from map uidIt->second.erase(key); - // No more configs for this uid, lets remove the active configs callback. - if (uidIt->second.empty()) { - auto itActiveConfigsChangedReceiver = mActiveConfigsChangedReceivers.find(uid); - if (itActiveConfigsChangedReceiver != mActiveConfigsChangedReceivers.end()) { - mActiveConfigsChangedReceivers.erase(itActiveConfigsChangedReceiver); - } - } - for (const sp<ConfigListener>& listener : mListeners) { broadcastList.push_back(listener); } } - auto itReceiver = mConfigReceivers.find(key); - if (itReceiver != mConfigReceivers.end()) { - // Remove from map - mConfigReceivers.erase(itReceiver); - } - // Remove from disk. There can still be a lingering file on disk so we check // whether or not the config was on memory. remove_saved_configs(key); @@ -238,12 +224,6 @@ void ConfigManager::RemoveConfigs(int uid) { // Remove from map remove_saved_configs(*it); removed.push_back(*it); - mConfigReceivers.erase(*it); - } - - auto itActiveConfigsChangedReceiver = mActiveConfigsChangedReceivers.find(uid); - if (itActiveConfigsChangedReceiver != mActiveConfigsChangedReceivers.end()) { - mActiveConfigsChangedReceivers.erase(itActiveConfigsChangedReceiver); } mConfigs.erase(uidIt); @@ -277,8 +257,6 @@ void ConfigManager::RemoveAllConfigs() { uidIt = mConfigs.erase(uidIt); } - mConfigReceivers.clear(); - mActiveConfigsChangedReceivers.clear(); for (const sp<ConfigListener>& listener : mListeners) { broadcastList.push_back(listener); } diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp index 50896f84da43..1d31873209b2 100644 --- a/cmds/statsd/src/external/StatsPullerManager.cpp +++ b/cmds/statsd/src/external/StatsPullerManager.cpp @@ -41,7 +41,6 @@ #include "StatsCallbackPullerDeprecated.h" #include "StatsCompanionServicePuller.h" #include "SubsystemSleepStatePuller.h" -#include "SurfaceflingerStatsPuller.h" #include "TrainInfoPuller.h" #include "statslog.h" @@ -273,10 +272,6 @@ std::map<PullerKey, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = { // App ops {{.atomTag = android::util::APP_OPS}, {.puller = new StatsCompanionServicePuller(android::util::APP_OPS)}}, - // SurfaceflingerStatsGlobalInfo - {{.atomTag = android::util::SURFACEFLINGER_STATS_GLOBAL_INFO}, - {.puller = - new SurfaceflingerStatsPuller(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO)}}, // VmsClientStats {{.atomTag = android::util::VMS_CLIENT_STATS}, {.additiveFields = {5, 6, 7, 8, 9, 10}, diff --git a/cmds/statsd/src/external/SurfaceflingerStatsPuller.cpp b/cmds/statsd/src/external/SurfaceflingerStatsPuller.cpp deleted file mode 100644 index 23b2236f35f2..000000000000 --- a/cmds/statsd/src/external/SurfaceflingerStatsPuller.cpp +++ /dev/null @@ -1,99 +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. - */ - -#include "SurfaceflingerStatsPuller.h" - -#include <cutils/compiler.h> - -#include <numeric> - -#include "logd/LogEvent.h" -#include "stats_log_util.h" -#include "statslog.h" - -namespace android { -namespace os { -namespace statsd { - -SurfaceflingerStatsPuller::SurfaceflingerStatsPuller(const int tagId) : StatsPuller(tagId) { -} - -bool SurfaceflingerStatsPuller::PullInternal(std::vector<std::shared_ptr<LogEvent>>* data) { - switch (mTagId) { - case android::util::SURFACEFLINGER_STATS_GLOBAL_INFO: - return pullGlobalInfo(data); - default: - break; - } - - return false; -} - -static int64_t getTotalTime( - const google::protobuf::RepeatedPtrField<surfaceflinger::SFTimeStatsHistogramBucketProto>& - buckets) { - int64_t total = 0; - for (const auto& bucket : buckets) { - if (bucket.time_millis() == 1000) { - continue; - } - - total += bucket.time_millis() * bucket.frame_count(); - } - - return total; -} - -bool SurfaceflingerStatsPuller::pullGlobalInfo(std::vector<std::shared_ptr<LogEvent>>* data) { - std::string protoBytes; - if (CC_UNLIKELY(mStatsProvider)) { - protoBytes = mStatsProvider(); - } else { - std::unique_ptr<FILE, decltype(&pclose)> pipe(popen("dumpsys SurfaceFlinger --timestats -dump --proto", "r"), pclose); - if (!pipe.get()) { - return false; - } - char buf[1024]; - size_t bytesRead = 0; - do { - bytesRead = fread(buf, 1, sizeof(buf), pipe.get()); - protoBytes.append(buf, bytesRead); - } while (bytesRead > 0); - } - surfaceflinger::SFTimeStatsGlobalProto proto; - proto.ParseFromString(protoBytes); - - int64_t totalTime = getTotalTime(proto.present_to_present()); - - data->clear(); - data->reserve(1); - std::shared_ptr<LogEvent> event = - make_shared<LogEvent>(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO, getWallClockNs(), - getElapsedRealtimeNs()); - if (!event->write(proto.total_frames())) return false; - if (!event->write(proto.missed_frames())) return false; - if (!event->write(proto.client_composition_frames())) return false; - if (!event->write(proto.display_on_time())) return false; - if (!event->write(totalTime)) return false; - event->init(); - data->emplace_back(event); - - return true; -} - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/cmds/statsd/src/external/SurfaceflingerStatsPuller.h b/cmds/statsd/src/external/SurfaceflingerStatsPuller.h deleted file mode 100644 index ed7153edf797..000000000000 --- a/cmds/statsd/src/external/SurfaceflingerStatsPuller.h +++ /dev/null @@ -1,48 +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. - */ - -#pragma once - -#include <timestatsproto/TimeStatsProtoHeader.h> - -#include "StatsPuller.h" - -namespace android { -namespace os { -namespace statsd { - -/** - * Pull metrics from Surfaceflinger - */ -class SurfaceflingerStatsPuller : public StatsPuller { -public: - explicit SurfaceflingerStatsPuller(const int tagId); - - // StatsPuller interface - bool PullInternal(std::vector<std::shared_ptr<LogEvent>>* data) override; - -protected: - // Test-only, for injecting fake data - using StatsProvider = std::function<std::string()>; - StatsProvider mStatsProvider; - -private: - bool pullGlobalInfo(std::vector<std::shared_ptr<LogEvent>>* data); -}; - -} // namespace statsd -} // namespace os -} // namespace android diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp index 35c6d373418e..e85b97514242 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp +++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp @@ -207,10 +207,7 @@ void DurationMetricProducer::onSlicedConditionMayChangeLocked_opt1(bool conditio &linkedConditionDimensionKey); if (trueConditionDimensions.find(linkedConditionDimensionKey) != trueConditionDimensions.end()) { - for (auto& condIt : whatIt.second) { - condIt.second->onConditionChanged( - currentUnSlicedPartCondition, eventTime); - } + whatIt.second->onConditionChanged(currentUnSlicedPartCondition, eventTime); } } } else { @@ -222,15 +219,11 @@ void DurationMetricProducer::onSlicedConditionMayChangeLocked_opt1(bool conditio &linkedConditionDimensionKey); if (dimensionsChangedToTrue->find(linkedConditionDimensionKey) != dimensionsChangedToTrue->end()) { - for (auto& condIt : whatIt.second) { - condIt.second->onConditionChanged(true, eventTime); - } + whatIt.second->onConditionChanged(true, eventTime); } if (dimensionsChangedToFalse->find(linkedConditionDimensionKey) != dimensionsChangedToFalse->end()) { - for (auto& condIt : whatIt.second) { - condIt.second->onConditionChanged(false, eventTime); - } + whatIt.second->onConditionChanged(false, eventTime); } } } @@ -247,9 +240,7 @@ void DurationMetricProducer::onSlicedConditionMayChangeInternalLocked(bool overa // Now for each of the on-going event, check if the condition has changed for them. for (auto& whatIt : mCurrentSlicedDurationTrackerMap) { - for (auto& pair : whatIt.second) { - pair.second->onSlicedConditionMayChange(overallCondition, eventTimeNs); - } + whatIt.second->onSlicedConditionMayChange(overallCondition, eventTimeNs); } } @@ -283,18 +274,14 @@ void DurationMetricProducer::onActiveStateChangedLocked(const int64_t& eventTime } for (auto& whatIt : mCurrentSlicedDurationTrackerMap) { - for (auto& pair : whatIt.second) { - pair.second->onConditionChanged(mIsActive, eventTimeNs); - } + whatIt.second->onConditionChanged(mIsActive, eventTimeNs); } } else if (mIsActive) { flushIfNeededLocked(eventTimeNs); onSlicedConditionMayChangeInternalLocked(mIsActive, eventTimeNs); } else { // mConditionSliced == true && !mIsActive for (auto& whatIt : mCurrentSlicedDurationTrackerMap) { - for (auto& pair : whatIt.second) { - pair.second->onConditionChanged(mIsActive, eventTimeNs); - } + whatIt.second->onConditionChanged(mIsActive, eventTimeNs); } } } @@ -310,9 +297,7 @@ void DurationMetricProducer::onConditionChangedLocked(const bool conditionMet, flushIfNeededLocked(eventTime); for (auto& whatIt : mCurrentSlicedDurationTrackerMap) { - for (auto& pair : whatIt.second) { - pair.second->onConditionChanged(conditionMet, eventTime); - } + whatIt.second->onConditionChanged(conditionMet, eventTime); } } @@ -425,19 +410,11 @@ void DurationMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs const int64_t& nextBucketStartTimeNs) { for (auto whatIt = mCurrentSlicedDurationTrackerMap.begin(); whatIt != mCurrentSlicedDurationTrackerMap.end();) { - for (auto it = whatIt->second.begin(); it != whatIt->second.end();) { - if (it->second->flushCurrentBucket(eventTimeNs, &mPastBuckets)) { - VLOG("erase bucket for key %s %s", whatIt->first.toString().c_str(), - it->first.toString().c_str()); - it = whatIt->second.erase(it); - } else { - ++it; - } - } - if (whatIt->second.empty()) { + if (whatIt->second->flushCurrentBucket(eventTimeNs, &mPastBuckets)) { + VLOG("erase bucket for key %s", whatIt->first.toString().c_str()); whatIt = mCurrentSlicedDurationTrackerMap.erase(whatIt); } else { - whatIt++; + ++whatIt; } } StatsdStats::getInstance().noteBucketCount(mMetricId); @@ -453,35 +430,15 @@ void DurationMetricProducer::dumpStatesLocked(FILE* out, bool verbose) const { (unsigned long)mCurrentSlicedDurationTrackerMap.size()); if (verbose) { for (const auto& whatIt : mCurrentSlicedDurationTrackerMap) { - for (const auto& slice : whatIt.second) { - fprintf(out, "\t(what)%s\t(states)%s\n", whatIt.first.toString().c_str(), - slice.first.toString().c_str()); - slice.second->dumpStates(out, verbose); - } + fprintf(out, "\t(what)%s\n", whatIt.first.toString().c_str()); + whatIt.second->dumpStates(out, verbose); } } } bool DurationMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey) { auto whatIt = mCurrentSlicedDurationTrackerMap.find(newKey.getDimensionKeyInWhat()); - if (whatIt != mCurrentSlicedDurationTrackerMap.end()) { - auto stateIt = whatIt->second.find(newKey.getStateValuesKey()); - if (stateIt != whatIt->second.end()) { - return false; - } - if (whatIt->second.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) { - size_t newTupleCount = whatIt->second.size() + 1; - StatsdStats::getInstance().noteMetricDimensionInConditionSize( - mConfigKey, mMetricId, newTupleCount); - // 2. Don't add more tuples, we are above the allowed threshold. Drop the data. - if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) { - ALOGE("DurationMetric %lld dropping data for state values key %s", - (long long)mMetricId, newKey.getStateValuesKey().toString().c_str()); - StatsdStats::getInstance().noteHardDimensionLimitReached(mMetricId); - return true; - } - } - } else { + if (whatIt == mCurrentSlicedDurationTrackerMap.end()) { // 1. Report the tuple count if the tuple count > soft limit if (mCurrentSlicedDurationTrackerMap.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) { size_t newTupleCount = mCurrentSlicedDurationTrackerMap.size() + 1; @@ -503,24 +460,16 @@ void DurationMetricProducer::handleStartEvent(const MetricDimensionKey& eventKey const ConditionKey& conditionKeys, bool condition, const LogEvent& event) { const auto& whatKey = eventKey.getDimensionKeyInWhat(); - const auto& stateKey = eventKey.getStateValuesKey(); auto whatIt = mCurrentSlicedDurationTrackerMap.find(whatKey); if (whatIt == mCurrentSlicedDurationTrackerMap.end()) { if (hitGuardRailLocked(eventKey)) { return; } - mCurrentSlicedDurationTrackerMap[whatKey][stateKey] = createDurationTracker(eventKey); - } else { - if (whatIt->second.find(stateKey) == whatIt->second.end()) { - if (hitGuardRailLocked(eventKey)) { - return; - } - mCurrentSlicedDurationTrackerMap[whatKey][stateKey] = createDurationTracker(eventKey); - } + mCurrentSlicedDurationTrackerMap[whatKey] = createDurationTracker(eventKey); } - auto it = mCurrentSlicedDurationTrackerMap.find(whatKey)->second.find(stateKey); + auto it = mCurrentSlicedDurationTrackerMap.find(whatKey); if (mUseWhatDimensionAsInternalDimension) { it->second->noteStart(whatKey, condition, event.GetElapsedTimestampNs(), conditionKeys); @@ -560,18 +509,14 @@ void DurationMetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, // Handles Stopall events. if (matcherIndex == mStopAllIndex) { for (auto& whatIt : mCurrentSlicedDurationTrackerMap) { - for (auto& pair : whatIt.second) { - pair.second->noteStopAll(event.GetElapsedTimestampNs()); - } + whatIt.second->noteStopAll(event.GetElapsedTimestampNs()); } return; } - HashableDimensionKey dimensionInWhat; + HashableDimensionKey dimensionInWhat = DEFAULT_DIMENSION_KEY; if (!mDimensionsInWhat.empty()) { filterValues(mDimensionsInWhat, event.getValues(), &dimensionInWhat); - } else { - dimensionInWhat = DEFAULT_DIMENSION_KEY; } // Handles Stop events. @@ -579,9 +524,7 @@ void DurationMetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, if (mUseWhatDimensionAsInternalDimension) { auto whatIt = mCurrentSlicedDurationTrackerMap.find(dimensionInWhat); if (whatIt != mCurrentSlicedDurationTrackerMap.end()) { - for (const auto& stateIt : whatIt->second) { - stateIt.second->noteStop(dimensionInWhat, event.GetElapsedTimestampNs(), false); - } + whatIt->second->noteStop(dimensionInWhat, event.GetElapsedTimestampNs(), false); } return; } @@ -593,10 +536,7 @@ void DurationMetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, auto whatIt = mCurrentSlicedDurationTrackerMap.find(dimensionInWhat); if (whatIt != mCurrentSlicedDurationTrackerMap.end()) { - for (const auto& stateIt : whatIt->second) { - stateIt.second->noteStop(internalDimensionKey, event.GetElapsedTimestampNs(), - false); - } + whatIt->second->noteStop(internalDimensionKey, event.GetElapsedTimestampNs(), false); } return; } @@ -619,8 +559,8 @@ void DurationMetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, condition = condition && mIsActive; - handleStartEvent(MetricDimensionKey(dimensionInWhat, DEFAULT_DIMENSION_KEY), - conditionKey, condition, event); + handleStartEvent(MetricDimensionKey(dimensionInWhat, DEFAULT_DIMENSION_KEY), conditionKey, + condition, event); } size_t DurationMetricProducer::byteSizeLocked() const { diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h index 45908fb48f75..06da0f64aedb 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.h +++ b/cmds/statsd/src/metrics/DurationMetricProducer.h @@ -132,8 +132,7 @@ private: std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>> mPastBuckets; // The duration trackers in the current bucket. - std::unordered_map<HashableDimensionKey, - std::unordered_map<HashableDimensionKey, std::unique_ptr<DurationTracker>>> + std::unordered_map<HashableDimensionKey, std::unique_ptr<DurationTracker>> mCurrentSlicedDurationTrackerMap; // Helper function to create a duration tracker given the metric aggregation type. diff --git a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h index 6b5c2994a0c8..afe93d445e1d 100644 --- a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h @@ -82,8 +82,6 @@ public: virtual ~DurationTracker(){}; - virtual unique_ptr<DurationTracker> clone(const int64_t eventTime) = 0; - virtual void noteStart(const HashableDimensionKey& key, bool condition, const int64_t eventTime, const ConditionKey& conditionKey) = 0; virtual void noteStop(const HashableDimensionKey& key, const int64_t eventTime, diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp index df66cb0c53e5..2be5855e90e6 100644 --- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp +++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp @@ -37,24 +37,6 @@ MaxDurationTracker::MaxDurationTracker(const ConfigKey& key, const int64_t& id, conditionSliced, fullLink, anomalyTrackers) { } -unique_ptr<DurationTracker> MaxDurationTracker::clone(const int64_t eventTime) { - auto clonedTracker = make_unique<MaxDurationTracker>(*this); - for (auto it = clonedTracker->mInfos.begin(); it != clonedTracker->mInfos.end();) { - if (it->second.state != kStopped) { - it->second.lastStartTime = eventTime; - it->second.lastDuration = 0; - it++; - } else { - it = clonedTracker->mInfos.erase(it); - } - } - if (clonedTracker->mInfos.empty()) { - return nullptr; - } else { - return clonedTracker; - } -} - bool MaxDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) { // ===========GuardRail============== if (mInfos.find(newKey) != mInfos.end()) { diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h index d0371da096ea..efb8dc70afd1 100644 --- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h @@ -38,8 +38,6 @@ public: MaxDurationTracker(const MaxDurationTracker& tracker) = default; - unique_ptr<DurationTracker> clone(const int64_t eventTime) override; - void noteStart(const HashableDimensionKey& key, bool condition, const int64_t eventTime, const ConditionKey& conditionKey) override; void noteStop(const HashableDimensionKey& key, const int64_t eventTime, diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp index b0fd975b2328..57f39656fdfe 100644 --- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp +++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp @@ -38,13 +38,6 @@ OringDurationTracker::OringDurationTracker( mLastStartTime = 0; } -unique_ptr<DurationTracker> OringDurationTracker::clone(const int64_t eventTime) { - auto clonedTracker = make_unique<OringDurationTracker>(*this); - clonedTracker->mLastStartTime = eventTime; - clonedTracker->mDuration = 0; - return clonedTracker; -} - bool OringDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) { // ===========GuardRail============== // 1. Report the tuple count if the tuple count > soft limit diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h index 43c48d5f536b..c3aad668aa78 100644 --- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h @@ -37,8 +37,6 @@ public: OringDurationTracker(const OringDurationTracker& tracker) = default; - unique_ptr<DurationTracker> clone(const int64_t eventTime) override; - void noteStart(const HashableDimensionKey& key, bool condition, const int64_t eventTime, const ConditionKey& conditionKey) override; void noteStop(const HashableDimensionKey& key, const int64_t eventTime, diff --git a/cmds/statsd/src/subscriber/SubscriberReporter.cpp b/cmds/statsd/src/subscriber/SubscriberReporter.cpp index a9a105f0fda7..a37cad14fcbc 100644 --- a/cmds/statsd/src/subscriber/SubscriberReporter.cpp +++ b/cmds/statsd/src/subscriber/SubscriberReporter.cpp @@ -69,12 +69,6 @@ void SubscriberReporter::unsetBroadcastSubscriber(const ConfigKey& configKey, } } -void SubscriberReporter::removeConfig(const ConfigKey& configKey) { - VLOG("SubscriberReporter::removeConfig called."); - lock_guard<std::mutex> lock(mLock); - mIntentMap.erase(configKey); -} - void SubscriberReporter::alertBroadcastSubscriber(const ConfigKey& configKey, const Subscription& subscription, const MetricDimensionKey& dimKey) const { diff --git a/cmds/statsd/src/subscriber/SubscriberReporter.h b/cmds/statsd/src/subscriber/SubscriberReporter.h index 8ccc8ee626d4..087a1b84b91f 100644 --- a/cmds/statsd/src/subscriber/SubscriberReporter.h +++ b/cmds/statsd/src/subscriber/SubscriberReporter.h @@ -59,9 +59,6 @@ public: */ void unsetBroadcastSubscriber(const ConfigKey& configKey, int64_t subscriberId); - /** Remove all information stored by SubscriberReporter about the given config. */ - void removeConfig(const ConfigKey& configKey); - /** * Sends a broadcast via the intentSender previously stored for the * given (configKey, subscriberId) pair by setBroadcastSubscriber. diff --git a/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp b/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp index 0bc3ebb81ce6..16b51d99535b 100644 --- a/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp +++ b/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp @@ -28,16 +28,16 @@ namespace statsd { #ifdef __ANDROID__ -const string kAndroid = "android"; const string kApp1 = "app1.sharing.1"; const int kConfigKey = 789130123; // Randomly chosen to avoid collisions with existing configs. +const int kCallingUid = 0; // Randomly chosen void SendConfig(StatsService& service, const StatsdConfig& config) { string str; config.SerializeToString(&str); std::vector<uint8_t> configAsVec(str.begin(), str.end()); bool success; - service.addConfiguration(kConfigKey, configAsVec, String16(kAndroid.c_str())); + service.addConfiguration(kConfigKey, configAsVec, kCallingUid); } ConfigMetricsReport GetReports(sp<StatsLogProcessor> processor, int64_t timestamp, @@ -50,7 +50,7 @@ ConfigMetricsReport GetReports(sp<StatsLogProcessor> processor, int64_t timestam ConfigMetricsReportList reports; reports.ParseFromArray(output.data(), output.size()); EXPECT_EQ(1, reports.reports_size()); - return reports.reports(0); + return reports.reports(kCallingUid); } StatsdConfig MakeConfig() { diff --git a/cmds/statsd/tests/external/SurfaceflingerStatsPuller_test.cpp b/cmds/statsd/tests/external/SurfaceflingerStatsPuller_test.cpp deleted file mode 100644 index 5b7a30d4a5aa..000000000000 --- a/cmds/statsd/tests/external/SurfaceflingerStatsPuller_test.cpp +++ /dev/null @@ -1,96 +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. - */ - -#undef LOG_TAG -#define LOG_TAG "SurfaceflingerStatsPuller_test" - -#include "src/external/SurfaceflingerStatsPuller.h" -#include "statslog.h" - -#include <gtest/gtest.h> -#include <log/log.h> - -#ifdef __ANDROID__ - -namespace android { -namespace os { -namespace statsd { - -class TestableSurfaceflingerStatsPuller : public SurfaceflingerStatsPuller { -public: - TestableSurfaceflingerStatsPuller(const int tagId) : SurfaceflingerStatsPuller(tagId){}; - - void injectStats(const StatsProvider& statsProvider) { - mStatsProvider = statsProvider; - } -}; - -class SurfaceflingerStatsPullerTest : public ::testing::Test { -public: - SurfaceflingerStatsPullerTest() { - const ::testing::TestInfo* const test_info = - ::testing::UnitTest::GetInstance()->current_test_info(); - ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name()); - } - - ~SurfaceflingerStatsPullerTest() { - const ::testing::TestInfo* const test_info = - ::testing::UnitTest::GetInstance()->current_test_info(); - ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name()); - } -}; - -TEST_F(SurfaceflingerStatsPullerTest, pullGlobalStats) { - surfaceflinger::SFTimeStatsGlobalProto proto; - proto.set_total_frames(1); - proto.set_missed_frames(2); - proto.set_client_composition_frames(2); - proto.set_display_on_time(4); - - auto bucketOne = proto.add_present_to_present(); - bucketOne->set_time_millis(2); - bucketOne->set_frame_count(4); - auto bucketTwo = proto.add_present_to_present(); - bucketTwo->set_time_millis(4); - bucketTwo->set_frame_count(1); - auto bucketThree = proto.add_present_to_present(); - bucketThree->set_time_millis(1000); - bucketThree->set_frame_count(1); - static constexpr int64_t expectedAnimationMillis = 12; - TestableSurfaceflingerStatsPuller puller(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO); - - puller.injectStats([&] { - return proto.SerializeAsString(); - }); - puller.ForceClearCache(); - vector<std::shared_ptr<LogEvent>> outData; - puller.Pull(&outData); - - ASSERT_EQ(1, outData.size()); - EXPECT_EQ(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO, outData[0]->GetTagId()); - EXPECT_EQ(proto.total_frames(), outData[0]->getValues()[0].mValue.long_value); - EXPECT_EQ(proto.missed_frames(), outData[0]->getValues()[1].mValue.long_value); - EXPECT_EQ(proto.client_composition_frames(), outData[0]->getValues()[2].mValue.long_value); - EXPECT_EQ(proto.display_on_time(), outData[0]->getValues()[3].mValue.long_value); - EXPECT_EQ(expectedAnimationMillis, outData[0]->getValues()[4].mValue.long_value); -} - -} // namespace statsd -} // namespace os -} // namespace android -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif diff --git a/config/boot-image-profile.txt b/config/boot-image-profile.txt index 4d0acb3c2330..4aaf72728cbf 100644 --- a/config/boot-image-profile.txt +++ b/config/boot-image-profile.txt @@ -22519,8 +22519,6 @@ HSPLcom/android/internal/telephony/PhoneFactory;->makeDefaultPhone(Landroid/cont HSPLcom/android/internal/telephony/PhoneFactory;->makeDefaultPhones(Landroid/content/Context;)V HSPLcom/android/internal/telephony/PhoneInternalInterface$DataActivityState;-><init>(Ljava/lang/String;I)V HSPLcom/android/internal/telephony/PhoneInternalInterface$DataActivityState;->values()[Lcom/android/internal/telephony/PhoneInternalInterface$DataActivityState; -HSPLcom/android/internal/telephony/PhoneStateIntentReceiver;-><init>(Landroid/content/Context;Landroid/os/Handler;)V -HSPLcom/android/internal/telephony/PhoneStateIntentReceiver;->notifyServiceState(I)V HSPLcom/android/internal/telephony/PhoneSubInfoController;->callPhoneMethodWithPermissionCheck(ILjava/lang/String;Ljava/lang/String;Lcom/android/internal/telephony/PhoneSubInfoController$CallPhoneMethodHelper;Lcom/android/internal/telephony/PhoneSubInfoController$PermissionCheckHelper;)Ljava/lang/Object; HSPLcom/android/internal/telephony/PhoneSubInfoController;->getCarrierInfoForImsiEncryption(IILjava/lang/String;)Landroid/telephony/ImsiEncryptionInfo; HSPLcom/android/internal/telephony/PhoneSubInfoController;->getGroupIdLevel1ForSubscriber(ILjava/lang/String;)Ljava/lang/String; @@ -37729,7 +37727,6 @@ Lcom/android/internal/telephony/PhoneFactory; Lcom/android/internal/telephony/PhoneInternalInterface$DataActivityState; Lcom/android/internal/telephony/PhoneInternalInterface; Lcom/android/internal/telephony/PhoneNotifier; -Lcom/android/internal/telephony/PhoneStateIntentReceiver; Lcom/android/internal/telephony/PhoneSubInfoController$CallPhoneMethodHelper; Lcom/android/internal/telephony/PhoneSubInfoController$PermissionCheckHelper; Lcom/android/internal/telephony/PhoneSubInfoController; diff --git a/config/preloaded-classes b/config/preloaded-classes index 97f009c09217..e53c74bdfc4b 100644 --- a/config/preloaded-classes +++ b/config/preloaded-classes @@ -4841,7 +4841,6 @@ com.android.internal.telephony.PhoneConstants$State com.android.internal.telephony.PhoneFactory com.android.internal.telephony.PhoneInternalInterface com.android.internal.telephony.PhoneNotifier -com.android.internal.telephony.PhoneStateIntentReceiver com.android.internal.telephony.PhoneSubInfoController$CallPhoneMethodHelper com.android.internal.telephony.PhoneSubInfoController$PermissionCheckHelper com.android.internal.telephony.PhoneSubInfoController diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index de7cc9d53f2f..4f3e8ec9fb51 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -45,8 +45,19 @@ public abstract class ActivityManagerInternal { // Access modes for handleIncomingUser. public static final int ALLOW_NON_FULL = 0; + /** + * Allows access to a caller with {@link android.Manifest.permission#INTERACT_ACROSS_USERS} + * if in the same profile group. + * Otherwise, {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required. + */ public static final int ALLOW_NON_FULL_IN_PROFILE = 1; 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. + */ + public static final int ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE = 3; /** * Verify that calling app has access to the given provider. @@ -278,6 +289,9 @@ public abstract class ActivityManagerInternal { public abstract boolean isBackgroundActivityStartsEnabled(); public abstract void reportCurKeyguardUsageEvent(boolean keyguardShowing); + /** @see com.android.server.am.ActivityManagerService#monitor */ + public abstract void monitor(); + /** Input dispatch timeout to a window, start the ANR process. */ public abstract long inputDispatchingTimedOut(int pid, boolean aboveSystem, String reason); public abstract boolean inputDispatchingTimedOut(Object proc, String activityShortComponentName, diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index a4f6f57c097e..b82a67556fc0 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -3349,8 +3349,8 @@ public final class ActivityThread extends ClientTransactionHandler { } @Override - public void handleStartActivity(ActivityClientRecord r, - PendingTransactionActions pendingActions) { + public void handleStartActivity(IBinder token, PendingTransactionActions pendingActions) { + final ActivityClientRecord r = mActivities.get(token); final Activity activity = r.activity; if (r.activity == null) { // TODO(lifecycler): What do we do in this case? @@ -3364,6 +3364,8 @@ public final class ActivityThread extends ClientTransactionHandler { return; } + unscheduleGcIdler(); + // Start activity.performStart("handleStartActivity"); r.setState(ON_START); @@ -3400,6 +3402,9 @@ public final class ActivityThread extends ClientTransactionHandler { + " did not call through to super.onPostCreate()"); } } + + updateVisibility(r, true /* show */); + mSomeActivitiesChanged = true; } /** @@ -4660,8 +4665,8 @@ public final class ActivityThread extends ClientTransactionHandler { @UnsupportedAppUsage final void performStopActivity(IBinder token, boolean saveState, String reason) { ActivityClientRecord r = mActivities.get(token); - performStopActivityInner(r, null /* stopInfo */, false /* keepShown */, saveState, - false /* finalStateRequest */, reason); + performStopActivityInner(r, null /* stopInfo */, saveState, false /* finalStateRequest */, + reason); } private static final class ProviderRefCount { @@ -4687,25 +4692,19 @@ public final class ActivityThread extends ClientTransactionHandler { } /** - * Core implementation of stopping an activity. Note this is a little - * tricky because the server's meaning of stop is slightly different - * than our client -- for the server, stop means to save state and give - * it the result when it is done, but the window may still be visible. - * For the client, we want to call onStop()/onStart() to indicate when - * the activity's UI visibility changes. + * Core implementation of stopping an activity. * @param r Target activity client record. * @param info Action that will report activity stop to server. - * @param keepShown Flag indicating whether the activity is still shown. * @param saveState Flag indicating whether the activity state should be saved. * @param finalStateRequest Flag indicating if this call is handling final lifecycle state * request for a transaction. * @param reason Reason for performing this operation. */ - private void performStopActivityInner(ActivityClientRecord r, StopInfo info, boolean keepShown, + private void performStopActivityInner(ActivityClientRecord r, StopInfo info, boolean saveState, boolean finalStateRequest, String reason) { if (localLOGV) Slog.v(TAG, "Performing stop of " + r); if (r != null) { - if (!keepShown && r.stopped) { + if (r.stopped) { if (r.activity.mFinished) { // If we are finishing, we won't call onResume() in certain // cases. So here we likewise don't want to call onStop() @@ -4740,9 +4739,7 @@ public final class ActivityThread extends ClientTransactionHandler { } } - if (!keepShown) { - callActivityOnStop(r, saveState, reason); - } + callActivityOnStop(r, saveState, reason); } } @@ -4810,20 +4807,19 @@ public final class ActivityThread extends ClientTransactionHandler { } @Override - public void handleStopActivity(IBinder token, boolean show, int configChanges, + public void handleStopActivity(IBinder token, int configChanges, PendingTransactionActions pendingActions, boolean finalStateRequest, String reason) { final ActivityClientRecord r = mActivities.get(token); r.activity.mConfigChangeFlags |= configChanges; final StopInfo stopInfo = new StopInfo(); - performStopActivityInner(r, stopInfo, show, true /* saveState */, finalStateRequest, + performStopActivityInner(r, stopInfo, true /* saveState */, finalStateRequest, reason); if (localLOGV) Slog.v( - TAG, "Finishing stop of " + r + ": show=" + show - + " win=" + r.window); + TAG, "Finishing stop of " + r + ": win=" + r.window); - updateVisibility(r, show); + updateVisibility(r, false); // Make sure any pending writes are now committed. if (!r.isPreHoneycomb()) { @@ -4859,34 +4855,6 @@ public final class ActivityThread extends ClientTransactionHandler { } } - @Override - public void handleWindowVisibility(IBinder token, boolean show) { - ActivityClientRecord r = mActivities.get(token); - - if (r == null) { - Log.w(TAG, "handleWindowVisibility: no activity for token " + token); - return; - } - - if (!show && !r.stopped) { - performStopActivityInner(r, null /* stopInfo */, show, false /* saveState */, - false /* finalStateRequest */, "handleWindowVisibility"); - } else if (show && r.getLifecycleState() == ON_STOP) { - // If we are getting ready to gc after going to the background, well - // we are back active so skip it. - unscheduleGcIdler(); - - r.activity.performRestart(true /* start */, "handleWindowVisibility"); - r.setState(ON_START); - } - if (r.activity.mDecor != null) { - if (false) Slog.v( - TAG, "Handle window " + r + " visibility: " + show); - updateVisibility(r, show); - } - mSomeActivitiesChanged = true; - } - // TODO: This method should be changed to use {@link #performStopActivityInner} to perform to // stop operation on the activity to reduce code duplication and the chance of fixing a bug in // one place and missing the other. diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 42c4d36e8e3e..4a8e4e2ae012 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -3917,10 +3917,53 @@ public class AppOpsManager { void visitHistoricalOps(@NonNull HistoricalOps ops); void visitHistoricalUidOps(@NonNull HistoricalUidOps ops); void visitHistoricalPackageOps(@NonNull HistoricalPackageOps ops); + void visitHistoricalFeatureOps(@NonNull HistoricalFeatureOps ops); void visitHistoricalOp(@NonNull HistoricalOp ops); } /** + * Specifies what parameters to filter historical appop requests for + * + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, prefix = { "FILTER_BY_" }, value = { + FILTER_BY_UID, + FILTER_BY_PACKAGE_NAME, + FILTER_BY_FEATURE_ID, + FILTER_BY_OP_NAMES + }) + public @interface HistoricalOpsRequestFilter {} + + /** + * Filter historical appop request by uid. + * + * @hide + */ + public static final int FILTER_BY_UID = 1<<0; + + /** + * Filter historical appop request by package name. + * + * @hide + */ + public static final int FILTER_BY_PACKAGE_NAME = 1<<1; + + /** + * Filter historical appop request by feature id. + * + * @hide + */ + public static final int FILTER_BY_FEATURE_ID = 1<<2; + + /** + * Filter historical appop request by op names. + * + * @hide + */ + public static final int FILTER_BY_OP_NAMES = 1<<3; + + /** * Request for getting historical app op usage. The request acts * as a filtering criteria when querying historical op usage. * @@ -3932,17 +3975,22 @@ public class AppOpsManager { public static final class HistoricalOpsRequest { private final int mUid; private final @Nullable String mPackageName; + private final @Nullable String mFeatureId; private final @Nullable List<String> mOpNames; + private final @HistoricalOpsRequestFilter int mFilter; private final long mBeginTimeMillis; private final long mEndTimeMillis; private final @OpFlags int mFlags; private HistoricalOpsRequest(int uid, @Nullable String packageName, - @Nullable List<String> opNames, long beginTimeMillis, long endTimeMillis, - @OpFlags int flags) { + @Nullable String featureId, @Nullable List<String> opNames, + @HistoricalOpsRequestFilter int filter, long beginTimeMillis, + long endTimeMillis, @OpFlags int flags) { mUid = uid; mPackageName = packageName; + mFeatureId = featureId; mOpNames = opNames; + mFilter = filter; mBeginTimeMillis = beginTimeMillis; mEndTimeMillis = endTimeMillis; mFlags = flags; @@ -3958,7 +4006,9 @@ public class AppOpsManager { public static final class Builder { private int mUid = Process.INVALID_UID; private @Nullable String mPackageName; + private @Nullable String mFeatureId; private @Nullable List<String> mOpNames; + private @HistoricalOpsRequestFilter int mFilter; private final long mBeginTimeMillis; private final long mEndTimeMillis; private @OpFlags int mFlags = OP_FLAGS_ALL; @@ -3991,6 +4041,13 @@ public class AppOpsManager { Preconditions.checkArgument(uid == Process.INVALID_UID || uid >= 0, "uid must be " + Process.INVALID_UID + " or non negative"); mUid = uid; + + if (uid == Process.INVALID_UID) { + mFilter &= ~FILTER_BY_UID; + } else { + mFilter |= FILTER_BY_UID; + } + return this; } @@ -4002,6 +4059,26 @@ public class AppOpsManager { */ public @NonNull Builder setPackageName(@Nullable String packageName) { mPackageName = packageName; + + if (packageName == null) { + mFilter &= ~FILTER_BY_PACKAGE_NAME; + } else { + mFilter |= FILTER_BY_PACKAGE_NAME; + } + + return this; + } + + /** + * Sets the feature id to query for. + * + * @param featureId The id of the feature. + * @return This builder. + */ + public @NonNull Builder setFeatureId(@Nullable String featureId) { + mFeatureId = featureId; + mFilter |= FILTER_BY_FEATURE_ID; + return this; } @@ -4020,6 +4097,13 @@ public class AppOpsManager { } } mOpNames = opNames; + + if (mOpNames == null) { + mFilter &= ~FILTER_BY_OP_NAMES; + } else { + mFilter |= FILTER_BY_OP_NAMES; + } + return this; } @@ -4044,8 +4128,8 @@ public class AppOpsManager { * @return a new {@link HistoricalOpsRequest}. */ public @NonNull HistoricalOpsRequest build() { - return new HistoricalOpsRequest(mUid, mPackageName, mOpNames, - mBeginTimeMillis, mEndTimeMillis, mFlags); + return new HistoricalOpsRequest(mUid, mPackageName, mFeatureId, mOpNames, + mFilter, mBeginTimeMillis, mEndTimeMillis, mFlags); } } } @@ -4202,15 +4286,18 @@ public class AppOpsManager { /** * AppPermissionUsage the ops to leave only the data we filter for. * - * @param uid Uid to filter for or {@link android.os.Process#INCIDENTD_UID} for all. - * @param packageName Package to filter for or null for all. - * @param opNames Ops to filter for or null for all. + * @param uid Uid to filter for. + * @param packageName Package to filter for. + * @param featureId Package to filter for. + * @param opNames Ops to filter for. + * @param filter Which parameters to filter on. * @param beginTimeMillis The begin time to filter for or {@link Long#MIN_VALUE} for all. * @param endTimeMillis The end time to filter for or {@link Long#MAX_VALUE} for all. * * @hide */ - public void filter(int uid, @Nullable String packageName, @Nullable String[] opNames, + public void filter(int uid, @Nullable String packageName, @Nullable String featureId, + @Nullable String[] opNames, @HistoricalOpsRequestFilter int filter, long beginTimeMillis, long endTimeMillis) { final long durationMillis = getDurationMillis(); mBeginTimeMillis = Math.max(mBeginTimeMillis, beginTimeMillis); @@ -4220,10 +4307,10 @@ public class AppOpsManager { final int uidCount = getUidCount(); for (int i = uidCount - 1; i >= 0; i--) { final HistoricalUidOps uidOp = mHistoricalUidOps.valueAt(i); - if (uid != Process.INVALID_UID && uid != uidOp.getUid()) { + if ((filter & FILTER_BY_UID) != 0 && uid != uidOp.getUid()) { mHistoricalUidOps.removeAt(i); } else { - uidOp.filter(packageName, opNames, scaleFactor); + uidOp.filter(packageName, featureId, opNames, filter, scaleFactor); } } } @@ -4251,25 +4338,28 @@ public class AppOpsManager { /** @hide */ @TestApi public void increaseAccessCount(int opCode, int uid, @NonNull String packageName, - @UidState int uidState, @OpFlags int flags, long increment) { + @Nullable String featureId, @UidState int uidState, @OpFlags int flags, + long increment) { getOrCreateHistoricalUidOps(uid).increaseAccessCount(opCode, - packageName, uidState, flags, increment); + packageName, featureId, uidState, flags, increment); } /** @hide */ @TestApi public void increaseRejectCount(int opCode, int uid, @NonNull String packageName, - @UidState int uidState, @OpFlags int flags, long increment) { + @Nullable String featureId, @UidState int uidState, @OpFlags int flags, + long increment) { getOrCreateHistoricalUidOps(uid).increaseRejectCount(opCode, - packageName, uidState, flags, increment); + packageName, featureId, uidState, flags, increment); } /** @hide */ @TestApi public void increaseAccessDuration(int opCode, int uid, @NonNull String packageName, - @UidState int uidState, @OpFlags int flags, long increment) { + @Nullable String featureId, @UidState int uidState, @OpFlags int flags, + long increment) { getOrCreateHistoricalUidOps(uid).increaseAccessDuration(opCode, - packageName, uidState, flags, increment); + packageName, featureId, uidState, flags, increment); } /** @hide */ @@ -4549,15 +4639,17 @@ public class AppOpsManager { } } - private void filter(@Nullable String packageName, @Nullable String[] opNames, + private void filter(@Nullable String packageName, @Nullable String featureId, + @Nullable String[] opNames, @HistoricalOpsRequestFilter int filter, double fractionToRemove) { final int packageCount = getPackageCount(); for (int i = packageCount - 1; i >= 0; i--) { final HistoricalPackageOps packageOps = getPackageOpsAt(i); - if (packageName != null && !packageName.equals(packageOps.getPackageName())) { + if ((filter & FILTER_BY_PACKAGE_NAME) != 0 && !packageName.equals( + packageOps.getPackageName())) { mHistoricalPackageOps.removeAt(i); } else { - packageOps.filter(opNames, fractionToRemove); + packageOps.filter(featureId, opNames, filter, fractionToRemove); } } } @@ -4574,21 +4666,24 @@ public class AppOpsManager { } private void increaseAccessCount(int opCode, @NonNull String packageName, - @UidState int uidState, @OpFlags int flags, long increment) { + @Nullable String featureId, @UidState int uidState, @OpFlags int flags, + long increment) { getOrCreateHistoricalPackageOps(packageName).increaseAccessCount( - opCode, uidState, flags, increment); + opCode, featureId, uidState, flags, increment); } private void increaseRejectCount(int opCode, @NonNull String packageName, - @UidState int uidState, @OpFlags int flags, long increment) { + @Nullable String featureId, @UidState int uidState, @OpFlags int flags, + long increment) { getOrCreateHistoricalPackageOps(packageName).increaseRejectCount( - opCode, uidState, flags, increment); + opCode, featureId, uidState, flags, increment); } private void increaseAccessDuration(int opCode, @NonNull String packageName, - @UidState int uidState, @OpFlags int flags, long increment) { + @Nullable String featureId, @UidState int uidState, @OpFlags int flags, + long increment) { getOrCreateHistoricalPackageOps(packageName).increaseAccessDuration( - opCode, uidState, flags, increment); + opCode, featureId, uidState, flags, increment); } /** @@ -4733,7 +4828,7 @@ public class AppOpsManager { @SystemApi public static final class HistoricalPackageOps implements Parcelable { private final @NonNull String mPackageName; - private @Nullable ArrayMap<String, HistoricalOp> mHistoricalOps; + private @Nullable ArrayMap<String, HistoricalFeatureOps> mHistoricalFeatureOps; /** @hide */ public HistoricalPackageOps(@NonNull String packageName) { @@ -4742,31 +4837,359 @@ public class AppOpsManager { private HistoricalPackageOps(@NonNull HistoricalPackageOps other) { mPackageName = other.mPackageName; - final int opCount = other.getOpCount(); + final int opCount = other.getFeatureCount(); for (int i = 0; i < opCount; i++) { - final HistoricalOp origOp = other.getOpAt(i); - final HistoricalOp cloneOp = new HistoricalOp(origOp); - if (mHistoricalOps == null) { - mHistoricalOps = new ArrayMap<>(opCount); + final HistoricalFeatureOps origOps = other.getFeatureOpsAt(i); + final HistoricalFeatureOps cloneOps = new HistoricalFeatureOps(origOps); + if (mHistoricalFeatureOps == null) { + mHistoricalFeatureOps = new ArrayMap<>(opCount); } - mHistoricalOps.put(cloneOp.getOpName(), cloneOp); + mHistoricalFeatureOps.put(cloneOps.getFeatureId(), cloneOps); } } private HistoricalPackageOps(@NonNull Parcel parcel) { mPackageName = parcel.readString(); - mHistoricalOps = parcel.createTypedArrayMap(HistoricalOp.CREATOR); + mHistoricalFeatureOps = parcel.createTypedArrayMap(HistoricalFeatureOps.CREATOR); } private @Nullable HistoricalPackageOps splice(double fractionToRemove) { HistoricalPackageOps splice = null; + final int featureCount = getFeatureCount(); + for (int i = 0; i < featureCount; i++) { + final HistoricalFeatureOps origOps = getFeatureOpsAt(i); + final HistoricalFeatureOps spliceOps = origOps.splice(fractionToRemove); + if (spliceOps != null) { + if (splice == null) { + splice = new HistoricalPackageOps(mPackageName); + } + if (splice.mHistoricalFeatureOps == null) { + splice.mHistoricalFeatureOps = new ArrayMap<>(); + } + splice.mHistoricalFeatureOps.put(spliceOps.getFeatureId(), spliceOps); + } + } + return splice; + } + + private void merge(@NonNull HistoricalPackageOps other) { + final int featureCount = other.getFeatureCount(); + for (int i = 0; i < featureCount; i++) { + final HistoricalFeatureOps otherFeatureOps = other.getFeatureOpsAt(i); + final HistoricalFeatureOps thisFeatureOps = getFeatureOps( + otherFeatureOps.getFeatureId()); + if (thisFeatureOps != null) { + thisFeatureOps.merge(otherFeatureOps); + } else { + if (mHistoricalFeatureOps == null) { + mHistoricalFeatureOps = new ArrayMap<>(); + } + mHistoricalFeatureOps.put(otherFeatureOps.getFeatureId(), otherFeatureOps); + } + } + } + + private void filter(@Nullable String featureId, @Nullable String[] opNames, + @HistoricalOpsRequestFilter int filter, double fractionToRemove) { + final int featureCount = getFeatureCount(); + for (int i = featureCount - 1; i >= 0; i--) { + final HistoricalFeatureOps featureOps = getFeatureOpsAt(i); + if ((filter & FILTER_BY_FEATURE_ID) != 0 && !Objects.equals(featureId, + featureOps.getFeatureId())) { + mHistoricalFeatureOps.removeAt(i); + } else { + featureOps.filter(opNames, filter, fractionToRemove); + } + } + } + + private void accept(@NonNull HistoricalOpsVisitor visitor) { + visitor.visitHistoricalPackageOps(this); + final int featureCount = getFeatureCount(); + for (int i = 0; i < featureCount; i++) { + getFeatureOpsAt(i).accept(visitor); + } + } + + private boolean isEmpty() { + final int featureCount = getFeatureCount(); + for (int i = featureCount - 1; i >= 0; i--) { + final HistoricalFeatureOps featureOps = mHistoricalFeatureOps.valueAt(i); + if (!featureOps.isEmpty()) { + return false; + } + } + return true; + } + + private void increaseAccessCount(int opCode, @Nullable String featureId, + @UidState int uidState, @OpFlags int flags, long increment) { + getOrCreateHistoricalFeatureOps(featureId).increaseAccessCount( + opCode, uidState, flags, increment); + } + + private void increaseRejectCount(int opCode, @Nullable String featureId, + @UidState int uidState, @OpFlags int flags, long increment) { + getOrCreateHistoricalFeatureOps(featureId).increaseRejectCount( + opCode, uidState, flags, increment); + } + + private void increaseAccessDuration(int opCode, @Nullable String featureId, + @UidState int uidState, @OpFlags int flags, long increment) { + getOrCreateHistoricalFeatureOps(featureId).increaseAccessDuration( + opCode, uidState, flags, increment); + } + + /** + * Gets the package name which the data represents. + * + * @return The package name which the data represents. + */ + public @NonNull String getPackageName() { + return mPackageName; + } + + private @NonNull HistoricalFeatureOps getOrCreateHistoricalFeatureOps( + @Nullable String featureId) { + if (mHistoricalFeatureOps == null) { + mHistoricalFeatureOps = new ArrayMap<>(); + } + HistoricalFeatureOps historicalFeatureOp = mHistoricalFeatureOps.get(featureId); + if (historicalFeatureOp == null) { + historicalFeatureOp = new HistoricalFeatureOps(featureId); + mHistoricalFeatureOps.put(featureId, historicalFeatureOp); + } + return historicalFeatureOp; + } + + /** + * Gets number historical app ops. + * + * @return The number historical app ops. + * @see #getOpAt(int) + */ + public @IntRange(from = 0) int getOpCount() { + int numOps = 0; + int numFeatures = getFeatureCount(); + + for (int code = 0; code < _NUM_OP; code++) { + String opName = opToPublicName(code); + + for (int featureNum = 0; featureNum < numFeatures; featureNum++) { + if (getFeatureOpsAt(featureNum).getOp(opName) != null) { + numOps++; + break; + } + } + } + + return numOps; + } + + /** + * Gets the historical op at a given index. + * + * <p>This combines the counts from all features. + * + * @param index The index to lookup. + * @return The op at the given index. + * @see #getOpCount() + */ + public @NonNull HistoricalOp getOpAt(@IntRange(from = 0) int index) { + int numOpsFound = 0; + int numFeatures = getFeatureCount(); + + for (int code = 0; code < _NUM_OP; code++) { + String opName = opToPublicName(code); + + for (int featureNum = 0; featureNum < numFeatures; featureNum++) { + if (getFeatureOpsAt(featureNum).getOp(opName) != null) { + if (numOpsFound == index) { + return getOp(opName); + } else { + numOpsFound++; + break; + } + } + } + } + + throw new IndexOutOfBoundsException(); + } + + /** + * Gets the historical entry for a given op name. + * + * <p>This combines the counts from all features. + * + * @param opName The op name. + * @return The historical entry for that op name. + */ + public @Nullable HistoricalOp getOp(@NonNull String opName) { + if (mHistoricalFeatureOps == null) { + return null; + } + + HistoricalOp combinedOp = null; + int numFeatures = getFeatureCount(); + for (int i = 0; i < numFeatures; i++) { + HistoricalOp featureOp = getFeatureOpsAt(i).getOp(opName); + if (featureOp != null) { + if (combinedOp == null) { + combinedOp = new HistoricalOp(featureOp); + } else { + combinedOp.merge(featureOp); + } + } + } + + return combinedOp; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int flags) { + parcel.writeString(mPackageName); + parcel.writeTypedArrayMap(mHistoricalFeatureOps, flags); + } + + public static final @android.annotation.NonNull Creator<HistoricalPackageOps> CREATOR = + new Creator<HistoricalPackageOps>() { + @Override + public @NonNull HistoricalPackageOps createFromParcel(@NonNull Parcel parcel) { + return new HistoricalPackageOps(parcel); + } + + @Override + public @NonNull HistoricalPackageOps[] newArray(int size) { + return new HistoricalPackageOps[size]; + } + }; + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + final HistoricalPackageOps other = (HistoricalPackageOps) obj; + if (!mPackageName.equals(other.mPackageName)) { + return false; + } + if (mHistoricalFeatureOps == null) { + if (other.mHistoricalFeatureOps != null) { + return false; + } + } else if (!mHistoricalFeatureOps.equals(other.mHistoricalFeatureOps)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int result = mPackageName != null ? mPackageName.hashCode() : 0; + result = 31 * result + (mHistoricalFeatureOps != null ? mHistoricalFeatureOps.hashCode() + : 0); + return result; + } + + /** + * Gets number of feature with historical ops. + * + * @return The number of feature with historical ops. + * + * @see #getFeatureOpsAt(int) + */ + public @IntRange(from = 0) int getFeatureCount() { + if (mHistoricalFeatureOps == null) { + return 0; + } + return mHistoricalFeatureOps.size(); + } + + /** + * Gets the historical feature ops at a given index. + * + * @param index The index. + * + * @return The historical feature ops at the given index. + * + * @see #getFeatureCount() + */ + public @NonNull HistoricalFeatureOps getFeatureOpsAt(@IntRange(from = 0) int index) { + if (mHistoricalFeatureOps == null) { + throw new IndexOutOfBoundsException(); + } + return mHistoricalFeatureOps.valueAt(index); + } + + /** + * Gets the historical feature ops for a given feature. + * + * @param featureId The feature id. + * + * @return The historical ops for the feature. + */ + public @Nullable HistoricalFeatureOps getFeatureOps(@NonNull String featureId) { + if (mHistoricalFeatureOps == null) { + return null; + } + return mHistoricalFeatureOps.get(featureId); + } + } + + /** + * This class represents historical app op information about a feature in a package. + * + * @hide + */ + @TestApi + @SystemApi + /* codegen verifier cannot deal with nested class parameters + @DataClass(genHiddenConstructor = true, + genEqualsHashCode = true, genHiddenCopyConstructor = true) */ + @DataClass.Suppress("getHistoricalOps") + public static final class HistoricalFeatureOps implements Parcelable { + /** Id of the {@link Context#createFeatureContext feature} in the package */ + private final @Nullable String mFeatureId; + + /** Ops for this feature */ + private @Nullable ArrayMap<String, HistoricalOp> mHistoricalOps; + + /** @hide */ + public HistoricalFeatureOps(@NonNull String featureId) { + mFeatureId = featureId; + } + + private HistoricalFeatureOps(@NonNull HistoricalFeatureOps other) { + mFeatureId = other.mFeatureId; + final int opCount = other.getOpCount(); + for (int i = 0; i < opCount; i++) { + final HistoricalOp origOp = other.getOpAt(i); + final HistoricalOp cloneOp = new HistoricalOp(origOp); + if (mHistoricalOps == null) { + mHistoricalOps = new ArrayMap<>(opCount); + } + mHistoricalOps.put(cloneOp.getOpName(), cloneOp); + } + } + + private @Nullable HistoricalFeatureOps splice(double fractionToRemove) { + HistoricalFeatureOps splice = null; final int opCount = getOpCount(); for (int i = 0; i < opCount; i++) { final HistoricalOp origOps = getOpAt(i); final HistoricalOp spliceOps = origOps.splice(fractionToRemove); if (spliceOps != null) { if (splice == null) { - splice = new HistoricalPackageOps(mPackageName); + splice = new HistoricalFeatureOps(mFeatureId, null); } if (splice.mHistoricalOps == null) { splice.mHistoricalOps = new ArrayMap<>(); @@ -4777,7 +5200,7 @@ public class AppOpsManager { return splice; } - private void merge(@NonNull HistoricalPackageOps other) { + private void merge(@NonNull HistoricalFeatureOps other) { final int opCount = other.getOpCount(); for (int i = 0; i < opCount; i++) { final HistoricalOp otherOp = other.getOpAt(i); @@ -4793,11 +5216,13 @@ public class AppOpsManager { } } - private void filter(@Nullable String[] opNames, double scaleFactor) { + private void filter(@Nullable String[] opNames, @HistoricalOpsRequestFilter int filter, + double scaleFactor) { final int opCount = getOpCount(); for (int i = opCount - 1; i >= 0; i--) { final HistoricalOp op = mHistoricalOps.valueAt(i); - if (opNames != null && !ArrayUtils.contains(opNames, op.getOpName())) { + if ((filter & FILTER_BY_OP_NAMES) != 0 && !ArrayUtils.contains(opNames, + op.getOpName())) { mHistoricalOps.removeAt(i); } else { op.filter(scaleFactor); @@ -4832,15 +5257,6 @@ public class AppOpsManager { } /** - * Gets the package name which the data represents. - * - * @return The package name which the data represents. - */ - public @NonNull String getPackageName() { - return mPackageName; - } - - /** * Gets number historical app ops. * * @return The number historical app ops. @@ -4880,19 +5296,8 @@ public class AppOpsManager { return mHistoricalOps.get(opName); } - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(@NonNull Parcel parcel, int flags) { - parcel.writeString(mPackageName); - parcel.writeTypedArrayMap(mHistoricalOps, flags); - } - private void accept(@NonNull HistoricalOpsVisitor visitor) { - visitor.visitHistoricalPackageOps(this); + visitor.visitHistoricalFeatureOps(this); final int opCount = getOpCount(); for (int i = 0; i < opCount; i++) { getOpAt(i).accept(visitor); @@ -4912,47 +5317,143 @@ public class AppOpsManager { return op; } - public static final @android.annotation.NonNull Creator<HistoricalPackageOps> CREATOR = - new Creator<HistoricalPackageOps>() { - @Override - public @NonNull HistoricalPackageOps createFromParcel(@NonNull Parcel parcel) { - return new HistoricalPackageOps(parcel); - } - @Override - public @NonNull HistoricalPackageOps[] newArray(int size) { - return new HistoricalPackageOps[size]; - } - }; + + // Code below generated by codegen v1.0.14. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/app/AppOpsManager.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + /** + * Creates a new HistoricalFeatureOps. + * + * @param featureId + * Id of the {@link Context#createFeatureContext feature} in the package + * @param historicalOps + * Ops for this feature + * @hide + */ + @DataClass.Generated.Member + public HistoricalFeatureOps( + @Nullable String featureId, + @Nullable ArrayMap<String,HistoricalOp> historicalOps) { + this.mFeatureId = featureId; + this.mHistoricalOps = historicalOps; + + // onConstructed(); // You can define this method to get a callback + } + + /** + * Id of the {@link Context#createFeatureContext feature} in the package + */ + @DataClass.Generated.Member + public @Nullable String getFeatureId() { + return mFeatureId; + } @Override - public boolean equals(@Nullable Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - final HistoricalPackageOps other = (HistoricalPackageOps) obj; - if (!mPackageName.equals(other.mPackageName)) { - return false; - } - if (mHistoricalOps == null) { - if (other.mHistoricalOps != null) { - return false; - } - } else if (!mHistoricalOps.equals(other.mHistoricalOps)) { - return false; - } - return true; + @DataClass.Generated.Member + public boolean equals(@Nullable Object o) { + // You can override field equality logic by defining either of the methods like: + // boolean fieldNameEquals(HistoricalFeatureOps other) { ... } + // boolean fieldNameEquals(FieldType otherValue) { ... } + + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + @SuppressWarnings("unchecked") + HistoricalFeatureOps that = (HistoricalFeatureOps) o; + //noinspection PointlessBooleanExpression + return true + && Objects.equals(mFeatureId, that.mFeatureId) + && Objects.equals(mHistoricalOps, that.mHistoricalOps); } @Override + @DataClass.Generated.Member public int hashCode() { - int result = mPackageName != null ? mPackageName.hashCode() : 0; - result = 31 * result + (mHistoricalOps != null ? mHistoricalOps.hashCode() : 0); - return result; + // You can override field hashCode logic by defining methods like: + // int fieldNameHashCode() { ... } + + int _hash = 1; + _hash = 31 * _hash + Objects.hashCode(mFeatureId); + _hash = 31 * _hash + Objects.hashCode(mHistoricalOps); + return _hash; + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@NonNull Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + byte flg = 0; + if (mFeatureId != null) flg |= 0x1; + if (mHistoricalOps != null) flg |= 0x2; + dest.writeByte(flg); + if (mFeatureId != null) dest.writeString(mFeatureId); + if (mHistoricalOps != null) dest.writeMap(mHistoricalOps); } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + /* package-private */ HistoricalFeatureOps(@NonNull Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + byte flg = in.readByte(); + String featureId = (flg & 0x1) == 0 ? null : in.readString(); + ArrayMap<String,HistoricalOp> historicalOps = null; + if ((flg & 0x2) != 0) { + historicalOps = new ArrayMap(); + in.readMap(historicalOps, HistoricalOp.class.getClassLoader()); + } + + this.mFeatureId = featureId; + this.mHistoricalOps = historicalOps; + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator<HistoricalFeatureOps> CREATOR + = new Parcelable.Creator<HistoricalFeatureOps>() { + @Override + public HistoricalFeatureOps[] newArray(int size) { + return new HistoricalFeatureOps[size]; + } + + @Override + public HistoricalFeatureOps createFromParcel(@NonNull Parcel in) { + return new HistoricalFeatureOps(in); + } + }; + + /* + @DataClass.Generated( + time = 1578113234821L, + codegenVersion = "1.0.14", + sourceFile = "frameworks/base/core/java/android/app/AppOpsManager.java", + inputSignatures = "private final @android.annotation.Nullable java.lang.String mFeatureId\nprivate @android.annotation.Nullable android.util.ArrayMap<java.lang.String,android.app.HistoricalOp> mHistoricalOps\nprivate @android.annotation.Nullable android.app.HistoricalFeatureOps splice(double)\nprivate void merge(android.app.HistoricalFeatureOps)\nprivate void filter(java.lang.String[],int,double)\nprivate boolean isEmpty()\nprivate void increaseAccessCount(int,int,int,long)\nprivate void increaseRejectCount(int,int,int,long)\nprivate void increaseAccessDuration(int,int,int,long)\npublic @android.annotation.IntRange(from=0L) int getOpCount()\npublic @android.annotation.NonNull android.app.HistoricalOp getOpAt(int)\npublic @android.annotation.Nullable android.app.HistoricalOp getOp(java.lang.String)\nprivate void accept(android.app.HistoricalOpsVisitor)\nprivate @android.annotation.NonNull android.app.HistoricalOp getOrCreateHistoricalOp(int)\nclass HistoricalFeatureOps extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genHiddenConstructor=true, genEqualsHashCode=true, genHiddenCopyConstructor=true)") + @Deprecated + private void __metadata() {} + */ + + //@formatter:on + // End of generated code + } /** @@ -5288,13 +5789,13 @@ public class AppOpsManager { if (mOp != other.mOp) { return false; } - if (!Objects.equals(mAccessCount, other.mAccessCount)) { + if (!equalsLongSparseLongArray(mAccessCount, other.mAccessCount)) { return false; } - if (!Objects.equals(mRejectCount, other.mRejectCount)) { + if (!equalsLongSparseLongArray(mRejectCount, other.mRejectCount)) { return false; } - return Objects.equals(mAccessDuration, other.mAccessDuration); + return equalsLongSparseLongArray(mAccessDuration, other.mAccessDuration); } @Override @@ -5621,9 +6122,9 @@ public class AppOpsManager { Objects.requireNonNull(executor, "executor cannot be null"); Objects.requireNonNull(callback, "callback cannot be null"); try { - mService.getHistoricalOps(request.mUid, request.mPackageName, request.mOpNames, - request.mBeginTimeMillis, request.mEndTimeMillis, request.mFlags, - new RemoteCallback((result) -> { + mService.getHistoricalOps(request.mUid, request.mPackageName, request.mFeatureId, + request.mOpNames, request.mFilter, request.mBeginTimeMillis, + request.mEndTimeMillis, request.mFlags, new RemoteCallback((result) -> { final HistoricalOps ops = result.getParcelable(KEY_HISTORICAL_OPS); final long identity = Binder.clearCallingIdentity(); try { @@ -5661,8 +6162,8 @@ public class AppOpsManager { Objects.requireNonNull(callback, "callback cannot be null"); try { mService.getHistoricalOpsFromDiskRaw(request.mUid, request.mPackageName, - request.mOpNames, request.mBeginTimeMillis, request.mEndTimeMillis, - request.mFlags, new RemoteCallback((result) -> { + request.mFeatureId, request.mOpNames, request.mFilter, request.mBeginTimeMillis, + request.mEndTimeMillis, request.mFlags, new RemoteCallback((result) -> { final HistoricalOps ops = result.getParcelable(KEY_HISTORICAL_OPS); final long identity = Binder.clearCallingIdentity(); try { @@ -7503,6 +8004,30 @@ public class AppOpsManager { return lastEvent; } + private static boolean equalsLongSparseLongArray(@Nullable LongSparseLongArray a, + @Nullable LongSparseLongArray b) { + if (a == b) { + return true; + } + + if (a == null || b == null) { + return false; + } + + if (a.size() != b.size()) { + return false; + } + + int numEntries = a.size(); + for (int i = 0; i < numEntries; i++) { + if (a.keyAt(i) != b.keyAt(i) || a.valueAt(i) != b.valueAt(i)) { + return false; + } + } + + return true; + } + private static void writeLongSparseLongArrayToParcel( @Nullable LongSparseLongArray array, @NonNull Parcel parcel) { if (array != null) { diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java index f9a689a7e1de..d2235f10da99 100644 --- a/core/java/android/app/ClientTransactionHandler.java +++ b/core/java/android/app/ClientTransactionHandler.java @@ -119,7 +119,6 @@ public abstract class ClientTransactionHandler { /** * Stop the activity. * @param token Target activity token. - * @param show Flag indicating whether activity is still shown. * @param configChanges Activity configuration changes. * @param pendingActions Pending actions to be used on this or later stages of activity * transaction. @@ -127,7 +126,7 @@ public abstract class ClientTransactionHandler { * request for a transaction. * @param reason Reason for performing this operation. */ - public abstract void handleStopActivity(IBinder token, boolean show, int configChanges, + public abstract void handleStopActivity(IBinder token, int configChanges, PendingTransactionActions pendingActions, boolean finalStateRequest, String reason); /** Report that activity was stopped to server. */ @@ -161,15 +160,12 @@ public abstract class ClientTransactionHandler { /** Request that an activity enter picture-in-picture. */ public abstract void handlePictureInPictureRequested(IBinder token); - /** Update window visibility. */ - public abstract void handleWindowVisibility(IBinder token, boolean show); - /** Perform activity launch. */ public abstract Activity handleLaunchActivity(ActivityThread.ActivityClientRecord r, PendingTransactionActions pendingActions, Intent customIntent); /** Perform activity start. */ - public abstract void handleStartActivity(ActivityThread.ActivityClientRecord r, + public abstract void handleStartActivity(IBinder token, PendingTransactionActions pendingActions); /** Get package info. */ diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java index 49c389a0c4a7..1278ff6817fd 100644 --- a/core/java/android/app/DownloadManager.java +++ b/core/java/android/app/DownloadManager.java @@ -16,7 +16,9 @@ package android.app; +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; @@ -40,11 +42,13 @@ import android.os.Environment; import android.os.FileUtils; import android.os.ParcelFileDescriptor; import android.os.RemoteException; +import android.provider.BaseColumns; import android.provider.Downloads; import android.provider.MediaStore; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; import android.text.TextUtils; +import android.util.LongSparseArray; import android.util.Pair; import java.io.File; @@ -1069,6 +1073,37 @@ public class DownloadManager { } /** + * Notify {@link DownloadManager} that the given {@link MediaStore} items + * were just deleted so that {@link DownloadManager} internal data + * structures can be cleaned up. + * + * @param idToMime map from {@link BaseColumns#_ID} to + * {@link ContentResolver#getType(Uri)}. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE) + public void onMediaStoreDownloadsDeleted(@NonNull LongSparseArray<String> idToMime) { + try (ContentProviderClient client = mResolver + .acquireUnstableContentProviderClient(mBaseUri)) { + final Bundle callExtras = new Bundle(); + final long[] ids = new long[idToMime.size()]; + final String[] mimeTypes = new String[idToMime.size()]; + for (int i = idToMime.size() - 1; i >= 0; --i) { + ids[i] = idToMime.keyAt(i); + mimeTypes[i] = idToMime.valueAt(i); + } + callExtras.putLongArray(android.provider.Downloads.EXTRA_IDS, ids); + callExtras.putStringArray(android.provider.Downloads.EXTRA_MIME_TYPES, + mimeTypes); + client.call(android.provider.Downloads.CALL_MEDIASTORE_DOWNLOADS_DELETED, + null, callExtras); + } catch (RemoteException e) { + // Should not happen + } + } + + /** * Enqueue a new download. The download will start automatically once the download manager is * ready to execute it and connectivity is available. * diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl index 700b3c1b620e..e5c046c2376c 100644 --- a/core/java/android/app/IActivityTaskManager.aidl +++ b/core/java/android/app/IActivityTaskManager.aidl @@ -68,6 +68,7 @@ import android.os.StrictMode; import android.os.WorkSource; import android.service.voice.IVoiceInteractionSession; import android.view.IRecentsAnimationRunner; +import android.view.ITaskOrganizer; import android.view.RemoteAnimationDefinition; import android.view.RemoteAnimationAdapter; import android.view.WindowContainerTransaction; @@ -121,6 +122,9 @@ interface IActivityTaskManager { in Intent intent, in String resolvedType, in IBinder resultTo, in String resultWho, int requestCode, int flags, in ProfilerInfo profilerInfo, in Bundle options, IBinder permissionToken, boolean ignoreTargetSecurity, int userId); + + void registerTaskOrganizer(in ITaskOrganizer organizer, int windowingMode); + boolean isActivityStartAllowedOnDisplay(int displayId, in Intent intent, in String resolvedType, int userId); diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 86f52af1a13b..fcdb7cc0855d 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -94,8 +94,11 @@ interface INotificationManager void updateNotificationChannelGroupForPackage(String pkg, int uid, in NotificationChannelGroup group); void updateNotificationChannelForPackage(String pkg, int uid, in NotificationChannel channel); NotificationChannel getNotificationChannel(String callingPkg, int userId, String pkg, String channelId); + NotificationChannel getConversationNotificationChannel(String callingPkg, int userId, String pkg, String channelId, String conversationId); + void createConversationNotificationChannelForPackage(String pkg, int uid, in NotificationChannel parentChannel, String conversationId); NotificationChannel getNotificationChannelForPackage(String pkg, int uid, String channelId, boolean includeDeleted); void deleteNotificationChannel(String pkg, String channelId); + void deleteConversationNotificationChannels(String pkg, int uid, String conversationId); ParceledListSlice getNotificationChannels(String callingPkg, String targetPkg, int userId); ParceledListSlice getNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted); int getNumNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted); diff --git a/core/java/android/app/LocalActivityManager.java b/core/java/android/app/LocalActivityManager.java index 4033aea32b55..7cdf85e0a6b8 100644 --- a/core/java/android/app/LocalActivityManager.java +++ b/core/java/android/app/LocalActivityManager.java @@ -177,7 +177,7 @@ public class LocalActivityManager { pendingActions = null; } - mActivityThread.handleStartActivity(clientRecord, pendingActions); + mActivityThread.handleStartActivity(r, pendingActions); r.curState = STARTED; if (desiredState == RESUMED) { diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java index 3eee1ae5c00c..a33c2c19d05c 100644 --- a/core/java/android/app/NotificationChannel.java +++ b/core/java/android/app/NotificationChannel.java @@ -23,6 +23,7 @@ import android.compat.annotation.UnsupportedAppUsage; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.content.pm.ShortcutInfo; import android.media.AudioAttributes; import android.net.Uri; import android.os.Parcel; @@ -57,6 +58,22 @@ public final class NotificationChannel implements Parcelable { public static final String DEFAULT_CHANNEL_ID = "miscellaneous"; /** + * The formatter used by the system to create an id for notification + * channels when it automatically creates conversation channels on behalf of an app. The format + * string takes two arguments, in this order: the + * {@link #getId()} of the original notification channel, and the + * {@link ShortcutInfo#getId() id} of the conversation. + */ + public static final String CONVERSATION_CHANNEL_ID_FORMAT = "%1$s : %2$s"; + + /** + * TODO: STOPSHIP remove + * Conversation id to use for apps that aren't providing them yet. + * @hide + */ + public static final String PLACEHOLDER_CONVERSATION_ID = "placeholder_id"; + + /** * The maximum length for text fields in a NotificationChannel. Fields will be truncated at this * limit. */ @@ -85,6 +102,8 @@ public final class NotificationChannel implements Parcelable { private static final String ATT_BLOCKABLE_SYSTEM = "blockable_system"; private static final String ATT_ALLOW_BUBBLE = "can_bubble"; private static final String ATT_ORIG_IMP = "orig_imp"; + private static final String ATT_PARENT_CHANNEL = "parent"; + private static final String ATT_CONVERSATION_ID = "conv_id"; private static final String DELIMITER = ","; /** @@ -147,7 +166,7 @@ public final class NotificationChannel implements Parcelable { private static final boolean DEFAULT_ALLOW_BUBBLE = true; @UnsupportedAppUsage - private final String mId; + private String mId; private String mName; private String mDesc; private int mImportance = DEFAULT_IMPORTANCE; @@ -172,6 +191,8 @@ public final class NotificationChannel implements Parcelable { private boolean mAllowBubbles = DEFAULT_ALLOW_BUBBLE; private boolean mImportanceLockedByOEM; private boolean mImportanceLockedDefaultApp; + private String mParentId = null; + private String mConversationId = null; /** * Creates a notification channel. @@ -236,6 +257,8 @@ public final class NotificationChannel implements Parcelable { mAllowBubbles = in.readBoolean(); mImportanceLockedByOEM = in.readBoolean(); mOriginalImportance = in.readInt(); + mParentId = in.readString(); + mConversationId = in.readString(); } @Override @@ -291,6 +314,8 @@ public final class NotificationChannel implements Parcelable { dest.writeBoolean(mAllowBubbles); dest.writeBoolean(mImportanceLockedByOEM); dest.writeInt(mOriginalImportance); + dest.writeString(mParentId); + dest.writeString(mConversationId); } /** @@ -363,6 +388,13 @@ public final class NotificationChannel implements Parcelable { // Modifiable by apps on channel creation. /** + * @hide + */ + public void setId(String id) { + mId = id; + } + + /** * Sets what group this channel belongs to. * * Group information is only used for presentation, not for behavior. @@ -502,6 +534,23 @@ public final class NotificationChannel implements Parcelable { } /** + * Sets this channel as being person-centric. Different settings and functionality may be + * exposed for people-centric channels. + * + * @param parentChannelId The {@link #getId()} id} of the generic channel that notifications of + * this type would be posted to in absence of a specific conversation id. + * For example, if this channel represents 'Messages from Person A', the + * parent channel would be 'Messages.' + * @param conversationId The {@link ShortcutInfo#getId()} of the shortcut representing this + * channel's conversation. + */ + public void setConversationId(@Nullable String parentChannelId, + @Nullable String conversationId) { + mParentId = parentChannelId; + mConversationId = conversationId; + } + + /** * Returns the id of this channel. */ public String getId() { @@ -622,6 +671,22 @@ public final class NotificationChannel implements Parcelable { } /** + * Returns the {@link #getId() id} of the parent notification channel to this channel, if it's + * a conversation related channel. See {@link #setConversationId(String, String)}. + */ + public @Nullable String getParentChannelId() { + return mParentId; + } + + /** + * Returns the {@link ShortcutInfo#getId() id} of the conversation backing this channel, if it's + * associated with a conversation. See {@link #setConversationId(String, String)}. + */ + public @Nullable String getConversationId() { + return mConversationId; + } + + /** * @hide */ @SystemApi @@ -761,6 +826,8 @@ public final class NotificationChannel implements Parcelable { setBlockableSystem(safeBool(parser, ATT_BLOCKABLE_SYSTEM, false)); setAllowBubbles(safeBool(parser, ATT_ALLOW_BUBBLE, DEFAULT_ALLOW_BUBBLE)); setOriginalImportance(safeInt(parser, ATT_ORIG_IMP, DEFAULT_IMPORTANCE)); + setConversationId(parser.getAttributeValue(ATT_PARENT_CHANNEL, null), + parser.getAttributeValue(ATT_CONVERSATION_ID, null)); } @Nullable @@ -885,6 +952,12 @@ public final class NotificationChannel implements Parcelable { if (getOriginalImportance() != DEFAULT_IMPORTANCE) { out.attribute(null, ATT_ORIG_IMP, Integer.toString(getOriginalImportance())); } + if (getParentChannelId() != null) { + out.attribute(null, ATT_PARENT_CHANNEL, getParentChannelId()); + } + if (getConversationId() != null) { + out.attribute(null, ATT_CONVERSATION_ID, getConversationId()); + } // mImportanceLockedDefaultApp and mImportanceLockedByOEM have a different source of // truth and so aren't written to this xml file @@ -1042,7 +1115,9 @@ public final class NotificationChannel implements Parcelable { && Objects.equals(getAudioAttributes(), that.getAudioAttributes()) && mImportanceLockedByOEM == that.mImportanceLockedByOEM && mImportanceLockedDefaultApp == that.mImportanceLockedDefaultApp - && mOriginalImportance == that.mOriginalImportance; + && mOriginalImportance == that.mOriginalImportance + && Objects.equals(getParentChannelId(), that.getParentChannelId()) + && Objects.equals(getConversationId(), that.getConversationId()); } @Override @@ -1052,7 +1127,8 @@ public final class NotificationChannel implements Parcelable { getUserLockedFields(), isFgServiceShown(), mVibrationEnabled, mShowBadge, isDeleted(), getGroup(), getAudioAttributes(), isBlockableSystem(), mAllowBubbles, - mImportanceLockedByOEM, mImportanceLockedDefaultApp, mOriginalImportance); + mImportanceLockedByOEM, mImportanceLockedDefaultApp, mOriginalImportance, + mParentId, mConversationId); result = 31 * result + Arrays.hashCode(mVibration); return result; } @@ -1063,26 +1139,7 @@ public final class NotificationChannel implements Parcelable { String output = "NotificationChannel{" + "mId='" + mId + '\'' + ", mName=" + redactedName - + ", mDescription=" + (!TextUtils.isEmpty(mDesc) ? "hasDescription " : "") - + ", mImportance=" + mImportance - + ", mBypassDnd=" + mBypassDnd - + ", mLockscreenVisibility=" + mLockscreenVisibility - + ", mSound=" + mSound - + ", mLights=" + mLights - + ", mLightColor=" + mLightColor - + ", mVibration=" + Arrays.toString(mVibration) - + ", mUserLockedFields=" + Integer.toHexString(mUserLockedFields) - + ", mFgServiceShown=" + mFgServiceShown - + ", mVibrationEnabled=" + mVibrationEnabled - + ", mShowBadge=" + mShowBadge - + ", mDeleted=" + mDeleted - + ", mGroup='" + mGroup + '\'' - + ", mAudioAttributes=" + mAudioAttributes - + ", mBlockableSystem=" + mBlockableSystem - + ", mAllowBubbles=" + mAllowBubbles - + ", mImportanceLockedByOEM=" + mImportanceLockedByOEM - + ", mImportanceLockedDefaultApp=" + mImportanceLockedDefaultApp - + ", mOriginalImp=" + mOriginalImportance + + getFieldsString() + '}'; pw.println(prefix + output); } @@ -1092,7 +1149,12 @@ public final class NotificationChannel implements Parcelable { return "NotificationChannel{" + "mId='" + mId + '\'' + ", mName=" + mName - + ", mDescription=" + (!TextUtils.isEmpty(mDesc) ? "hasDescription " : "") + + getFieldsString() + + '}'; + } + + private String getFieldsString() { + return ", mDescription=" + (!TextUtils.isEmpty(mDesc) ? "hasDescription " : "") + ", mImportance=" + mImportance + ", mBypassDnd=" + mBypassDnd + ", mLockscreenVisibility=" + mLockscreenVisibility @@ -1112,7 +1174,8 @@ public final class NotificationChannel implements Parcelable { + ", mImportanceLockedByOEM=" + mImportanceLockedByOEM + ", mImportanceLockedDefaultApp=" + mImportanceLockedDefaultApp + ", mOriginalImp=" + mOriginalImportance - + '}'; + + ", mParent=" + mParentId + + ", mConversationId" + mConversationId; } /** @hide */ diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index fdbb8bb0d4c5..61c109885e05 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -843,6 +843,25 @@ public class NotificationManager { } /** + * Returns the notification channel settings for a given channel and conversation id. + * + * <p>The channel must belong to your package, or to a package you are an approved notification + * delegate for (see {@link #canNotifyAsPackage(String)}), or it will not be returned. To query + * a channel as a notification delegate, call this method from a context created for that + * package (see {@link Context#createPackageContext(String, int)}).</p> + */ + public @Nullable NotificationChannel getNotificationChannel(@NonNull String channelId, + @NonNull String conversationId) { + INotificationManager service = getService(); + try { + return service.getConversationNotificationChannel(mContext.getOpPackageName(), + mContext.getUserId(), mContext.getPackageName(), channelId, conversationId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Returns all notification channels belonging to the calling package. * * <p>Approved notification delegates (see {@link #canNotifyAsPackage(String)}) can query diff --git a/core/java/android/app/StatsManager.java b/core/java/android/app/StatsManager.java index 83d1de60cac7..dde6dda8e448 100644 --- a/core/java/android/app/StatsManager.java +++ b/core/java/android/app/StatsManager.java @@ -26,7 +26,6 @@ import android.content.Context; import android.os.IBinder; import android.os.IPullAtomCallback; import android.os.IPullAtomResultReceiver; -import android.os.IStatsCompanionService; import android.os.IStatsManagerService; import android.os.IStatsPullerCallback; import android.os.IStatsd; @@ -61,9 +60,6 @@ public final class StatsManager { private IStatsd mService; @GuardedBy("sLock") - private IStatsCompanionService mStatsCompanion; - - @GuardedBy("sLock") private IStatsManagerService mStatsManagerService; /** @@ -157,11 +153,11 @@ public final class StatsManager { public void addConfig(long configKey, byte[] config) throws StatsUnavailableException { synchronized (sLock) { try { - IStatsd service = getIStatsdLocked(); + IStatsManagerService service = getIStatsManagerServiceLocked(); // can throw IllegalArgumentException service.addConfiguration(configKey, config, mContext.getOpPackageName()); } catch (RemoteException e) { - Slog.e(TAG, "Failed to connect to statsd when adding configuration"); + Slog.e(TAG, "Failed to connect to statsmanager when adding configuration"); throw new StatsUnavailableException("could not connect", e); } catch (SecurityException e) { throw new StatsUnavailableException(e.getMessage(), e); @@ -194,10 +190,10 @@ public final class StatsManager { public void removeConfig(long configKey) throws StatsUnavailableException { synchronized (sLock) { try { - IStatsd service = getIStatsdLocked(); + IStatsManagerService service = getIStatsManagerServiceLocked(); service.removeConfiguration(configKey, mContext.getOpPackageName()); } catch (RemoteException e) { - Slog.e(TAG, "Failed to connect to statsd when removing configuration"); + Slog.e(TAG, "Failed to connect to statsmanager when removing configuration"); throw new StatsUnavailableException("could not connect", e); } catch (SecurityException e) { throw new StatsUnavailableException(e.getMessage(), e); @@ -390,10 +386,10 @@ public final class StatsManager { public byte[] getReports(long configKey) throws StatsUnavailableException { synchronized (sLock) { try { - IStatsd service = getIStatsdLocked(); + IStatsManagerService service = getIStatsManagerServiceLocked(); return service.getData(configKey, mContext.getOpPackageName()); } catch (RemoteException e) { - Slog.e(TAG, "Failed to connect to statsd when getting data"); + Slog.e(TAG, "Failed to connect to statsmanager when getting data"); throw new StatsUnavailableException("could not connect", e); } catch (SecurityException e) { throw new StatsUnavailableException(e.getMessage(), e); @@ -427,10 +423,10 @@ public final class StatsManager { public byte[] getStatsMetadata() throws StatsUnavailableException { synchronized (sLock) { try { - IStatsd service = getIStatsdLocked(); + IStatsManagerService service = getIStatsManagerServiceLocked(); return service.getMetadata(mContext.getOpPackageName()); } catch (RemoteException e) { - Slog.e(TAG, "Failed to connect to statsd when getting metadata"); + Slog.e(TAG, "Failed to connect to statsmanager when getting metadata"); throw new StatsUnavailableException("could not connect", e); } catch (SecurityException e) { throw new StatsUnavailableException(e.getMessage(), e); @@ -462,21 +458,19 @@ public final class StatsManager { throws StatsUnavailableException { synchronized (sLock) { try { - IStatsd service = getIStatsdLocked(); + IStatsManagerService service = getIStatsManagerServiceLocked(); if (service == null) { - if (DEBUG) { - Slog.d(TAG, "Failed to find statsd when getting experiment IDs"); - } - return new long[0]; + throw new StatsUnavailableException("Failed to find statsmanager when " + + "getting experiment IDs"); } return service.getRegisteredExperimentIds(); } catch (RemoteException e) { if (DEBUG) { Slog.d(TAG, - "Failed to connect to StatsCompanionService when getting " + "Failed to connect to StatsManagerService when getting " + "registered experiment IDs"); } - return new long[0]; + throw new StatsUnavailableException("could not connect", e); } } } @@ -540,7 +534,7 @@ public final class StatsManager { } synchronized (sLock) { try { - IStatsCompanionService service = getIStatsCompanionServiceLocked(); + IStatsManagerService service = getIStatsManagerServiceLocked(); PullAtomCallbackInternal rec = new PullAtomCallbackInternal(atomTag, callback, executor); service.registerPullAtomCallback(atomTag, coolDownNs, timeoutNs, additiveFields, @@ -562,7 +556,7 @@ public final class StatsManager { public void unregisterPullAtomCallback(int atomTag) { synchronized (sLock) { try { - IStatsCompanionService service = getIStatsCompanionServiceLocked(); + IStatsManagerService service = getIStatsManagerServiceLocked(); service.unregisterPullAtomCallback(atomTag); } catch (RemoteException e) { throw new RuntimeException("Unable to unregister pull atom callback"); @@ -748,16 +742,6 @@ public final class StatsManager { } @GuardedBy("sLock") - private IStatsCompanionService getIStatsCompanionServiceLocked() { - if (mStatsCompanion != null) { - return mStatsCompanion; - } - mStatsCompanion = IStatsCompanionService.Stub.asInterface( - ServiceManager.getService("statscompanion")); - return mStatsCompanion; - } - - @GuardedBy("sLock") private IStatsManagerService getIStatsManagerServiceLocked() { if (mStatsManagerService != null) { return mStatsManagerService; diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java index a1765c85d57b..42563b5a1561 100644 --- a/core/java/android/app/StatusBarManager.java +++ b/core/java/android/app/StatusBarManager.java @@ -153,6 +153,11 @@ public class StatusBarManager { */ public static final int DEFAULT_SETUP_DISABLE2_FLAGS = DISABLE2_ROTATE_SUGGESTIONS; + /** + * disable flags to be applied when the device is sim-locked. + */ + private static final int DEFAULT_SIM_LOCKED_DISABLED_FLAGS = DISABLE_EXPAND; + /** @hide */ public static final int NAVIGATION_HINT_BACK_ALT = 1 << 0; /** @hide */ @@ -262,6 +267,7 @@ public class StatusBarManager { * @hide */ @UnsupportedAppUsage + @TestApi public void expandNotificationsPanel() { try { final IStatusBarService svc = getService(); @@ -279,6 +285,7 @@ public class StatusBarManager { * @hide */ @UnsupportedAppUsage + @TestApi public void collapsePanels() { try { final IStatusBarService svc = getService(); @@ -385,6 +392,30 @@ public class StatusBarManager { } /** + * Enable or disable expansion of the status bar. When the device is SIM-locked, the status + * bar should not be expandable. + * + * @param disabled If {@code true}, the status bar will be set to non-expandable. If + * {@code false}, re-enables expansion of the status bar. + * @hide + */ + @SystemApi + @TestApi + @RequiresPermission(android.Manifest.permission.STATUS_BAR) + public void setDisabledForSimNetworkLock(boolean disabled) { + try { + final int userId = Binder.getCallingUserHandle().getIdentifier(); + final IStatusBarService svc = getService(); + if (svc != null) { + svc.disableForUser(disabled ? DEFAULT_SIM_LOCKED_DISABLED_FLAGS : DISABLE_NONE, + mToken, mContext.getPackageName(), userId); + } + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + + /** * Get this app's currently requested disabled components * * @return a new DisableInfo diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index d3132d85ad2f..2aac94c6f5da 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -88,7 +88,6 @@ import android.telephony.data.ApnSetting; import android.util.ArraySet; import android.util.Log; -import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.BackgroundThread; import com.android.internal.util.Preconditions; @@ -6757,6 +6756,34 @@ public class DevicePolicyManager { } /** + * @hide + * Privileged apps can use this method to find out if the device was provisioned as + * organization-owend device with a managed profile. + * + * This, together with checking whether the device has a device owner (by calling + * {@link #isDeviceManaged()}), could be used to learn whether the device is owned by an + * organization or an individual: + * If this method returns true OR {@link #isDeviceManaged()} returns true, then + * the device is owned by an organization. Otherwise, it's owned by an individual. + * + * @return {@code true} if the device was provisioned as organization-owned device, + * {@code false} otherwise. + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MANAGE_USERS) + public boolean isOrganizationOwnedDeviceWithManagedProfile() { + throwIfParentInstance("isOrganizationOwnedDeviceWithManagedProfile"); + if (mService != null) { + try { + return mService.isOrganizationOwnedDeviceWithManagedProfile(); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + return false; + } + + /** * Returns whether the specified package can read the device identifiers. * * @param packageName The package name of the app to check for device identifier access. @@ -11215,12 +11242,12 @@ public class DevicePolicyManager { * #setCrossProfilePackages(ComponentName, Set)}.</li> * <li>The default package names set by the OEM that are allowed to request user consent for * cross-profile communication without being explicitly enabled by the admin, via - * {@link R.array#cross_profile_apps}</li> + * {@link com.android.internal.R.array#cross_profile_apps}</li> * </ul> * * @return the combined set of whitelisted package names set via * {@link #setCrossProfilePackages(ComponentName, Set)} and - * {@link R.array#cross_profile_apps} + * {@link com.android.internal.R.array#cross_profile_apps} * * @hide */ @@ -11338,4 +11365,39 @@ public class DevicePolicyManager { } return false; } + + /** + * Called by Device owner to set packages as protected. User will not be able to clear app + * data or force-stop protected packages. + * + * @param admin which {@link DeviceAdminReceiver} this request is associated with + * @param packages The package names to protect. + * @throws SecurityException if {@code admin} is not a device owner. + */ + public void setProtectedPackages(@NonNull ComponentName admin, @NonNull List<String> packages) { + if (mService != null) { + try { + mService.setProtectedPackages(admin, packages); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + } + + /** + * Returns the list of packages protected by the device owner. + * + * @param admin which {@link DeviceAdminReceiver} this request is associated with + * @throws SecurityException if {@code admin} is not a device owner. + */ + public @NonNull List<String> getProtectedPackages(@NonNull ComponentName admin) { + if (mService != null) { + try { + return mService.getProtectedPackages(admin); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + return Collections.emptyList(); + } } diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java index f299d456a18f..e6c89d9071e6 100644 --- a/core/java/android/app/admin/DevicePolicyManagerInternal.java +++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java @@ -17,9 +17,12 @@ package android.app.admin; import android.annotation.UserIdInt; +import android.content.ComponentName; import android.content.Intent; +import android.os.UserHandle; import java.util.List; +import java.util.Set; /** * Device policy manager local system service interface. @@ -165,4 +168,23 @@ public abstract class DevicePolicyManagerInternal { * Do not call it directly. Use {@link DevicePolicyCache#getInstance()} instead. */ protected abstract DeviceStateCache getDeviceStateCache(); + + /** + * Returns the combined set of the following: + * <ul> + * <li>The package names that the admin has previously set as allowed to request user consent + * for cross-profile communication, via {@link + * DevicePolicyManager#setCrossProfilePackages(ComponentName, Set)}.</li> + * <li>The default package names that are allowed to request user consent for cross-profile + * communication without being explicitly enabled by the admin , via {@link + * DevicePolicyManager#setDefaultCrossProfilePackages(ComponentName, UserHandle, Set)}.</li> + * </ul> + * + * @return the combined set of whitelisted package names set via + * {@link DevicePolicyManager#setCrossProfilePackages(ComponentName, Set)} and + * {@link DevicePolicyManager#setDefaultCrossProfilePackages(ComponentName, UserHandle, Set)} + * + * @hide + */ + public abstract List<String> getAllCrossProfilePackages(); } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 08c5dff97884..3eec46bd010e 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -156,6 +156,7 @@ interface IDevicePolicyManager { void setProfileName(in ComponentName who, String profileName); void clearProfileOwner(in ComponentName who); boolean hasUserSetupCompleted(); + boolean isOrganizationOwnedDeviceWithManagedProfile(); boolean checkDeviceIdentifierAccess(in String packageName, int pid, int uid); @@ -452,4 +453,8 @@ interface IDevicePolicyManager { boolean startViewCalendarEventInManagedProfile(String packageName, long eventId, long start, long end, boolean allDay, int flags); boolean setKeyGrantForApp(in ComponentName admin, String callerPackage, String alias, String packageName, boolean hasGrant); + + void setProtectedPackages(in ComponentName admin, in List<String> packages); + + List<String> getProtectedPackages(in ComponentName admin); } diff --git a/core/java/android/app/servertransaction/WindowVisibilityItem.java b/core/java/android/app/servertransaction/StartActivityItem.java index 115d1ececc0e..4fbe02b9cf76 100644 --- a/core/java/android/app/servertransaction/WindowVisibilityItem.java +++ b/core/java/android/app/servertransaction/StartActivityItem.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 The Android Open Source Project + * 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. @@ -24,41 +24,44 @@ import android.os.Parcel; import android.os.Trace; /** - * Window visibility change message. + * Request to move an activity to started and visible state. * @hide */ -public class WindowVisibilityItem extends ClientTransactionItem { +public class StartActivityItem extends ActivityLifecycleItem { - private boolean mShowWindow; + private static final String TAG = "StartActivityItem"; @Override public void execute(ClientTransactionHandler client, IBinder token, PendingTransactionActions pendingActions) { - Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, - mShowWindow ? "activityShowWindow" : "activityHideWindow"); - client.handleWindowVisibility(token, mShowWindow); + Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "startActivityItem"); + client.handleStartActivity(token, pendingActions); Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); } + @Override + public int getTargetState() { + return ON_START; + } + // ObjectPoolItem implementation - private WindowVisibilityItem() {} + private StartActivityItem() {} /** Obtain an instance initialized with provided params. */ - public static WindowVisibilityItem obtain(boolean showWindow) { - WindowVisibilityItem instance = ObjectPool.obtain(WindowVisibilityItem.class); + public static StartActivityItem obtain() { + StartActivityItem instance = ObjectPool.obtain(StartActivityItem.class); if (instance == null) { - instance = new WindowVisibilityItem(); + instance = new StartActivityItem(); } - instance.mShowWindow = showWindow; return instance; } @Override public void recycle() { - mShowWindow = false; + super.recycle(); ObjectPool.recycle(this); } @@ -68,24 +71,24 @@ public class WindowVisibilityItem extends ClientTransactionItem { /** Write to Parcel. */ @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeBoolean(mShowWindow); + // Empty } /** Read from Parcel. */ - private WindowVisibilityItem(Parcel in) { - mShowWindow = in.readBoolean(); + private StartActivityItem(Parcel in) { + // Empty } - public static final @android.annotation.NonNull Creator<WindowVisibilityItem> CREATOR = - new Creator<WindowVisibilityItem>() { - public WindowVisibilityItem createFromParcel(Parcel in) { - return new WindowVisibilityItem(in); - } + public static final @android.annotation.NonNull Creator<StartActivityItem> CREATOR = + new Creator<StartActivityItem>() { + public StartActivityItem createFromParcel(Parcel in) { + return new StartActivityItem(in); + } - public WindowVisibilityItem[] newArray(int size) { - return new WindowVisibilityItem[size]; - } - }; + public StartActivityItem[] newArray(int size) { + return new StartActivityItem[size]; + } + }; @Override public boolean equals(Object o) { @@ -95,17 +98,17 @@ public class WindowVisibilityItem extends ClientTransactionItem { if (o == null || getClass() != o.getClass()) { return false; } - final WindowVisibilityItem other = (WindowVisibilityItem) o; - return mShowWindow == other.mShowWindow; + return true; } @Override public int hashCode() { - return 17 + 31 * (mShowWindow ? 1 : 0); + return 17; } @Override public String toString() { - return "WindowVisibilityItem{showWindow=" + mShowWindow + "}"; + return "StartActivityItem{}"; } } + diff --git a/core/java/android/app/servertransaction/StopActivityItem.java b/core/java/android/app/servertransaction/StopActivityItem.java index 63efa6fe7c17..8668bd49c8f5 100644 --- a/core/java/android/app/servertransaction/StopActivityItem.java +++ b/core/java/android/app/servertransaction/StopActivityItem.java @@ -31,14 +31,13 @@ public class StopActivityItem extends ActivityLifecycleItem { private static final String TAG = "StopActivityItem"; - private boolean mShowWindow; private int mConfigChanges; @Override public void execute(ClientTransactionHandler client, IBinder token, PendingTransactionActions pendingActions) { Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStop"); - client.handleStopActivity(token, mShowWindow, mConfigChanges, pendingActions, + client.handleStopActivity(token, mConfigChanges, pendingActions, true /* finalStateRequest */, "STOP_ACTIVITY_ITEM"); Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); } @@ -59,13 +58,15 @@ public class StopActivityItem extends ActivityLifecycleItem { private StopActivityItem() {} - /** Obtain an instance initialized with provided params. */ - public static StopActivityItem obtain(boolean showWindow, int configChanges) { + /** + * Obtain an instance initialized with provided params. + * @param configChanges Configuration pieces that changed. + */ + public static StopActivityItem obtain(int configChanges) { StopActivityItem instance = ObjectPool.obtain(StopActivityItem.class); if (instance == null) { instance = new StopActivityItem(); } - instance.mShowWindow = showWindow; instance.mConfigChanges = configChanges; return instance; @@ -74,7 +75,6 @@ public class StopActivityItem extends ActivityLifecycleItem { @Override public void recycle() { super.recycle(); - mShowWindow = false; mConfigChanges = 0; ObjectPool.recycle(this); } @@ -85,13 +85,11 @@ public class StopActivityItem extends ActivityLifecycleItem { /** Write to Parcel. */ @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeBoolean(mShowWindow); dest.writeInt(mConfigChanges); } /** Read from Parcel. */ private StopActivityItem(Parcel in) { - mShowWindow = in.readBoolean(); mConfigChanges = in.readInt(); } @@ -115,20 +113,18 @@ public class StopActivityItem extends ActivityLifecycleItem { return false; } final StopActivityItem other = (StopActivityItem) o; - return mShowWindow == other.mShowWindow && mConfigChanges == other.mConfigChanges; + return mConfigChanges == other.mConfigChanges; } @Override public int hashCode() { int result = 17; - result = 31 * result + (mShowWindow ? 1 : 0); result = 31 * result + mConfigChanges; return result; } @Override public String toString() { - return "StopActivityItem{showWindow=" + mShowWindow + ",configChanges=" + mConfigChanges - + "}"; + return "StopActivityItem{configChanges=" + mConfigChanges + "}"; } } diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java index 20e0da3ead8a..17fcda587322 100644 --- a/core/java/android/app/servertransaction/TransactionExecutor.java +++ b/core/java/android/app/servertransaction/TransactionExecutor.java @@ -218,7 +218,7 @@ public class TransactionExecutor { null /* customIntent */); break; case ON_START: - mTransactionHandler.handleStartActivity(r, mPendingActions); + mTransactionHandler.handleStartActivity(r.token, mPendingActions); break; case ON_RESUME: mTransactionHandler.handleResumeActivity(r.token, false /* finalStateRequest */, @@ -230,8 +230,8 @@ public class TransactionExecutor { "LIFECYCLER_PAUSE_ACTIVITY"); break; case ON_STOP: - mTransactionHandler.handleStopActivity(r.token, false /* show */, - 0 /* configChanges */, mPendingActions, false /* finalStateRequest */, + mTransactionHandler.handleStopActivity(r.token, 0 /* configChanges */, + mPendingActions, false /* finalStateRequest */, "LIFECYCLER_STOP_ACTIVITY"); break; case ON_DESTROY: diff --git a/core/java/android/app/servertransaction/TransactionExecutorHelper.java b/core/java/android/app/servertransaction/TransactionExecutorHelper.java index 0ea8c3c159fa..6df92a78cc9f 100644 --- a/core/java/android/app/servertransaction/TransactionExecutorHelper.java +++ b/core/java/android/app/servertransaction/TransactionExecutorHelper.java @@ -183,8 +183,7 @@ public class TransactionExecutorHelper { lifecycleItem = PauseActivityItem.obtain(); break; case ON_STOP: - lifecycleItem = StopActivityItem.obtain(r.isVisibleFromServer(), - 0 /* configChanges */); + lifecycleItem = StopActivityItem.obtain(0 /* configChanges */); break; default: lifecycleItem = ResumeActivityItem.obtain(false /* isForward */); diff --git a/core/java/android/app/timedetector/ManualTimeSuggestion.java b/core/java/android/app/timedetector/ManualTimeSuggestion.java index 55f92be14cd0..50de73855511 100644 --- a/core/java/android/app/timedetector/ManualTimeSuggestion.java +++ b/core/java/android/app/timedetector/ManualTimeSuggestion.java @@ -20,7 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; -import android.util.TimestampedValue; +import android.os.TimestampedValue; import java.util.ArrayList; import java.util.Arrays; diff --git a/core/java/android/app/timedetector/NetworkTimeSuggestion.java b/core/java/android/app/timedetector/NetworkTimeSuggestion.java index 4c55ba12d881..17e9c5a79fa5 100644 --- a/core/java/android/app/timedetector/NetworkTimeSuggestion.java +++ b/core/java/android/app/timedetector/NetworkTimeSuggestion.java @@ -20,7 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; -import android.util.TimestampedValue; +import android.os.TimestampedValue; import java.util.ArrayList; import java.util.Arrays; diff --git a/core/java/android/app/timedetector/PhoneTimeSuggestion.java b/core/java/android/app/timedetector/PhoneTimeSuggestion.java index 4a89a1245473..479e4b4efb4c 100644 --- a/core/java/android/app/timedetector/PhoneTimeSuggestion.java +++ b/core/java/android/app/timedetector/PhoneTimeSuggestion.java @@ -20,7 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; -import android.util.TimestampedValue; +import android.os.TimestampedValue; import java.util.ArrayList; import java.util.Collections; diff --git a/core/java/android/app/timedetector/TimeDetector.java b/core/java/android/app/timedetector/TimeDetector.java index af9ece00f491..54dd1bed5361 100644 --- a/core/java/android/app/timedetector/TimeDetector.java +++ b/core/java/android/app/timedetector/TimeDetector.java @@ -24,8 +24,8 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceManager.ServiceNotFoundException; import android.os.SystemClock; +import android.os.TimestampedValue; import android.util.Log; -import android.util.TimestampedValue; /** * The interface through which system components can send signals to the TimeDetectorService. diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index b1b6f0d61f9f..cb1f05573d93 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -2670,6 +2670,9 @@ public final class BluetoothAdapter { } else if (profile == BluetoothProfile.PAN) { BluetoothPan pan = new BluetoothPan(context, listener); return true; + } else if (profile == BluetoothProfile.PBAP) { + BluetoothPbap pbap = new BluetoothPbap(context, listener); + return true; } else if (profile == BluetoothProfile.HEALTH) { Log.e(TAG, "getProfileProxy(): BluetoothHealth is deprecated"); return false; @@ -2742,6 +2745,10 @@ public final class BluetoothAdapter { 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(); diff --git a/core/java/android/bluetooth/BluetoothHidDevice.java b/core/java/android/bluetooth/BluetoothHidDevice.java index e9b0be2c4cd6..a923be62fbce 100644 --- a/core/java/android/bluetooth/BluetoothHidDevice.java +++ b/core/java/android/bluetooth/BluetoothHidDevice.java @@ -16,8 +16,12 @@ package android.bluetooth; +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.content.Context; import android.os.Binder; import android.os.IBinder; @@ -36,6 +40,7 @@ import java.util.concurrent.Executor; */ 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. @@ -682,4 +687,62 @@ public final class BluetoothHidDevice implements BluetoothProfile { return result; } + + /** + * 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 + @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + public boolean setConnectionPolicy(@NonNull BluetoothDevice device, + @ConnectionPolicy int connectionPolicy) { + log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); + try { + final IBluetoothHidDevice 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); + } + 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; + } + } + + 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/BluetoothHidHost.java b/core/java/android/bluetooth/BluetoothHidHost.java index 8f5cdf01f572..c1233b800a51 100644 --- a/core/java/android/bluetooth/BluetoothHidHost.java +++ b/core/java/android/bluetooth/BluetoothHidHost.java @@ -17,9 +17,12 @@ package android.bluetooth; 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.SuppressLint; import android.annotation.SystemApi; import android.content.Context; import android.os.Binder; @@ -43,6 +46,7 @@ import java.util.List; * * @hide */ +@SystemApi public final class BluetoothHidHost implements BluetoothProfile { private static final String TAG = "BluetoothHidHost"; private static final boolean DBG = true; @@ -66,6 +70,7 @@ public final class BluetoothHidHost implements BluetoothProfile { * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to * receive. */ + @SuppressLint("ActionValue") @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED"; @@ -325,7 +330,7 @@ public final class BluetoothHidHost implements BluetoothProfile { * {@inheritDoc} */ @Override - public List<BluetoothDevice> getConnectedDevices() { + public @NonNull List<BluetoothDevice> getConnectedDevices() { if (VDBG) log("getConnectedDevices()"); final IBluetoothHidHost service = getService(); if (service != null && isEnabled()) { @@ -342,6 +347,8 @@ public final class BluetoothHidHost implements BluetoothProfile { /** * {@inheritDoc} + * + * @hide */ @Override public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { @@ -363,7 +370,7 @@ public final class BluetoothHidHost implements BluetoothProfile { * {@inheritDoc} */ @Override - public int getConnectionState(BluetoothDevice device) { + public int getConnectionState(@Nullable BluetoothDevice device) { if (VDBG) log("getState(" + device + ")"); final IBluetoothHidHost service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { @@ -409,7 +416,7 @@ public final class BluetoothHidHost implements BluetoothProfile { */ @SystemApi @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) - public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) { + public boolean setConnectionPolicy(@Nullable BluetoothDevice device, int connectionPolicy) { if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); final IBluetoothHidHost service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { @@ -457,7 +464,7 @@ public final class BluetoothHidHost implements BluetoothProfile { */ @SystemApi @RequiresPermission(Manifest.permission.BLUETOOTH) - public int getConnectionPolicy(BluetoothDevice device) { + public int getConnectionPolicy(@Nullable BluetoothDevice device) { if (VDBG) log("getConnectionPolicy(" + device + ")"); final IBluetoothHidHost service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { diff --git a/core/java/android/bluetooth/BluetoothMap.java b/core/java/android/bluetooth/BluetoothMap.java index 917e7fa04e4c..467470674286 100644 --- a/core/java/android/bluetooth/BluetoothMap.java +++ b/core/java/android/bluetooth/BluetoothMap.java @@ -17,7 +17,10 @@ package android.bluetooth; import android.Manifest; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; @@ -35,21 +38,35 @@ import java.util.List; * * @hide */ +@SystemApi public final class BluetoothMap implements BluetoothProfile { private static final String TAG = "BluetoothMap"; private static final boolean DBG = true; private static final boolean VDBG = false; + /** @hide */ + @SuppressLint("ActionValue") + @SystemApi 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 */ + /** + * 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. */ + /** + * Connection canceled before completion. + * + * @hide + */ public static final int RESULT_CANCELED = 2; private BluetoothAdapter mAdapter; @@ -71,6 +88,7 @@ public final class BluetoothMap implements BluetoothProfile { mProfileConnector.connect(context, listener); } + @SuppressLint("GenericException") protected void finalize() throws Throwable { try { close(); @@ -84,6 +102,8 @@ public final class BluetoothMap implements BluetoothProfile { * Other public functions of BluetoothMap will return default error * results once close() has been called. Multiple invocations of close() * are ok. + * + * @hide */ public synchronized void close() { mProfileConnector.disconnect(); @@ -98,6 +118,8 @@ public final class BluetoothMap implements BluetoothProfile { * * @return One of the STATE_ return codes, or STATE_ERROR if this proxy object is currently not * connected to the Map service. + * + * @hide */ public int getState() { if (VDBG) log("getState()"); @@ -120,6 +142,8 @@ public final class BluetoothMap implements BluetoothProfile { * * @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 */ public BluetoothDevice getClient() { if (VDBG) log("getClient()"); @@ -141,6 +165,8 @@ public final class BluetoothMap implements BluetoothProfile { * 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 */ public boolean isConnected(BluetoothDevice device) { if (VDBG) log("isConnected(" + device + ")"); @@ -161,6 +187,8 @@ public final class BluetoothMap implements BluetoothProfile { /** * Initiate connection. Initiation of outgoing connections is not * supported for MAP server. + * + * @hide */ public boolean connect(BluetoothDevice device) { if (DBG) log("connect(" + device + ")" + "not supported for MAPS"); @@ -172,6 +200,8 @@ public final class BluetoothMap implements BluetoothProfile { * * @param device Remote Bluetooth Device * @return false on error, true otherwise + * + * @hide */ @UnsupportedAppUsage public boolean disconnect(BluetoothDevice device) { @@ -196,6 +226,8 @@ public final class BluetoothMap implements BluetoothProfile { * 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 @@ -214,8 +246,11 @@ public final class BluetoothMap implements BluetoothProfile { * Get the list of connected devices. Currently at most one. * * @return list of connected devices + * + * @hide */ - public List<BluetoothDevice> getConnectedDevices() { + @SystemApi + public @NonNull List<BluetoothDevice> getConnectedDevices() { if (DBG) log("getConnectedDevices()"); final IBluetoothMap service = getService(); if (service != null && isEnabled()) { @@ -234,6 +269,8 @@ public final class BluetoothMap implements BluetoothProfile { * Get the list of devices matching specified states. Currently at most one. * * @return list of matching devices + * + * @hide */ public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { if (DBG) log("getDevicesMatchingStates()"); @@ -254,6 +291,8 @@ public final class BluetoothMap implements BluetoothProfile { * Get connection state of device * * @return device connection state + * + * @hide */ public int getConnectionState(BluetoothDevice device) { if (DBG) log("getConnectionState(" + device + ")"); @@ -301,7 +340,7 @@ public final class BluetoothMap implements BluetoothProfile { */ @SystemApi @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) - public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) { + public boolean setConnectionPolicy(@Nullable BluetoothDevice device, int connectionPolicy) { if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); final IBluetoothMap service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { @@ -349,7 +388,7 @@ public final class BluetoothMap implements BluetoothProfile { */ @SystemApi @RequiresPermission(Manifest.permission.BLUETOOTH) - public int getConnectionPolicy(BluetoothDevice device) { + public int getConnectionPolicy(@Nullable BluetoothDevice device) { if (VDBG) log("getConnectionPolicy(" + device + ")"); final IBluetoothMap service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { diff --git a/core/java/android/bluetooth/BluetoothPan.java b/core/java/android/bluetooth/BluetoothPan.java index 42f27f2a9c98..024bb06098ab 100644 --- a/core/java/android/bluetooth/BluetoothPan.java +++ b/core/java/android/bluetooth/BluetoothPan.java @@ -16,9 +16,11 @@ package android.bluetooth; +import android.Manifest; 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; @@ -256,6 +258,41 @@ public final class BluetoothPan implements BluetoothProfile { } /** + * 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_ADMIN) + public boolean setConnectionPolicy(@NonNull BluetoothDevice device, + @ConnectionPolicy int connectionPolicy) { + if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); + try { + final IBluetoothPan 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); + } + 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; + } + } + + /** * {@inheritDoc} */ @Override diff --git a/core/java/android/bluetooth/BluetoothPbap.java b/core/java/android/bluetooth/BluetoothPbap.java index c579fdf5048f..e07ca521e77d 100644 --- a/core/java/android/bluetooth/BluetoothPbap.java +++ b/core/java/android/bluetooth/BluetoothPbap.java @@ -16,7 +16,10 @@ package android.bluetooth; +import android.Manifest; +import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SuppressLint; import android.annotation.SystemApi; @@ -271,6 +274,42 @@ public class BluetoothPbap implements BluetoothProfile { } /** + * Pbap does not store connection policy, so this function only disconnects pbap if + * connectionPolicy is {@link #CONNECTION_POLICY_FORBIDDEN}. + * + * <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 determines whether to disconnect the device + * @return true if pbap is successfully disconnected, false otherwise + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + public boolean setConnectionPolicy(@NonNull BluetoothDevice device, + @ConnectionPolicy int connectionPolicy) { + if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); + try { + final IBluetoothPbap service = mService; + if (service != null && isEnabled() + && isValidDevice(device)) { + if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN + && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) { + return false; + } + return service.setConnectionPolicy(device, connectionPolicy); + } + 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. diff --git a/core/java/android/content/ComponentName.java b/core/java/android/content/ComponentName.java index 27960b0dd9f0..1967a01549c9 100644 --- a/core/java/android/content/ComponentName.java +++ b/core/java/android/content/ComponentName.java @@ -305,6 +305,12 @@ public final class ComponentName implements Parcelable, Cloneable, Comparable<Co proto.end(token); } + /** + * {@inheritDoc} + * + * <p>Two components are considered to be equal if the packages in which they reside have the + * same name, and if the classes that implement each component also have the same name. + */ @Override public boolean equals(Object obj) { try { diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java index 393d48891d64..85826fd4e669 100644 --- a/core/java/android/content/ContentProvider.java +++ b/core/java/android/content/ContentProvider.java @@ -27,6 +27,7 @@ import static android.os.Trace.TRACE_TAG_DATABASE; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.app.AppOpsManager; import android.compat.annotation.UnsupportedAppUsage; import android.content.pm.PackageManager; @@ -942,7 +943,18 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall return null; } - /** {@hide} */ + /** + * Return the package name of the caller that initiated the request being + * processed on the current thread. The returned package will have + * <em>not</em> been verified to belong to the calling UID. Returns + * {@code null} if not currently processing a request. + * <p> + * This will always return {@code null} when processing + * {@link #getType(Uri)} or {@link #getStreamTypes(Uri, String)} requests. + * + * @see Binder#getCallingUid() + * @see Context#grantUriPermission(String, Uri, int) + */ public final @Nullable String getCallingPackageUnchecked() { final Pair<String, String> pkg = mCallingPackage.get(); if (pkg != null) { @@ -952,7 +964,14 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall return null; } - /** {@hide} */ + /** + * Called whenever the value of {@link #getCallingPackage()} changes, giving + * the provider an opportunity to invalidate any security related caching it + * may be performing. + * <p> + * This typically happens when a {@link ContentProvider} makes a nested call + * back into itself when already processing a call from a remote process. + */ public void onCallingPackageChanged() { } @@ -1390,8 +1409,11 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall * @param uri The URI to query. This will be the full URI sent by the client. * @param projection The list of columns to put into the cursor. * If {@code null} provide a default set of columns. - * @param queryArgs A Bundle containing all additional information necessary for the query. - * Values in the Bundle may include SQL style arguments. + * @param queryArgs A Bundle containing additional information necessary for + * the operation. Arguments may include SQL style arguments, such + * as {@link ContentResolver#QUERY_ARG_SQL_LIMIT}, but note that + * the documentation for each individual provider will indicate + * which arguments they support. * @param cancellationSignal A signal to cancel the operation in progress, * or {@code null}. * @return a Cursor or {@code null}. @@ -1525,8 +1547,24 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall return false; } - /** {@hide} */ + /** + * Perform a detailed internal check on a {@link Uri} to determine if a UID + * is able to access it with specific mode flags. + * <p> + * This method is typically used when the provider implements more dynamic + * access controls that cannot be expressed with {@code <path-permission>} + * style static rules. + * + * @param uri the {@link Uri} to perform an access check on. + * @param uid the UID to check the permission for. + * @param modeFlags the access flags to use for the access check, such as + * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION}. + * @return {@link PackageManager#PERMISSION_GRANTED} if access is allowed, + * otherwise {@link PackageManager#PERMISSION_DENIED}. + * @hide + */ @Override + @SystemApi public int checkUriPermission(@NonNull Uri uri, int uid, @Intent.AccessUriMode int modeFlags) { return PackageManager.PERMISSION_DENIED; } @@ -1574,9 +1612,14 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall * * @param uri The content:// URI of the insertion request. * @param values A set of column_name/value pairs to add to the database. - * @param extras A Bundle containing all additional information necessary - * for the insert. + * @param extras A Bundle containing additional information necessary for + * the operation. Arguments may include SQL style arguments, such + * as {@link ContentResolver#QUERY_ARG_SQL_LIMIT}, but note that + * the documentation for each individual provider will indicate + * which arguments they support. * @return The URI for the newly inserted item. + * @throws IllegalArgumentException if the provider doesn't support one of + * the requested Bundle arguments. */ @Override public @Nullable Uri insert(@NonNull Uri uri, @Nullable ContentValues values, @@ -1653,10 +1696,13 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall * * @param uri The full URI to query, including a row ID (if a specific * record is requested). - * @param extras A Bundle containing all additional information necessary - * for the delete. Values in the Bundle may include SQL style - * arguments. - * @return The number of rows affected. + * @param extras A Bundle containing additional information necessary for + * the operation. Arguments may include SQL style arguments, such + * as {@link ContentResolver#QUERY_ARG_SQL_LIMIT}, but note that + * the documentation for each individual provider will indicate + * which arguments they support. + * @throws IllegalArgumentException if the provider doesn't support one of + * the requested Bundle arguments. * @throws SQLException */ @Override @@ -1699,10 +1745,14 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall * @param uri The URI to query. This can potentially have a record ID if * this is an update request for a specific record. * @param values A set of column_name/value pairs to update in the database. - * @param extras A Bundle containing all additional information necessary - * for the update. Values in the Bundle may include SQL style - * arguments. + * @param extras A Bundle containing additional information necessary for + * the operation. Arguments may include SQL style arguments, such + * as {@link ContentResolver#QUERY_ARG_SQL_LIMIT}, but note that + * the documentation for each individual provider will indicate + * which arguments they support. * @return the number of rows affected. + * @throws IllegalArgumentException if the provider doesn't support one of + * the requested Bundle arguments. */ @Override public int update(@NonNull Uri uri, @Nullable ContentValues values, diff --git a/core/java/android/content/ContentProviderOperation.java b/core/java/android/content/ContentProviderOperation.java index 93f42879d946..494d2aeaf42a 100644 --- a/core/java/android/content/ContentProviderOperation.java +++ b/core/java/android/content/ContentProviderOperation.java @@ -18,7 +18,7 @@ package android.content; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; diff --git a/core/java/android/content/ContentProviderResult.java b/core/java/android/content/ContentProviderResult.java index 11dda83a6e6c..4fb1ddb958d5 100644 --- a/core/java/android/content/ContentProviderResult.java +++ b/core/java/android/content/ContentProviderResult.java @@ -36,7 +36,7 @@ public class ContentProviderResult implements Parcelable { public final @Nullable Uri uri; public final @Nullable Integer count; public final @Nullable Bundle extras; - public final @Nullable Exception exception; + public final @Nullable Throwable exception; public ContentProviderResult(@NonNull Uri uri) { this(Objects.requireNonNull(uri), null, null, null); @@ -50,12 +50,12 @@ public class ContentProviderResult implements Parcelable { this(null, null, Objects.requireNonNull(extras), null); } - public ContentProviderResult(@NonNull Exception exception) { + public ContentProviderResult(@NonNull Throwable exception) { this(null, null, null, exception); } /** {@hide} */ - public ContentProviderResult(Uri uri, Integer count, Bundle extras, Exception exception) { + public ContentProviderResult(Uri uri, Integer count, Bundle extras, Throwable exception) { this.uri = uri; this.count = count; this.extras = extras; @@ -79,7 +79,7 @@ public class ContentProviderResult implements Parcelable { extras = null; } if (source.readInt() != 0) { - exception = (Exception) ParcelableException.readFromParcel(source); + exception = ParcelableException.readFromParcel(source); } else { exception = null; } diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index 1d3c6505f677..6cd1cd3354c2 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -984,7 +984,11 @@ public abstract class ContentResolver implements ContentInterface { * retrieve. * @param projection A list of which columns to return. Passing null will * return all columns, which is inefficient. - * @param queryArgs A Bundle containing any arguments to the query. + * @param queryArgs A Bundle containing additional information necessary for + * the operation. Arguments may include SQL style arguments, such + * as {@link ContentResolver#QUERY_ARG_SQL_LIMIT}, but note that + * the documentation for each individual provider will indicate + * which arguments they support. * @param cancellationSignal A signal to cancel the operation in progress, or null if none. * If the operation is canceled, then {@link OperationCanceledException} will be thrown * when the query is executed. @@ -1925,9 +1929,15 @@ public abstract class ContentResolver implements ContentInterface { * @param url The URL of the table to insert into. * @param values The initial values for the newly inserted row. The key is the column name for * the field. Passing an empty ContentValues will create an empty row. - * @param extras A Bundle containing all additional information necessary for the insert. + * @param extras A Bundle containing additional information necessary for + * the operation. Arguments may include SQL style arguments, such + * as {@link ContentResolver#QUERY_ARG_SQL_LIMIT}, but note that + * the documentation for each individual provider will indicate + * which arguments they support. * @return the URL of the newly created row. May return <code>null</code> if the underlying * content provider returns <code>null</code>, or if it crashes. + * @throws IllegalArgumentException if the provider doesn't support one of + * the requested Bundle arguments. */ @Override public final @Nullable Uri insert(@RequiresPermission.Write @NonNull Uri url, @@ -2061,9 +2071,14 @@ public abstract class ContentResolver implements ContentInterface { * If the content provider supports transactions, the deletion will be atomic. * * @param url The URL of the row to delete. - * @param extras A Bundle containing all additional information necessary for the delete. - * Values in the Bundle may include SQL style arguments. + * @param extras A Bundle containing additional information necessary for + * the operation. Arguments may include SQL style arguments, such + * as {@link ContentResolver#QUERY_ARG_SQL_LIMIT}, but note that + * the documentation for each individual provider will indicate + * which arguments they support. * @return The number of rows deleted. + * @throws IllegalArgumentException if the provider doesn't support one of + * the requested Bundle arguments. */ @Override public final int delete(@RequiresPermission.Write @NonNull Uri url, @Nullable Bundle extras) { @@ -2121,10 +2136,15 @@ public abstract class ContentResolver implements ContentInterface { * @param uri The URI to modify. * @param values The new field values. The key is the column name for the field. A null value will remove an existing field value. - * @param extras A Bundle containing all additional information necessary for the update. - * Values in the Bundle may include SQL style arguments. + * @param extras A Bundle containing additional information necessary for + * the operation. Arguments may include SQL style arguments, such + * as {@link ContentResolver#QUERY_ARG_SQL_LIMIT}, but note that + * the documentation for each individual provider will indicate + * which arguments they support. * @return the number of rows updated. * @throws NullPointerException if uri or values are null + * @throws IllegalArgumentException if the provider doesn't support one of + * the requested Bundle arguments. */ @Override public final int update(@RequiresPermission.Write @NonNull Uri uri, @@ -3851,15 +3871,47 @@ public abstract class ContentResolver implements ContentInterface { } } + /** + * Decode a path generated by {@link #encodeToFile(Uri)} back into + * the original {@link Uri}. + * <p> + * This is used to offer a way to intercept filesystem calls in + * {@link ContentProvider} unaware code and redirect them to a + * {@link ContentProvider} when they attempt to use {@code _DATA} columns + * that are otherwise deprecated. + * + * @hide + */ + @SystemApi + public static @NonNull Uri decodeFromFile(@NonNull File file) { + return translateDeprecatedDataPath(file.getAbsolutePath()); + } + + /** + * Encode a {@link Uri} into an opaque filesystem path which can then be + * resurrected by {@link #decodeFromFile(File)}. + * <p> + * This is used to offer a way to intercept filesystem calls in + * {@link ContentProvider} unaware code and redirect them to a + * {@link ContentProvider} when they attempt to use {@code _DATA} columns + * that are otherwise deprecated. + * + * @hide + */ + @SystemApi + public static @NonNull File encodeToFile(@NonNull Uri uri) { + return new File(translateDeprecatedDataPath(uri)); + } + /** {@hide} */ - public static Uri translateDeprecatedDataPath(String path) { + public static @NonNull Uri translateDeprecatedDataPath(@NonNull String path) { final String ssp = "//" + path.substring(DEPRECATE_DATA_PREFIX.length()); return Uri.parse(new Uri.Builder().scheme(SCHEME_CONTENT) .encodedOpaquePart(ssp).build().toString()); } /** {@hide} */ - public static String translateDeprecatedDataPath(Uri uri) { + public static @NonNull String translateDeprecatedDataPath(@NonNull Uri uri) { return DEPRECATE_DATA_PREFIX + uri.getEncodedSchemeSpecificPart().substring(2); } } diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 4815d7847115..44b2df691876 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -3240,15 +3240,40 @@ public abstract class Context { } /** - * Same as {@link #bindService(Intent, ServiceConnection, int)}, but with an explicit userHandle - * argument for use by system server and other multi-user aware code. - * @hide + * 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. + * + * @param service Identifies the service to connect to. The Intent must + * specify an explicit component name. + * @param conn Receives information as the service is started and stopped. + * This must be a valid ServiceConnection object; it must not be null. + * @param flags Operation options for the binding. May be 0, + * {@link #BIND_AUTO_CREATE}, {@link #BIND_DEBUG_UNBIND}, + * {@link #BIND_NOT_FOREGROUND}, {@link #BIND_ABOVE_CLIENT}, + * {@link #BIND_ALLOW_OOM_MANAGEMENT}, {@link #BIND_WAIVE_PRIORITY}. + * {@link #BIND_IMPORTANT}, or + * {@link #BIND_ADJUST_WITH_ACTIVITY}. + * @return {@code true} if the system is in the process of bringing up a + * service that your client has permission to bind to; {@code false} + * if the system couldn't find the service. If this value is {@code true}, you + * should later call {@link #unbindService} to release the + * connection. + * + * @throws SecurityException if the client does not have the required permission to bind. */ - @SystemApi @SuppressWarnings("unused") - @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) - public boolean bindServiceAsUser(@RequiresPermission Intent service, ServiceConnection conn, - int flags, UserHandle user) { + @RequiresPermission(anyOf = { + android.Manifest.permission.INTERACT_ACROSS_USERS, + android.Manifest.permission.INTERACT_ACROSS_PROFILES + }) + public boolean bindServiceAsUser( + @NonNull @RequiresPermission Intent service, @NonNull ServiceConnection conn, int flags, + @NonNull UserHandle user) { throw new RuntimeException("Not implemented. Must override in a subclass."); } @@ -3992,6 +4017,7 @@ public abstract class Context { */ public static final String NETWORK_STATS_SERVICE = "netstats"; /** {@hide} */ + @SystemApi public static final String NETWORK_POLICY_SERVICE = "netpolicy"; /** {@hide} */ public static final String NETWORK_WATCHLIST_SERVICE = "network_watchlist"; @@ -4015,6 +4041,7 @@ public abstract class Context { * @see android.net.wifi.WifiCondManager * @hide */ + @SystemApi public static final String WIFI_COND_SERVICE = "wificond"; /** diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 3bb0f9222237..c8f587f71bca 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -6440,19 +6440,22 @@ public class Intent implements Parcelable, Cloneable { */ public static final int FLAG_RECEIVER_NO_ABORT = 0x08000000; /** - * If set, when sending a broadcast <i>before boot has completed</i> only + * If set, when sending a broadcast <i>before the system has fully booted up + * (which is even before {@link #ACTION_LOCKED_BOOT_COMPLETED} has been sent)"</i> only * registered receivers will be called -- no BroadcastReceiver components * will be launched. Sticky intent state will be recorded properly even * if no receivers wind up being called. If {@link #FLAG_RECEIVER_REGISTERED_ONLY} * is specified in the broadcast intent, this flag is unnecessary. * - * <p>This flag is only for use by system sevices as a convenience to - * avoid having to implement a more complex mechanism around detection + * <p>This flag is only for use by system services (even services from mainline modules) as a + * convenience to avoid having to implement a more complex mechanism around detection * of boot completion. * + * <p>This is useful to system server mainline modules + * * @hide */ - @UnsupportedAppUsage + @SystemApi public static final int FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT = 0x04000000; /** * Set when this broadcast is for a boot upgrade, a special mode that diff --git a/core/java/android/content/pm/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java index e897b917fcc2..9d5751480a80 100644 --- a/core/java/android/content/pm/CrossProfileApps.java +++ b/core/java/android/content/pm/CrossProfileApps.java @@ -16,20 +16,25 @@ package android.content.pm; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.content.res.Resources; import android.graphics.drawable.Drawable; +import android.net.Uri; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; +import android.provider.Settings; import com.android.internal.R; import com.android.internal.util.UserIcons; import java.util.List; +import java.util.Set; /** * Class for handling cross profile operations. Apps can use this class to interact with its @@ -169,6 +174,86 @@ public class CrossProfileApps { } } + /** + * Returns whether the calling package can request to interact across profiles. + * + * <p>The package's current ability to interact across profiles can be checked with + * {@link #canInteractAcrossProfiles()}. + * + * <p>Specifically, returns whether the following are all true: + * <ul> + * <li>{@link #getTargetUserProfiles()} returns a non-empty list for the calling user.</li> + * <li>The calling app has requested</li> + * {@code android.Manifest.permission.INTERACT_ACROSS_PROFILES} in its manifest. + * <li>The calling package has either been whitelisted by default by the OEM or has been + * explicitly whitelisted by the admin via + * {@link android.app.admin.DevicePolicyManager#setCrossProfilePackages(ComponentName, Set)}. + * </li> + * </ul> + * + * @return true if the calling package can request to interact across profiles. + */ + public boolean canRequestInteractAcrossProfiles() { + try { + return mService.canRequestInteractAcrossProfiles(mContext.getPackageName()); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + + /** + * Returns whether the calling package can interact across profiles. + * + * <p>The package's current ability to request to interact across profiles can be checked with + * {@link #canRequestInteractAcrossProfiles()}. + * + * <p>Specifically, returns whether the following are all true: + * <ul> + * <li>{@link #getTargetUserProfiles()} returns a non-empty list for the calling user.</li> + * <li>The user has previously consented to cross-profile communication for the calling + * package.</li> + * <li>The calling package has either been whitelisted by default by the OEM or has been + * explicitly whitelisted by the admin via + * {@link android.app.admin.DevicePolicyManager#setCrossProfilePackages(ComponentName, Set)}. + * </li> + * </ul> + * + * @return true if the calling package can interact across profiles. + * @throws SecurityException if {@code mContext.getPackageName()} does not belong to the + * calling UID. + */ + public boolean canInteractAcrossProfiles() { + try { + return mService.canInteractAcrossProfiles(mContext.getPackageName()); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + + /** + * Returns an {@link Intent} to open the settings page that allows the user to decide whether + * the calling app can interact across profiles. The current state is given by + * {@link #canInteractAcrossProfiles()}. + * + * <p>Returns {@code null} if {@link #canRequestInteractAcrossProfiles()} is {@code false}. + * + * @return an {@link Intent} to open the settings page that allows the user to decide whether + * the app can interact across profiles + * + * @throws SecurityException if {@code mContext.getPackageName()} does not belong to the + * calling UID. + */ + public @Nullable Intent createRequestInteractAcrossProfilesIntent() { + if (!canRequestInteractAcrossProfiles()) { + return null; + } + final Intent settingsIntent = new Intent(); + settingsIntent.setAction(Settings.ACTION_MANAGE_CROSS_PROFILE_ACCESS); + final Uri packageUri = Uri.parse("package:" + mContext.getPackageName()); + settingsIntent.setData(packageUri); + return settingsIntent; + } + private void verifyCanAccessUser(UserHandle userHandle) { if (!getTargetUserProfiles().contains(userHandle)) { throw new SecurityException("Not allowed to access " + userHandle); diff --git a/core/java/android/content/pm/ICrossProfileApps.aidl b/core/java/android/content/pm/ICrossProfileApps.aidl index d2d66cbda610..c5db0ccebf52 100644 --- a/core/java/android/content/pm/ICrossProfileApps.aidl +++ b/core/java/android/content/pm/ICrossProfileApps.aidl @@ -30,4 +30,6 @@ interface ICrossProfileApps { void startActivityAsUser(in IApplicationThread caller, in String callingPackage, in ComponentName component, int userId, boolean launchMainActivity); List<UserHandle> getTargetUserProfiles(in String callingPackage); + boolean canInteractAcrossProfiles(in String callingPackage); + boolean canRequestInteractAcrossProfiles(in String callingPackage); }
\ No newline at end of file diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index f792127bc075..4bfc40e698b9 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -2323,6 +2323,13 @@ public abstract class PackageManager { public static final String FEATURE_TELEPHONY_GSM = "android.hardware.telephony.gsm"; /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device + * has a telephony radio that support data. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_TELEPHONY_DATA = "android.hardware.telephony.data"; + + /** * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: * The device supports telephony carrier restriction mechanism. * @@ -2918,6 +2925,18 @@ public abstract class PackageManager { public static final String FEATURE_IPSEC_TUNNELS = "android.software.ipsec_tunnels"; /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has + * the requisite hardware support to support reboot escrow of synthetic password for updates. + * + * <p>This feature implies that the device has the RebootEscrow HAL implementation. + * + * @hide + */ + @SystemApi + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_REBOOT_ESCROW = "android.hardware.reboot_escrow"; + + /** * Extra field name for the URI to a verification file. Passed to a package * verifier. * diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java index ac2e373f000d..6639b3d5c0b5 100644 --- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java +++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java @@ -18,7 +18,7 @@ package android.content.pm.parsing; import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageParser; diff --git a/core/java/android/content/pm/parsing/ComponentParseUtils.java b/core/java/android/content/pm/parsing/ComponentParseUtils.java index 595685729a0c..f04a30ce4239 100644 --- a/core/java/android/content/pm/parsing/ComponentParseUtils.java +++ b/core/java/android/content/pm/parsing/ComponentParseUtils.java @@ -27,8 +27,8 @@ import android.annotation.CallSuper; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringRes; -import android.annotation.UnsupportedAppUsage; import android.app.ActivityTaskManager; +import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Intent; import android.content.IntentFilter; diff --git a/core/java/android/content/rollback/PackageRollbackInfo.java b/core/java/android/content/rollback/PackageRollbackInfo.java index 6378db0ebbbd..b273cd67479c 100644 --- a/core/java/android/content/rollback/PackageRollbackInfo.java +++ b/core/java/android/content/rollback/PackageRollbackInfo.java @@ -76,6 +76,11 @@ public final class PackageRollbackInfo implements Parcelable { */ private final boolean mIsApex; + /** + * Whether this instance represents the PackageRollbackInfo for an APK in APEX. + */ + private final boolean mIsApkInApex; + /* * The list of users for which snapshots have been saved. */ @@ -157,6 +162,10 @@ public final class PackageRollbackInfo implements Parcelable { public @PackageManager.RollbackDataPolicy int getRollbackDataPolicy() { return mRollbackDataPolicy; } + /** @hide */ + public boolean isApkInApex() { + return mIsApkInApex; + } /** @hide */ public IntArray getSnapshottedUsers() { @@ -190,17 +199,18 @@ public final class PackageRollbackInfo implements Parcelable { public PackageRollbackInfo(VersionedPackage packageRolledBackFrom, VersionedPackage packageRolledBackTo, @NonNull IntArray pendingBackups, @NonNull ArrayList<RestoreInfo> pendingRestores, - boolean isApex, @NonNull IntArray snapshottedUsers, + boolean isApex, boolean isApkInApex, @NonNull IntArray snapshottedUsers, @NonNull SparseLongArray ceSnapshotInodes) { this(packageRolledBackFrom, packageRolledBackTo, pendingBackups, pendingRestores, isApex, - snapshottedUsers, ceSnapshotInodes, PackageManager.RollbackDataPolicy.RESTORE); + isApkInApex, snapshottedUsers, ceSnapshotInodes, + PackageManager.RollbackDataPolicy.RESTORE); } /** @hide */ public PackageRollbackInfo(VersionedPackage packageRolledBackFrom, VersionedPackage packageRolledBackTo, @NonNull IntArray pendingBackups, @NonNull ArrayList<RestoreInfo> pendingRestores, - boolean isApex, @NonNull IntArray snapshottedUsers, + boolean isApex, boolean isApkInApex, @NonNull IntArray snapshottedUsers, @NonNull SparseLongArray ceSnapshotInodes, @PackageManager.RollbackDataPolicy int rollbackDataPolicy) { this.mVersionRolledBackFrom = packageRolledBackFrom; @@ -209,6 +219,7 @@ public final class PackageRollbackInfo implements Parcelable { this.mPendingRestores = pendingRestores; this.mIsApex = isApex; this.mRollbackDataPolicy = rollbackDataPolicy; + this.mIsApkInApex = isApkInApex; this.mSnapshottedUsers = snapshottedUsers; this.mCeSnapshotInodes = ceSnapshotInodes; } @@ -217,6 +228,7 @@ public final class PackageRollbackInfo implements Parcelable { this.mVersionRolledBackFrom = VersionedPackage.CREATOR.createFromParcel(in); this.mVersionRolledBackTo = VersionedPackage.CREATOR.createFromParcel(in); this.mIsApex = in.readBoolean(); + this.mIsApkInApex = in.readBoolean(); this.mPendingRestores = null; this.mPendingBackups = null; this.mSnapshottedUsers = null; @@ -234,6 +246,7 @@ public final class PackageRollbackInfo implements Parcelable { mVersionRolledBackFrom.writeToParcel(out, flags); mVersionRolledBackTo.writeToParcel(out, flags); out.writeBoolean(mIsApex); + out.writeBoolean(mIsApkInApex); } public static final @NonNull Parcelable.Creator<PackageRollbackInfo> CREATOR = diff --git a/core/java/android/database/sqlite/SQLiteQueryBuilder.java b/core/java/android/database/sqlite/SQLiteQueryBuilder.java index bba14c39de72..36ec67ee1471 100644 --- a/core/java/android/database/sqlite/SQLiteQueryBuilder.java +++ b/core/java/android/database/sqlite/SQLiteQueryBuilder.java @@ -35,8 +35,8 @@ import com.android.internal.util.ArrayUtils; import libcore.util.EmptyArray; import java.util.Arrays; +import java.util.Collection; import java.util.Iterator; -import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; @@ -56,7 +56,7 @@ public class SQLiteQueryBuilder { "(?i)(AVG|COUNT|MAX|MIN|SUM|TOTAL|GROUP_CONCAT)\\((.+)\\)"); private Map<String, String> mProjectionMap = null; - private List<Pattern> mProjectionGreylist = null; + private Collection<Pattern> mProjectionGreylist = null; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private String mTables = ""; @@ -196,20 +196,16 @@ public class SQLiteQueryBuilder { * Sets a projection greylist of columns that will be allowed through, even * when {@link #setStrict(boolean)} is enabled. This provides a way for * abusive custom columns like {@code COUNT(*)} to continue working. - * - * @hide */ - public void setProjectionGreylist(@Nullable List<Pattern> projectionGreylist) { + public void setProjectionGreylist(@Nullable Collection<Pattern> projectionGreylist) { mProjectionGreylist = projectionGreylist; } /** * Gets the projection greylist for the query, as last configured by - * {@link #setProjectionGreylist(List)}. - * - * @hide + * {@link #setProjectionGreylist}. */ - public @Nullable List<Pattern> getProjectionGreylist() { + public @Nullable Collection<Pattern> getProjectionGreylist() { return mProjectionGreylist; } @@ -244,25 +240,27 @@ public class SQLiteQueryBuilder { } /** - * When set, the selection is verified against malicious arguments. - * When using this class to create a statement using + * When set, the selection is verified against malicious arguments. When + * using this class to create a statement using * {@link #buildQueryString(boolean, String, String[], String, String, String, String, String)}, - * non-numeric limits will raise an exception. If a projection map is specified, fields - * not in that map will be ignored. - * If this class is used to execute the statement directly using + * non-numeric limits will raise an exception. If a projection map is + * specified, fields not in that map will be ignored. If this class is used + * to execute the statement directly using * {@link #query(SQLiteDatabase, String[], String, String[], String, String, String)} * or * {@link #query(SQLiteDatabase, String[], String, String[], String, String, String, String)}, - * additionally also parenthesis escaping selection are caught. - * - * To summarize: To get maximum protection against malicious third party apps (for example - * content provider consumers), make sure to do the following: + * additionally also parenthesis escaping selection are caught. To + * summarize: To get maximum protection against malicious third party apps + * (for example content provider consumers), make sure to do the following: * <ul> * <li>Set this value to true</li> * <li>Use a projection map</li> - * <li>Use one of the query overloads instead of getting the statement as a sql string</li> + * <li>Use one of the query overloads instead of getting the statement as a + * sql string</li> * </ul> - * By default, this value is false. + * <p> + * This feature is disabled by default on each newly constructed + * {@link SQLiteQueryBuilder} and needs to be manually enabled. */ public void setStrict(boolean strict) { if (strict) { @@ -287,6 +285,9 @@ public class SQLiteQueryBuilder { * This enforcement applies to {@link #insert}, {@link #query}, and * {@link #update} operations. Any enforcement failures will throw an * {@link IllegalArgumentException}. + * <p> + * This feature is disabled by default on each newly constructed + * {@link SQLiteQueryBuilder} and needs to be manually enabled. */ public void setStrictColumns(boolean strictColumns) { if (strictColumns) { @@ -323,6 +324,9 @@ public class SQLiteQueryBuilder { * {@link #delete} operations. This enforcement does not apply to trusted * inputs, such as those provided by {@link #appendWhere}. Any enforcement * failures will throw an {@link IllegalArgumentException}. + * <p> + * This feature is disabled by default on each newly constructed + * {@link SQLiteQueryBuilder} and needs to be manually enabled. */ public void setStrictGrammar(boolean strictGrammar) { if (strictGrammar) { diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl index 638d81b2f635..6e1987c47d20 100644 --- a/core/java/android/hardware/input/IInputManager.aidl +++ b/core/java/android/hardware/input/IInputManager.aidl @@ -90,4 +90,11 @@ interface IInputManager { /** Create an input monitor for gestures. */ InputMonitor monitorGestureInput(String name, int displayId); + + // Add a runtime association between the input port and the display port. This overrides any + // static associations. + void addPortAssociation(in String inputPort, int displayPort); + // Remove the runtime association between the input port and the display port. Any existing + // static association for the cleared input port will be restored. + void removePortAssociation(in String inputPort); } diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java index 8d32db013fb3..83f01a5dca35 100644 --- a/core/java/android/hardware/input/InputManager.java +++ b/core/java/android/hardware/input/InputManager.java @@ -17,6 +17,7 @@ package android.hardware.input; import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemService; @@ -963,6 +964,41 @@ public final class InputManager { } } + /** + * Add a runtime association between the input port and the display port. This overrides any + * static associations. + * @param inputPort The port of the input device. + * @param displayPort The physical port of the associated display. + * <p> + * Requires {@link android.Manifest.permissions.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY_BY_PORT}. + * </p> + * @hide + */ + public void addPortAssociation(@NonNull String inputPort, int displayPort) { + try { + mIm.addPortAssociation(inputPort, displayPort); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + + /** + * Remove the runtime association between the input port and the display port. Any existing + * static association for the cleared input port will be restored. + * @param inputPort The port of the input device to be cleared. + * <p> + * Requires {@link android.Manifest.permissions.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY_BY_PORT}. + * </p> + * @hide + */ + public void removePortAssociation(@NonNull String inputPort) { + try { + mIm.removePortAssociation(inputPort); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + private void populateInputDevicesLocked() { if (mInputDevicesChangedListener == null) { final InputDevicesChangedListener listener = new InputDevicesChangedListener(); diff --git a/core/java/android/hardware/soundtrigger/ConversionUtil.java b/core/java/android/hardware/soundtrigger/ConversionUtil.java index 8231c58a105e..43f3787e15da 100644 --- a/core/java/android/hardware/soundtrigger/ConversionUtil.java +++ b/core/java/android/hardware/soundtrigger/ConversionUtil.java @@ -16,6 +16,7 @@ package android.hardware.soundtrigger; +import android.annotation.Nullable; import android.hardware.soundtrigger.ModelParams; import android.media.AudioFormat; import android.media.audio.common.AudioConfig; @@ -32,8 +33,6 @@ import android.media.soundtrigger_middleware.SoundModel; import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor; import android.media.soundtrigger_middleware.SoundTriggerModuleProperties; -import android.annotation.Nullable; - import java.util.Arrays; import java.util.UUID; @@ -48,6 +47,7 @@ class ConversionUtil { properties.description, properties.uuid, properties.version, + properties.supportedModelArch, properties.maxSoundModels, properties.maxKeyPhrases, properties.maxUsers, diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java index b808088c4ee5..d87200931830 100644 --- a/core/java/android/hardware/soundtrigger/SoundTrigger.java +++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java @@ -47,8 +47,7 @@ import java.util.Arrays; import java.util.UUID; /** - * The SoundTrigger class provides access via JNI to the native service managing - * the sound trigger HAL. + * The SoundTrigger class provides access to the service managing the sound trigger HAL. * * @hide */ @@ -102,6 +101,14 @@ public class SoundTrigger { /** Voice detection engine version */ public final int version; + /** + * String naming the architecture used for running the supported models. + * (eg. a platform running models on a DSP could implement this string to convey the DSP + * architecture used) + */ + @NonNull + public final String supportedModelArch; + /** Maximum number of active sound models */ public final int maxSoundModels; @@ -131,15 +138,17 @@ public class SoundTrigger { public final boolean returnsTriggerInEvent; ModuleProperties(int id, @NonNull String implementor, @NonNull String description, - @NonNull String uuid, int version, int maxSoundModels, int maxKeyphrases, - int maxUsers, int recognitionModes, boolean supportsCaptureTransition, - int maxBufferMs, boolean supportsConcurrentCapture, - int powerConsumptionMw, boolean returnsTriggerInEvent) { + @NonNull String uuid, int version, @NonNull String supportedModelArch, + int maxSoundModels, int maxKeyphrases, int maxUsers, int recognitionModes, + boolean supportsCaptureTransition, int maxBufferMs, + boolean supportsConcurrentCapture, int powerConsumptionMw, + boolean returnsTriggerInEvent) { this.id = id; this.implementor = requireNonNull(implementor); this.description = requireNonNull(description); this.uuid = UUID.fromString(requireNonNull(uuid)); this.version = version; + this.supportedModelArch = requireNonNull(supportedModelArch); this.maxSoundModels = maxSoundModels; this.maxKeyphrases = maxKeyphrases; this.maxUsers = maxUsers; @@ -168,6 +177,7 @@ public class SoundTrigger { String description = in.readString(); String uuid = in.readString(); int version = in.readInt(); + String supportedModelArch = in.readString(); int maxSoundModels = in.readInt(); int maxKeyphrases = in.readInt(); int maxUsers = in.readInt(); @@ -178,7 +188,7 @@ public class SoundTrigger { int powerConsumptionMw = in.readInt(); boolean returnsTriggerInEvent = in.readByte() == 1; return new ModuleProperties(id, implementor, description, uuid, version, - maxSoundModels, maxKeyphrases, maxUsers, recognitionModes, + supportedModelArch, maxSoundModels, maxKeyphrases, maxUsers, recognitionModes, supportsCaptureTransition, maxBufferMs, supportsConcurrentCapture, powerConsumptionMw, returnsTriggerInEvent); } @@ -190,6 +200,7 @@ public class SoundTrigger { dest.writeString(description); dest.writeString(uuid.toString()); dest.writeInt(version); + dest.writeString(supportedModelArch); dest.writeInt(maxSoundModels); dest.writeInt(maxKeyphrases); dest.writeInt(maxUsers); @@ -209,7 +220,8 @@ public class SoundTrigger { @Override public String toString() { return "ModuleProperties [id=" + id + ", implementor=" + implementor + ", description=" - + description + ", uuid=" + uuid + ", version=" + version + ", maxSoundModels=" + + description + ", uuid=" + uuid + ", version=" + version + + " , supportedModelArch=" + supportedModelArch + ", maxSoundModels=" + maxSoundModels + ", maxKeyphrases=" + maxKeyphrases + ", maxUsers=" + maxUsers + ", recognitionModes=" + recognitionModes + ", supportsCaptureTransition=" + supportsCaptureTransition + ", maxBufferMs=" @@ -589,19 +601,19 @@ public class SoundTrigger { } } - /***************************************************************************** + /** * A ModelParamRange is a representation of supported parameter range for a * given loaded model. - ****************************************************************************/ + */ public static final class ModelParamRange implements Parcelable { /** - * start of supported range inclusive + * The inclusive start of supported range. */ public final int start; /** - * end of supported range inclusive + * The inclusive end of supported range. */ public final int end; @@ -610,31 +622,65 @@ public class SoundTrigger { this.end = end; } + /** @hide */ private ModelParamRange(@NonNull Parcel in) { this.start = in.readInt(); this.end = in.readInt(); } @NonNull - public static final Creator<ModelParamRange> CREATOR = new Creator<ModelParamRange>() { - @Override - @NonNull - public ModelParamRange createFromParcel(@NonNull Parcel in) { - return new ModelParamRange(in); - } - - @Override - @NonNull - public ModelParamRange[] newArray(int size) { - return new ModelParamRange[size]; - } - }; + public static final Creator<ModelParamRange> CREATOR = + new Creator<ModelParamRange>() { + @Override + @NonNull + public ModelParamRange createFromParcel(@NonNull Parcel in) { + return new ModelParamRange(in); + } + + @Override + @NonNull + public ModelParamRange[] newArray(int size) { + return new ModelParamRange[size]; + } + }; + /** @hide */ @Override public int describeContents() { return 0; } + /** @hide */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (start); + result = prime * result + (end); + return result; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + ModelParamRange other = (ModelParamRange) obj; + if (start != other.start) { + return false; + } + if (end != other.end) { + return false; + } + return true; + } + @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeInt(start); diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java index 73b9d1739061..67fdda37ed3b 100644 --- a/core/java/android/hardware/usb/UsbManager.java +++ b/core/java/android/hardware/usb/UsbManager.java @@ -91,7 +91,7 @@ public class UsbManager { * * {@hide} */ - @UnsupportedAppUsage + @SystemApi public static final String ACTION_USB_STATE = "android.hardware.usb.action.USB_STATE"; @@ -164,7 +164,7 @@ public class UsbManager { * * {@hide} */ - @UnsupportedAppUsage + @SystemApi public static final String USB_CONNECTED = "connected"; /** @@ -181,6 +181,7 @@ public class UsbManager { * * {@hide} */ + @SystemApi public static final String USB_CONFIGURED = "configured"; /** @@ -217,6 +218,7 @@ public class UsbManager { * * {@hide} */ + @SystemApi public static final String USB_FUNCTION_RNDIS = "rndis"; /** @@ -319,6 +321,7 @@ public class UsbManager { * Code for the charging usb function. Passed into {@link #setCurrentFunctions(long)} * {@hide} */ + @SystemApi public static final long FUNCTION_NONE = 0; /** @@ -337,6 +340,7 @@ public class UsbManager { * Code for the rndis usb function. Passed as a mask into {@link #setCurrentFunctions(long)} * {@hide} */ + @SystemApi public static final long FUNCTION_RNDIS = GadgetFunction.RNDIS; /** @@ -698,6 +702,8 @@ public class UsbManager { * * {@hide} */ + @SystemApi + @RequiresPermission(Manifest.permission.MANAGE_USB) public void setCurrentFunctions(long functions) { try { mService.setCurrentFunctions(functions); @@ -737,6 +743,8 @@ public class UsbManager { * * {@hide} */ + @SystemApi + @RequiresPermission(Manifest.permission.MANAGE_USB) public long getCurrentFunctions() { try { return mService.getCurrentFunctions(); diff --git a/core/java/android/net/ConnectivityDiagnosticsManager.java b/core/java/android/net/ConnectivityDiagnosticsManager.java new file mode 100644 index 000000000000..6afdb5ef1b16 --- /dev/null +++ b/core/java/android/net/ConnectivityDiagnosticsManager.java @@ -0,0 +1,242 @@ +/* + * 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.net; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.os.PersistableBundle; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.concurrent.Executor; + +/** + * Class that provides utilities for collecting network connectivity diagnostics information. + * Connectivity information is made available through triggerable diagnostics tools and by listening + * to System validations. Some diagnostics information may be permissions-restricted. + * + * <p>ConnectivityDiagnosticsManager is intended for use by applications offering network + * connectivity on a user device. These tools will provide several mechanisms for these applications + * to be alerted to network conditions as well as diagnose potential network issues themselves. + * + * <p>The primary responsibilities of this class are to: + * + * <ul> + * <li>Allow permissioned applications to register and unregister callbacks for network event + * notifications + * <li>Invoke callbacks for network event notifications, including: + * <ul> + * <li>Network validations + * <li>Data stalls + * <li>Connectivity reports from applications + * </ul> + * </ul> + */ +public class ConnectivityDiagnosticsManager { + public static final int DETECTION_METHOD_DNS_EVENTS = 1; + public static final int DETECTION_METHOD_TCP_METRICS = 2; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef( + prefix = {"DETECTION_METHOD_"}, + value = {DETECTION_METHOD_DNS_EVENTS, DETECTION_METHOD_TCP_METRICS}) + public @interface DetectionMethod {} + + /** @hide */ + public ConnectivityDiagnosticsManager() {} + + /** Class that includes connectivity information for a specific Network at a specific time. */ + public static class ConnectivityReport { + /** The Network for which this ConnectivityReport applied */ + @NonNull public final Network network; + + /** + * The timestamp for the report. The timestamp is taken from {@link + * System#currentTimeMillis}. + */ + public final long reportTimestamp; + + /** LinkProperties available on the Network at the reported timestamp */ + @NonNull public final LinkProperties linkProperties; + + /** NetworkCapabilities available on the Network at the reported timestamp */ + @NonNull public final NetworkCapabilities networkCapabilities; + + /** PersistableBundle that may contain additional info about the report */ + @NonNull public final PersistableBundle additionalInfo; + + /** + * Constructor for ConnectivityReport. + * + * <p>Apps should obtain instances through {@link + * ConnectivityDiagnosticsCallback#onConnectivityReport} instead of instantiating their own + * instances (unless for testing purposes). + * + * @param network The Network for which this ConnectivityReport applies + * @param reportTimestamp The timestamp for the report + * @param linkProperties The LinkProperties available on network at reportTimestamp + * @param networkCapabilities The NetworkCapabilities available on network at + * reportTimestamp + * @param additionalInfo A PersistableBundle that may contain additional info about the + * report + */ + public ConnectivityReport( + @NonNull Network network, + long reportTimestamp, + @NonNull LinkProperties linkProperties, + @NonNull NetworkCapabilities networkCapabilities, + @NonNull PersistableBundle additionalInfo) { + this.network = network; + this.reportTimestamp = reportTimestamp; + this.linkProperties = linkProperties; + this.networkCapabilities = networkCapabilities; + this.additionalInfo = additionalInfo; + } + } + + /** Class that includes information for a suspected data stall on a specific Network */ + public static class DataStallReport { + /** The Network for which this DataStallReport applied */ + @NonNull public final Network network; + + /** + * The timestamp for the report. The timestamp is taken from {@link + * System#currentTimeMillis}. + */ + public final long reportTimestamp; + + /** The detection method used to identify the suspected data stall */ + @DetectionMethod public final int detectionMethod; + + /** PersistableBundle that may contain additional information on the suspected data stall */ + @NonNull public final PersistableBundle stallDetails; + + /** + * Constructor for DataStallReport. + * + * <p>Apps should obtain instances through {@link + * ConnectivityDiagnosticsCallback#onDataStallSuspected} instead of instantiating their own + * instances (unless for testing purposes). + * + * @param network The Network for which this DataStallReport applies + * @param reportTimestamp The timestamp for the report + * @param detectionMethod The detection method used to identify this data stall + * @param stallDetails A PersistableBundle that may contain additional info about the report + */ + public DataStallReport( + @NonNull Network network, + long reportTimestamp, + @DetectionMethod int detectionMethod, + @NonNull PersistableBundle stallDetails) { + this.network = network; + this.reportTimestamp = reportTimestamp; + this.detectionMethod = detectionMethod; + this.stallDetails = stallDetails; + } + } + + /** + * Abstract base class for Connectivity Diagnostics callbacks. Used for notifications about + * network connectivity events. Must be extended by applications wanting notifications. + */ + public abstract static class ConnectivityDiagnosticsCallback { + /** + * Called when the platform completes a data connectivity check. This will also be invoked + * upon registration with the latest report. + * + * <p>The Network specified in the ConnectivityReport may not be active any more when this + * method is invoked. + * + * @param report The ConnectivityReport containing information about a connectivity check + */ + public void onConnectivityReport(@NonNull ConnectivityReport report) {} + + /** + * Called when the platform suspects a data stall on some Network. + * + * <p>The Network specified in the DataStallReport may not be active any more when this + * method is invoked. + * + * @param report The DataStallReport containing information about the suspected data stall + */ + public void onDataStallSuspected(@NonNull DataStallReport report) {} + + /** + * Called when any app reports connectivity to the System. + * + * @param network The Network for which connectivity has been reported + * @param hasConnectivity The connectivity reported to the System + */ + public void onNetworkConnectivityReported( + @NonNull Network network, boolean hasConnectivity) {} + } + + /** + * Registers a ConnectivityDiagnosticsCallback with the System. + * + * <p>Only apps that offer network connectivity to the user are allowed to register callbacks. + * This includes: + * + * <ul> + * <li>Carrier apps with active subscriptions + * <li>Active VPNs + * <li>WiFi Suggesters + * </ul> + * + * <p>Callbacks will be limited to receiving notifications for networks over which apps provide + * connectivity. + * + * <p>If a registering app loses its relevant permissions, any callbacks it registered will + * silently stop receiving callbacks. + * + * <p>Each register() call <b>MUST</b> use a unique ConnectivityDiagnosticsCallback instance. If + * a single instance is registered with multiple NetworkRequests, an IllegalArgumentException + * will be thrown. + * + * @param request The NetworkRequest that will be used to match with Networks for which + * callbacks will be fired + * @param e The Executor to be used for running the callback method invocations + * @param callback The ConnectivityDiagnosticsCallback that the caller wants registered with the + * System + * @throws IllegalArgumentException if the same callback instance is registered with multiple + * NetworkRequests + * @throws SecurityException if the caller does not have appropriate permissions to register a + * callback + */ + public void registerConnectivityDiagnosticsCallback( + @NonNull NetworkRequest request, + @NonNull Executor e, + @NonNull ConnectivityDiagnosticsCallback callback) { + // TODO(b/143187964): implement ConnectivityDiagnostics functionality + throw new UnsupportedOperationException("registerCallback() not supported yet"); + } + + /** + * Unregisters a ConnectivityDiagnosticsCallback with the System. + * + * <p>If the given callback is not currently registered with the System, this operation will be + * a no-op. + * + * @param callback The ConnectivityDiagnosticsCallback to be unregistered from the System. + */ + public void unregisterConnectivityDiagnosticsCallback( + @NonNull ConnectivityDiagnosticsCallback callback) { + // TODO(b/143187964): implement ConnectivityDiagnostics functionality + throw new UnsupportedOperationException("registerCallback() not supported yet"); + } +} diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 6da16a82c822..03d4200bd90f 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -363,7 +363,7 @@ public class ConnectivityManager { @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) @UnsupportedAppUsage public static final String ACTION_TETHER_STATE_CHANGED = - "android.net.conn.TETHER_STATE_CHANGED"; + TetheringManager.ACTION_TETHER_STATE_CHANGED; /** * @hide @@ -371,14 +371,14 @@ public class ConnectivityManager { * tethering and currently available for tethering. */ @UnsupportedAppUsage - public static final String EXTRA_AVAILABLE_TETHER = "availableArray"; + public static final String EXTRA_AVAILABLE_TETHER = TetheringManager.EXTRA_AVAILABLE_TETHER; /** * @hide * gives a String[] listing all the interfaces currently in local-only * mode (ie, has DHCPv4+IPv6-ULA support and no packet forwarding) */ - public static final String EXTRA_ACTIVE_LOCAL_ONLY = "localOnlyArray"; + public static final String EXTRA_ACTIVE_LOCAL_ONLY = TetheringManager.EXTRA_ACTIVE_LOCAL_ONLY; /** * @hide @@ -386,7 +386,7 @@ public class ConnectivityManager { * (ie, has DHCPv4 support and packets potentially forwarded/NATed) */ @UnsupportedAppUsage - public static final String EXTRA_ACTIVE_TETHER = "tetherArray"; + public static final String EXTRA_ACTIVE_TETHER = TetheringManager.EXTRA_ACTIVE_TETHER; /** * @hide @@ -395,7 +395,7 @@ public class ConnectivityManager { * for any interfaces listed here. */ @UnsupportedAppUsage - public static final String EXTRA_ERRORED_TETHER = "erroredArray"; + public static final String EXTRA_ERRORED_TETHER = TetheringManager.EXTRA_ERRORED_TETHER; /** * Broadcast Action: The captive portal tracker has finished its test. @@ -445,7 +445,7 @@ public class ConnectivityManager { * @see #startTethering(int, boolean, OnStartTetheringCallback) * @hide */ - public static final int TETHERING_INVALID = -1; + public static final int TETHERING_INVALID = TetheringManager.TETHERING_INVALID; /** * Wifi tethering type. @@ -453,7 +453,7 @@ public class ConnectivityManager { * @hide */ @SystemApi - public static final int TETHERING_WIFI = 0; + public static final int TETHERING_WIFI = TetheringManager.TETHERING_WIFI; /** * USB tethering type. @@ -461,7 +461,7 @@ public class ConnectivityManager { * @hide */ @SystemApi - public static final int TETHERING_USB = 1; + public static final int TETHERING_USB = TetheringManager.TETHERING_USB; /** * Bluetooth tethering type. @@ -469,7 +469,7 @@ public class ConnectivityManager { * @hide */ @SystemApi - public static final int TETHERING_BLUETOOTH = 2; + public static final int TETHERING_BLUETOOTH = TetheringManager.TETHERING_BLUETOOTH; /** * Wifi P2p tethering type. @@ -477,41 +477,41 @@ public class ConnectivityManager { * need to start from #startTethering(int, boolean, OnStartTetheringCallback). * @hide */ - public static final int TETHERING_WIFI_P2P = 3; + public static final int TETHERING_WIFI_P2P = TetheringManager.TETHERING_WIFI_P2P; /** * Extra used for communicating with the TetherService. Includes the type of tethering to * enable if any. * @hide */ - public static final String EXTRA_ADD_TETHER_TYPE = "extraAddTetherType"; + public static final String EXTRA_ADD_TETHER_TYPE = TetheringManager.EXTRA_ADD_TETHER_TYPE; /** * Extra used for communicating with the TetherService. Includes the type of tethering for * which to cancel provisioning. * @hide */ - public static final String EXTRA_REM_TETHER_TYPE = "extraRemTetherType"; + public static final String EXTRA_REM_TETHER_TYPE = TetheringManager.EXTRA_REM_TETHER_TYPE; /** * Extra used for communicating with the TetherService. True to schedule a recheck of tether * provisioning. * @hide */ - public static final String EXTRA_SET_ALARM = "extraSetAlarm"; + public static final String EXTRA_SET_ALARM = TetheringManager.EXTRA_SET_ALARM; /** * Tells the TetherService to run a provision check now. * @hide */ - public static final String EXTRA_RUN_PROVISION = "extraRunProvision"; + public static final String EXTRA_RUN_PROVISION = TetheringManager.EXTRA_RUN_PROVISION; /** * Extra used for communicating with the TetherService. Contains the {@link ResultReceiver} * which will receive provisioning results. Can be left empty. * @hide */ - public static final String EXTRA_PROVISION_CALLBACK = "extraProvisionCallback"; + public static final String EXTRA_PROVISION_CALLBACK = TetheringManager.EXTRA_PROVISION_CALLBACK; /** * The absence of a connection type. @@ -3107,6 +3107,63 @@ public class ConnectivityManager { } } + /** + * Registers the specified {@link NetworkProvider}. + * Each listener must only be registered once. The listener can be unregistered with + * {@link #unregisterNetworkProvider}. + * + * @param provider the provider to register + * @return the ID of the provider. This ID must be used by the provider when registering + * {@link android.net.NetworkAgent}s. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) + public int registerNetworkProvider(@NonNull NetworkProvider provider) { + if (provider.getProviderId() != NetworkProvider.ID_NONE) { + // TODO: Provide a better method for checking this by moving NetworkFactory.SerialNumber + // out of NetworkFactory. + throw new IllegalStateException("NetworkProviders can only be registered once"); + } + + try { + int providerId = mService.registerNetworkProvider(provider.getMessenger(), + provider.getName()); + provider.setProviderId(providerId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + return provider.getProviderId(); + } + + /** + * Unregisters the specified NetworkProvider. + * + * @param provider the provider to unregister + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) + public void unregisterNetworkProvider(@NonNull NetworkProvider provider) { + try { + mService.unregisterNetworkProvider(provider.getMessenger()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + provider.setProviderId(NetworkProvider.ID_NONE); + } + + + /** @hide exposed via the NetworkProvider class. */ + @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) + public void declareNetworkRequestUnfulfillable(@NonNull NetworkRequest request) { + try { + mService.declareNetworkRequestUnfulfillable(request); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + // TODO : remove this method. It is a stopgap measure to help sheperding a number // of dependent changes that would conflict throughout the automerger graph. Having this // temporarily helps with the process of going through with all these dependent changes across diff --git a/core/java/android/net/DhcpInfo.java b/core/java/android/net/DhcpInfo.java index 98bab44e19fb..912df67a0b45 100644 --- a/core/java/android/net/DhcpInfo.java +++ b/core/java/android/net/DhcpInfo.java @@ -16,8 +16,8 @@ package android.net; -import android.os.Parcelable; import android.os.Parcel; +import android.os.Parcelable; /** * A simple object for retrieving the results of a DHCP request. @@ -67,12 +67,12 @@ public class DhcpInfo implements Parcelable { buf.append(NetworkUtils.intToInetAddress(addr).getHostAddress()); } - /** Implement the Parcelable interface {@hide} */ + /** Implement the Parcelable interface */ public int describeContents() { return 0; } - /** Implement the Parcelable interface {@hide} */ + /** Implement the Parcelable interface */ public void writeToParcel(Parcel dest, int flags) { dest.writeInt(ipAddress); dest.writeInt(gateway); @@ -83,7 +83,7 @@ public class DhcpInfo implements Parcelable { dest.writeInt(leaseDuration); } - /** Implement the Parcelable interface {@hide} */ + /** Implement the Parcelable interface */ public static final @android.annotation.NonNull Creator<DhcpInfo> CREATOR = new Creator<DhcpInfo>() { public DhcpInfo createFromParcel(Parcel in) { diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index 09c02efbcfc4..e6a0379ff629 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -142,12 +142,16 @@ interface IConnectivityManager void setAirplaneMode(boolean enable); - int registerNetworkFactory(in Messenger messenger, in String name); - boolean requestBandwidthUpdate(in Network network); + int registerNetworkFactory(in Messenger messenger, in String name); void unregisterNetworkFactory(in Messenger messenger); + int registerNetworkProvider(in Messenger messenger, in String name); + void unregisterNetworkProvider(in Messenger messenger); + + void declareNetworkRequestUnfulfillable(in NetworkRequest request); + int registerNetworkAgent(in Messenger messenger, in NetworkInfo ni, in LinkProperties lp, in NetworkCapabilities nc, int score, in NetworkMisc misc, in int factorySerialNumber); diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java index 45d0c7313fca..09ec6c35fcb9 100644 --- a/core/java/android/net/IpSecManager.java +++ b/core/java/android/net/IpSecManager.java @@ -17,7 +17,6 @@ package android.net; import static com.android.internal.util.Preconditions.checkNotNull; -import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.RequiresFeature; import android.annotation.RequiresPermission; @@ -26,6 +25,7 @@ import android.annotation.SystemService; import android.annotation.TestApi; import android.content.Context; import android.content.pm.PackageManager; +import android.net.annotations.PolicyDirection; import android.os.Binder; import android.os.ParcelFileDescriptor; import android.os.RemoteException; @@ -41,8 +41,6 @@ import dalvik.system.CloseGuard; import java.io.FileDescriptor; import java.io.IOException; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.Socket; @@ -78,11 +76,6 @@ public final class IpSecManager { */ public static final int DIRECTION_OUT = 1; - /** @hide */ - @IntDef(value = {DIRECTION_IN, DIRECTION_OUT}) - @Retention(RetentionPolicy.SOURCE) - public @interface PolicyDirection {} - /** * The Security Parameter Index (SPI) 0 indicates an unknown or invalid index. * diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java index 33c39d489835..739e8178e68e 100644 --- a/core/java/android/net/NetworkCapabilities.java +++ b/core/java/android/net/NetworkCapabilities.java @@ -587,15 +587,14 @@ public final class NetworkCapabilities implements Parcelable { } /** - * Removes the NET_CAPABILITY_NOT_RESTRICTED capability if all the capabilities it provides are - * typically provided by restricted networks. + * Deduces that all the capabilities it provides are typically provided by restricted networks + * or not. * - * TODO: consider: - * - Renaming it to guessRestrictedCapability and make it set the - * restricted capability bit in addition to clearing it. + * @return {@code true} if the network should be restricted. * @hide */ - public void maybeMarkCapabilitiesRestricted() { + @SystemApi + public boolean deduceRestrictedCapability() { // Check if we have any capability that forces the network to be restricted. final boolean forceRestrictedCapability = (mNetworkCapabilities & FORCE_RESTRICTED_CAPABILITIES) != 0; @@ -609,8 +608,17 @@ public final class NetworkCapabilities implements Parcelable { final boolean hasRestrictedCapabilities = (mNetworkCapabilities & RESTRICTED_CAPABILITIES) != 0; - if (forceRestrictedCapability - || (hasRestrictedCapabilities && !hasUnrestrictedCapabilities)) { + return forceRestrictedCapability + || (hasRestrictedCapabilities && !hasUnrestrictedCapabilities); + } + + /** + * Removes the NET_CAPABILITY_NOT_RESTRICTED capability if deducing the network is restricted. + * + * @hide + */ + public void maybeMarkCapabilitiesRestricted() { + if (deduceRestrictedCapability()) { removeCapability(NET_CAPABILITY_NOT_RESTRICTED); } } diff --git a/core/java/android/net/NetworkProvider.java b/core/java/android/net/NetworkProvider.java new file mode 100644 index 000000000000..2c0e4aa700b1 --- /dev/null +++ b/core/java/android/net/NetworkProvider.java @@ -0,0 +1,166 @@ +/* + * 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.net; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.Messenger; +import android.util.Log; + +/** + * Base class for network providers such as telephony or Wi-Fi. NetworkProviders connect the device + * to networks and makes them available to to the core network stack by creating + * {@link NetworkAgent}s. The networks can then provide connectivity to apps and can be interacted + * with via networking APIs such as {@link ConnectivityManager}. + * + * Subclasses should implement {@link #onNetworkRequested} and {@link #onRequestWithdrawn} to + * receive {@link NetworkRequest}s sent by the system and by apps. A network that is not the + * best (highest-scoring) network for any request is generally not used by the system, and torn + * down. + * + * @hide + */ +@SystemApi +public class NetworkProvider { + /** + * {@code providerId} value that indicates the absence of a provider. It is the providerId of + * any NetworkProvider that is not currently registered, and of any NetworkRequest that is not + * currently being satisfied by a network. + */ + public static final int ID_NONE = -1; + + /** + * A hardcoded ID for NetworkAgents representing VPNs. These agents are not created by any + * provider, so they use this constant for clarity instead of NONE. + * @hide only used by ConnectivityService. + */ + public static final int ID_VPN = -2; + + /** + * The first providerId value that will be allocated. + * @hide only used by ConnectivityService. + */ + public static final int FIRST_PROVIDER_ID = 1; + + /** @hide only used by ConnectivityService */ + public static final int CMD_REQUEST_NETWORK = 1; + /** @hide only used by ConnectivityService */ + public static final int CMD_CANCEL_REQUEST = 2; + + private final Messenger mMessenger; + private final String mName; + private final ConnectivityManager mCm; + + private int mProviderId = ID_NONE; + + /** + * Constructs a new NetworkProvider. + * + * @param looper the Looper on which to run {@link #onNetworkRequested} and + * {@link #onRequestWithdrawn}. + * @param name the name of the listener, used only for debugging. + * + * @hide + */ + @SystemApi + public NetworkProvider(@NonNull Context context, @NonNull Looper looper, @NonNull String name) { + mCm = ConnectivityManager.from(context); + + Handler handler = new Handler(looper) { + @Override + public void handleMessage(Message m) { + switch (m.what) { + case CMD_REQUEST_NETWORK: + onNetworkRequested((NetworkRequest) m.obj, m.arg1, m.arg2); + break; + case CMD_CANCEL_REQUEST: + onRequestWithdrawn((NetworkRequest) m.obj); + break; + default: + Log.e(mName, "Unhandled message: " + m.what); + } + } + }; + mMessenger = new Messenger(handler); + mName = name; + } + + // TODO: consider adding a register() method so ConnectivityManager does not need to call this. + public @Nullable Messenger getMessenger() { + return mMessenger; + } + + public @NonNull String getName() { + return mName; + } + + /** + * Returns the ID of this provider. This is known only once the provider is registered via + * {@link ConnectivityManager#registerNetworkProvider()}, otherwise the ID is {@link #ID_NONE}. + * This ID must be used when registering any {@link NetworkAgent}s. + */ + public int getProviderId() { + return mProviderId; + } + + /** @hide */ + public void setProviderId(int providerId) { + mProviderId = providerId; + } + + /** + * Called when a NetworkRequest is received. The request may be a new request or an existing + * request with a different score. + * + * @param request the NetworkRequest being received + * @param score the score of the network currently satisfying the request, or 0 if none. + * @param providerId the ID of the provider that created the network currently satisfying this + * request, or {@link #ID_NONE} if none. + * + * @hide + */ + @SystemApi + public void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId) {} + + /** + * Called when a NetworkRequest is withdrawn. + * @hide + */ + @SystemApi + public void onRequestWithdrawn(@NonNull NetworkRequest request) {} + + /** + * Asserts that no provider will ever be able to satisfy the specified request. The provider + * must only call this method if it knows that it is the only provider on the system capable of + * satisfying this request, and that the request cannot be satisfied. The application filing the + * request will receive an {@link NetworkCallback#onUnavailable()} callback. + * + * @param request the request that cannot be fulfilled + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) + public void declareNetworkRequestUnfulfillable(@NonNull NetworkRequest request) { + mCm.declareNetworkRequestUnfulfillable(request); + } +} diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java index 431773dc2622..9731f3ca186d 100644 --- a/core/java/android/net/NetworkRequest.java +++ b/core/java/android/net/NetworkRequest.java @@ -247,9 +247,8 @@ public class NetworkRequest implements Parcelable { * removing even the capabilities that are set by default when the object is constructed. * * @return The builder to facilitate chaining. - * @hide */ - @UnsupportedAppUsage + @NonNull public Builder clearCapabilities() { mNetworkCapabilities.clearAll(); return this; diff --git a/core/java/android/net/NetworkSpecifier.java b/core/java/android/net/NetworkSpecifier.java index 2bc3eb56ec2d..cf31d217c967 100644 --- a/core/java/android/net/NetworkSpecifier.java +++ b/core/java/android/net/NetworkSpecifier.java @@ -16,6 +16,9 @@ package android.net; +import android.annotation.Nullable; +import android.annotation.SystemApi; + /** * Describes specific properties of a requested network for use in a {@link NetworkRequest}. * @@ -31,7 +34,8 @@ public abstract class NetworkSpecifier { * * @hide */ - public abstract boolean satisfiedBy(NetworkSpecifier other); + @SystemApi + public abstract boolean satisfiedBy(@Nullable NetworkSpecifier other); /** * Optional method which can be overridden by concrete implementations of NetworkSpecifier to @@ -45,6 +49,7 @@ public abstract class NetworkSpecifier { * * @hide */ + @SystemApi public void assertValidFromUid(int requestorUid) { // empty } @@ -68,6 +73,8 @@ public abstract class NetworkSpecifier { * * @hide */ + @SystemApi + @Nullable public NetworkSpecifier redact() { // TODO (b/122160111): convert default to null once all platform NetworkSpecifiers // implement this method. diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java index 45bf4d2e1358..96d7a80886a5 100644 --- a/core/java/android/net/NetworkStats.java +++ b/core/java/android/net/NetworkStats.java @@ -19,6 +19,9 @@ package android.net; import static android.os.Process.CLAT_UID; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.os.Parcelable; @@ -48,59 +51,104 @@ import java.util.function.Predicate; * @hide */ // @NotThreadSafe -public class NetworkStats implements Parcelable { +@SystemApi +public final class NetworkStats implements Parcelable { private static final String TAG = "NetworkStats"; + /** {@link #iface} value when interface details unavailable. */ - public static final String IFACE_ALL = null; + @SuppressLint("CompileTimeConstant") + @Nullable public static final String IFACE_ALL = null; + /** + * Virtual network interface for video telephony. This is for VT data usage counting + * purpose. + */ + public static final String IFACE_VT = "vt_data0"; + /** {@link #uid} value when UID details unavailable. */ public static final int UID_ALL = -1; - /** {@link #tag} value matching any tag. */ + /** Special UID value for data usage by tethering. */ + public static final int UID_TETHERING = -5; + + /** + * {@link #tag} value matching any tag. + * @hide + */ // TODO: Rename TAG_ALL to TAG_ANY. public static final int TAG_ALL = -1; - /** {@link #set} value for all sets combined, not including debug sets. */ + /** + * {@link #set} value for all sets combined, not including debug sets. + * @hide + */ public static final int SET_ALL = -1; /** {@link #set} value where background data is accounted. */ public static final int SET_DEFAULT = 0; /** {@link #set} value where foreground data is accounted. */ public static final int SET_FOREGROUND = 1; - /** All {@link #set} value greater than SET_DEBUG_START are debug {@link #set} values. */ + /** + * All {@link #set} value greater than SET_DEBUG_START are debug {@link #set} values. + * @hide + */ public static final int SET_DEBUG_START = 1000; - /** Debug {@link #set} value when the VPN stats are moved in. */ + /** + * Debug {@link #set} value when the VPN stats are moved in. + * @hide + */ public static final int SET_DBG_VPN_IN = 1001; - /** Debug {@link #set} value when the VPN stats are moved out of a vpn UID. */ + /** + * Debug {@link #set} value when the VPN stats are moved out of a vpn UID. + * @hide + */ public static final int SET_DBG_VPN_OUT = 1002; - /** Include all interfaces when filtering */ - public static final String[] INTERFACES_ALL = null; + /** + * Include all interfaces when filtering + * @hide + */ + public @Nullable static final String[] INTERFACES_ALL = null; /** {@link #tag} value for total data across all tags. */ // TODO: Rename TAG_NONE to TAG_ALL. public static final int TAG_NONE = 0; - /** {@link #metered} value to account for all metered states. */ + /** + * {@link #metered} value to account for all metered states. + * @hide + */ public static final int METERED_ALL = -1; /** {@link #metered} value where native, unmetered data is accounted. */ public static final int METERED_NO = 0; /** {@link #metered} value where metered data is accounted. */ public static final int METERED_YES = 1; - /** {@link #roaming} value to account for all roaming states. */ + /** + * {@link #roaming} value to account for all roaming states. + * @hide + */ public static final int ROAMING_ALL = -1; /** {@link #roaming} value where native, non-roaming data is accounted. */ public static final int ROAMING_NO = 0; /** {@link #roaming} value where roaming data is accounted. */ public static final int ROAMING_YES = 1; - /** {@link #onDefaultNetwork} value to account for all default network states. */ + /** + * {@link #onDefaultNetwork} value to account for all default network states. + * @hide + */ public static final int DEFAULT_NETWORK_ALL = -1; /** {@link #onDefaultNetwork} value to account for usage while not the default network. */ public static final int DEFAULT_NETWORK_NO = 0; /** {@link #onDefaultNetwork} value to account for usage while the default network. */ public static final int DEFAULT_NETWORK_YES = 1; - /** Denotes a request for stats at the interface level. */ + /** + * Denotes a request for stats at the interface level. + * @hide + */ public static final int STATS_PER_IFACE = 0; - /** Denotes a request for stats at the interface and UID level. */ + /** + * Denotes a request for stats at the interface and UID level. + * @hide + */ public static final int STATS_PER_UID = 1; private static final String CLATD_INTERFACE_PREFIX = "v4-"; @@ -144,60 +192,78 @@ public class NetworkStats implements Parcelable { @UnsupportedAppUsage private long[] operations; + /** @hide */ + @SystemApi public static class Entry { + /** @hide */ @UnsupportedAppUsage public String iface; + /** @hide */ @UnsupportedAppUsage public int uid; + /** @hide */ @UnsupportedAppUsage public int set; + /** @hide */ @UnsupportedAppUsage public int tag; /** * Note that this is only populated w/ the default value when read from /proc or written * to disk. We merge in the correct value when reporting this value to clients of * getSummary(). + * @hide */ public int metered; /** * Note that this is only populated w/ the default value when read from /proc or written * to disk. We merge in the correct value when reporting this value to clients of * getSummary(). + * @hide */ public int roaming; /** * Note that this is only populated w/ the default value when read from /proc or written * to disk. We merge in the correct value when reporting this value to clients of * getSummary(). + * @hide */ public int defaultNetwork; + /** @hide */ @UnsupportedAppUsage public long rxBytes; + /** @hide */ @UnsupportedAppUsage public long rxPackets; + /** @hide */ @UnsupportedAppUsage public long txBytes; + /** @hide */ @UnsupportedAppUsage public long txPackets; + /** @hide */ + @UnsupportedAppUsage public long operations; + /** @hide */ @UnsupportedAppUsage public Entry() { this(IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L); } + /** @hide */ public Entry(long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) { this(IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, rxPackets, txBytes, txPackets, operations); } + /** @hide */ public Entry(String iface, int uid, int set, int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) { this(iface, uid, set, tag, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, rxBytes, rxPackets, txBytes, txPackets, operations); } - public Entry(String iface, int uid, int set, int tag, int metered, int roaming, + public Entry(@Nullable String iface, int uid, int set, int tag, int metered, int roaming, int defaultNetwork, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) { this.iface = iface; @@ -214,15 +280,18 @@ public class NetworkStats implements Parcelable { this.operations = operations; } + /** @hide */ public boolean isNegative() { return rxBytes < 0 || rxPackets < 0 || txBytes < 0 || txPackets < 0 || operations < 0; } + /** @hide */ public boolean isEmpty() { return rxBytes == 0 && rxPackets == 0 && txBytes == 0 && txPackets == 0 && operations == 0; } + /** @hide */ public void add(Entry another) { this.rxBytes += another.rxBytes; this.rxPackets += another.rxPackets; @@ -249,6 +318,7 @@ public class NetworkStats implements Parcelable { return builder.toString(); } + /** @hide */ @Override public boolean equals(Object o) { if (o instanceof Entry) { @@ -262,13 +332,13 @@ public class NetworkStats implements Parcelable { return false; } + /** @hide */ @Override public int hashCode() { return Objects.hash(uid, set, tag, metered, roaming, defaultNetwork, iface); } } - @UnsupportedAppUsage public NetworkStats(long elapsedRealtime, int initialSize) { this.elapsedRealtime = elapsedRealtime; this.size = 0; @@ -292,6 +362,7 @@ public class NetworkStats implements Parcelable { } } + /** @hide */ @UnsupportedAppUsage public NetworkStats(Parcel parcel) { elapsedRealtime = parcel.readLong(); @@ -312,7 +383,7 @@ public class NetworkStats implements Parcelable { } @Override - public void writeToParcel(Parcel dest, int flags) { + public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeLong(elapsedRealtime); dest.writeInt(size); dest.writeInt(capacity); @@ -330,19 +401,23 @@ public class NetworkStats implements Parcelable { dest.writeLongArray(operations); } + /** + * @hide + */ @Override public NetworkStats clone() { final NetworkStats clone = new NetworkStats(elapsedRealtime, size); NetworkStats.Entry entry = null; for (int i = 0; i < size; i++) { entry = getValues(i, entry); - clone.addValues(entry); + clone.addEntry(entry); } return clone; } /** * Clear all data stored in this object. + * @hide */ public void clear() { this.capacity = 0; @@ -360,25 +435,28 @@ public class NetworkStats implements Parcelable { this.operations = EmptyArray.LONG; } + /** @hide */ @VisibleForTesting public NetworkStats addIfaceValues( String iface, long rxBytes, long rxPackets, long txBytes, long txPackets) { - return addValues( + return addEntry( iface, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, rxPackets, txBytes, txPackets, 0L); } + /** @hide */ @VisibleForTesting - public NetworkStats addValues(String iface, int uid, int set, int tag, long rxBytes, + public NetworkStats addEntry(String iface, int uid, int set, int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) { - return addValues(new Entry( + return addEntry(new Entry( iface, uid, set, tag, rxBytes, rxPackets, txBytes, txPackets, operations)); } + /** @hide */ @VisibleForTesting - public NetworkStats addValues(String iface, int uid, int set, int tag, int metered, int roaming, + public NetworkStats addEntry(String iface, int uid, int set, int tag, int metered, int roaming, int defaultNetwork, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) { - return addValues(new Entry( + return addEntry(new Entry( iface, uid, set, tag, metered, roaming, defaultNetwork, rxBytes, rxPackets, txBytes, txPackets, operations)); } @@ -386,8 +464,9 @@ public class NetworkStats implements Parcelable { /** * Add new stats entry, copying from given {@link Entry}. The {@link Entry} * object can be recycled across multiple calls. + * @hide */ - public NetworkStats addValues(Entry entry) { + public NetworkStats addEntry(Entry entry) { if (size >= capacity) { final int newLength = Math.max(size, 10) * 3 / 2; iface = Arrays.copyOf(iface, newLength); @@ -428,6 +507,7 @@ public class NetworkStats implements Parcelable { /** * Return specific stats entry. + * @hide */ @UnsupportedAppUsage public Entry getValues(int i, Entry recycle) { @@ -467,10 +547,12 @@ public class NetworkStats implements Parcelable { operations[dest] = operations[src]; } + /** @hide */ public long getElapsedRealtime() { return elapsedRealtime; } + /** @hide */ public void setElapsedRealtime(long time) { elapsedRealtime = time; } @@ -478,21 +560,25 @@ public class NetworkStats implements Parcelable { /** * Return age of this {@link NetworkStats} object with respect to * {@link SystemClock#elapsedRealtime()}. + * @hide */ public long getElapsedRealtimeAge() { return SystemClock.elapsedRealtime() - elapsedRealtime; } + /** @hide */ @UnsupportedAppUsage public int size() { return size; } + /** @hide */ @VisibleForTesting public int internalSize() { return capacity; } + /** @hide */ @Deprecated public NetworkStats combineValues(String iface, int uid, int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) { @@ -501,6 +587,7 @@ public class NetworkStats implements Parcelable { txPackets, operations); } + /** @hide */ public NetworkStats combineValues(String iface, int uid, int set, int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) { return combineValues(new Entry( @@ -509,16 +596,20 @@ public class NetworkStats implements Parcelable { /** * Combine given values with an existing row, or create a new row if - * {@link #findIndex(String, int, int, int, int)} is unable to find match. Can - * also be used to subtract values from existing rows. + * {@link #findIndex(String, int, int, int, int, int, int)} is unable to find match. Can + * also be used to subtract values from existing rows. This method mutates the referencing + * {@link NetworkStats} object. + * + * @param entry the {@link Entry} to combine. + * @return a reference to this mutated {@link NetworkStats} object. + * @hide */ - @UnsupportedAppUsage - public NetworkStats combineValues(Entry entry) { + public @NonNull NetworkStats combineValues(@NonNull Entry entry) { final int i = findIndex(entry.iface, entry.uid, entry.set, entry.tag, entry.metered, entry.roaming, entry.defaultNetwork); if (i == -1) { // only create new entry when positive contribution - addValues(entry); + addEntry(entry); } else { rxBytes[i] += entry.rxBytes; rxPackets[i] += entry.rxPackets; @@ -530,10 +621,33 @@ public class NetworkStats implements Parcelable { } /** + * Add given values with an existing row, or create a new row if + * {@link #findIndex(String, int, int, int, int, int, int)} is unable to find match. Can + * also be used to subtract values from existing rows. + * + * @param entry the {@link Entry} to add. + * @return a new constructed {@link NetworkStats} object that contains the result. + */ + public @NonNull NetworkStats addValues(@NonNull Entry entry) { + return this.clone().combineValues(entry); + } + + /** + * Add the given {@link NetworkStats} objects. + * + * @return the sum of two objects. + */ + public @NonNull NetworkStats add(@NonNull NetworkStats another) { + final NetworkStats ret = this.clone(); + ret.combineAllValues(another); + return ret; + } + + /** * Combine all values from another {@link NetworkStats} into this object. + * @hide */ - @UnsupportedAppUsage - public void combineAllValues(NetworkStats another) { + public void combineAllValues(@NonNull NetworkStats another) { NetworkStats.Entry entry = null; for (int i = 0; i < another.size; i++) { entry = another.getValues(i, entry); @@ -543,6 +657,7 @@ public class NetworkStats implements Parcelable { /** * Find first stats index that matches the requested parameters. + * @hide */ public int findIndex(String iface, int uid, int set, int tag, int metered, int roaming, int defaultNetwork) { @@ -560,6 +675,7 @@ public class NetworkStats implements Parcelable { /** * Find first stats index that matches the requested parameters, starting * search around the hinted index as an optimization. + * @hide */ @VisibleForTesting public int findIndexHinted(String iface, int uid, int set, int tag, int metered, int roaming, @@ -589,6 +705,7 @@ public class NetworkStats implements Parcelable { * Splice in {@link #operations} from the given {@link NetworkStats} based * on matching {@link #uid} and {@link #tag} rows. Ignores {@link #iface}, * since operation counts are at data layer. + * @hide */ public void spliceOperationsFrom(NetworkStats stats) { for (int i = 0; i < size; i++) { @@ -604,6 +721,7 @@ public class NetworkStats implements Parcelable { /** * Return list of unique interfaces known by this data structure. + * @hide */ public String[] getUniqueIfaces() { final HashSet<String> ifaces = new HashSet<String>(); @@ -617,6 +735,7 @@ public class NetworkStats implements Parcelable { /** * Return list of unique UIDs known by this data structure. + * @hide */ @UnsupportedAppUsage public int[] getUniqueUids() { @@ -636,6 +755,7 @@ public class NetworkStats implements Parcelable { /** * Return total bytes represented by this snapshot object, usually used when * checking if a {@link #subtract(NetworkStats)} delta passes a threshold. + * @hide */ @UnsupportedAppUsage public long getTotalBytes() { @@ -645,6 +765,7 @@ public class NetworkStats implements Parcelable { /** * Return total of all fields represented by this snapshot object. + * @hide */ @UnsupportedAppUsage public Entry getTotal(Entry recycle) { @@ -654,6 +775,7 @@ public class NetworkStats implements Parcelable { /** * Return total of all fields represented by this snapshot object matching * the requested {@link #uid}. + * @hide */ @UnsupportedAppUsage public Entry getTotal(Entry recycle, int limitUid) { @@ -663,11 +785,13 @@ public class NetworkStats implements Parcelable { /** * Return total of all fields represented by this snapshot object matching * the requested {@link #iface}. + * @hide */ public Entry getTotal(Entry recycle, HashSet<String> limitIface) { return getTotal(recycle, limitIface, UID_ALL, false); } + /** @hide */ @UnsupportedAppUsage public Entry getTotalIncludingTags(Entry recycle) { return getTotal(recycle, null, UID_ALL, true); @@ -717,6 +841,7 @@ public class NetworkStats implements Parcelable { /** * Fast path for battery stats. + * @hide */ public long getTotalPackets() { long total = 0; @@ -729,9 +854,12 @@ public class NetworkStats implements Parcelable { /** * Subtract the given {@link NetworkStats}, effectively leaving the delta * between two snapshots in time. Assumes that statistics rows collect over - * time, and that none of them have disappeared. + * time, and that none of them have disappeared. This method does not mutate + * the referencing object. + * + * @return the delta between two objects. */ - public NetworkStats subtract(NetworkStats right) { + public @NonNull NetworkStats subtract(@NonNull NetworkStats right) { return subtract(this, right, null, null); } @@ -742,6 +870,7 @@ public class NetworkStats implements Parcelable { * <p> * If counters have rolled backwards, they are clamped to {@code 0} and * reported to the given {@link NonMonotonicObserver}. + * @hide */ public static <C> NetworkStats subtract(NetworkStats left, NetworkStats right, NonMonotonicObserver<C> observer, C cookie) { @@ -759,6 +888,7 @@ public class NetworkStats implements Parcelable { * If <var>recycle</var> is supplied, this NetworkStats object will be * reused (and returned) as the result if it is large enough to contain * the data. + * @hide */ public static <C> NetworkStats subtract(NetworkStats left, NetworkStats right, NonMonotonicObserver<C> observer, C cookie, NetworkStats recycle) { @@ -817,7 +947,7 @@ public class NetworkStats implements Parcelable { entry.operations = Math.max(entry.operations, 0); } - result.addValues(entry); + result.addEntry(entry); } return result; @@ -847,6 +977,7 @@ public class NetworkStats implements Parcelable { * @param stackedTraffic Stats with traffic stacked on top of our ifaces. Will also be mutated. * @param stackedIfaces Mapping ipv6if -> ipv4if interface where traffic is counted on both. * @param useBpfStats True if eBPF is in use. + * @hide */ public static void apply464xlatAdjustments(NetworkStats baseTraffic, NetworkStats stackedTraffic, Map<String, String> stackedIfaces, boolean useBpfStats) { @@ -899,6 +1030,7 @@ public class NetworkStats implements Parcelable { * {@link #apply464xlatAdjustments(NetworkStats, NetworkStats, Map)} with {@code this} as * base and stacked traffic. * @param stackedIfaces Mapping ipv6if -> ipv4if interface where traffic is counted on both. + * @hide */ public void apply464xlatAdjustments(Map<String, String> stackedIfaces, boolean useBpfStats) { apply464xlatAdjustments(this, this, stackedIfaces, useBpfStats); @@ -907,6 +1039,7 @@ public class NetworkStats implements Parcelable { /** * Return total statistics grouped by {@link #iface}; doesn't mutate the * original structure. + * @hide */ public NetworkStats groupedByIface() { final NetworkStats stats = new NetworkStats(elapsedRealtime, 10); @@ -938,6 +1071,7 @@ public class NetworkStats implements Parcelable { /** * Return total statistics grouped by {@link #uid}; doesn't mutate the * original structure. + * @hide */ public NetworkStats groupedByUid() { final NetworkStats stats = new NetworkStats(elapsedRealtime, 10); @@ -968,6 +1102,7 @@ public class NetworkStats implements Parcelable { /** * Remove all rows that match one of specified UIDs. + * @hide */ public void removeUids(int[] uids) { int nextOutputEntry = 0; @@ -989,6 +1124,7 @@ public class NetworkStats implements Parcelable { * @param limitUid UID to filter for, or {@link #UID_ALL}. * @param limitIfaces Interfaces to filter for, or {@link #INTERFACES_ALL}. * @param limitTag Tag to filter for, or {@link #TAG_ALL}. + * @hide */ public void filter(int limitUid, String[] limitIfaces, int limitTag) { if (limitUid == UID_ALL && limitTag == TAG_ALL && limitIfaces == INTERFACES_ALL) { @@ -1004,6 +1140,7 @@ public class NetworkStats implements Parcelable { * Only keep entries with {@link #set} value less than {@link #SET_DEBUG_START}. * * <p>This mutates the original structure in place. + * @hide */ public void filterDebugEntries() { filter(e -> e.set < SET_DEBUG_START); @@ -1024,6 +1161,7 @@ public class NetworkStats implements Parcelable { size = nextOutputEntry; } + /** @hide */ public void dump(String prefix, PrintWriter pw) { pw.print(prefix); pw.print("NetworkStats: elapsedRealtime="); pw.println(elapsedRealtime); @@ -1047,6 +1185,7 @@ public class NetworkStats implements Parcelable { /** * Return text description of {@link #set} value. + * @hide */ public static String setToString(int set) { switch (set) { @@ -1067,6 +1206,7 @@ public class NetworkStats implements Parcelable { /** * Return text description of {@link #set} value. + * @hide */ public static String setToCheckinString(int set) { switch (set) { @@ -1087,6 +1227,7 @@ public class NetworkStats implements Parcelable { /** * @return true if the querySet matches the dataSet. + * @hide */ public static boolean setMatches(int querySet, int dataSet) { if (querySet == dataSet) { @@ -1098,6 +1239,7 @@ public class NetworkStats implements Parcelable { /** * Return text description of {@link #tag} value. + * @hide */ public static String tagToString(int tag) { return "0x" + Integer.toHexString(tag); @@ -1105,6 +1247,7 @@ public class NetworkStats implements Parcelable { /** * Return text description of {@link #metered} value. + * @hide */ public static String meteredToString(int metered) { switch (metered) { @@ -1121,6 +1264,7 @@ public class NetworkStats implements Parcelable { /** * Return text description of {@link #roaming} value. + * @hide */ public static String roamingToString(int roaming) { switch (roaming) { @@ -1137,6 +1281,7 @@ public class NetworkStats implements Parcelable { /** * Return text description of {@link #defaultNetwork} value. + * @hide */ public static String defaultNetworkToString(int defaultNetwork) { switch (defaultNetwork) { @@ -1151,6 +1296,7 @@ public class NetworkStats implements Parcelable { } } + /** @hide */ @Override public String toString() { final CharArrayWriter writer = new CharArrayWriter(); @@ -1163,8 +1309,7 @@ public class NetworkStats implements Parcelable { return 0; } - @UnsupportedAppUsage - public static final @android.annotation.NonNull Creator<NetworkStats> CREATOR = new Creator<NetworkStats>() { + public static final @NonNull Creator<NetworkStats> CREATOR = new Creator<NetworkStats>() { @Override public NetworkStats createFromParcel(Parcel in) { return new NetworkStats(in); @@ -1176,6 +1321,7 @@ public class NetworkStats implements Parcelable { } }; + /** @hide */ public interface NonMonotonicObserver<C> { public void foundNonMonotonic( NetworkStats left, int leftIndex, NetworkStats right, int rightIndex, C cookie); @@ -1195,6 +1341,7 @@ public class NetworkStats implements Parcelable { * @param tunUid uid of the VPN application * @param tunIface iface of the vpn tunnel * @param underlyingIfaces underlying network ifaces used by the VPN application + * @hide */ public void migrateTun(int tunUid, @NonNull String tunIface, @NonNull String[] underlyingIfaces) { diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java index 162d6e27bd42..8108cf08d5c3 100644 --- a/core/java/android/net/TrafficStats.java +++ b/core/java/android/net/TrafficStats.java @@ -89,7 +89,7 @@ public class TrafficStats { * * @hide */ - public static final int UID_TETHERING = -5; + public static final int UID_TETHERING = NetworkStats.UID_TETHERING; /** * Tag values in this range are reserved for the network stack. The network stack is diff --git a/core/java/android/net/annotations/PolicyDirection.java b/core/java/android/net/annotations/PolicyDirection.java new file mode 100644 index 000000000000..febd9b406111 --- /dev/null +++ b/core/java/android/net/annotations/PolicyDirection.java @@ -0,0 +1,35 @@ +/* + * 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.net.annotations; + +import android.annotation.IntDef; +import android.net.IpSecManager; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * IPsec traffic direction. + * + * <p>Mainline modules cannot reference hidden @IntDef. Moving this annotation to a separate class + * to allow others to statically include it. + * + * @hide + */ +@IntDef(value = {IpSecManager.DIRECTION_IN, IpSecManager.DIRECTION_OUT}) +@Retention(RetentionPolicy.SOURCE) +public @interface PolicyDirection {} diff --git a/core/java/android/net/netstats/provider/AbstractNetworkStatsProvider.java b/core/java/android/net/netstats/provider/AbstractNetworkStatsProvider.java new file mode 100644 index 000000000000..740aa92ad484 --- /dev/null +++ b/core/java/android/net/netstats/provider/AbstractNetworkStatsProvider.java @@ -0,0 +1,70 @@ +/* + * 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.net.netstats.provider; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.net.NetworkStats; + +/** + * A base class that allows external modules to implement a custom network statistics provider. + * @hide + */ +@SystemApi +public abstract class AbstractNetworkStatsProvider { + /** + * A value used by {@link #setLimit} and {@link #setAlert} indicates there is no limit. + */ + public static final int QUOTA_UNLIMITED = -1; + + /** + * Called by {@code NetworkStatsService} when global polling is needed. Custom + * implementation of providers MUST respond to it by calling + * {@link NetworkStatsProviderCallback#onStatsUpdated} within one minute. Responding + * later than this may cause the stats to be dropped. + * + * @param token a positive number identifying the new state of the system under which + * {@link NetworkStats} have to be gathered from now on. When this is called, + * custom implementations of providers MUST report the latest stats with the + * previous token, under which stats were being gathered so far. + */ + public abstract void requestStatsUpdate(int token); + + /** + * Called by {@code NetworkStatsService} when setting the interface quota for the specified + * upstream interface. When this is called, the custom implementation should block all egress + * packets on the {@code iface} associated with the provider when {@code quotaBytes} bytes have + * been reached, and MUST respond to it by calling + * {@link NetworkStatsProviderCallback#onLimitReached()}. + * + * @param iface the interface requiring the operation. + * @param quotaBytes the quota defined as the number of bytes, starting from zero and counting + * from now. A value of {@link #QUOTA_UNLIMITED} indicates there is no limit. + */ + public abstract void setLimit(@NonNull String iface, long quotaBytes); + + /** + * Called by {@code NetworkStatsService} when setting the alert bytes. Custom implementations + * MUST call {@link NetworkStatsProviderCallback#onAlertReached()} when {@code quotaBytes} bytes + * have been reached. Unlike {@link #setLimit(String, long)}, the custom implementation should + * not block all egress packets. + * + * @param quotaBytes the quota defined as the number of bytes, starting from zero and counting + * from now. A value of {@link #QUOTA_UNLIMITED} indicates there is no alert. + */ + public abstract void setAlert(long quotaBytes); +} diff --git a/core/java/android/net/netstats/provider/INetworkStatsProvider.aidl b/core/java/android/net/netstats/provider/INetworkStatsProvider.aidl new file mode 100644 index 000000000000..55b3d4edb157 --- /dev/null +++ b/core/java/android/net/netstats/provider/INetworkStatsProvider.aidl @@ -0,0 +1,28 @@ +/* + * 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.net.netstats.provider; + +/** + * Interface for NetworkStatsService to query network statistics and set data limits. + * + * @hide + */ +oneway interface INetworkStatsProvider { + void requestStatsUpdate(int token); + void setLimit(String iface, long quotaBytes); + void setAlert(long quotaBytes); +} diff --git a/core/java/android/net/netstats/provider/INetworkStatsProviderCallback.aidl b/core/java/android/net/netstats/provider/INetworkStatsProviderCallback.aidl new file mode 100644 index 000000000000..3ea9318f10d4 --- /dev/null +++ b/core/java/android/net/netstats/provider/INetworkStatsProviderCallback.aidl @@ -0,0 +1,31 @@ +/* + * 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.net.netstats.provider; + +import android.net.NetworkStats; + +/** + * Interface for implementor of {@link INetworkStatsProviderCallback} to push events + * such as network statistics update or notify limit reached. + * @hide + */ +oneway interface INetworkStatsProviderCallback { + void onStatsUpdated(int token, in NetworkStats ifaceStats, in NetworkStats uidStats); + void onAlertReached(); + void onLimitReached(); + void unregister(); +} diff --git a/core/java/android/net/netstats/provider/NetworkStatsProviderCallback.java b/core/java/android/net/netstats/provider/NetworkStatsProviderCallback.java new file mode 100644 index 000000000000..e17a8eee7ff0 --- /dev/null +++ b/core/java/android/net/netstats/provider/NetworkStatsProviderCallback.java @@ -0,0 +1,98 @@ +/* + * 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.net.netstats.provider; + +import android.annotation.NonNull; +import android.annotation.SuppressLint; +import android.annotation.SystemApi; +import android.net.NetworkStats; +import android.os.RemoteException; + +/** + * A callback class that allows callers to report events to the system. + * @hide + */ +@SystemApi +@SuppressLint("CallbackMethodName") +public class NetworkStatsProviderCallback { + @NonNull private final INetworkStatsProviderCallback mBinder; + + /** @hide */ + public NetworkStatsProviderCallback(@NonNull INetworkStatsProviderCallback binder) { + mBinder = binder; + } + + /** + * Notify the system of new network statistics. + * + * Send the network statistics recorded since the last call to {@link #onStatsUpdated}. Must be + * called within one minute of {@link AbstractNetworkStatsProvider#requestStatsUpdate(int)} + * being called. The provider can also call this whenever it wants to reports new stats for any + * reason. Note that the system will not necessarily immediately propagate the statistics to + * reflect the update. + * + * @param token the token under which these stats were gathered. Providers can call this method + * with the current token as often as they want, until the token changes. + * {@see AbstractNetworkStatsProvider#requestStatsUpdate()} + * @param ifaceStats the {@link NetworkStats} per interface to be reported. + * The provider should not include any traffic that is already counted by + * kernel interface counters. + * @param uidStats the same stats as above, but counts {@link NetworkStats} + * per uid. + */ + public void onStatsUpdated(int token, @NonNull NetworkStats ifaceStats, + @NonNull NetworkStats uidStats) { + try { + mBinder.onStatsUpdated(token, ifaceStats, uidStats); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + } + + /** + * Notify system that the quota set by {@code setAlert} has been reached. + */ + public void onAlertReached() { + try { + mBinder.onAlertReached(); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + } + + /** + * Notify system that the quota set by {@code setLimit} has been reached. + */ + public void onLimitReached() { + try { + mBinder.onLimitReached(); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + } + + /** + * Unregister the provider and the referencing callback. + */ + public void unregister() { + try { + mBinder.unregister(); + } catch (RemoteException e) { + // Ignore error. + } + } +} diff --git a/core/java/android/net/netstats/provider/NetworkStatsProviderWrapper.java b/core/java/android/net/netstats/provider/NetworkStatsProviderWrapper.java new file mode 100644 index 000000000000..4bf7c9bc086e --- /dev/null +++ b/core/java/android/net/netstats/provider/NetworkStatsProviderWrapper.java @@ -0,0 +1,48 @@ +/* + * 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.net.netstats.provider; + +import android.annotation.NonNull; + +/** + * A wrapper class of {@link INetworkStatsProvider} that hides the binder interface from exposing + * to outer world. + * + * @hide + */ +public class NetworkStatsProviderWrapper extends INetworkStatsProvider.Stub { + @NonNull final AbstractNetworkStatsProvider mProvider; + + public NetworkStatsProviderWrapper(AbstractNetworkStatsProvider provider) { + mProvider = provider; + } + + @Override + public void requestStatsUpdate(int token) { + mProvider.requestStatsUpdate(token); + } + + @Override + public void setLimit(@NonNull String iface, long quotaBytes) { + mProvider.setLimit(iface, quotaBytes); + } + + @Override + public void setAlert(long quotaBytes) { + mProvider.setAlert(quotaBytes); + } +} diff --git a/core/java/android/os/ExternalVibration.java b/core/java/android/os/ExternalVibration.java index 2991db2dac02..e62244f5dea0 100644 --- a/core/java/android/os/ExternalVibration.java +++ b/core/java/android/os/ExternalVibration.java @@ -161,7 +161,6 @@ public class ExternalVibration implements Parcelable { out.writeInt(mUid); out.writeString(mPkg); writeAudioAttributes(mAttrs, out, flags); - out.writeParcelable(mAttrs, flags); out.writeStrongBinder(mController.asBinder()); out.writeStrongBinder(mToken); } diff --git a/core/java/android/os/IRecoverySystem.aidl b/core/java/android/os/IRecoverySystem.aidl index c5ceecd7c8b9..2561e1ea69c6 100644 --- a/core/java/android/os/IRecoverySystem.aidl +++ b/core/java/android/os/IRecoverySystem.aidl @@ -17,6 +17,7 @@ package android.os; +import android.content.IntentSender; import android.os.IRecoverySystemProgressListener; /** @hide */ @@ -26,4 +27,7 @@ interface IRecoverySystem { boolean setupBcb(in String command); boolean clearBcb(); void rebootRecoveryWithCommand(in String command); + boolean requestLskf(in String updateToken, in IntentSender sender); + boolean clearLskf(); + boolean rebootWithLskf(in String updateToken, in String reason); } diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl index e81a505a1715..edaaf81cd906 100644 --- a/core/java/android/os/IUserManager.aidl +++ b/core/java/android/os/IUserManager.aidl @@ -62,7 +62,7 @@ interface IUserManager { boolean canAddMoreManagedProfiles(int userId, boolean allowedToRemoveOne); UserInfo getProfileParent(int userId); boolean isSameProfileGroup(int userId, int otherUserHandle); - String getUserTypeForUser(int userId); + boolean isUserOfType(int userId, in String userType); @UnsupportedAppUsage UserInfo getUserInfo(int userId); String getUserAccount(int userId); diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index c9ebed3bd0a2..1a4dac78855f 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -1886,29 +1886,7 @@ public final class Parcel { public final void writeException(@NonNull Exception e) { AppOpsManager.prefixParcelWithAppOpsIfNeeded(this); - int code = 0; - if (e instanceof Parcelable - && (e.getClass().getClassLoader() == Parcelable.class.getClassLoader())) { - // We only send Parcelable exceptions that are in the - // BootClassLoader to ensure that the receiver can unpack them - code = EX_PARCELABLE; - } else if (e instanceof SecurityException) { - code = EX_SECURITY; - } else if (e instanceof BadParcelableException) { - code = EX_BAD_PARCELABLE; - } else if (e instanceof IllegalArgumentException) { - code = EX_ILLEGAL_ARGUMENT; - } else if (e instanceof NullPointerException) { - code = EX_NULL_POINTER; - } else if (e instanceof IllegalStateException) { - code = EX_ILLEGAL_STATE; - } else if (e instanceof NetworkOnMainThreadException) { - code = EX_NETWORK_MAIN_THREAD; - } else if (e instanceof UnsupportedOperationException) { - code = EX_UNSUPPORTED_OPERATION; - } else if (e instanceof ServiceSpecificException) { - code = EX_SERVICE_SPECIFIC; - } + int code = getExceptionCode(e); writeInt(code); StrictMode.clearGatheredViolations(); if (code == 0) { @@ -1922,20 +1900,7 @@ public final class Parcel { if (sParcelExceptionStackTrace && (timeNow - sLastWriteExceptionStackTrace > WRITE_EXCEPTION_STACK_TRACE_THRESHOLD_MS)) { sLastWriteExceptionStackTrace = timeNow; - final int sizePosition = dataPosition(); - writeInt(0); // Header size will be filled in later - StackTraceElement[] stackTrace = e.getStackTrace(); - final int truncatedSize = Math.min(stackTrace.length, 5); - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < truncatedSize; i++) { - sb.append("\tat ").append(stackTrace[i]).append('\n'); - } - writeString(sb.toString()); - final int payloadPosition = dataPosition(); - setDataPosition(sizePosition); - // Write stack trace header size. Used in native side to skip the header - writeInt(payloadPosition - sizePosition); - setDataPosition(payloadPosition); + writeStackTrace(e); } else { writeInt(0); } @@ -1956,6 +1921,52 @@ public final class Parcel { } } + /** @hide */ + public static int getExceptionCode(@NonNull Throwable e) { + int code = 0; + if (e instanceof Parcelable + && (e.getClass().getClassLoader() == Parcelable.class.getClassLoader())) { + // We only send Parcelable exceptions that are in the + // BootClassLoader to ensure that the receiver can unpack them + code = EX_PARCELABLE; + } else if (e instanceof SecurityException) { + code = EX_SECURITY; + } else if (e instanceof BadParcelableException) { + code = EX_BAD_PARCELABLE; + } else if (e instanceof IllegalArgumentException) { + code = EX_ILLEGAL_ARGUMENT; + } else if (e instanceof NullPointerException) { + code = EX_NULL_POINTER; + } else if (e instanceof IllegalStateException) { + code = EX_ILLEGAL_STATE; + } else if (e instanceof NetworkOnMainThreadException) { + code = EX_NETWORK_MAIN_THREAD; + } else if (e instanceof UnsupportedOperationException) { + code = EX_UNSUPPORTED_OPERATION; + } else if (e instanceof ServiceSpecificException) { + code = EX_SERVICE_SPECIFIC; + } + return code; + } + + /** @hide */ + public void writeStackTrace(@NonNull Throwable e) { + final int sizePosition = dataPosition(); + writeInt(0); // Header size will be filled in later + StackTraceElement[] stackTrace = e.getStackTrace(); + final int truncatedSize = Math.min(stackTrace.length, 5); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < truncatedSize; i++) { + sb.append("\tat ").append(stackTrace[i]).append('\n'); + } + writeString(sb.toString()); + final int payloadPosition = dataPosition(); + setDataPosition(sizePosition); + // Write stack trace header size. Used in native side to skip the header + writeInt(payloadPosition - sizePosition); + setDataPosition(payloadPosition); + } + /** * Special function for writing information at the front of the Parcel * indicating that no exception occurred. @@ -2069,14 +2080,7 @@ public final class Parcel { if (remoteStackTrace != null) { RemoteException cause = new RemoteException( "Remote stack trace:\n" + remoteStackTrace, null, false, false); - try { - Throwable rootCause = ExceptionUtils.getRootCause(e); - if (rootCause != null) { - rootCause.initCause(cause); - } - } catch (RuntimeException ex) { - Log.e(TAG, "Cannot set cause " + cause + " for " + e, ex); - } + ExceptionUtils.appendCause(e, cause); } SneakyThrow.sneakyThrow(e); } @@ -2088,6 +2092,14 @@ public final class Parcel { * @param msg The exception message. */ private Exception createException(int code, String msg) { + Exception exception = createExceptionOrNull(code, msg); + return exception != null + ? exception + : new RuntimeException("Unknown exception code: " + code + " msg " + msg); + } + + /** @hide */ + public Exception createExceptionOrNull(int code, String msg) { switch (code) { case EX_PARCELABLE: if (readInt() > 0) { @@ -2111,9 +2123,9 @@ public final class Parcel { return new UnsupportedOperationException(msg); case EX_SERVICE_SPECIFIC: return new ServiceSpecificException(readInt(), msg); + default: + return null; } - return new RuntimeException("Unknown exception code: " + code - + " msg " + msg); } /** diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java index 983053bbe7fd..89ddf8cbd96a 100644 --- a/core/java/android/os/ParcelFileDescriptor.java +++ b/core/java/android/os/ParcelFileDescriptor.java @@ -31,6 +31,9 @@ import static android.system.OsConstants.S_ISLNK; import static android.system.OsConstants.S_ISREG; import static android.system.OsConstants.S_IWOTH; +import android.annotation.NonNull; +import android.annotation.SuppressLint; +import android.annotation.SystemApi; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.BroadcastReceiver; @@ -253,6 +256,9 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { * be opened with the requested mode. * @see #parseMode(String) */ + // We can't accept a generic Executor here, since we need to use + // MessageQueue.addOnFileDescriptorEventListener() + @SuppressLint("ExecutorRegistration") public static ParcelFileDescriptor open(File file, int mode, Handler handler, final OnCloseListener listener) throws IOException { if (handler == null) { @@ -268,9 +274,22 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { return fromFd(fd, handler, listener); } - /** {@hide} */ - public static ParcelFileDescriptor fromPfd(ParcelFileDescriptor pfd, Handler handler, - final OnCloseListener listener) throws IOException { + /** + * Create a new ParcelFileDescriptor wrapping an already-opened file. + * + * @param pfd The already-opened file. + * @param handler to call listener from. + * @param listener to be invoked when the returned descriptor has been + * closed. + * @return a new ParcelFileDescriptor pointing to the given file. + * @hide + */ + @SystemApi + // We can't accept a generic Executor here, since we need to use + // MessageQueue.addOnFileDescriptorEventListener() + @SuppressLint("ExecutorRegistration") + public static @NonNull ParcelFileDescriptor wrap(@NonNull ParcelFileDescriptor pfd, + @NonNull Handler handler, @NonNull OnCloseListener listener) throws IOException { final FileDescriptor original = new FileDescriptor(); original.setInt$(pfd.detachFd()); return fromFd(original, handler, listener); diff --git a/core/java/android/os/PersistableBundle.java b/core/java/android/os/PersistableBundle.java index b40283f49408..7a837e167fb0 100644 --- a/core/java/android/os/PersistableBundle.java +++ b/core/java/android/os/PersistableBundle.java @@ -16,17 +16,24 @@ package android.os; +import static java.nio.charset.StandardCharsets.UTF_8; + +import android.annotation.NonNull; import android.annotation.Nullable; import android.util.ArrayMap; import android.util.proto.ProtoOutputStream; +import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlPullParserFactory; import org.xmlpull.v1.XmlSerializer; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.util.ArrayList; /** @@ -339,4 +346,44 @@ public final class PersistableBundle extends BaseBundle implements Cloneable, Pa proto.end(token); } + + /** + * Writes the content of the {@link PersistableBundle} to a {@link OutputStream}. + * + * <p>The content can be read by a {@link #readFromStream}. + * + * @see #readFromStream + */ + public void writeToStream(@NonNull OutputStream outputStream) throws IOException { + FastXmlSerializer serializer = new FastXmlSerializer(); + serializer.setOutput(outputStream, UTF_8.name()); + serializer.startTag(null, "bundle"); + try { + saveToXml(serializer); + } catch (XmlPullParserException e) { + throw new IOException(e); + } + serializer.endTag(null, "bundle"); + serializer.flush(); + } + + /** + * Reads a {@link PersistableBundle} from an {@link InputStream}. + * + * <p>The stream must be generated by {@link #writeToStream}. + * + * @see #writeToStream + */ + @NonNull + public static PersistableBundle readFromStream(@NonNull InputStream inputStream) + throws IOException { + try { + XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser(); + parser.setInput(inputStream, UTF_8.name()); + parser.next(); + return PersistableBundle.restoreFromXml(parser); + } catch (XmlPullParserException e) { + throw new IOException(e); + } + } } diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 94623bc71346..5d80ab6453cc 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -68,10 +68,9 @@ public class Process { public static final int LOG_UID = 1007; /** - * Defines the UID/GID for the WIFI supplicant process. - * @hide + * Defines the UID/GID for the WIFI native processes like wificond, supplicant, hostapd, + * vendor HAL, etc. */ - @UnsupportedAppUsage public static final int WIFI_UID = 1010; /** @@ -89,6 +88,12 @@ public class Process { public static final int DRM_UID = 1019; /** + * Defines the GID for the group that allows write access to the internal media storage. + * @hide + */ + public static final int SDCARD_RW_GID = 1015; + + /** * Defines the UID/GID for the group that controls VPN services. * @hide */ @@ -752,11 +757,12 @@ public class Process { /** * Set the priority of a thread, based on Linux priorities. - * - * @param tid The identifier of the thread/process to change. + * + * @param tid The identifier of the thread/process to change. It should be + * the native thread id but not the managed id of {@link java.lang.Thread}. * @param priority A Linux priority level, from -20 for highest scheduling * priority to 19 for lowest scheduling priority. - * + * * @throws IllegalArgumentException Throws IllegalArgumentException if * <var>tid</var> does not exist. * @throws SecurityException Throws SecurityException if your process does diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java index 190182072356..cdcb3ff94264 100644 --- a/core/java/android/os/RecoverySystem.java +++ b/core/java/android/os/RecoverySystem.java @@ -18,6 +18,8 @@ package android.os; import static java.nio.charset.StandardCharsets.UTF_8; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.annotation.SystemApi; @@ -29,6 +31,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.IntentSender; import android.content.pm.PackageManager; import android.provider.Settings; import android.telephony.SubscriptionInfo; @@ -624,22 +627,91 @@ public class RecoverySystem { } /** - * Schedule to install the given package on next boot. The caller needs to - * ensure that the package must have been processed (uncrypt'd) if needed. - * It sets up the command in BCB (bootloader control block), which will - * be read by the bootloader and the recovery image. + * Prepare to apply an unattended update by asking the user for their Lock Screen Knowledge + * Factor (LSKF). If supplied, the {@code intentSender} will be called when the system is setup + * and ready to apply the OTA. + * <p> + * When the system is already prepared for update and this API is called again with the same + * {@code updateToken}, it will not call the intent sender nor request the user enter their Lock + * Screen Knowledge Factor. + * <p> + * When this API is called again with a different {@code updateToken}, the prepared-for-update + * status is reset and process repeats as though it's the initial call to this method as + * described in the first paragraph. * - * @param Context the Context to use. - * @param packageFile the package to be installed. - * - * @throws IOException if there were any errors setting up the BCB. + * @param context the Context to use. + * @param updateToken token used to indicate which update was prepared + * @param intentSender the intent to call when the update is prepared; may be {@code null} + * @throws IOException if there were any errors setting up unattended update + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.RECOVERY) + public static void prepareForUnattendedUpdate(@NonNull Context context, + @NonNull String updateToken, @Nullable IntentSender intentSender) throws IOException { + if (updateToken == null) { + throw new NullPointerException("updateToken == null"); + } + RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE); + if (!rs.requestLskf(updateToken, intentSender)) { + throw new IOException("preparation for update failed"); + } + } + + /** + * Request that any previously requested Lock Screen Knowledge Factor (LSKF) is cleared and + * the preparation for unattended update is reset. * + * @param context the Context to use. + * @throws IOException if there were any errors setting up unattended update * @hide */ @SystemApi @RequiresPermission(android.Manifest.permission.RECOVERY) - public static void scheduleUpdateOnBoot(Context context, File packageFile) + public static boolean clearPrepareForUnattendedUpdate(@NonNull Context context) throws IOException { + RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE); + return rs.clearLskf(); + } + + /** + * Request that the device reboot and apply the update that has been prepared. The + * {@code updateToken} must match what was given for {@link #prepareForUnattendedUpdate} or + * this will return {@code false}. + * + * @param context the Context to use. + * @param updateToken the token used to call {@link #prepareForUnattendedUpdate} before + * @param reason the reboot reason to give to the {@link PowerManager} + * @throws IOException if there were any errors setting up unattended update + * @return false if the reboot couldn't proceed because the device wasn't ready for an + * unattended reboot or if the {@code updateToken} did not match the previously + * given token + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.RECOVERY) + public static boolean rebootAndApply(@NonNull Context context, @NonNull String updateToken, + @NonNull String reason) throws IOException { + if (updateToken == null) { + throw new NullPointerException("updateToken == null"); + } + RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE); + return rs.rebootWithLskf(updateToken, reason); + } + + /** + * Schedule to install the given package on next boot. The caller needs to ensure that the + * package must have been processed (uncrypt'd) if needed. It sets up the command in BCB + * (bootloader control block), which will be read by the bootloader and the recovery image. + * + * @param context the Context to use. + * @param packageFile the package to be installed. + * @throws IOException if there were any errors setting up the BCB. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.RECOVERY) + public static void scheduleUpdateOnBoot(Context context, File packageFile) throws IOException { String filename = packageFile.getCanonicalPath(); boolean securityUpdate = filename.endsWith("_s.zip"); @@ -1204,6 +1276,49 @@ public class RecoverySystem { } /** + * Begins the process of asking the user for the Lock Screen Knowledge Factor. + * + * @param updateToken token that will be used in calls to {@link #rebootAndApply} to ensure + * that the preparation was for the correct update + * @return true if the request was correct + * @throws IOException if the recovery system service could not be contacted + */ + private boolean requestLskf(String updateToken, IntentSender sender) throws IOException { + try { + return mService.requestLskf(updateToken, sender); + } catch (RemoteException e) { + throw new IOException("could request update"); + } + } + + /** + * Calls the recovery system service and clears the setup for the OTA. + * + * @return true if the setup for OTA was cleared + * @throws IOException if the recovery system service could not be contacted + */ + private boolean clearLskf() throws IOException { + try { + return mService.clearLskf(); + } catch (RemoteException e) { + throw new IOException("could not clear LSKF"); + } + } + + /** + * Calls the recovery system service to reboot and apply update. + * + * @param updateToken the update token for which the update was prepared + */ + private boolean rebootWithLskf(String updateToken, String reason) throws IOException { + try { + return mService.rebootWithLskf(updateToken, reason); + } catch (RemoteException e) { + throw new IOException("could not reboot for update"); + } + } + + /** * Internally, recovery treats each line of the command file as a separate * argv, so we only need to protect against newlines and nulls. */ diff --git a/core/java/android/os/TelephonyServiceManager.java b/core/java/android/os/TelephonyServiceManager.java index 1211dd60f591..064cf7d825b5 100644 --- a/core/java/android/os/TelephonyServiceManager.java +++ b/core/java/android/os/TelephonyServiceManager.java @@ -18,6 +18,7 @@ package android.os; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.content.Context; /** * Provides a way to register and obtain the system service binder objects managed by the telephony @@ -51,8 +52,8 @@ public class TelephonyServiceManager { /** * Register a system server binding object for a service. */ - public void register(@NonNull IBinder binder) { - ServiceManager.addService(mServiceName, binder); + public void register(@NonNull IBinder service) { + ServiceManager.addService(mServiceName, service); } /** @@ -114,25 +115,123 @@ public class TelephonyServiceManager { */ @NonNull public ServiceRegisterer getTelephonyServiceRegisterer() { - return new ServiceRegisterer("phone"); - } - - -// TODO: Add more services... -// -// /** -// * Returns {@link ServiceRegisterer} for the "subscription" service. -// */ -// @NonNull -// public ServiceRegisterer getSubscriptionServiceRegisterer() { -// return new ServiceRegisterer("isub"); -// } -// -// /** -// * Returns {@link ServiceRegisterer} for the "SMS" service. -// */ -// @NonNull -// public ServiceRegisterer getSmsServiceRegisterer() { -// return new ServiceRegisterer("isms"); -// } + return new ServiceRegisterer(Context.TELEPHONY_SERVICE); + } + + /** + * Returns {@link ServiceRegisterer} for the telephony registry service. + */ + @NonNull + public ServiceRegisterer getTelephonyRegistryServiceRegisterer() { + return new ServiceRegisterer("telephony.registry"); + } + + /** + * Returns {@link ServiceRegisterer} for the telephony IMS service. + */ + @NonNull + public ServiceRegisterer getTelephonyImsServiceRegisterer() { + return new ServiceRegisterer(Context.TELEPHONY_IMS_SERVICE); + } + + /** + * Returns {@link ServiceRegisterer} for the telephony RCS message service. + */ + @NonNull + public ServiceRegisterer getTelephonyRcsMessageServiceRegisterer() { + return new ServiceRegisterer(Context.TELEPHONY_RCS_MESSAGE_SERVICE); + } + + /** + * Returns {@link ServiceRegisterer} for the subscription service. + */ + @NonNull + public ServiceRegisterer getSubscriptionServiceRegisterer() { + return new ServiceRegisterer("isub"); + } + + /** + * Returns {@link ServiceRegisterer} for the network policy service. + */ + @NonNull + public ServiceRegisterer getNetworkPolicyServiceRegisterer() { + return new ServiceRegisterer(Context.NETWORK_POLICY_SERVICE); + } + + /** + * Returns {@link ServiceRegisterer} for the phone sub service. + */ + @NonNull + public ServiceRegisterer getPhoneSubServiceRegisterer() { + return new ServiceRegisterer("iphonesubinfo"); + } + + /** + * Returns {@link ServiceRegisterer} for the opportunistic network service. + */ + @NonNull + public ServiceRegisterer getOpportunisticNetworkServiceRegisterer() { + return new ServiceRegisterer("ions"); + } + + /** + * Returns {@link ServiceRegisterer} for the carrier config service. + */ + @NonNull + public ServiceRegisterer getCarrierConfigServiceRegisterer() { + return new ServiceRegisterer(Context.CARRIER_CONFIG_SERVICE); + } + + /** + * Returns {@link ServiceRegisterer} for the "SMS" service. + */ + @NonNull + public ServiceRegisterer getSmsServiceRegisterer() { + return new ServiceRegisterer("isms"); + } + + /** + * Returns {@link ServiceRegisterer} for the eUICC controller service. + */ + @NonNull + public ServiceRegisterer getEuiccControllerService() { + return new ServiceRegisterer("econtroller"); + } + + @NonNull + public ServiceRegisterer getEuiccCardControllerServiceRegisterer() { + return new ServiceRegisterer("euicc_card_controller"); + } + + /** + * Returns {@link ServiceRegisterer} for the package manager service. + */ + @NonNull + public ServiceRegisterer getPackageManagerServiceRegisterer() { + return new ServiceRegisterer("package"); + } + + /** + * Returns {@link ServiceRegisterer} for the permission manager service. + */ + @NonNull + public ServiceRegisterer getPermissionManagerServiceRegisterer() { + return new ServiceRegisterer("permissionmgr"); + } + + /** + * Returns {@link ServiceRegisterer} for the ICC phone book service. + */ + @NonNull + public ServiceRegisterer getIccPhoneBookServiceRegisterer() { + return new ServiceRegisterer("simphonebook"); + } + + /** + * Returns {@link ServiceRegisterer} for the window service. + */ + @NonNull + public ServiceRegisterer getWindowServiceRegisterer() { + return new ServiceRegisterer(Context.WINDOW_SERVICE); + } } diff --git a/core/java/android/util/TimestampedValue.java b/core/java/android/os/TimestampedValue.java index 45056730b08b..348574ed43c7 100644 --- a/core/java/android/util/TimestampedValue.java +++ b/core/java/android/os/TimestampedValue.java @@ -14,13 +14,10 @@ * limitations under the License. */ -package android.util; +package android.os; import android.annotation.NonNull; import android.annotation.Nullable; -import android.os.Parcel; -import android.os.Parcelable; -import android.os.SystemClock; import java.util.Objects; diff --git a/core/java/android/os/UpdateEngine.java b/core/java/android/os/UpdateEngine.java index a9ddffe7d55c..73e1adf134f2 100644 --- a/core/java/android/os/UpdateEngine.java +++ b/core/java/android/os/UpdateEngine.java @@ -16,8 +16,10 @@ package android.os; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SystemApi; +import android.content.res.AssetFileDescriptor; import android.os.IUpdateEngine; import android.os.IUpdateEngineCallback; import android.os.RemoteException; @@ -140,8 +142,43 @@ public class UpdateEngine { * {@code SWITCH_SLOT_ON_REBOOT=0}. See {@link #applyPayload}. */ public static final int UPDATED_BUT_NOT_ACTIVE = 52; + + /** + * Error code: there is not enough space on the device to apply the update. User should + * be prompted to free up space and re-try the update. + * + * <p>See {@link UpdateEngine#allocateSpace}. + */ + public static final int NOT_ENOUGH_SPACE = 60; + + /** + * Error code: the device is corrupted and no further updates may be applied. + * + * <p>See {@link UpdateEngine#cleanupAppliedPayload}. + */ + public static final int DEVICE_CORRUPTED = 61; } + /** @hide */ + @IntDef(value = { + ErrorCodeConstants.SUCCESS, + ErrorCodeConstants.ERROR, + ErrorCodeConstants.FILESYSTEM_COPIER_ERROR, + ErrorCodeConstants.POST_INSTALL_RUNNER_ERROR, + ErrorCodeConstants.PAYLOAD_MISMATCHED_TYPE_ERROR, + ErrorCodeConstants.INSTALL_DEVICE_OPEN_ERROR, + ErrorCodeConstants.KERNEL_DEVICE_OPEN_ERROR, + ErrorCodeConstants.DOWNLOAD_TRANSFER_ERROR, + ErrorCodeConstants.PAYLOAD_HASH_MISMATCH_ERROR, + ErrorCodeConstants.PAYLOAD_SIZE_MISMATCH_ERROR, + ErrorCodeConstants.DOWNLOAD_PAYLOAD_VERIFICATION_ERROR, + ErrorCodeConstants.PAYLOAD_TIMESTAMP_ERROR, + ErrorCodeConstants.UPDATED_BUT_NOT_ACTIVE, + ErrorCodeConstants.NOT_ENOUGH_SPACE, + ErrorCodeConstants.DEVICE_CORRUPTED, + }) + public @interface ErrorCode {} + /** * Status codes for update engine. Values must agree with the ones in * {@code system/update_engine/client_library/include/update_engine/update_status.h}. @@ -313,16 +350,17 @@ public class UpdateEngine { } /** - * Applies the payload passed as ParcelFileDescriptor {@code pfd} instead of - * using the {@code file://} scheme. + * Applies the payload passed as AssetFileDescriptor {@code assetFd} + * instead of using the {@code file://} scheme. * * <p>See {@link #applyPayload(String)} for {@code offset}, {@code size} and * {@code headerKeyValuePairs} parameters. */ - public void applyPayload(@NonNull ParcelFileDescriptor pfd, long offset, long size, + public void applyPayload(@NonNull AssetFileDescriptor assetFd, @NonNull String[] headerKeyValuePairs) { try { - mUpdateEngine.applyPayloadFd(pfd, offset, size, headerKeyValuePairs); + mUpdateEngine.applyPayloadFd(assetFd.getParcelFileDescriptor(), + assetFd.getStartOffset(), assetFd.getLength(), headerKeyValuePairs); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -419,4 +457,138 @@ public class UpdateEngine { throw e.rethrowFromSystemServer(); } } + + /** + * Return value of {@link #allocateSpace.} + */ + public static final class AllocateSpaceResult { + private @ErrorCode int mErrorCode = ErrorCodeConstants.SUCCESS; + private long mFreeSpaceRequired = 0; + private AllocateSpaceResult() {} + /** + * Error code. + * + * @return The following error codes: + * <ul> + * <li>{@link ErrorCodeConstants#SUCCESS} if space has been allocated + * successfully.</li> + * <li>{@link ErrorCodeConstants#NOT_ENOUGH_SPACE} if insufficient + * space.</li> + * <li>Other {@link ErrorCodeConstants} for other errors.</li> + * </ul> + */ + @ErrorCode + public int errorCode() { + return mErrorCode; + } + + /** + * Estimated total space that needs to be available on the userdata partition to apply the + * payload (in bytes). + * + * <p> + * Note that in practice, more space needs to be made available before applying the payload + * to keep the device working. + * + * @return The following values: + * <ul> + * <li>zero if {@link #errorCode} returns {@link ErrorCodeConstants#SUCCESS}</li> + * <li>non-zero if {@link #errorCode} returns {@link ErrorCodeConstants#NOT_ENOUGH_SPACE}. + * Value is the estimated total space required on userdata partition.</li> + * </ul> + * @throws IllegalStateException if {@link #errorCode} is not one of the above. + * + */ + public long freeSpaceRequired() { + if (mErrorCode == ErrorCodeConstants.SUCCESS) { + return 0; + } + if (mErrorCode == ErrorCodeConstants.NOT_ENOUGH_SPACE) { + return mFreeSpaceRequired; + } + throw new IllegalStateException(String.format( + "freeSpaceRequired() is not available when error code is %d", mErrorCode)); + } + } + + /** + * Initialize partitions for a payload associated with the given payload + * metadata {@code payloadMetadataFilename} by preallocating required space. + * + * <p>This function should be called after payload has been verified after + * {@link #verifyPayloadMetadata}. This function does not verify whether + * the given payload is applicable or not. + * + * <p>Implementation of {@code allocateSpace} uses + * {@code headerKeyValuePairs} to determine whether space has been allocated + * for a different or same payload previously. If space has been allocated + * for a different payload before, space will be reallocated for the given + * payload. If space has been allocated for the same payload, no actions to + * storage devices are taken. + * + * <p>This function is synchronous and may take a non-trivial amount of + * time. Callers should call this function in a background thread. + * + * @param payloadMetadataFilename See {@link #verifyPayloadMetadata}. + * @param headerKeyValuePairs See {@link #applyPayload}. + * @return See {@link AllocateSpaceResult}. + */ + @NonNull + public AllocateSpaceResult allocateSpace( + @NonNull String payloadMetadataFilename, + @NonNull String[] headerKeyValuePairs) { + AllocateSpaceResult result = new AllocateSpaceResult(); + try { + result.mFreeSpaceRequired = mUpdateEngine.allocateSpaceForPayload( + payloadMetadataFilename, + headerKeyValuePairs); + result.mErrorCode = result.mFreeSpaceRequired == 0 + ? ErrorCodeConstants.SUCCESS + : ErrorCodeConstants.NOT_ENOUGH_SPACE; + return result; + } catch (ServiceSpecificException e) { + result.mErrorCode = e.errorCode; + result.mFreeSpaceRequired = 0; + return result; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Cleanup files used by the previous update and free up space after the + * device has been booted successfully into the new build. + * + * <p>In particular, this function waits until delta files for snapshots for + * Virtual A/B update are merged to OS partitions, then delete these delta + * files. + * + * <p>This function is synchronous and may take a non-trivial amount of + * time. Callers should call this function in a background thread. + * + * <p>This function does not delete payload binaries downloaded for a + * non-streaming OTA update. + * + * @return One of the following: + * <ul> + * <li>{@link ErrorCodeConstants#SUCCESS} if execution is successful.</li> + * <li>{@link ErrorCodeConstants#ERROR} if a transient error has occurred. + * The device should be able to recover after a reboot. The function should + * be retried after the reboot.</li> + * <li>{@link ErrorCodeConstants#DEVICE_CORRUPTED} if a permanent error is + * encountered. Device is corrupted, and future updates must not be applied. + * The device cannot recover without flashing and factory resets. + * </ul> + * + * @throws ServiceSpecificException if other transient errors has occurred. + * A reboot may or may not help resolving the issue. + */ + @ErrorCode + public int cleanupAppliedPayload() { + try { + return mUpdateEngine.cleanupSuccessfulUpdate(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/os/UpdateEngineCallback.java b/core/java/android/os/UpdateEngineCallback.java index f07294e222e2..7fe7024f25b3 100644 --- a/core/java/android/os/UpdateEngineCallback.java +++ b/core/java/android/os/UpdateEngineCallback.java @@ -44,5 +44,6 @@ public abstract class UpdateEngineCallback { * unsuccessfully. The value of {@code errorCode} will be one of the * values from {@link UpdateEngine.ErrorCodeConstants}. */ - public abstract void onPayloadApplicationComplete(int errorCode); + public abstract void onPayloadApplicationComplete( + @UpdateEngine.ErrorCode int errorCode); } diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index fa9569bbe0ef..d8fadfb41189 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -1626,39 +1626,35 @@ public class UserManager { } /** - * Returns the calling user's user type. + * Returns whether the current user is of the given user type, such as + * {@link UserManager#USER_TYPE_FULL_GUEST}. * - * // TODO(b/142482943): Decide on the appropriate permission requirements. - * - * @return the name of the user type, such as {@link UserManager#USER_TYPE_PROFILE_MANAGED}. + * @return true if the user is of the given user type. * @hide */ - public @NonNull String getUserType() { + @RequiresPermission(android.Manifest.permission.MANAGE_USERS) + public boolean isUserOfType(@NonNull String userType) { try { - return mService.getUserTypeForUser(UserHandle.myUserId()); + return mService.isUserOfType(UserHandle.myUserId(), userType); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } } /** - * Returns the given user's user type. - * - * // TODO(b/142482943): Decide on the appropriate permission requirements. - * Requires {@link android.Manifest.permission#MANAGE_USERS} or - * {@link android.Manifest.permission#INTERACT_ACROSS_USERS} permission, otherwise the caller - * must be in the same profile group of specified user. + * Returns whether the given user is of the given user type, such as + * {@link UserManager#USER_TYPE_FULL_GUEST}. * * @param userHandle the user handle of the user whose type is being requested. - * @return the name of the user's user type, e.g. {@link UserManager#USER_TYPE_PROFILE_MANAGED}, - * or {@code null} if there is no such user. + * @param userType the name of the user's user type, e.g. + * {@link UserManager#USER_TYPE_PROFILE_MANAGED}. + * @return true if the userHandle user is of type userType * @hide */ - @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS, - android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true) - public @Nullable String getUserTypeForUser(@NonNull UserHandle userHandle) { + @RequiresPermission(android.Manifest.permission.MANAGE_USERS) + public boolean isUserOfType(@NonNull UserHandle userHandle, @NonNull String userType) { try { - return mService.getUserTypeForUser(userHandle.getIdentifier()); + return mService.isUserOfType(userHandle.getIdentifier(), userType); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -1854,10 +1850,7 @@ public class UserManager { * Checks if the calling app is running in a managed profile. * * @return whether the caller is in a managed profile. - * @hide */ - @SystemApi - @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isManagedProfile() { // No need for synchronization. Once it becomes non-null, it'll be non-null forever. // Worst case we might end up calling the AIDL method multiple times but that's fine. diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java index 62b8953b158a..3846f894c4c3 100644 --- a/core/java/android/os/ZygoteProcess.java +++ b/core/java/android/os/ZygoteProcess.java @@ -594,6 +594,8 @@ public class ZygoteProcess { argsForZygote.add("--mount-external-legacy"); } else if (mountExternal == Zygote.MOUNT_EXTERNAL_PASS_THROUGH) { argsForZygote.add("--mount-external-pass-through"); + } else if (mountExternal == Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE) { + argsForZygote.add("--mount-external-android-writable"); } argsForZygote.add("--target-sdk-version=" + targetSdkVersion); diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 8959fcf7ac18..f0a11748fbd6 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -31,6 +31,7 @@ import static android.content.ContentResolver.DEPRECATE_DATA_PREFIX; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import android.annotation.BytesLong; +import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -72,6 +73,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceManager.ServiceNotFoundException; import android.os.SystemProperties; +import android.os.UserHandle; import android.provider.MediaStore; import android.provider.Settings; import android.sysprop.VoldProperties; @@ -93,7 +95,6 @@ import com.android.internal.os.AppFuseMount; import com.android.internal.os.FuseAppLoop; import com.android.internal.os.FuseUnavailableMountException; import com.android.internal.os.RoSystemProperties; -import com.android.internal.os.SomeArgs; import com.android.internal.util.Preconditions; import dalvik.system.BlockGuard; @@ -114,6 +115,7 @@ import java.util.List; import java.util.Objects; import java.util.UUID; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -305,109 +307,85 @@ public class StorageManager { private final Looper mLooper; private final AtomicInteger mNextNonce = new AtomicInteger(0); + @GuardedBy("mDelegates") private final ArrayList<StorageEventListenerDelegate> mDelegates = new ArrayList<>(); - private static class StorageEventListenerDelegate extends IStorageEventListener.Stub implements - Handler.Callback { - private static final int MSG_STORAGE_STATE_CHANGED = 1; - private static final int MSG_VOLUME_STATE_CHANGED = 2; - private static final int MSG_VOLUME_RECORD_CHANGED = 3; - private static final int MSG_VOLUME_FORGOTTEN = 4; - private static final int MSG_DISK_SCANNED = 5; - private static final int MSG_DISK_DESTROYED = 6; + private class StorageEventListenerDelegate extends IStorageEventListener.Stub { + final Executor mExecutor; + final StorageEventListener mListener; + final StorageVolumeCallback mCallback; - final StorageEventListener mCallback; - final Handler mHandler; - - public StorageEventListenerDelegate(StorageEventListener callback, Looper looper) { + public StorageEventListenerDelegate(@NonNull Executor executor, + @NonNull StorageEventListener listener, @NonNull StorageVolumeCallback callback) { + mExecutor = executor; + mListener = listener; mCallback = callback; - mHandler = new Handler(looper, this); - } - - @Override - public boolean handleMessage(Message msg) { - final SomeArgs args = (SomeArgs) msg.obj; - switch (msg.what) { - case MSG_STORAGE_STATE_CHANGED: - mCallback.onStorageStateChanged((String) args.arg1, (String) args.arg2, - (String) args.arg3); - args.recycle(); - return true; - case MSG_VOLUME_STATE_CHANGED: - mCallback.onVolumeStateChanged((VolumeInfo) args.arg1, args.argi2, args.argi3); - args.recycle(); - return true; - case MSG_VOLUME_RECORD_CHANGED: - mCallback.onVolumeRecordChanged((VolumeRecord) args.arg1); - args.recycle(); - return true; - case MSG_VOLUME_FORGOTTEN: - mCallback.onVolumeForgotten((String) args.arg1); - args.recycle(); - return true; - case MSG_DISK_SCANNED: - mCallback.onDiskScanned((DiskInfo) args.arg1, args.argi2); - args.recycle(); - return true; - case MSG_DISK_DESTROYED: - mCallback.onDiskDestroyed((DiskInfo) args.arg1); - args.recycle(); - return true; - } - args.recycle(); - return false; } @Override public void onUsbMassStorageConnectionChanged(boolean connected) throws RemoteException { - // Ignored + mExecutor.execute(() -> { + mListener.onUsbMassStorageConnectionChanged(connected); + }); } @Override public void onStorageStateChanged(String path, String oldState, String newState) { - final SomeArgs args = SomeArgs.obtain(); - args.arg1 = path; - args.arg2 = oldState; - args.arg3 = newState; - mHandler.obtainMessage(MSG_STORAGE_STATE_CHANGED, args).sendToTarget(); + mExecutor.execute(() -> { + mListener.onStorageStateChanged(path, oldState, newState); + + if (path != null) { + for (StorageVolume sv : getStorageVolumes()) { + if (Objects.equals(path, sv.getPath())) { + mCallback.onStateChanged(sv); + } + } + } + }); } @Override public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) { - final SomeArgs args = SomeArgs.obtain(); - args.arg1 = vol; - args.argi2 = oldState; - args.argi3 = newState; - mHandler.obtainMessage(MSG_VOLUME_STATE_CHANGED, args).sendToTarget(); + mExecutor.execute(() -> { + mListener.onVolumeStateChanged(vol, oldState, newState); + + final File path = vol.getPathForUser(UserHandle.myUserId()); + if (path != null) { + for (StorageVolume sv : getStorageVolumes()) { + if (Objects.equals(path.getAbsolutePath(), sv.getPath())) { + mCallback.onStateChanged(sv); + } + } + } + }); } @Override public void onVolumeRecordChanged(VolumeRecord rec) { - final SomeArgs args = SomeArgs.obtain(); - args.arg1 = rec; - mHandler.obtainMessage(MSG_VOLUME_RECORD_CHANGED, args).sendToTarget(); + mExecutor.execute(() -> { + mListener.onVolumeRecordChanged(rec); + }); } @Override public void onVolumeForgotten(String fsUuid) { - final SomeArgs args = SomeArgs.obtain(); - args.arg1 = fsUuid; - mHandler.obtainMessage(MSG_VOLUME_FORGOTTEN, args).sendToTarget(); + mExecutor.execute(() -> { + mListener.onVolumeForgotten(fsUuid); + }); } @Override public void onDiskScanned(DiskInfo disk, int volumeCount) { - final SomeArgs args = SomeArgs.obtain(); - args.arg1 = disk; - args.argi2 = volumeCount; - mHandler.obtainMessage(MSG_DISK_SCANNED, args).sendToTarget(); + mExecutor.execute(() -> { + mListener.onDiskScanned(disk, volumeCount); + }); } @Override public void onDiskDestroyed(DiskInfo disk) throws RemoteException { - final SomeArgs args = SomeArgs.obtain(); - args.arg1 = disk; - mHandler.obtainMessage(MSG_DISK_DESTROYED, args).sendToTarget(); + mExecutor.execute(() -> { + mListener.onDiskDestroyed(disk); + }); } } @@ -525,8 +503,8 @@ public class StorageManager { @UnsupportedAppUsage public void registerListener(StorageEventListener listener) { synchronized (mDelegates) { - final StorageEventListenerDelegate delegate = new StorageEventListenerDelegate(listener, - mLooper); + final StorageEventListenerDelegate delegate = new StorageEventListenerDelegate( + mContext.getMainExecutor(), listener, new StorageVolumeCallback()); try { mStorageManager.registerListener(delegate); } catch (RemoteException e) { @@ -548,7 +526,76 @@ public class StorageManager { synchronized (mDelegates) { for (Iterator<StorageEventListenerDelegate> i = mDelegates.iterator(); i.hasNext();) { final StorageEventListenerDelegate delegate = i.next(); - if (delegate.mCallback == listener) { + if (delegate.mListener == listener) { + try { + mStorageManager.unregisterListener(delegate); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + i.remove(); + } + } + } + } + + /** + * Callback that delivers {@link StorageVolume} related events. + * <p> + * For example, this can be used to detect when a volume changes to the + * {@link Environment#MEDIA_MOUNTED} or {@link Environment#MEDIA_UNMOUNTED} + * states. + * + * @see StorageManager#registerStorageVolumeCallback + * @see StorageManager#unregisterStorageVolumeCallback + */ + public static class StorageVolumeCallback { + /** + * Called when {@link StorageVolume#getState()} changes, such as + * changing to the {@link Environment#MEDIA_MOUNTED} or + * {@link Environment#MEDIA_UNMOUNTED} states. + * <p> + * The given argument is a snapshot in time and can be used to process + * events in the order they occurred, or you can call + * {@link StorageManager#getStorageVolumes()} to observe the latest + * value. + */ + public void onStateChanged(@NonNull StorageVolume volume) { } + } + + /** + * Registers the given callback to listen for {@link StorageVolume} changes. + * <p> + * For example, this can be used to detect when a volume changes to the + * {@link Environment#MEDIA_MOUNTED} or {@link Environment#MEDIA_UNMOUNTED} + * states. + * + * @see StorageManager#unregisterStorageVolumeCallback + */ + public void registerStorageVolumeCallback(@CallbackExecutor @NonNull Executor executor, + @NonNull StorageVolumeCallback callback) { + synchronized (mDelegates) { + final StorageEventListenerDelegate delegate = new StorageEventListenerDelegate( + executor, new StorageEventListener(), callback); + try { + mStorageManager.registerListener(delegate); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + mDelegates.add(delegate); + } + } + + /** + * Unregisters the given callback from listening for {@link StorageVolume} + * changes. + * + * @see StorageManager#registerStorageVolumeCallback + */ + public void unregisterStorageVolumeCallback(@NonNull StorageVolumeCallback callback) { + synchronized (mDelegates) { + for (Iterator<StorageEventListenerDelegate> i = mDelegates.iterator(); i.hasNext();) { + final StorageEventListenerDelegate delegate = i.next(); + if (delegate.mCallback == callback) { try { mStorageManager.unregisterListener(delegate); } catch (RemoteException e) { @@ -829,7 +876,14 @@ public class StorageManager { */ public @NonNull UUID getUuidForPath(@NonNull File path) throws IOException { Preconditions.checkNotNull(path); - final String pathString = path.getCanonicalPath(); + String pathString = path.getCanonicalPath(); + if (path.getPath().startsWith("/sdcard")) { + // On FUSE enabled devices, realpath(2) /sdcard is /mnt/user/<userid>/emulated/<userid> + // as opposed to /storage/emulated/<userid>. + // And vol.path below expects to match with a path starting with /storage + pathString = pathString.replaceFirst("^/mnt/user/[0-9]+/", "/storage/"); + } + if (FileUtils.contains(Environment.getDataDirectory().getAbsolutePath(), pathString)) { return UUID_DEFAULT; } diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java index 2ab226f81bb4..e251f8072b1f 100644 --- a/core/java/android/os/storage/StorageVolume.java +++ b/core/java/android/os/storage/StorageVolume.java @@ -18,6 +18,7 @@ package android.os.storage; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; @@ -162,9 +163,13 @@ public final class StorageVolume implements Parcelable { mState = in.readString(); } - /** {@hide} */ - @UnsupportedAppUsage - public String getId() { + /** + * Return an opaque ID that can be used to identify this volume. + * + * @hide + */ + @SystemApi + public @NonNull String getId() { return mId; } diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java index 0a3c333ae7b1..ef8a2860cf4f 100644 --- a/core/java/android/provider/DocumentsContract.java +++ b/core/java/android/provider/DocumentsContract.java @@ -553,7 +553,9 @@ public final class DocumentsContract { /** * Flag indicating that a document is a directory that wants to block itself * from being selected when the user launches an {@link Intent#ACTION_OPEN_DOCUMENT_TREE} - * intent. Only valid when {@link #COLUMN_MIME_TYPE} is {@link #MIME_TYPE_DIR}. + * intent. Individual files can still be selected when launched via other intents + * like {@link Intent#ACTION_OPEN_DOCUMENT} and {@link Intent#ACTION_GET_CONTENT}. + * Only valid when {@link #COLUMN_MIME_TYPE} is {@link #MIME_TYPE_DIR}. * <p> * Note that this flag <em>only</em> applies to the single directory to which it is * applied. It does <em>not</em> block the user from selecting either a parent or diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index bbaf94ad6ea0..a31c3d1749c6 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -68,7 +68,6 @@ import android.os.ResultReceiver; import android.os.ServiceManager; import android.os.UserHandle; import android.speech.tts.TextToSpeech; -import android.telephony.SubscriptionManager; import android.text.TextUtils; import android.util.AndroidException; import android.util.ArrayMap; @@ -219,7 +218,9 @@ public final class Settings { * @hide */ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) - public static final String ACTION_TETHER_PROVISIONING = + @SystemApi + @TestApi + public static final String ACTION_TETHER_PROVISIONING_UI = "android.settings.TETHER_PROVISIONING_UI"; /** @@ -388,6 +389,21 @@ public final class Settings { "android.settings.MANAGE_UNKNOWN_APP_SOURCES"; /** + * Activity Action: Show settings to allow configuration of cross-profile access for apps + * + * Input: Optionally, the Intent's data URI can specify the application package name to + * directly invoke the management GUI specific to the package name. For example + * "package:com.my.app". + * <p> + * Output: Nothing. + * + * @hide + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_MANAGE_CROSS_PROFILE_ACCESS = + "android.settings.MANAGE_CROSS_PROFILE_ACCESS"; + + /** * Activity Action: Show the "Open by Default" page in a particular application's details page. * <p> * In some cases, a matching Activity may not exist, so ensure you safeguard against this. @@ -9698,6 +9714,8 @@ public final class Settings { * is interpreted as |false|. * @hide */ + @SystemApi + @TestApi public static final String TETHER_OFFLOAD_DISABLED = "tether_offload_disabled"; /** @@ -12459,16 +12477,17 @@ public final class Settings { /** * Whether the Volte is enabled. If this setting is not set then we use the Carrier Config - * value {@link CarrierConfigManager#KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL}. + * value + * {@link android.telephony.CarrierConfigManager#KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL}. * <p> * Type: int (0 for false, 1 for true) * @hide - * @deprecated Use {@link android.telephony.SubscriptionManager#ENHANCED_4G_MODE_ENABLED} + * @deprecated Use {@link android.provider.Telephony.SimInfo#ENHANCED_4G_MODE_ENABLED} * instead. */ @Deprecated public static final String ENHANCED_4G_MODE_ENABLED = - SubscriptionManager.ENHANCED_4G_MODE_ENABLED; + Telephony.SimInfo.ENHANCED_4G_MODE_ENABLED; /** * Whether VT (Video Telephony over IMS) is enabled @@ -12476,10 +12495,10 @@ public final class Settings { * Type: int (0 for false, 1 for true) * * @hide - * @deprecated Use {@link android.telephony.SubscriptionManager#VT_IMS_ENABLED} instead. + * @deprecated Use {@link android.provider.Telephony.SimInfo#VT_IMS_ENABLED} instead. */ @Deprecated - public static final String VT_IMS_ENABLED = SubscriptionManager.VT_IMS_ENABLED; + public static final String VT_IMS_ENABLED = Telephony.SimInfo.VT_IMS_ENABLED; /** * Whether WFC is enabled @@ -12487,10 +12506,10 @@ public final class Settings { * Type: int (0 for false, 1 for true) * * @hide - * @deprecated Use {@link android.telephony.SubscriptionManager#WFC_IMS_ENABLED} instead. + * @deprecated Use {@link android.provider.Telephony.SimInfo#WFC_IMS_ENABLED} instead. */ @Deprecated - public static final String WFC_IMS_ENABLED = SubscriptionManager.WFC_IMS_ENABLED; + public static final String WFC_IMS_ENABLED = Telephony.SimInfo.WFC_IMS_ENABLED; /** * WFC mode on home/non-roaming network. @@ -12498,10 +12517,10 @@ public final class Settings { * Type: int - 2=Wi-Fi preferred, 1=Cellular preferred, 0=Wi-Fi only * * @hide - * @deprecated Use {@link android.telephony.SubscriptionManager#WFC_IMS_MODE} instead. + * @deprecated Use {@link android.provider.Telephony.SimInfo#WFC_IMS_MODE} instead. */ @Deprecated - public static final String WFC_IMS_MODE = SubscriptionManager.WFC_IMS_MODE; + public static final String WFC_IMS_MODE = Telephony.SimInfo.WFC_IMS_MODE; /** * WFC mode on roaming network. @@ -12509,11 +12528,11 @@ public final class Settings { * Type: int - see {@link #WFC_IMS_MODE} for values * * @hide - * @deprecated Use {@link android.telephony.SubscriptionManager#WFC_IMS_ROAMING_MODE} + * @deprecated Use {@link android.provider.Telephony.SimInfo#WFC_IMS_ROAMING_MODE} * instead. */ @Deprecated - public static final String WFC_IMS_ROAMING_MODE = SubscriptionManager.WFC_IMS_ROAMING_MODE; + public static final String WFC_IMS_ROAMING_MODE = Telephony.SimInfo.WFC_IMS_ROAMING_MODE; /** * Whether WFC roaming is enabled @@ -12521,12 +12540,12 @@ public final class Settings { * Type: int (0 for false, 1 for true) * * @hide - * @deprecated Use {@link android.telephony.SubscriptionManager#WFC_IMS_ROAMING_ENABLED} + * @deprecated Use {@link android.provider.Telephony.SimInfo#WFC_IMS_ROAMING_ENABLED} * instead */ @Deprecated public static final String WFC_IMS_ROAMING_ENABLED = - SubscriptionManager.WFC_IMS_ROAMING_ENABLED; + Telephony.SimInfo.WFC_IMS_ROAMING_ENABLED; /** * Whether user can enable/disable LTE as a preferred network. A carrier might control @@ -14400,6 +14419,42 @@ public final class Settings { }; /** + * Activity Action: Show screen for controlling which apps have access to manage external + * storage. + * <p> + * In some cases, a matching Activity may not exist, so ensure you safeguard against this. + * <p> + * If you want to control a specific app's access to manage external storage, use + * {@link #ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION} instead. + * <p> + * Output: Nothing. + * @see #ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION = + "android.settings.MANAGE_ALL_FILES_ACCESS_PERMISSION"; + + /** + * Activity Action: Show screen for controlling if the app specified in the data URI of the + * intent can manage external storage. + * <p> + * Launching the corresponding activity requires the permission + * {@link Manifest.permission#MANAGE_EXTERNAL_STORAGE}. + * <p> + * In some cases, a matching Activity may not exist, so ensure you safeguard against this. + * <p> + * Input: The Intent's data URI MUST specify the application package name whose ability of + * managing external storage you want to control. + * For example "package:com.my.app". + * <p> + * Output: Nothing. + * @see #ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION = + "android.settings.MANAGE_APP_ALL_FILES_ACCESS_PERMISSION"; + + /** * Performs a strict and comprehensive check of whether a calling package is allowed to * write/modify system settings, as the condition differs for pre-M, M+, and * privileged/preinstalled apps. If the provided uid does not match the @@ -14425,8 +14480,9 @@ public final class Settings { * current time. * @hide */ - public static boolean checkAndNoteWriteSettingsOperation(Context context, int uid, - String callingPackage, boolean throwException) { + @SystemApi + public static boolean checkAndNoteWriteSettingsOperation(@NonNull Context context, int uid, + @NonNull String callingPackage, boolean throwException) { return isCallingPackageAllowedToPerformAppOpsProtectedOperation(context, uid, callingPackage, throwException, AppOpsManager.OP_WRITE_SETTINGS, PM_WRITE_SETTINGS, true); diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java index 565a522ffc16..2e7ac3f505fa 100644 --- a/core/java/android/provider/Telephony.java +++ b/core/java/android/provider/Telephony.java @@ -38,11 +38,13 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Parcel; +import android.telephony.CarrierConfigManager; import android.telephony.Rlog; import android.telephony.ServiceState; import android.telephony.SmsMessage; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; +import android.telephony.UiccAccessRule; import android.text.TextUtils; import android.util.Patterns; @@ -4216,7 +4218,7 @@ public final class Telephony { * The subscription which received this cell broadcast message. * <P>Type: INTEGER</P> */ - public static final String SUB_ID = "sub_id"; + public static final String SUBSCRIPTION_ID = "sub_id"; /** * The slot which received this cell broadcast message. @@ -4474,7 +4476,7 @@ public final class Telephony { public static final String[] QUERY_COLUMNS_FWK = { _ID, SLOT_INDEX, - SUB_ID, + SUBSCRIPTION_ID, GEOGRAPHICAL_SCOPE, PLMN, LAC, @@ -4980,5 +4982,402 @@ public final class Telephony { */ @NonNull public static final Uri CONTENT_URI = Uri.parse("content://telephony/siminfo"); + + /** + * TelephonyProvider unique key column name is the subscription id. + * <P>Type: TEXT (String)</P> + */ + public static final String UNIQUE_KEY_SUBSCRIPTION_ID = "_id"; + + /** + * TelephonyProvider column name for a unique identifier for the subscription within the + * specific subscription type. For example, it contains SIM ICC Identifier subscriptions + * on Local SIMs. and Mac-address for Remote-SIM Subscriptions for Bluetooth devices. + * <P>Type: TEXT (String)</P> + */ + public static final String ICC_ID = "icc_id"; + + /** + * TelephonyProvider column name for user SIM_SlOT_INDEX + * <P>Type: INTEGER (int)</P> + */ + public static final String SIM_SLOT_INDEX = "sim_id"; + + /** + * SIM is not inserted + */ + public static final int SIM_NOT_INSERTED = -1; + + /** + * TelephonyProvider column name Subscription-type. + * <P>Type: INTEGER (int)</P> {@link #SUBSCRIPTION_TYPE_LOCAL_SIM} for Local-SIM + * Subscriptions, {@link #SUBSCRIPTION_TYPE_REMOTE_SIM} for Remote-SIM Subscriptions. + * Default value is 0. + */ + public static final String SUBSCRIPTION_TYPE = "subscription_type"; + + /** + * This constant is to designate a subscription as a Local-SIM Subscription. + * <p> A Local-SIM can be a physical SIM inserted into a sim-slot in the device, or eSIM on + * the device. + * </p> + */ + public static final int SUBSCRIPTION_TYPE_LOCAL_SIM = 0; + + /** + * This constant is to designate a subscription as a Remote-SIM Subscription. + * <p> + * A Remote-SIM subscription is for a SIM on a phone connected to this device via some + * connectivity mechanism, for example bluetooth. Similar to Local SIM, this subscription + * can be used for SMS, Voice and data by proxying data through the connected device. + * Certain data of the SIM, such as IMEI, are not accessible for Remote SIMs. + * </p> + * + * <p> + * A Remote-SIM is available only as long the phone stays connected to this device. + * When the phone disconnects, Remote-SIM subscription is removed from this device and is + * no longer known. All data associated with the subscription, such as stored SMS, call + * logs, contacts etc, are removed from this device. + * </p> + * + * <p> + * If the phone re-connects to this device, a new Remote-SIM subscription is created for + * the phone. The Subscription Id associated with the new subscription is different from + * the Subscription Id of the previous Remote-SIM subscription created (and removed) for the + * phone; i.e., new Remote-SIM subscription treats the reconnected phone as a Remote-SIM + * that was never seen before. + * </p> + */ + public static final int SUBSCRIPTION_TYPE_REMOTE_SIM = 1; + + /** + * TelephonyProvider column name data_enabled_override_rules. + * It's a list of rules for overriding data enabled settings. The syntax is + * For example, "mms=nonDefault" indicates enabling data for mms in non-default + * subscription. + * "default=nonDefault&inVoiceCall" indicates enabling data for internet in non-default + * subscription and while is in voice call. + * + * Default value is empty string. + */ + public static final String DATA_ENABLED_OVERRIDE_RULES = "data_enabled_override_rules"; + + /** + * TelephonyProvider column name for user displayed name. + * <P>Type: TEXT (String)</P> + */ + public static final String DISPLAY_NAME = "display_name"; + + /** + * TelephonyProvider column name for the service provider name for the SIM. + * <P>Type: TEXT (String)</P> + */ + public static final String CARRIER_NAME = "carrier_name"; + + /** + * TelephonyProvider column name for source of the user displayed name. + * <P>Type: INT (int)</P> with one of the NAME_SOURCE_XXXX values below + */ + public static final String NAME_SOURCE = "name_source"; + + /** The name_source is the default, which is from the carrier id. */ + public static final int NAME_SOURCE_DEFAULT = 0; + + /** + * The name_source is from SIM EF_SPN. + */ + public static final int NAME_SOURCE_SIM_SPN = 1; + + /** + * The name_source is from user input + */ + public static final int NAME_SOURCE_USER_INPUT = 2; + + /** + * The name_source is carrier (carrier app, carrier config, etc.) + */ + public static final int NAME_SOURCE_CARRIER = 3; + + /** + * The name_source is from SIM EF_PNN. + */ + public static final int NAME_SOURCE_SIM_PNN = 4; + + /** + * TelephonyProvider column name for the color of a SIM. + * <P>Type: INTEGER (int)</P> + */ + public static final String COLOR = "color"; + + /** TelephonyProvider column name for the default color of a SIM {@hide} */ + public static final int COLOR_DEFAULT = 0; + + /** + * TelephonyProvider column name for the phone number of a SIM. + * <P>Type: TEXT (String)</P> + */ + public static final String NUMBER = "number"; + + /** + * TelephonyProvider column name for the number display format of a SIM. + * <P>Type: INTEGER (int)</P> + * @hide + */ + public static final String DISPLAY_NUMBER_FORMAT = "display_number_format"; + + /** + * TelephonyProvider column name for the default display format of a SIM + * @hide + */ + public static final int DISPLAY_NUMBER_DEFAULT = 1; + + /** + * TelephonyProvider column name for whether data roaming is enabled. + * <P>Type: INTEGER (int)</P> + */ + public static final String DATA_ROAMING = "data_roaming"; + + /** Indicates that data roaming is enabled for a subscription */ + public static final int DATA_ROAMING_ENABLE = 1; + + /** Indicates that data roaming is disabled for a subscription */ + public static final int DATA_ROAMING_DISABLE = 0; + + /** TelephonyProvider column name for default data roaming setting: disable */ + public static final int DATA_ROAMING_DEFAULT = DATA_ROAMING_DISABLE; + + /** + * TelephonyProvider column name for subscription carrier id. + * @see TelephonyManager#getSimCarrierId() + * <p>Type: INTEGER (int) </p> + */ + public static final String CARRIER_ID = "carrier_id"; + + /** + * A comma-separated list of EHPLMNs associated with the subscription + * <P>Type: TEXT (String)</P> + */ + public static final String EHPLMNS = "ehplmns"; + + /** + * A comma-separated list of HPLMNs associated with the subscription + * <P>Type: TEXT (String)</P> + */ + public static final String HPLMNS = "hplmns"; + + /** + * TelephonyProvider column name for the MCC associated with a SIM, stored as a string. + * <P>Type: TEXT (String)</P> + */ + public static final String MCC_STRING = "mcc_string"; + + /** + * TelephonyProvider column name for the MNC associated with a SIM, stored as a string. + * <P>Type: TEXT (String)</P> + */ + public static final String MNC_STRING = "mnc_string"; + + /** + * TelephonyProvider column name for the MCC associated with a SIM. + * <P>Type: INTEGER (int)</P> + */ + public static final String MCC = "mcc"; + + /** + * TelephonyProvider column name for the MNC associated with a SIM. + * <P>Type: INTEGER (int)</P> + */ + public static final String MNC = "mnc"; + + /** + * TelephonyProvider column name for the iso country code associated with a SIM. + * <P>Type: TEXT (String)</P> + */ + public static final String ISO_COUNTRY_CODE = "iso_country_code"; + + /** + * TelephonyProvider column name for the sim provisioning status associated with a SIM. + * <P>Type: INTEGER (int)</P> + * @hide + */ + public static final String SIM_PROVISIONING_STATUS = "sim_provisioning_status"; + + /** The sim is provisioned {@hide} */ + public static final int SIM_PROVISIONED = 0; + + /** + * TelephonyProvider column name for whether a subscription is embedded (that is, present on + * an eSIM). + * <p>Type: INTEGER (int), 1 for embedded or 0 for non-embedded. + */ + public static final String IS_EMBEDDED = "is_embedded"; + + /** + * TelephonyProvider column name for SIM card identifier. For UICC card it is the ICCID of + * the current enabled profile on the card, while for eUICC card it is the EID of the card. + * <P>Type: TEXT (String)</P> + */ + public static final String CARD_ID = "card_id"; + + /** + * TelephonyProvider column name for the encoded {@link UiccAccessRule}s from + * {@link UiccAccessRule#encodeRules}. Only present if {@link #IS_EMBEDDED} is 1. + * <p>TYPE: BLOB + */ + public static final String ACCESS_RULES = "access_rules"; + + /** + * TelephonyProvider column name for the encoded {@link UiccAccessRule}s from + * {@link UiccAccessRule#encodeRules} but for the rules that come from CarrierConfigs. + * Only present if there are access rules in CarrierConfigs + * <p>TYPE: BLOB + */ + public static final String ACCESS_RULES_FROM_CARRIER_CONFIGS = + "access_rules_from_carrier_configs"; + + /** + * TelephonyProvider column name identifying whether an embedded subscription is on a + * removable card. Such subscriptions are marked inaccessible as soon as the current card + * is removed. Otherwise, they will remain accessible unless explicitly deleted. Only + * present if {@link #IS_EMBEDDED} is 1. + * <p>TYPE: INTEGER (int), 1 for removable or 0 for non-removable. + */ + public static final String IS_REMOVABLE = "is_removable"; + + /** TelephonyProvider column name for extreme threat in CB settings */ + public static final String CB_EXTREME_THREAT_ALERT = "enable_cmas_extreme_threat_alerts"; + + /** TelephonyProvider column name for severe threat in CB settings */ + public static final String CB_SEVERE_THREAT_ALERT = "enable_cmas_severe_threat_alerts"; + + /** TelephonyProvider column name for amber alert in CB settings */ + public static final String CB_AMBER_ALERT = "enable_cmas_amber_alerts"; + + /** TelephonyProvider column name for emergency alert in CB settings */ + public static final String CB_EMERGENCY_ALERT = "enable_emergency_alerts"; + + /** TelephonyProvider column name for alert sound duration in CB settings */ + public static final String CB_ALERT_SOUND_DURATION = "alert_sound_duration"; + + /** TelephonyProvider column name for alert reminder interval in CB settings */ + public static final String CB_ALERT_REMINDER_INTERVAL = "alert_reminder_interval"; + + /** TelephonyProvider column name for enabling vibrate in CB settings */ + public static final String CB_ALERT_VIBRATE = "enable_alert_vibrate"; + + /** TelephonyProvider column name for enabling alert speech in CB settings */ + public static final String CB_ALERT_SPEECH = "enable_alert_speech"; + + /** TelephonyProvider column name for ETWS test alert in CB settings */ + public static final String CB_ETWS_TEST_ALERT = "enable_etws_test_alerts"; + + /** TelephonyProvider column name for enable channel50 alert in CB settings */ + public static final String CB_CHANNEL_50_ALERT = "enable_channel_50_alerts"; + + /** TelephonyProvider column name for CMAS test alert in CB settings */ + public static final String CB_CMAS_TEST_ALERT = "enable_cmas_test_alerts"; + + /** TelephonyProvider column name for Opt out dialog in CB settings */ + public static final String CB_OPT_OUT_DIALOG = "show_cmas_opt_out_dialog"; + + /** + * TelephonyProvider column name for enable Volte. + * + * If this setting is not initialized (set to -1) then we use the Carrier Config value + * {@link CarrierConfigManager#KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL}. + */ + public static final String ENHANCED_4G_MODE_ENABLED = "volte_vt_enabled"; + + /** TelephonyProvider column name for enable VT (Video Telephony over IMS) */ + public static final String VT_IMS_ENABLED = "vt_ims_enabled"; + + /** TelephonyProvider column name for enable Wifi calling */ + public static final String WFC_IMS_ENABLED = "wfc_ims_enabled"; + + /** TelephonyProvider column name for Wifi calling mode */ + public static final String WFC_IMS_MODE = "wfc_ims_mode"; + + /** TelephonyProvider column name for Wifi calling mode in roaming */ + public static final String WFC_IMS_ROAMING_MODE = "wfc_ims_roaming_mode"; + + /** TelephonyProvider column name for enable Wifi calling in roaming */ + public static final String WFC_IMS_ROAMING_ENABLED = "wfc_ims_roaming_enabled"; + + /** + * TelephonyProvider column name for whether a subscription is opportunistic, that is, + * whether the network it connects to is limited in functionality or coverage. + * For example, CBRS. + * <p>Type: INTEGER (int), 1 for opportunistic or 0 for non-opportunistic. + */ + public static final String IS_OPPORTUNISTIC = "is_opportunistic"; + + /** + * TelephonyProvider column name for group ID. Subscriptions with same group ID + * are considered bundled together, and should behave as a single subscription at + * certain scenarios. + */ + public static final String GROUP_UUID = "group_uuid"; + + /** + * TelephonyProvider column name for group owner. It's the package name who created + * the subscription group. + */ + public static final String GROUP_OWNER = "group_owner"; + + /** + * TelephonyProvider column name for whether a subscription is metered or not, that is, + * whether the network it connects to charges for subscription or not. For example, paid + * CBRS or unpaid. + * @hide + */ + public static final String IS_METERED = "is_metered"; + + /** + * TelephonyProvider column name for the profile class of a subscription + * Only present if {@link #IS_EMBEDDED} is 1. + * <P>Type: INTEGER (int)</P> + */ + public static final String PROFILE_CLASS = "profile_class"; + + /** + * A testing profile can be pre-loaded or downloaded onto + * the eUICC and provides connectivity to test equipment + * for the purpose of testing the device and the eUICC. It + * is not intended to store any operator credentials. + */ + public static final int PROFILE_CLASS_TESTING = 0; + + /** + * A provisioning profile is pre-loaded onto the eUICC and + * provides connectivity to a mobile network solely for the + * purpose of provisioning profiles. + */ + public static final int PROFILE_CLASS_PROVISIONING = 1; + + /** + * An operational profile can be pre-loaded or downloaded + * onto the eUICC and provides services provided by the + * operator. + */ + public static final int PROFILE_CLASS_OPERATIONAL = 2; + + /** + * The profile class is unset. This occurs when profile class + * info is not available. The subscription either has no profile + * metadata or the profile metadata did not encode profile class. + */ + public static final int PROFILE_CLASS_UNSET = -1; + + /** Default profile class */ + public static final int PROFILE_CLASS_DEFAULT = PROFILE_CLASS_UNSET; + + /** + * IMSI (International Mobile Subscriber Identity). + * <P>Type: TEXT </P> + */ + public static final String IMSI = "imsi"; + + /** Whether uicc applications is set to be enabled or disabled. By default it's enabled. */ + public static final String UICC_APPLICATIONS_ENABLED = "uicc_applications_enabled"; } } diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java index d51e4ca6398f..939ae878816a 100644 --- a/core/java/android/service/autofill/FillResponse.java +++ b/core/java/android/service/autofill/FillResponse.java @@ -609,10 +609,12 @@ public final class FillResponse implements Parcelable { "must add at least 1 dataset when using header or footer"); } - for (final Dataset dataset : mDatasets) { - if (dataset.getFieldInlinePresentation(0) != null) { - mSupportsInlineSuggestions = true; - break; + if (mDatasets != null) { + for (final Dataset dataset : mDatasets) { + if (dataset.getFieldInlinePresentation(0) != null) { + mSupportsInlineSuggestions = true; + break; + } } } diff --git a/core/java/android/service/notification/INotificationListener.aidl b/core/java/android/service/notification/INotificationListener.aidl index 5977bafd7cf1..4ead3fc67260 100644 --- a/core/java/android/service/notification/INotificationListener.aidl +++ b/core/java/android/service/notification/INotificationListener.aidl @@ -49,6 +49,9 @@ oneway interface INotificationListener void onNotificationEnqueuedWithChannel(in IStatusBarNotificationHolder notificationHolder, in NotificationChannel channel); void onNotificationSnoozedUntilContext(in IStatusBarNotificationHolder notificationHolder, String snoozeCriterionId); void onNotificationsSeen(in List<String> keys); + void onPanelRevealed(int items); + void onPanelHidden(); + void onNotificationVisibilityChanged(String key, boolean isVisible); void onNotificationExpansionChanged(String key, boolean userAction, boolean expanded); void onNotificationDirectReply(String key); void onSuggestedReplySent(String key, in CharSequence reply, int source); diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java index da4025419656..e976e18602c1 100644 --- a/core/java/android/service/notification/NotificationAssistantService.java +++ b/core/java/android/service/notification/NotificationAssistantService.java @@ -182,6 +182,32 @@ public abstract class NotificationAssistantService extends NotificationListenerS } /** + * Implement this to know when the notification panel is revealed + * + * @param items Number of items on the panel at time of opening + */ + public void onPanelRevealed(int items) { + + } + + /** + * Implement this to know when the notification panel is hidden + */ + public void onPanelHidden() { + + } + + /** + * Implement this to know when a notification becomes visible or hidden from the user. + * + * @param key the notification key + * @param isVisible whether the notification is visible. + */ + public void onNotificationVisibilityChanged(@NonNull String key, boolean isVisible) { + + } + + /** * Implement this to know when a notification change (expanded / collapsed) is visible to user. * * @param key the notification key @@ -337,6 +363,30 @@ public abstract class NotificationAssistantService extends NotificationListenerS } @Override + public void onPanelRevealed(int items) { + SomeArgs args = SomeArgs.obtain(); + args.argi1 = items; + mHandler.obtainMessage(MyHandler.MSG_ON_PANEL_REVEALED, + args).sendToTarget(); + } + + @Override + public void onPanelHidden() { + SomeArgs args = SomeArgs.obtain(); + mHandler.obtainMessage(MyHandler.MSG_ON_PANEL_HIDDEN, + args).sendToTarget(); + } + + @Override + public void onNotificationVisibilityChanged(String key, boolean isVisible) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = key; + args.argi1 = isVisible ? 1 : 0; + mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_VISIBILITY_CHANGED, + args).sendToTarget(); + } + + @Override public void onNotificationExpansionChanged(String key, boolean isUserAction, boolean isExpanded) { SomeArgs args = SomeArgs.obtain(); @@ -394,6 +444,9 @@ public abstract class NotificationAssistantService extends NotificationListenerS public static final int MSG_ON_SUGGESTED_REPLY_SENT = 6; public static final int MSG_ON_ACTION_INVOKED = 7; public static final int MSG_ON_ALLOWED_ADJUSTMENTS_CHANGED = 8; + public static final int MSG_ON_PANEL_REVEALED = 9; + public static final int MSG_ON_PANEL_HIDDEN = 10; + public static final int MSG_ON_NOTIFICATION_VISIBILITY_CHANGED = 11; public MyHandler(Looper looper) { super(looper, null, false); @@ -480,6 +533,25 @@ public abstract class NotificationAssistantService extends NotificationListenerS onAllowedAdjustmentsChanged(); break; } + case MSG_ON_PANEL_REVEALED: { + SomeArgs args = (SomeArgs) msg.obj; + int items = args.argi1; + args.recycle(); + onPanelRevealed(items); + break; + } + case MSG_ON_PANEL_HIDDEN: { + onPanelHidden(); + break; + } + case MSG_ON_NOTIFICATION_VISIBILITY_CHANGED: { + SomeArgs args = (SomeArgs) msg.obj; + String key = (String) args.arg1; + boolean isVisible = args.argi1 == 1; + args.recycle(); + onNotificationVisibilityChanged(key, isVisible); + break; + } } } } diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index 80d054b700f8..fd04f499a432 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -1383,6 +1383,22 @@ public abstract class NotificationListenerService extends Service { } @Override + public void onPanelRevealed(int items) throws RemoteException { + // no-op in the listener + } + + @Override + public void onPanelHidden() throws RemoteException { + // no-op in the listener + } + + @Override + public void onNotificationVisibilityChanged( + String key, boolean isVisible) { + // no-op in the listener + } + + @Override public void onNotificationSnoozedUntilContext( IStatusBarNotificationHolder notificationHolder, String snoozeCriterionId) throws RemoteException { diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java index 67925bfea2c2..d7c6d0f265c6 100644 --- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java +++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java @@ -168,6 +168,22 @@ public class AlwaysOnHotwordDetector { public static final int RECOGNITION_MODE_USER_IDENTIFICATION = SoundTrigger.RECOGNITION_MODE_USER_IDENTIFICATION; + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, prefix = { "MODEL_PARAM_" }, value = { + MODEL_PARAM_THRESHOLD_FACTOR, + }) + public @interface ModelParams {} + + /** + * Controls the sensitivity threshold adjustment factor for a given model. + * Negative value corresponds to less sensitive model (high threshold) and + * a positive value corresponds to a more sensitive model (low threshold). + * Default value is 0. + */ + public static final int MODEL_PARAM_THRESHOLD_FACTOR = + android.hardware.soundtrigger.ModelParams.THRESHOLD_FACTOR; + static final String TAG = "AlwaysOnHotwordDetector"; static final boolean DBG = false; @@ -198,6 +214,53 @@ public class AlwaysOnHotwordDetector { private int mAvailability = STATE_NOT_READY; /** + * A ModelParamRange is a representation of supported parameter range for a + * given loaded model. + */ + public static final class ModelParamRange { + private final SoundTrigger.ModelParamRange mModelParamRange; + + /** @hide */ + ModelParamRange(SoundTrigger.ModelParamRange modelParamRange) { + mModelParamRange = modelParamRange; + } + + /** + * The inclusive start of supported range. + * + * @return start of range + */ + public int start() { + return mModelParamRange.start; + } + + /** + * The inclusive end of supported range. + * + * @return end of range + */ + public int end() { + return mModelParamRange.end; + } + + @Override + @NonNull + public String toString() { + return mModelParamRange.toString(); + } + + @Override + public boolean equals(@Nullable Object obj) { + return mModelParamRange.equals(obj); + } + + @Override + public int hashCode() { + return mModelParamRange.hashCode(); + } + } + + /** * Additional payload for {@link Callback#onDetected}. */ public static class EventPayload { @@ -445,6 +508,83 @@ public class AlwaysOnHotwordDetector { } /** + * Set a model specific {@link ModelParams} with the given value. This + * parameter will keep its value for the duration the model is loaded regardless of starting and + * stopping recognition. Once the model is unloaded, the value will be lost. + * {@link AlwaysOnHotwordDetector#queryParameter} should be checked first before calling this + * method. + * + * @param modelParam {@link ModelParams} + * @param value Value to set + * @return - {@link SoundTrigger#STATUS_OK} in case of success + * - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached + * - {@link SoundTrigger#STATUS_BAD_VALUE} invalid input parameter + * - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or + * if API is not supported by HAL + */ + public int setParameter(@ModelParams int modelParam, int value) { + if (DBG) { + Slog.d(TAG, "setParameter(" + modelParam + ", " + value + ")"); + } + + synchronized (mLock) { + if (mAvailability == STATE_INVALID) { + throw new IllegalStateException("setParameter called on an invalid detector"); + } + + return setParameterLocked(modelParam, value); + } + } + + /** + * Get a model specific {@link ModelParams}. This parameter will keep its value + * for the duration the model is loaded regardless of starting and stopping recognition. + * Once the model is unloaded, the value will be lost. If the value is not set, a default + * value is returned. See {@link ModelParams} for parameter default values. + * {@link AlwaysOnHotwordDetector#queryParameter} should be checked first before + * calling this method. + * + * @param modelParam {@link ModelParams} + * @return value of parameter + */ + public int getParameter(@ModelParams int modelParam) { + if (DBG) { + Slog.d(TAG, "getParameter(" + modelParam + ")"); + } + + synchronized (mLock) { + if (mAvailability == STATE_INVALID) { + throw new IllegalStateException("getParameter called on an invalid detector"); + } + + return getParameterLocked(modelParam); + } + } + + /** + * Determine if parameter control is supported for the given model handle. + * This method should be checked prior to calling {@link AlwaysOnHotwordDetector#setParameter} + * or {@link AlwaysOnHotwordDetector#getParameter}. + * + * @param modelParam {@link ModelParams} + * @return supported range of parameter, null if not supported + */ + @Nullable + public ModelParamRange queryParameter(@ModelParams int modelParam) { + if (DBG) { + Slog.d(TAG, "queryParameter(" + modelParam + ")"); + } + + synchronized (mLock) { + if (mAvailability == STATE_INVALID) { + throw new IllegalStateException("queryParameter called on an invalid detector"); + } + + return queryParameterLocked(modelParam); + } + } + + /** * Creates an intent to start the enrollment for the associated keyphrase. * This intent must be invoked using {@link Context#startForegroundService(Intent)}. * Starting re-enrollment is only valid if the keyphrase is un-enrolled, @@ -601,6 +741,47 @@ public class AlwaysOnHotwordDetector { return code; } + private int setParameterLocked(@ModelParams int modelParam, int value) { + try { + int code = mModelManagementService.setParameter(mVoiceInteractionService, + mKeyphraseMetadata.id, modelParam, value); + + if (code != STATUS_OK) { + Slog.w(TAG, "setParameter failed with error code " + code); + } + + return code; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + private int getParameterLocked(@ModelParams int modelParam) { + try { + return mModelManagementService.getParameter(mVoiceInteractionService, + mKeyphraseMetadata.id, modelParam); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Nullable + private ModelParamRange queryParameterLocked(@ModelParams int modelParam) { + try { + SoundTrigger.ModelParamRange modelParamRange = + mModelManagementService.queryParameter(mVoiceInteractionService, + mKeyphraseMetadata.id, modelParam); + + if (modelParamRange == null) { + return null; + } + + return new ModelParamRange(modelParamRange); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + private void notifyStateChangedLocked() { Message message = Message.obtain(mHandler, MSG_AVAILABILITY_CHANGED); message.arg1 = mAvailability; diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java index b94475ac05ce..9387a2c79c6c 100644 --- a/core/java/android/telephony/TelephonyRegistryManager.java +++ b/core/java/android/telephony/TelephonyRegistryManager.java @@ -24,6 +24,7 @@ import android.content.Context; import android.os.Binder; import android.os.RemoteException; import android.os.ServiceManager; +import android.telephony.Annotation.ApnType; import android.telephony.Annotation.CallState; import android.telephony.Annotation.DataActivityType; import android.telephony.Annotation.DataFailureCause; @@ -352,7 +353,7 @@ public class TelephonyRegistryManager { * @param subId for which data connection state changed. * @param slotIndex for which data connections state changed. Can be derived from subId except * when subId is invalid. - * @param apnType the APN type that triggered this update + * @param apnType the apn type bitmask, defined with {@code ApnSetting#TYPE_*} flags. * @param preciseState the PreciseDataConnectionState * * @see android.telephony.PreciseDataConnection @@ -360,7 +361,7 @@ public class TelephonyRegistryManager { * @hide */ public void notifyDataConnectionForSubscriber(int slotIndex, int subId, - String apnType, PreciseDataConnectionState preciseState) { + @ApnType int apnType, PreciseDataConnectionState preciseState) { try { sRegistry.notifyDataConnectionForSubscriber( slotIndex, subId, apnType, preciseState); @@ -553,13 +554,13 @@ public class TelephonyRegistryManager { * @param subId for which data connection failed. * @param slotIndex for which data conenction failed. Can be derived from subId except when * subId is invalid. - * @param apnType the apnType, "ims" for IMS APN, "emergency" for EMERGENCY APN. + * @param apnType the apn type bitmask, defined with {@code ApnSetting#TYPE_*} flags. * @param apn the APN {@link ApnSetting#getApnName()} of this data connection. * @param failCause data fail cause. * * @hide */ - public void notifyPreciseDataConnectionFailed(int subId, int slotIndex, String apnType, + public void notifyPreciseDataConnectionFailed(int subId, int slotIndex, @ApnType int apnType, String apn, @DataFailureCause int failCause) { try { sRegistry.notifyPreciseDataConnectionFailed(slotIndex, subId, apnType, apn, failCause); diff --git a/core/java/android/telephony/WapPushManagerConnector.java b/core/java/android/telephony/WapPushManagerConnector.java new file mode 100644 index 000000000000..a9df50685d5d --- /dev/null +++ b/core/java/android/telephony/WapPushManagerConnector.java @@ -0,0 +1,178 @@ +/* + * 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.telephony; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SdkConstant; +import android.annotation.SystemApi; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.IBinder; +import android.os.RemoteException; + +import com.android.internal.telephony.IWapPushManager; +import com.android.internal.util.Preconditions; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * APIs for platform to connect to the WAP push manager service. + * + * <p>To start connection, {@link #bindToWapPushManagerService} should be called. + * + * <p>Upon completion {@link #unbindWapPushManagerService} should be called to unbind the service. + * + * @hide + */ +@SystemApi +public final class WapPushManagerConnector { + private final Context mContext; + + private volatile WapPushManagerConnection mConnection; + private volatile IWapPushManager mWapPushManager; + private String mWapPushManagerPackage; + + /** + * The {@link android.content.Intent} that must be declared as handled by the + * WAP push manager service. + * @hide + */ + @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) + public static final String SERVICE_INTERFACE = + "com.android.internal.telephony.IWapPushManager"; + + /** @hide */ + @IntDef(flag = true, prefix = {"RESULT_"}, value = { + RESULT_MESSAGE_HANDLED, + RESULT_APP_QUERY_FAILED, + RESULT_SIGNATURE_NO_MATCH, + RESULT_INVALID_RECEIVER_NAME, + RESULT_EXCEPTION_CAUGHT, + RESULT_FURTHER_PROCESSING, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ProcessMessageResult{} + + /** {@link #processMessage} return value: Message is handled. */ + public static final int RESULT_MESSAGE_HANDLED = 0x1; + /** {@link #processMessage} return value: Application ID or content type was not found. */ + public static final int RESULT_APP_QUERY_FAILED = 0x2; + /** {@link #processMessage} return value: Receiver application signature check failed. */ + public static final int RESULT_SIGNATURE_NO_MATCH = 0x4; + /** {@link #processMessage} return value: Receiver application was not found. */ + public static final int RESULT_INVALID_RECEIVER_NAME = 0x8; + /** {@link #processMessage} return value: Unknown exception. */ + public static final int RESULT_EXCEPTION_CAUGHT = 0x10; + /** {@link #processMessage} return value: further processing needed. */ + public static final int RESULT_FURTHER_PROCESSING = 0x8000; + + /** The application package name of the WAP push manager service. */ + private static final String SERVICE_PACKAGE = "com.android.smspush"; + + public WapPushManagerConnector(@NonNull Context context) { + mContext = context; + } + + /** + * Binds to the WAP push manager service. This method should be called exactly once. + * + * @return {@code true} upon successfully binding to a service, {@code false} otherwise + */ + public boolean bindToWapPushManagerService() { + Preconditions.checkState(mConnection == null); + + Intent intent = new Intent(SERVICE_INTERFACE); + ComponentName component = intent.resolveSystemService(mContext.getPackageManager(), 0); + intent.setComponent(component); + mConnection = new WapPushManagerConnection(); + if (component != null + && mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE)) { + mWapPushManagerPackage = component.getPackageName(); + return true; + } + return false; + } + + /** + * Returns the package name of WAP push manager service application connected to, + * or {@code null} if not connected. + */ + @Nullable + public String getConnectedWapPushManagerServicePackage() { + return mWapPushManagerPackage; + } + + /** + * Processes WAP push message and triggers the {@code intent}. + * + * @see RESULT_MESSAGE_HANDLED + * @see RESULT_APP_QUERY_FAILED + * @see RESULT_SIGNATURE_NO_MATCH + * @see RESULT_INVALID_RECEIVER_NAME + * @see RESULT_EXCEPTION_CAUGHT + * @see RESULT_FURTHER_PROCESSING + */ + @ProcessMessageResult + public int processMessage( + @NonNull String applicationId, @NonNull String contentType, @NonNull Intent intent) { + try { + return mWapPushManager.processMessage(applicationId, contentType, intent); + } catch (NullPointerException | RemoteException e) { + return RESULT_EXCEPTION_CAUGHT; + } + } + + /** + * Unbinds the WAP push manager service. This method should be called exactly once. + */ + public void unbindWapPushManagerService() { + Preconditions.checkNotNull(mConnection); + + mContext.unbindService(mConnection); + mConnection = null; + } + + private class WapPushManagerConnection implements ServiceConnection { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + // Because we have bound to an explicit + // service that is running in our own process, we can + // cast its IBinder to a concrete class and directly access it. + mWapPushManager = IWapPushManager.Stub.asInterface(service); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + mWapPushManager = null; + } + + @Override + public void onNullBinding(ComponentName name) { + onServiceDisconnected(name); + } + + @Override + public void onBindingDied(ComponentName name) { + onServiceDisconnected(name); + } + } +} diff --git a/core/java/android/text/SpannableStringInternal.java b/core/java/android/text/SpannableStringInternal.java index b85ef76bd2d5..4c9328a1c372 100644 --- a/core/java/android/text/SpannableStringInternal.java +++ b/core/java/android/text/SpannableStringInternal.java @@ -40,9 +40,10 @@ import java.lang.reflect.Array; if (source instanceof Spanned) { if (source instanceof SpannableStringInternal) { - copySpans((SpannableStringInternal) source, start, end, ignoreNoCopySpan); + copySpansFromInternal( + (SpannableStringInternal) source, start, end, ignoreNoCopySpan); } else { - copySpans((Spanned) source, start, end, ignoreNoCopySpan); + copySpansFromSpanned((Spanned) source, start, end, ignoreNoCopySpan); } } } @@ -65,7 +66,7 @@ import java.lang.reflect.Array; * @param end End index in the source object. * @param ignoreNoCopySpan whether to copy NoCopySpans in the {@code source} */ - private void copySpans(Spanned src, int start, int end, boolean ignoreNoCopySpan) { + private void copySpansFromSpanned(Spanned src, int start, int end, boolean ignoreNoCopySpan) { Object[] spans = src.getSpans(start, end, Object.class); for (int i = 0; i < spans.length; i++) { @@ -94,7 +95,7 @@ import java.lang.reflect.Array; * @param end End index in the source object. * @param ignoreNoCopySpan copy NoCopySpan for backward compatible reasons. */ - private void copySpans(SpannableStringInternal src, int start, int end, + private void copySpansFromInternal(SpannableStringInternal src, int start, int end, boolean ignoreNoCopySpan) { int count = 0; final int[] srcData = src.mSpanData; @@ -555,12 +556,12 @@ import java.lang.reflect.Array; */ @UnsupportedAppUsage private void copySpans(Spanned src, int start, int end) { - copySpans(src, start, end, false); + copySpansFromSpanned(src, start, end, false); } @UnsupportedAppUsage private void copySpans(SpannableStringInternal src, int start, int end) { - copySpans(src, start, end, false); + copySpansFromInternal(src, start, end, false); } diff --git a/core/java/android/util/Log.java b/core/java/android/util/Log.java index 3f679bba88cd..f324113da925 100644 --- a/core/java/android/util/Log.java +++ b/core/java/android/util/Log.java @@ -387,6 +387,26 @@ public final class Log { public static native int println_native(int bufID, int priority, String tag, String msg); /** + * Send a log message to the "radio" log buffer, which can be dumped with + * {@code adb logcat -b radio}. + * + * <p>Only the telephony mainline module should use it. + * + * <p>Note ART will protect {@code MODULE_LIBRARIES} system APIs from regular app code. + * + * @param priority Log priority. + * @param tag Used to identify the source of a log message. It usually identifies + * the class or activity where the log call occurs. + * @param message The message you would like logged. + * @hide + */ + // @SystemApi(client= SystemApi.Client.MODULE_LIBRARIES) // TODO Uncomment once http://ag/9956147 is in. + public static int logToRadioBuffer(@Level int priority, @Nullable String tag, + @Nullable String message) { + return println_native(LOG_ID_RADIO, priority, tag, message); + } + + /** * Return the maximum payload the log daemon accepts without truncation. * @return LOGGER_ENTRY_MAX_PAYLOAD. */ diff --git a/core/java/android/util/NtpTrustedTime.java b/core/java/android/util/NtpTrustedTime.java index 2223d14ad658..fa994ba76fd7 100644 --- a/core/java/android/util/NtpTrustedTime.java +++ b/core/java/android/util/NtpTrustedTime.java @@ -25,6 +25,7 @@ import android.net.Network; import android.net.NetworkInfo; import android.net.SntpClient; import android.os.SystemClock; +import android.os.TimestampedValue; import android.provider.Settings; import android.text.TextUtils; diff --git a/core/java/android/view/IDisplayWindowInsetsController.aidl b/core/java/android/view/IDisplayWindowInsetsController.aidl new file mode 100644 index 000000000000..429c3aeba9b3 --- /dev/null +++ b/core/java/android/view/IDisplayWindowInsetsController.aidl @@ -0,0 +1,48 @@ +/** + * 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.view.InsetsSourceControl; +import android.view.InsetsState; + +/** + * Singular controller of insets to use when there isn't another obvious controller available. + * Specifically, this will take over insets control in multi-window. + * @hide + */ +oneway interface IDisplayWindowInsetsController { + + /** + * @see IWindow#insetsChanged + */ + void insetsChanged(in InsetsState insetsState); + + /** + * @see IWindow#insetsControlChanged + */ + void insetsControlChanged(in InsetsState insetsState, in InsetsSourceControl[] activeControls); + + /** + * @see IWindow#showInsets + */ + void showInsets(int types, boolean fromIme); + + /** + * @see IWindow#hideInsets + */ + void hideInsets(int types, boolean fromIme); +} diff --git a/core/java/android/view/ITaskOrganizer.aidl b/core/java/android/view/ITaskOrganizer.aidl new file mode 100644 index 000000000000..e92aafed6f22 --- /dev/null +++ b/core/java/android/view/ITaskOrganizer.aidl @@ -0,0 +1,38 @@ +/* //device/java/android/android/view/ITaskOrganizer.aidl +** +** 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.view; + +import android.view.IWindowContainer; +import android.view.SurfaceControl; +import android.app.ActivityManager; + +/** + * Interface for ActivityTaskManager/WindowManager to delegate control of tasks. + * {@hide} + */ +oneway interface ITaskOrganizer { + void taskAppeared(in IWindowContainer container, + in ActivityManager.RunningTaskInfo taskInfo); + void taskVanished(in IWindowContainer container); + + /** + * Called upon completion of + * ActivityTaskManagerService#applyTaskOrganizerTransaction + */ + void transactionReady(int id, in SurfaceControl.Transaction t); +}
\ No newline at end of file diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 9496827b1a84..993bdc4d6543 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -35,6 +35,7 @@ import android.os.ParcelFileDescriptor; import android.view.IApplicationToken; import android.view.IAppTransitionAnimationSpecsFuture; import android.view.IDockedStackListener; +import android.view.IDisplayWindowInsetsController; import android.view.IDisplayWindowListener; import android.view.IDisplayFoldListener; import android.view.IDisplayWindowRotationController; @@ -49,6 +50,7 @@ import android.view.IWindowSession; import android.view.IWindowSessionCallback; import android.view.KeyEvent; import android.view.InputEvent; +import android.view.InsetsState; import android.view.MagnificationSpec; import android.view.MotionEvent; import android.view.InputChannel; @@ -711,4 +713,16 @@ interface IWindowManager * @return true if the display was successfully mirrored. */ boolean mirrorDisplay(int displayId, out SurfaceControl outSurfaceControl); + + /** + * When in multi-window mode, the provided displayWindowInsetsController will control insets + * animations. + */ + void setDisplayWindowInsetsController( + int displayId, in IDisplayWindowInsetsController displayWindowInsetsController); + + /** + * Called when a remote process modifies insets on a display window container. + */ + void modifyDisplayWindowInsets(int displayId, in InsetsState state); } diff --git a/core/java/android/view/InsetsAnimationControlCallbacks.java b/core/java/android/view/InsetsAnimationControlCallbacks.java index 6fdadc60afea..27edb0b69bdd 100644 --- a/core/java/android/view/InsetsAnimationControlCallbacks.java +++ b/core/java/android/view/InsetsAnimationControlCallbacks.java @@ -16,17 +16,28 @@ package android.view; +import android.view.InsetsController.LayoutInsetsDuringAnimation; +import android.view.WindowInsetsAnimationCallback.AnimationBounds; +import android.view.WindowInsetsAnimationCallback.InsetsAnimation; + /** * Provide an interface to let InsetsAnimationControlImpl call back into its owner. * @hide */ public interface InsetsAnimationControlCallbacks { + /** - * Dispatch the animation started event to all listeners. - * @param animation + * Executes the necessary code to start the animation in the correct order, including: + * <ul> + * <li>Dispatch {@link WindowInsetsAnimationCallback#onPrepare}</li> + * <li>Update insets state and run layout according to {@code layoutDuringAnimation}</li> + * <li>Dispatch {@link WindowInsetsAnimationCallback#onStart}</li> + * <li>Dispatch {@link WindowInsetsAnimationControlListener#onReady}</li> + * </ul> */ - void dispatchAnimationStarted(WindowInsetsAnimationCallback.InsetsAnimation animation, - WindowInsetsAnimationCallback.AnimationBounds bounds); + void startAnimation(InsetsAnimationControlImpl controller, + WindowInsetsAnimationControlListener listener, int types, InsetsAnimation animation, + AnimationBounds bounds, @LayoutInsetsDuringAnimation int layoutDuringAnimation); /** * Schedule the apply by posting the animation callback. diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java index 771695c2b873..6589e75c7bc2 100644 --- a/core/java/android/view/InsetsAnimationControlImpl.java +++ b/core/java/android/view/InsetsAnimationControlImpl.java @@ -16,6 +16,8 @@ package android.view; +import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_HIDDEN; +import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN; import static android.view.InsetsState.ISIDE_BOTTOM; import static android.view.InsetsState.ISIDE_FLOATING; import static android.view.InsetsState.ISIDE_LEFT; @@ -30,6 +32,7 @@ import android.util.ArraySet; import android.util.SparseArray; import android.util.SparseIntArray; import android.util.SparseSetArray; +import android.view.InsetsController.LayoutInsetsDuringAnimation; import android.view.InsetsState.InternalInsetsSide; import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams; import android.view.WindowInsets.Type.InsetsType; @@ -80,7 +83,8 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll public InsetsAnimationControlImpl(SparseArray<InsetsSourceControl> controls, Rect frame, InsetsState state, WindowInsetsAnimationControlListener listener, @InsetsType int types, - InsetsAnimationControlCallbacks controller, long durationMs, boolean fade) { + InsetsAnimationControlCallbacks controller, long durationMs, boolean fade, + @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation) { mControls = controls; mListener = listener; mTypes = types; @@ -95,14 +99,11 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll mFrame = new Rect(frame); buildTypeSourcesMap(mTypeSideMap, mSideSourceMap, mControls); - // TODO: Check for controllability first and wait for IME if needed. - listener.onReady(this, types); - mAnimation = new WindowInsetsAnimationCallback.InsetsAnimation(mTypes, InsetsController.INTERPOLATOR, durationMs); mAnimation.setAlpha(getCurrentAlpha()); - mController.dispatchAnimationStarted(mAnimation, - new AnimationBounds(mHiddenInsets, mShownInsets)); + mController.startAnimation(this, listener, types, mAnimation, + new AnimationBounds(mHiddenInsets, mShownInsets), layoutInsetsDuringAnimation); } @Override @@ -257,10 +258,6 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll for (int i = items.size() - 1; i >= 0; i--) { final InsetsSourceControl control = items.valueAt(i); final InsetsSource source = mInitialInsetsState.getSource(control.getType()); - if (control == null) { - // TODO: remove this check when we ensure the elements will not be null. - continue; - } final SurfaceControl leash = control.getLeash(); mTmpMatrix.setTranslate(control.getSurfacePosition().x, control.getSurfacePosition().y); diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 4d4ace27cac9..775490c757d4 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -37,16 +37,20 @@ import android.util.SparseArray; import android.view.InsetsSourceConsumer.ShowResult; import android.view.InsetsState.InternalInsetsType; import android.view.SurfaceControl.Transaction; +import android.view.ViewTreeObserver.OnPreDrawListener; import android.view.WindowInsets.Type; import android.view.WindowInsets.Type.InsetsType; import android.view.WindowInsetsAnimationCallback.AnimationBounds; import android.view.WindowInsetsAnimationCallback.InsetsAnimation; +import android.view.WindowManager.LayoutParams.SoftInputModeFlags; import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; import com.android.internal.annotations.VisibleForTesting; import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; /** @@ -67,6 +71,37 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private @interface AnimationDirection{} /** + * Layout mode during insets animation: The views should be laid out as if the changing inset + * types are fully shown. Before starting the animation, {@link View#onApplyWindowInsets} will + * be called as if the changing insets types are shown, which will result in the views being + * laid out as if the insets are fully shown. + */ + static final int LAYOUT_INSETS_DURING_ANIMATION_SHOWN = 0; + + /** + * Layout mode during insets animation: The views should be laid out as if the changing inset + * types are fully hidden. Before starting the animation, {@link View#onApplyWindowInsets} will + * be called as if the changing insets types are hidden, which will result in the views being + * laid out as if the insets are fully hidden. + */ + static final int LAYOUT_INSETS_DURING_ANIMATION_HIDDEN = 1; + + /** + * Determines the behavior of how the views should be laid out during an insets animation that + * is controlled by the application by calling {@link #controlWindowInsetsAnimation}. + * <p> + * When the animation is system-initiated, the layout mode is always chosen such that the + * pre-animation layout will represent the opposite of the starting state, i.e. when insets + * are appearing, {@link #LAYOUT_INSETS_DURING_ANIMATION_SHOWN} will be used. When insets + * are disappearing, {@link #LAYOUT_INSETS_DURING_ANIMATION_HIDDEN} will be used. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = {LAYOUT_INSETS_DURING_ANIMATION_SHOWN, + LAYOUT_INSETS_DURING_ANIMATION_HIDDEN}) + @interface LayoutInsetsDuringAnimation { + } + + /** * Translation animation evaluator. */ private static TypeEvaluator<Insets> sEvaluator = (fraction, startValue, endValue) -> Insets.of( @@ -109,11 +144,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @Override public void onReady(WindowInsetsAnimationController controller, int types) { mController = controller; - if (mShow) { - showDirectly(types); - } else { - hideDirectly(types); - } + mAnimationDirection = mShow ? DIRECTION_SHOW : DIRECTION_HIDE; mAnimator = ObjectAnimator.ofObject( controller, @@ -131,7 +162,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation onAnimationFinish(); } }); + mStartingAnimation = true; mAnimator.start(); + mStartingAnimation = false; } @Override @@ -185,6 +218,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private int mPendingTypesToShow; private int mLastLegacySoftInputMode; + private boolean mStartingAnimation; private SyncRtSurfaceTransactionApplier mApplier; @@ -266,6 +300,14 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } /** + * @see InsetsState#calculateVisibleInsets(Rect, Rect, int) + */ + public Rect calculateVisibleInsets(Rect legacyVisibleInsets, + @SoftInputModeFlags int softInputMode) { + return mState.calculateVisibleInsets(mFrame, legacyVisibleInsets, softInputMode); + } + + /** * Called when the server has dispatched us a new set of inset controls. */ public void onControlsChanged(InsetsSourceControl[] activeControls) { @@ -312,7 +354,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation // Only one animator (with multiple InsetsType) can run at a time. // previous one should be cancelled for simplicity. cancelExistingAnimation(); - } else if (consumer.isVisible() + } else if (consumer.isRequestedVisible() && (mAnimationDirection == DIRECTION_NONE || mAnimationDirection == DIRECTION_HIDE)) { // no-op: already shown or animating in (because window visibility is @@ -338,7 +380,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i)); if (mAnimationDirection == DIRECTION_SHOW) { cancelExistingAnimation(); - } else if (!consumer.isVisible() + } else if (!consumer.isRequestedVisible() && (mAnimationDirection == DIRECTION_NONE || mAnimationDirection == DIRECTION_HIDE)) { // no-op: already hidden or animating out. @@ -363,20 +405,21 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation listener.onCancelled(); return; } - controlAnimationUnchecked(types, listener, mFrame, fromIme, durationMs, false /* fade */); + controlAnimationUnchecked(types, listener, mFrame, fromIme, durationMs, false /* fade */, + getLayoutInsetsDuringAnimationMode(types)); } private void controlAnimationUnchecked(@InsetsType int types, WindowInsetsAnimationControlListener listener, Rect frame, boolean fromIme, - long durationMs, boolean fade) { + long durationMs, boolean fade, + @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation) { if (types == 0) { // nothing to animate. return; } cancelExistingControllers(types); - final ArraySet<Integer> internalTypes = mState.toInternalType(types); - final SparseArray<InsetsSourceConsumer> consumers = new SparseArray<>(); + final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types); final SparseArray<InsetsSourceControl> controls = new SparseArray<>(); Pair<Integer, Boolean> typesReadyPair = collectSourceControls( @@ -399,7 +442,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } final InsetsAnimationControlImpl controller = new InsetsAnimationControlImpl(controls, - frame, mState, listener, typesReady, this, durationMs, fade); + frame, mState, listener, typesReady, this, durationMs, fade, + layoutInsetsDuringAnimation); mAnimationControls.add(controller); } @@ -413,7 +457,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation boolean isReady = true; for (int i = internalTypes.size() - 1; i >= 0; i--) { InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i)); - boolean setVisible = !consumer.isVisible(); + boolean setVisible = !consumer.isRequestedVisible(); if (setVisible) { // Show request switch(consumer.requestShow(fromIme)) { @@ -441,7 +485,10 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } typesReady |= InsetsState.toPublicType(consumer.getType()); } - controls.put(consumer.getType(), consumer.getControl()); + final InsetsSourceControl control = consumer.getControl(); + if (control != null) { + controls.put(consumer.getType(), control); + } } return new Pair<>(typesReady, isReady); } @@ -452,6 +499,29 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation return typesReady; } + private @LayoutInsetsDuringAnimation int getLayoutInsetsDuringAnimationMode( + @InsetsType int types) { + + final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types); + + // Generally, we want to layout the opposite of the current state. This is to make animation + // callbacks easy to use: The can capture the layout values and then treat that as end-state + // during the animation. + // + // However, if controlling multiple sources, we want to treat it as shown if any of the + // types is currently hidden. + for (int i = internalTypes.size() - 1; i >= 0; i--) { + InsetsSourceConsumer consumer = mSourceConsumers.get(internalTypes.valueAt(i)); + if (consumer == null) { + continue; + } + if (!consumer.isRequestedVisible()) { + return LAYOUT_INSETS_DURING_ANIMATION_SHOWN; + } + } + return LAYOUT_INSETS_DURING_ANIMATION_HIDDEN; + } + private void cancelExistingControllers(@InsetsType int types) { for (int i = mAnimationControls.size() - 1; i >= 0; i--) { InsetsAnimationControlImpl control = mAnimationControls.get(i); @@ -595,7 +665,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation // and hidden state insets are correct. controlAnimationUnchecked( types, listener, mState.getDisplayFrame(), fromIme, listener.getDurationMs(), - true /* fade */); + true /* fade */, show + ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN + : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN); } private void hideDirectly(@InsetsType int types) { @@ -627,18 +699,40 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @VisibleForTesting @Override - public void dispatchAnimationStarted(InsetsAnimation animation, AnimationBounds bounds) { - mViewRoot.mView.dispatchWindowInsetsAnimationStarted(animation, bounds); + public void startAnimation(InsetsAnimationControlImpl controller, + WindowInsetsAnimationControlListener listener, int types, InsetsAnimation animation, + AnimationBounds bounds, int layoutDuringAnimation) { + if (layoutDuringAnimation == LAYOUT_INSETS_DURING_ANIMATION_SHOWN) { + showDirectly(types); + } else { + hideDirectly(types); + } + mViewRoot.mView.dispatchWindowInsetsAnimationPrepare(animation); + mViewRoot.mView.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() { + @Override + public boolean onPreDraw() { + mViewRoot.mView.getViewTreeObserver().removeOnPreDrawListener(this); + mViewRoot.mView.dispatchWindowInsetsAnimationStart(animation, bounds); + listener.onReady(controller, types); + return true; + } + }); + mViewRoot.mView.invalidate(); } @VisibleForTesting public void dispatchAnimationFinished(InsetsAnimation animation) { - mViewRoot.mView.dispatchWindowInsetsAnimationFinished(animation); + mViewRoot.mView.dispatchWindowInsetsAnimationFinish(animation); } @VisibleForTesting @Override public void scheduleApplyChangeInsets() { + if (mStartingAnimation) { + mAnimCallback.run(); + mAnimCallbackScheduled = false; + return; + } if (!mAnimCallbackScheduled) { mViewRoot.mChoreographer.postCallback(Choreographer.CALLBACK_INSETS_ANIMATION, mAnimCallback, null /* token*/); diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java index 324d562bfcfb..67ccfd6707f4 100644 --- a/core/java/android/view/InsetsSource.java +++ b/core/java/android/view/InsetsSource.java @@ -16,6 +16,7 @@ package android.view; +import android.annotation.Nullable; import android.graphics.Insets; import android.graphics.Rect; import android.os.Parcel; @@ -23,6 +24,7 @@ import android.os.Parcelable; import android.view.InsetsState.InternalInsetsType; import java.io.PrintWriter; +import java.util.Objects; /** * Represents the state of a single window generating insets for clients. @@ -34,6 +36,7 @@ public class InsetsSource implements Parcelable { /** Frame of the source in screen coordinate space */ private final Rect mFrame; + private @Nullable Rect mVisibleFrame; private boolean mVisible; private final Rect mTmpFrame = new Rect(); @@ -54,6 +57,10 @@ public class InsetsSource implements Parcelable { mFrame.set(frame); } + public void setVisibleFrame(@Nullable Rect visibleFrame) { + mVisibleFrame = visibleFrame != null ? new Rect(visibleFrame) : visibleFrame; + } + public void setVisible(boolean visible) { mVisible = visible; } @@ -66,6 +73,10 @@ public class InsetsSource implements Parcelable { return mFrame; } + public @Nullable Rect getVisibleFrame() { + return mVisibleFrame; + } + public boolean isVisible() { return mVisible; } @@ -79,10 +90,22 @@ public class InsetsSource implements Parcelable { * source. */ public Insets calculateInsets(Rect relativeFrame, boolean ignoreVisibility) { + return calculateInsets(relativeFrame, mFrame, ignoreVisibility); + } + + /** + * Like {@link #calculateInsets(Rect, boolean)}, but will return visible insets. + */ + public Insets calculateVisibleInsets(Rect relativeFrame) { + return calculateInsets(relativeFrame, mVisibleFrame != null ? mVisibleFrame : mFrame, + false /* ignoreVisibility */); + } + + private Insets calculateInsets(Rect relativeFrame, Rect frame, boolean ignoreVisibility) { if (!ignoreVisibility && !mVisible) { return Insets.NONE; } - if (!mTmpFrame.setIntersect(mFrame, relativeFrame)) { + if (!mTmpFrame.setIntersect(frame, relativeFrame)) { return Insets.NONE; } @@ -110,6 +133,9 @@ public class InsetsSource implements Parcelable { pw.print(prefix); pw.print("InsetsSource type="); pw.print(InsetsState.typeToString(mType)); pw.print(" frame="); pw.print(mFrame.toShortString()); + if (mVisibleFrame != null) { + pw.print(" visibleFrmae="); pw.print(mVisibleFrame.toShortString()); + } pw.print(" visible="); pw.print(mVisible); pw.println(); } @@ -123,6 +149,7 @@ public class InsetsSource implements Parcelable { if (mType != that.mType) return false; if (mVisible != that.mVisible) return false; + if (!Objects.equals(mVisibleFrame, that.mVisibleFrame)) return false; return mFrame.equals(that.mFrame); } @@ -137,6 +164,7 @@ public class InsetsSource implements Parcelable { public InsetsSource(Parcel in) { mType = in.readInt(); mFrame = in.readParcelable(null /* loader */); + mVisibleFrame = in.readParcelable(null /* loader */); mVisible = in.readBoolean(); } @@ -149,6 +177,7 @@ public class InsetsSource implements Parcelable { public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mType); dest.writeParcelable(mFrame, 0 /* flags*/); + dest.writeParcelable(mVisibleFrame, 0 /* flags */); dest.writeBoolean(mVisible); } diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java index c6d9898a425c..b2a5d915c2a6 100644 --- a/core/java/android/view/InsetsSourceConsumer.java +++ b/core/java/android/view/InsetsSourceConsumer.java @@ -53,7 +53,7 @@ public class InsetsSourceConsumer { } protected final InsetsController mController; - protected boolean mVisible; + protected boolean mRequestedVisible; private final Supplier<Transaction> mTransactionSupplier; private final @InternalInsetsType int mType; private final InsetsState mState; @@ -66,7 +66,7 @@ public class InsetsSourceConsumer { mState = state; mTransactionSupplier = transactionSupplier; mController = controller; - mVisible = InsetsState.getDefaultVisibility(type); + mRequestedVisible = InsetsState.getDefaultVisibility(type); } public void setControl(@Nullable InsetsSourceControl control) { @@ -94,12 +94,12 @@ public class InsetsSourceConsumer { @VisibleForTesting public void show() { - setVisible(true); + setRequestedVisible(true); } @VisibleForTesting public void hide() { - setVisible(false); + setRequestedVisible(false); } /** @@ -126,16 +126,16 @@ public class InsetsSourceConsumer { if (mSourceControl == null) { return false; } - if (mState.getSource(mType).isVisible() == mVisible) { + if (mState.getSource(mType).isVisible() == mRequestedVisible) { return false; } - mState.getSource(mType).setVisible(mVisible); + mState.getSource(mType).setVisible(mRequestedVisible); return true; } @VisibleForTesting - public boolean isVisible() { - return mVisible; + public boolean isRequestedVisible() { + return mRequestedVisible; } /** @@ -157,11 +157,15 @@ public class InsetsSourceConsumer { // no-op for types that always return ShowResult#SHOW_IMMEDIATELY. } - private void setVisible(boolean visible) { - if (mVisible == visible) { + /** + * Sets requested visibility from the client, regardless of whether we are able to control it at + * the moment. + */ + private void setRequestedVisible(boolean requestedVisible) { + if (mRequestedVisible == requestedVisible) { return; } - mVisible = visible; + mRequestedVisible = requestedVisible; applyLocalVisibilityOverride(); mController.notifyVisibilityChanged(); } @@ -173,7 +177,7 @@ public class InsetsSourceConsumer { } final Transaction t = mTransactionSupplier.get(); - if (mVisible) { + if (mRequestedVisible) { t.show(mSourceControl.getLeash()); } else { t.hide(mSourceControl.getLeash()); diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index e3fed3a5dce3..e33ca70c222e 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -19,10 +19,17 @@ package android.view; import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL; import static android.view.ViewRootImpl.NEW_INSETS_MODE_IME; import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE; +import static android.view.ViewRootImpl.sNewInsetsMode; +import static android.view.WindowInsets.Type.IME; import static android.view.WindowInsets.Type.MANDATORY_SYSTEM_GESTURES; import static android.view.WindowInsets.Type.SIZE; import static android.view.WindowInsets.Type.SYSTEM_GESTURES; +import static android.view.WindowInsets.Type.ime; import static android.view.WindowInsets.Type.indexOf; +import static android.view.WindowInsets.Type.isVisibleInsetsType; +import static android.view.WindowInsets.Type.systemBars; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST; import android.annotation.IntDef; import android.annotation.Nullable; @@ -36,6 +43,7 @@ import android.util.SparseIntArray; import android.view.WindowInsets.Type; import android.view.WindowInsets.Type.InsetsType; import android.view.WindowManager.LayoutParams; +import android.view.WindowManager.LayoutParams.SoftInputModeFlags; import java.io.PrintWriter; import java.lang.annotation.Retention; @@ -156,11 +164,10 @@ public class InsetsState implements Parcelable { && source.getType() != ITYPE_IME; boolean skipSystemBars = ViewRootImpl.sNewInsetsMode != NEW_INSETS_MODE_FULL && (type == ITYPE_STATUS_BAR || type == ITYPE_NAVIGATION_BAR); - boolean skipIme = source.getType() == ITYPE_IME - && (legacySoftInputMode & LayoutParams.SOFT_INPUT_ADJUST_RESIZE) == 0; boolean skipLegacyTypes = ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE - && (toPublicType(type) & Type.compatSystemInsets()) != 0; - if (skipSystemBars || skipIme || skipLegacyTypes || skipNonImeInImeMode) { + && (type == ITYPE_STATUS_BAR || type == ITYPE_NAVIGATION_BAR + || type == ITYPE_IME); + if (skipSystemBars || skipLegacyTypes || skipNonImeInImeMode) { typeVisibilityMap[indexOf(toPublicType(type))] = source.isVisible(); continue; } @@ -175,8 +182,37 @@ public class InsetsState implements Parcelable { typeMaxInsetsMap, null /* typeSideMap */, null /* typeVisibilityMap */); } } + final int softInputAdjustMode = legacySoftInputMode & SOFT_INPUT_MASK_ADJUST; return new WindowInsets(typeInsetsMap, typeMaxInsetsMap, typeVisibilityMap, isScreenRound, - alwaysConsumeSystemBars, cutout); + alwaysConsumeSystemBars, cutout, softInputAdjustMode == SOFT_INPUT_ADJUST_RESIZE + ? systemBars() | ime() + : systemBars()); + } + + public Rect calculateVisibleInsets(Rect frame, Rect legacyVisibleInsets, + @SoftInputModeFlags int softInputMode) { + if (sNewInsetsMode == NEW_INSETS_MODE_NONE) { + return legacyVisibleInsets; + } + + Insets insets = Insets.NONE; + for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) { + InsetsSource source = mSources.get(type); + if (source == null) { + continue; + } + if (sNewInsetsMode != NEW_INSETS_MODE_FULL && type != ITYPE_IME) { + continue; + } + + // Ignore everything that's not a system bar or IME. + int publicType = InsetsState.toPublicType(type); + if (!isVisibleInsetsType(publicType, softInputMode)) { + continue; + } + insets = Insets.max(source.calculateVisibleInsets(frame), insets); + } + return insets.toRect(); } private void processSource(InsetsSource source, Rect relativeFrame, boolean ignoreVisibility, diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index f6d6522f80d6..ff8455ab0915 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -677,6 +677,22 @@ public final class SurfaceControl implements Parcelable { } /** + * Set the initial visibility for the SurfaceControl. + * + * @param hidden Whether the Surface is initially HIDDEN. + * @hide + */ + @NonNull + public Builder setHidden(boolean hidden) { + if (hidden) { + mFlags |= HIDDEN; + } else { + mFlags &= ~HIDDEN; + } + return this; + } + + /** * Set a parent surface for our new SurfaceControl. * * Child surfaces are constrained to the onscreen region of their parent. @@ -789,8 +805,7 @@ public final class SurfaceControl implements Parcelable { * @param name The surface name, must not be null. * @param w The surface initial width. * @param h The surface initial height. - * @param flags The surface creation flags. Should always include {@link #HIDDEN} - * in the creation flags. + * @param flags The surface creation flags. * @param metadata Initial metadata. * @throws throws OutOfResourcesException If the SurfaceControl cannot be created. */ @@ -801,15 +816,6 @@ public final class SurfaceControl implements Parcelable { throw new IllegalArgumentException("name must not be null"); } - if ((flags & SurfaceControl.HIDDEN) == 0) { - Log.w(TAG, "Surfaces should always be created with the HIDDEN flag set " - + "to ensure that they are not made visible prematurely before " - + "all of the surface's properties have been configured. " - + "Set the other properties and make the surface visible within " - + "a transaction. New surface name: " + name, - new Throwable()); - } - mName = name; mWidth = w; mHeight = h; diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 52ea2b2142ad..17825444a524 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -379,7 +379,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall * This gets called on a RenderThread worker thread, so members accessed here must * be protected by a lock. */ - final boolean useBLAST = ViewRootImpl.USE_BLAST_BUFFERQUEUE; + final boolean useBLAST = WindowManagerGlobal.USE_BLAST_ADAPTER; viewRoot.registerRtFrameCallback(frame -> { try { final SurfaceControl.Transaction t = useBLAST ? @@ -1107,7 +1107,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall private void applySurfaceTransforms(SurfaceControl surface, SurfaceControl.Transaction t, Rect position, long frameNumber) { - if (frameNumber > 0 && ViewRootImpl.USE_BLAST_BUFFERQUEUE == false) { + if (frameNumber > 0 && !WindowManagerGlobal.USE_BLAST_ADAPTER) { final ViewRootImpl viewRoot = getViewRootImpl(); t.deferTransactionUntilSurface(surface, viewRoot.mSurface, @@ -1125,7 +1125,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } private void setParentSpaceRectangle(Rect position, long frameNumber) { - final boolean useBLAST = ViewRootImpl.USE_BLAST_BUFFERQUEUE; + final boolean useBLAST = WindowManagerGlobal.USE_BLAST_ADAPTER; final ViewRootImpl viewRoot = getViewRootImpl(); final SurfaceControl.Transaction t = useBLAST ? viewRoot.getBLASTSyncTransaction() : mRtTransaction; @@ -1186,7 +1186,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall @Override public void positionLost(long frameNumber) { - boolean useBLAST = ViewRootImpl.USE_BLAST_BUFFERQUEUE; + boolean useBLAST = WindowManagerGlobal.USE_BLAST_ADAPTER; if (DEBUG) { Log.d(TAG, String.format("%d windowPositionLost, frameNr = %d", System.identityHashCode(this), frameNumber)); @@ -1524,7 +1524,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall @Override public void invalidate(boolean invalidateCache) { super.invalidate(invalidateCache); - if (ViewRootImpl.USE_BLAST_BUFFERQUEUE == false) { + if (!WindowManagerGlobal.USE_BLAST_ADAPTER) { return; } final ViewRootImpl viewRoot = getViewRootImpl(); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 0db80e2749c3..13d609b16541 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -11117,7 +11117,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Dispatches {@link WindowInsetsAnimationCallback#onStarted(InsetsAnimation, AnimationBounds)} + * Dispatches {@link WindowInsetsAnimationCallback#onPrepare(InsetsAnimation)} + * when Window Insets animation is being prepared. + * @param animation current animation + * + * @see WindowInsetsAnimationCallback#onPrepare(InsetsAnimation) + */ + public void dispatchWindowInsetsAnimationPrepare( + @NonNull InsetsAnimation animation) { + if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationCallback != null) { + mListenerInfo.mWindowInsetsAnimationCallback.onPrepare(animation); + } + } + + /** + * Dispatches {@link WindowInsetsAnimationCallback#onStart(InsetsAnimation, AnimationBounds)} * when Window Insets animation is started. * @param animation current animation * @param bounds the upper and lower {@link AnimationBounds} that provides range of @@ -11125,10 +11139,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @return the upper and lower {@link AnimationBounds}. */ @NonNull - public AnimationBounds dispatchWindowInsetsAnimationStarted( + public AnimationBounds dispatchWindowInsetsAnimationStart( @NonNull InsetsAnimation animation, @NonNull AnimationBounds bounds) { if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationCallback != null) { - return mListenerInfo.mWindowInsetsAnimationCallback.onStarted(animation, bounds); + return mListenerInfo.mWindowInsetsAnimationCallback.onStart(animation, bounds); } return bounds; } @@ -11149,13 +11163,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Dispatches {@link WindowInsetsAnimationCallback#onFinished(InsetsAnimation)} + * Dispatches {@link WindowInsetsAnimationCallback#onFinish(InsetsAnimation)} * when Window Insets animation finishes. * @param animation The current ongoing {@link InsetsAnimation}. */ - public void dispatchWindowInsetsAnimationFinished(@NonNull InsetsAnimation animation) { + public void dispatchWindowInsetsAnimationFinish(@NonNull InsetsAnimation animation) { if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationCallback != null) { - mListenerInfo.mWindowInsetsAnimationCallback.onFinished(animation); + mListenerInfo.mWindowInsetsAnimationCallback.onFinish(animation); } } diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 5fb71773db8f..047d7da7536f 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -7199,13 +7199,23 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } @Override + public void dispatchWindowInsetsAnimationPrepare( + @NonNull InsetsAnimation animation) { + super.dispatchWindowInsetsAnimationPrepare(animation); + final int count = getChildCount(); + for (int i = 0; i < count; i++) { + getChildAt(i).dispatchWindowInsetsAnimationPrepare(animation); + } + } + + @Override @NonNull - public AnimationBounds dispatchWindowInsetsAnimationStarted( + public AnimationBounds dispatchWindowInsetsAnimationStart( @NonNull InsetsAnimation animation, @NonNull AnimationBounds bounds) { - super.dispatchWindowInsetsAnimationStarted(animation, bounds); + super.dispatchWindowInsetsAnimationStart(animation, bounds); final int count = getChildCount(); for (int i = 0; i < count; i++) { - getChildAt(i).dispatchWindowInsetsAnimationStarted(animation, bounds); + getChildAt(i).dispatchWindowInsetsAnimationStart(animation, bounds); } return bounds; } @@ -7222,11 +7232,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } @Override - public void dispatchWindowInsetsAnimationFinished(@NonNull InsetsAnimation animation) { - super.dispatchWindowInsetsAnimationFinished(animation); + public void dispatchWindowInsetsAnimationFinish(@NonNull InsetsAnimation animation) { + super.dispatchWindowInsetsAnimationFinish(animation); final int count = getChildCount(); for (int i = 0; i < count; i++) { - getChildAt(i).dispatchWindowInsetsAnimationFinished(animation); + getChildAt(i).dispatchWindowInsetsAnimationFinish(animation); } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 522ff9a3bcb5..ca8ba4ca1b8a 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -192,11 +192,6 @@ public final class ViewRootImpl implements ViewParent, private static final boolean MT_RENDERER_AVAILABLE = true; /** - * @hide - */ - public static final boolean USE_BLAST_BUFFERQUEUE = false; - - /** * If set to 2, the view system will switch from using rectangles retrieved from window to * dispatch to the view hierarchy to using {@link InsetsController}, that derives the insets * directly from the full configuration, enabling richer information about the insets state, as @@ -1312,7 +1307,7 @@ public final class ViewRootImpl implements ViewParent, } mWindowAttributes.privateFlags |= compatibleWindowFlag; - if (USE_BLAST_BUFFERQUEUE) { + if (WindowManagerGlobal.USE_BLAST_ADAPTER) { mWindowAttributes.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST; } @@ -1469,6 +1464,7 @@ public final class ViewRootImpl implements ViewParent, return; } mApplyInsetsRequested = true; + requestLayout(); // If this changes during traversal, no need to schedule another one as it will dispatch it // during the current traversal. @@ -2107,6 +2103,12 @@ public final class ViewRootImpl implements ViewParent, Trace.traceEnd(Trace.TRACE_TAG_VIEW); } + private void updateVisibleInsets() { + Rect visibleInsets = mInsetsController.calculateVisibleInsets(mPendingVisibleInsets, + mWindowAttributes.softInputMode); + mAttachInfo.mVisibleInsets.set(visibleInsets); + } + InsetsController getInsetsController() { return mInsetsController; } @@ -2255,7 +2257,7 @@ public final class ViewRootImpl implements ViewParent, insetsChanged = true; } if (!mPendingVisibleInsets.equals(mAttachInfo.mVisibleInsets)) { - mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets); + updateVisibleInsets(); if (DEBUG_LAYOUT) Log.v(mTag, "Visible insets changing to: " + mAttachInfo.mVisibleInsets); } @@ -2321,6 +2323,7 @@ public final class ViewRootImpl implements ViewParent, if (mApplyInsetsRequested) { mApplyInsetsRequested = false; + updateVisibleInsets(); dispatchApplyInsets(host); if (mLayoutRequested) { // Short-circuit catching a new layout request here, so @@ -2505,7 +2508,7 @@ public final class ViewRootImpl implements ViewParent, contentInsetsChanged = true; } if (visibleInsetsChanged) { - mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets); + updateVisibleInsets(); if (DEBUG_LAYOUT) Log.v(mTag, "Visible insets changing to: " + mAttachInfo.mVisibleInsets); } @@ -7273,7 +7276,7 @@ public final class ViewRootImpl implements ViewParent, mPendingStableInsets, mPendingBackDropFrame, mPendingDisplayCutout, mPendingMergedConfiguration, mSurfaceControl, mTempInsets); if (mSurfaceControl.isValid()) { - if (USE_BLAST_BUFFERQUEUE == false) { + if (!WindowManagerGlobal.USE_BLAST_ADAPTER) { mSurface.copyFrom(mSurfaceControl); } else { mSurface.transferFrom(getOrCreateBLASTSurface( diff --git a/core/java/android/view/WindowContainerTransaction.java b/core/java/android/view/WindowContainerTransaction.java index 607a87047733..253794f70ef2 100644 --- a/core/java/android/view/WindowContainerTransaction.java +++ b/core/java/android/view/WindowContainerTransaction.java @@ -62,6 +62,18 @@ public class WindowContainerTransaction implements Parcelable { return this; } + /** + * Notify activies within the hiearchy of a container that they have entered picture-in-picture + * mode with the given bounds. + */ + public WindowContainerTransaction scheduleFinishEnterPip(IWindowContainer container, + Rect bounds) { + Change chg = getOrCreateChange(container.asBinder()); + chg.mSchedulePipCallback = true; + chg.mPinnedBounds = new Rect(bounds); + return this; + } + public Map<IBinder, Change> getChanges() { return mChanges; } @@ -104,12 +116,20 @@ public class WindowContainerTransaction implements Parcelable { private @ActivityInfo.Config int mConfigSetMask = 0; private @WindowConfiguration.WindowConfig int mWindowSetMask = 0; + private boolean mSchedulePipCallback = false; + private Rect mPinnedBounds = null; + public Change() {} protected Change(Parcel in) { mConfiguration.readFromParcel(in); mConfigSetMask = in.readInt(); mWindowSetMask = in.readInt(); + mSchedulePipCallback = (in.readInt() != 0); + if (mSchedulePipCallback ) { + mPinnedBounds = new Rect(); + mPinnedBounds.readFromParcel(in); + } } public Configuration getConfiguration() { @@ -126,6 +146,14 @@ public class WindowContainerTransaction implements Parcelable { return mWindowSetMask; } + /** + * Returns the bounds to be used for scheduling the enter pip callback + * or null if no callback is to be scheduled. + */ + public Rect getEnterPipBounds() { + return mPinnedBounds; + } + @Override public String toString() { final boolean changesBounds = @@ -151,6 +179,11 @@ public class WindowContainerTransaction implements Parcelable { mConfiguration.writeToParcel(dest, flags); dest.writeInt(mConfigSetMask); dest.writeInt(mWindowSetMask); + + dest.writeInt(mSchedulePipCallback ? 1 : 0); + if (mSchedulePipCallback ) { + mPinnedBounds.writeToParcel(dest, flags); + } } @Override diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java index a9cc50f9e65e..9291b5652425 100644 --- a/core/java/android/view/WindowInsets.java +++ b/core/java/android/view/WindowInsets.java @@ -27,8 +27,10 @@ import static android.view.WindowInsets.Type.STATUS_BARS; import static android.view.WindowInsets.Type.SYSTEM_GESTURES; import static android.view.WindowInsets.Type.TAPPABLE_ELEMENT; import static android.view.WindowInsets.Type.all; -import static android.view.WindowInsets.Type.compatSystemInsets; import static android.view.WindowInsets.Type.indexOf; +import static android.view.WindowInsets.Type.systemBars; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST; import android.annotation.IntDef; import android.annotation.IntRange; @@ -40,6 +42,7 @@ import android.graphics.Insets; import android.graphics.Rect; import android.util.SparseArray; import android.view.WindowInsets.Type.InsetsType; +import android.view.WindowManager.LayoutParams.SoftInputModeFlags; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethod; @@ -87,6 +90,8 @@ public final class WindowInsets { private final boolean mStableInsetsConsumed; private final boolean mDisplayCutoutConsumed; + private final int mCompatInsetTypes; + /** * Since new insets may be added in the future that existing apps couldn't * know about, this fully empty constant shouldn't be made available to apps @@ -112,7 +117,7 @@ public final class WindowInsets { boolean isRound, boolean alwaysConsumeSystemBars, DisplayCutout displayCutout) { this(createCompatTypeMap(systemWindowInsetsRect), createCompatTypeMap(stableInsetsRect), createCompatVisibilityMap(createCompatTypeMap(systemWindowInsetsRect)), - isRound, alwaysConsumeSystemBars, displayCutout); + isRound, alwaysConsumeSystemBars, displayCutout, systemBars()); } /** @@ -131,7 +136,7 @@ public final class WindowInsets { @Nullable Insets[] typeMaxInsetsMap, boolean[] typeVisibilityMap, boolean isRound, - boolean alwaysConsumeSystemBars, DisplayCutout displayCutout) { + boolean alwaysConsumeSystemBars, DisplayCutout displayCutout, int compatInsetTypes) { mSystemWindowInsetsConsumed = typeInsetsMap == null; mTypeInsetsMap = mSystemWindowInsetsConsumed ? new Insets[SIZE] @@ -145,6 +150,7 @@ public final class WindowInsets { mTypeVisibilityMap = typeVisibilityMap; mIsRound = isRound; mAlwaysConsumeSystemBars = alwaysConsumeSystemBars; + mCompatInsetTypes = compatInsetTypes; mDisplayCutoutConsumed = displayCutout == null; mDisplayCutout = (mDisplayCutoutConsumed || displayCutout.isEmpty()) @@ -160,7 +166,8 @@ public final class WindowInsets { this(src.mSystemWindowInsetsConsumed ? null : src.mTypeInsetsMap, src.mStableInsetsConsumed ? null : src.mTypeMaxInsetsMap, src.mTypeVisibilityMap, src.mIsRound, - src.mAlwaysConsumeSystemBars, displayCutoutCopyConstructorArgument(src)); + src.mAlwaysConsumeSystemBars, displayCutoutCopyConstructorArgument(src), + src.mCompatInsetTypes); } private static DisplayCutout displayCutoutCopyConstructorArgument(WindowInsets w) { @@ -211,7 +218,8 @@ public final class WindowInsets { /** @hide */ @UnsupportedAppUsage public WindowInsets(Rect systemWindowInsets) { - this(createCompatTypeMap(systemWindowInsets), null, new boolean[SIZE], false, false, null); + this(createCompatTypeMap(systemWindowInsets), null, new boolean[SIZE], false, false, null, + systemBars()); } /** @@ -280,7 +288,7 @@ public final class WindowInsets { */ @NonNull public Insets getSystemWindowInsets() { - return getInsets(mTypeInsetsMap, compatSystemInsets()); + return getInsets(mTypeInsetsMap, mCompatInsetTypes); } /** @@ -439,7 +447,8 @@ public final class WindowInsets { mStableInsetsConsumed ? null : mTypeMaxInsetsMap, mTypeVisibilityMap, mIsRound, mAlwaysConsumeSystemBars, - null /* displayCutout */); + null /* displayCutout */, + mCompatInsetTypes); } @@ -485,7 +494,8 @@ public final class WindowInsets { return new WindowInsets(null, mStableInsetsConsumed ? null : mTypeMaxInsetsMap, mTypeVisibilityMap, mIsRound, mAlwaysConsumeSystemBars, - displayCutoutCopyConstructorArgument(this)); + displayCutoutCopyConstructorArgument(this), + mCompatInsetTypes); } // TODO(b/119190588): replace @code with @link below @@ -555,7 +565,7 @@ public final class WindowInsets { */ @NonNull public Insets getStableInsets() { - return getInsets(mTypeMaxInsetsMap, compatSystemInsets()); + return getInsets(mTypeMaxInsetsMap, mCompatInsetTypes); } /** @@ -733,7 +743,8 @@ public final class WindowInsets { public WindowInsets consumeStableInsets() { return new WindowInsets(mSystemWindowInsetsConsumed ? null : mTypeInsetsMap, null, mTypeVisibilityMap, mIsRound, mAlwaysConsumeSystemBars, - displayCutoutCopyConstructorArgument(this)); + displayCutoutCopyConstructorArgument(this), + mCompatInsetTypes); } /** @@ -817,7 +828,8 @@ public final class WindowInsets { ? null : mDisplayCutout == null ? DisplayCutout.NO_CUTOUT - : mDisplayCutout.inset(left, top, right, bottom)); + : mDisplayCutout.inset(left, top, right, bottom), + mCompatInsetTypes); } @Override @@ -1134,7 +1146,8 @@ public final class WindowInsets { public WindowInsets build() { return new WindowInsets(mSystemInsetsConsumed ? null : mTypeInsetsMap, mStableInsetsConsumed ? null : mTypeMaxInsetsMap, mTypeVisibilityMap, - mIsRound, mAlwaysConsumeSystemBars, mDisplayCutout); + mIsRound, mAlwaysConsumeSystemBars, mDisplayCutout, + systemBars()); } } @@ -1271,15 +1284,6 @@ public final class WindowInsets { } /** - * @return Inset types representing the list of bars that traditionally were denoted as - * system insets. - * @hide - */ - static @InsetsType int compatSystemInsets() { - return STATUS_BARS | NAVIGATION_BARS | IME; - } - - /** * @return All inset types combined. * * TODO: Figure out if this makes sense at all, mixing e.g {@link #systemGestures()} and @@ -1288,6 +1292,17 @@ public final class WindowInsets { public static @InsetsType int all() { return 0xFFFFFFFF; } + + /** + * Checks whether the specified type is considered to be part of visible insets. + * @hide + */ + public static boolean isVisibleInsetsType(int type, + @SoftInputModeFlags int softInputModeFlags) { + int softInputMode = softInputModeFlags & SOFT_INPUT_MASK_ADJUST; + return (type & Type.systemBars()) != 0 + || (softInputMode != SOFT_INPUT_ADJUST_NOTHING && (type & Type.ime()) != 0); + } } /** diff --git a/core/java/android/view/WindowInsetsAnimationCallback.java b/core/java/android/view/WindowInsetsAnimationCallback.java index 5e71f271f1d4..e84c3e33c000 100644 --- a/core/java/android/view/WindowInsetsAnimationCallback.java +++ b/core/java/android/view/WindowInsetsAnimationCallback.java @@ -20,6 +20,7 @@ import android.annotation.FloatRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.Insets; +import android.view.WindowInsets.Type; import android.view.WindowInsets.Type.InsetsType; import android.view.animation.Interpolator; @@ -30,7 +31,47 @@ import android.view.animation.Interpolator; public interface WindowInsetsAnimationCallback { /** - * Called when an inset animation gets started. + * Called when an insets animation is about to start and before the views have been laid out in + * the end state of the animation. The ordering of events during an insets animation is the + * following: + * <p> + * <ul> + * <li>Application calls {@link WindowInsetsController#hideInputMethod()}, + * {@link WindowInsetsController#showInputMethod()}, + * {@link WindowInsetsController#controlInputMethodAnimation(long, WindowInsetsAnimationControlListener)}</li> + * <li>onPrepare is called on the view hierarchy listeners</li> + * <li>{@link View#onApplyWindowInsets} will be called with the end state of the + * animation</li> + * <li>View hierarchy gets laid out according to the changes the application has requested + * due to the new insets being dispatched</li> + * <li>{@link #onStart} is called <em>before</em> the view + * hierarchy gets drawn in the new laid out state</li> + * <li>{@link #onProgress} is called immediately after with the animation start state</li> + * <li>The frame gets drawn.</li> + * </ul> + * <p> + * This ordering allows the application to inspect the end state after the animation has + * finished, and then revert to the starting state of the animation in the first + * {@link #onProgress} callback by using post-layout view properties like {@link View#setX} and + * related methods. + * <p> + * Note: If the animation is application controlled by using + * {@link WindowInsetsController#controlInputMethodAnimation}, the end state of the animation + * is undefined as the application may decide on the end state only by passing in the + * {@code shown} parameter when calling {@link WindowInsetsAnimationController#finish}. In this + * situation, the system will dispatch the insets in the opposite visibility state before the + * animation starts. Example: When controlling the input method with + * {@link WindowInsetsController#controlInputMethodAnimation} and the input method is currently + * showing, {@link View#onApplyWindowInsets} will receive a {@link WindowInsets} instance for + * which {@link WindowInsets#isVisible} will return {@code false} for {@link Type#ime}. + * + * @param animation The animation that is about to start. + */ + default void onPrepare(@NonNull InsetsAnimation animation) { + } + + /** + * Called when an insets animation gets started. * <p> * Note that, like {@link #onProgress}, dispatch of the animation start event is hierarchical: * It will starts at the root of the view hierarchy and then traverse it and invoke the callback @@ -45,7 +86,7 @@ public interface WindowInsetsAnimationCallback { * subtree of the hierarchy. */ @NonNull - default AnimationBounds onStarted( + default AnimationBounds onStart( @NonNull InsetsAnimation animation, @NonNull AnimationBounds bounds) { return bounds; } @@ -72,12 +113,12 @@ public interface WindowInsetsAnimationCallback { WindowInsets onProgress(@NonNull WindowInsets insets); /** - * Called when an inset animation has finished. + * Called when an insets animation has finished. * * @param animation The animation that has finished running. This will be the same instance as - * passed into {@link #onStarted} + * passed into {@link #onStart} */ - default void onFinished(@NonNull InsetsAnimation animation) { + default void onFinish(@NonNull InsetsAnimation animation) { } /** @@ -253,14 +294,14 @@ public interface WindowInsetsAnimationCallback { /** * Insets both the lower and upper bound by the specified insets. This is to be used in - * {@link WindowInsetsAnimationCallback#onStarted} to indicate that a part of the insets has + * {@link WindowInsetsAnimationCallback#onStart} to indicate that a part of the insets has * been used to offset or clip its children, and the children shouldn't worry about that * part anymore. * * @param insets The amount to inset. * @return A copy of this instance inset in the given directions. * @see WindowInsets#inset - * @see WindowInsetsAnimationCallback#onStarted + * @see WindowInsetsAnimationCallback#onStart */ @NonNull public AnimationBounds inset(@NonNull Insets insets) { diff --git a/core/java/android/view/WindowInsetsAnimationControlListener.java b/core/java/android/view/WindowInsetsAnimationControlListener.java index 8a226c1bbe23..f91254de33ff 100644 --- a/core/java/android/view/WindowInsetsAnimationControlListener.java +++ b/core/java/android/view/WindowInsetsAnimationControlListener.java @@ -16,6 +16,7 @@ package android.view; +import android.annotation.Hide; import android.annotation.NonNull; import android.view.WindowInsets.Type.InsetsType; import android.view.inputmethod.EditorInfo; @@ -26,6 +27,12 @@ import android.view.inputmethod.EditorInfo; public interface WindowInsetsAnimationControlListener { /** + * @hide + */ + default void onPrepare(int types) { + } + + /** * Called when the animation is ready to be controlled. This may be delayed when the IME needs * to redraw because of an {@link EditorInfo} change, or when the window is starting up. * diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java index 6de56be2f3c5..9d7f292dbdf5 100644 --- a/core/java/android/view/WindowInsetsController.java +++ b/core/java/android/view/WindowInsetsController.java @@ -149,7 +149,8 @@ public interface WindowInsetsController { * * @param types The {@link InsetsType}s the application has requested to control. * @param durationMillis duration of animation in - * {@link java.util.concurrent.TimeUnit#MILLISECONDS} + * {@link java.util.concurrent.TimeUnit#MILLISECONDS}, or -1 if the + * animation doesn't have a predetermined duration. * @param listener The {@link WindowInsetsAnimationControlListener} that gets called when the * windows are ready to be controlled, among other callbacks. * @hide @@ -162,7 +163,8 @@ public interface WindowInsetsController { * modifying the position of the IME when it's causing insets. * * @param durationMillis duration of the animation in - * {@link java.util.concurrent.TimeUnit#MILLISECONDS} + * {@link java.util.concurrent.TimeUnit#MILLISECONDS}, or -1 if the + * animation doesn't have a predetermined duration. * @param listener The {@link WindowInsetsAnimationControlListener} that gets called when the * IME are ready to be controlled, among other callbacks. */ diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java index 95780023563d..7d5564e1c8be 100644 --- a/core/java/android/view/WindowManagerGlobal.java +++ b/core/java/android/view/WindowManagerGlobal.java @@ -57,6 +57,12 @@ public final class WindowManagerGlobal { private static final String TAG = "WindowManager"; /** + * This flag controls whether ViewRootImpl will utilize the Blast Adapter + * to send buffer updates to SurfaceFlinger + */ + public static final boolean USE_BLAST_ADAPTER = false; + + /** * The user is navigating with keys (not the touch screen), so * navigational focus should be shown. */ diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java index ff31bccfa648..2e5a4b57da18 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -31,6 +31,7 @@ import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; import android.annotation.UserIdInt; +import android.app.RemoteAction; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; @@ -1209,6 +1210,61 @@ public final class AccessibilityManager { } /** + * Register the provided {@link RemoteAction} with the given actionId + * + * @param action The remote action to be registered with the given actionId as system action. + * @param actionId The id uniquely identify the system action. + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY) + public void registerSystemAction(@NonNull RemoteAction action, int actionId) { + final IAccessibilityManager service; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return; + } + } + try { + service.registerSystemAction(action, actionId); + + if (DEBUG) { + Log.i(LOG_TAG, "System action " + action.getTitle() + " is registered."); + } + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error registering system action " + action.getTitle() + " ", re); + } + } + + /** + * Unregister a system action with the given actionId + * + * @param actionId The id uniquely identify the system action to be unregistered. + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY) + public void unregisterSystemAction(int actionId) { + final IAccessibilityManager service; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return; + } + } + try { + service.unregisterSystemAction(actionId); + + if (DEBUG) { + Log.i(LOG_TAG, "System action with actionId " + actionId + " is unregistered."); + } + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error unregistering system action with actionId " + actionId + " ", re); + } + } + + /** * Notifies that the accessibility button in the system's navigation area has been clicked * * @param displayId The logical display id. diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl index 36515b3ba094..392db574d988 100644 --- a/core/java/android/view/accessibility/IAccessibilityManager.aidl +++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl @@ -16,6 +16,7 @@ package android.view.accessibility; +import android.app.RemoteAction; import android.accessibilityservice.AccessibilityServiceInfo; import android.accessibilityservice.IAccessibilityServiceConnection; import android.accessibilityservice.IAccessibilityServiceClient; @@ -82,4 +83,7 @@ interface IAccessibilityManager { int getAccessibilityWindowId(IBinder windowToken); long getRecommendedTimeoutMillis(); + + oneway void registerSystemAction(in RemoteAction action, int actionId); + oneway void unregisterSystemAction(int actionId); } diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 67ce8d2e49b5..f3007a794344 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -93,9 +93,12 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.CancellationException; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; /** @@ -429,7 +432,10 @@ public final class InputMethodManager { * in a background thread. Later, if there is an actual startInput it will wait on * main thread till the background thread completes. */ - private CompletableFuture<Void> mWindowFocusGainFuture; + private Future<?> mWindowFocusGainFuture; + + private ExecutorService mStartInputWorker = Executors.newSingleThreadExecutor( + new ImeThreadFactory("StartInputWorker")); /** * The instance that has previously been sent to the input method. @@ -790,6 +796,19 @@ public final class InputMethodManager { } } + private static class ImeThreadFactory implements ThreadFactory { + private final String mThreadName; + + ImeThreadFactory(String name) { + mThreadName = name; + } + + @Override + public Thread newThread(Runnable r) { + return new Thread(r, mThreadName); + } + } + final IInputMethodClient.Stub mClient = new IInputMethodClient.Stub() { @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { @@ -1978,7 +1997,7 @@ public final class InputMethodManager { if (mWindowFocusGainFuture != null) { mWindowFocusGainFuture.cancel(false/* mayInterruptIfRunning */); } - mWindowFocusGainFuture = CompletableFuture.runAsync(() -> { + mWindowFocusGainFuture = mStartInputWorker.submit(() -> { if (checkFocusNoStartInput(forceNewFocus1)) { // We need to restart input on the current focus view. This // should be done in conjunction with telling the system service diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 95522ba33250..20af76b0d5ca 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -1462,7 +1462,11 @@ public class Editor { return false; } - void onTouchEvent(MotionEvent event) { + /** + * Handles touch events on an editable text view, implementing cursor movement, selection, etc. + */ + @VisibleForTesting + public void onTouchEvent(MotionEvent event) { final boolean filterOutEvent = shouldFilterOutTouchEvent(event); mLastButtonState = event.getButtonState(); if (filterOutEvent) { @@ -2424,7 +2428,9 @@ public class Editor { return mSelectionControllerEnabled; } - private InsertionPointCursorController getInsertionController() { + /** Returns the controller for the insertion cursor. */ + @VisibleForTesting + public @Nullable InsertionPointCursorController getInsertionController() { if (!mInsertionControllerEnabled) { return null; } @@ -2439,8 +2445,9 @@ public class Editor { return mInsertionPointCursorController; } - @Nullable - SelectionModifierCursorController getSelectionController() { + /** Returns the controller for selection. */ + @VisibleForTesting + public @Nullable SelectionModifierCursorController getSelectionController() { if (!mSelectionControllerEnabled) { return null; } @@ -5723,11 +5730,16 @@ public class Editor { } } - class InsertionPointCursorController implements CursorController { + /** Controller for the insertion cursor. */ + @VisibleForTesting + public class InsertionPointCursorController implements CursorController { private InsertionHandleView mHandle; private boolean mIsDraggingCursor; public void onTouchEvent(MotionEvent event) { + if (getSelectionController().isCursorBeingModified()) { + return; + } switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: mIsDraggingCursor = false; @@ -5900,15 +5912,15 @@ public class Editor { } } - class SelectionModifierCursorController implements CursorController { + /** Controller for selection. */ + @VisibleForTesting + public class SelectionModifierCursorController implements CursorController { // The cursor controller handles, lazily created when shown. private SelectionHandleView mStartHandle; private SelectionHandleView mEndHandle; // The offsets of that last touch down event. Remembered to start selection there. private int mMinTouchOffset, mMaxTouchOffset; - private boolean mGestureStayedInTapRegion; - // Where the user first starts the drag motion. private int mStartOffset = -1; @@ -6015,8 +6027,7 @@ public class Editor { eventX, eventY); // Double tap detection - if (mGestureStayedInTapRegion - && mTouchState.isMultiTapInSameArea() + if (mTouchState.isMultiTapInSameArea() && (isMouse || isPositionOnText(eventX, eventY))) { if (TextView.DEBUG_CURSOR) { logCursor("SelectionModifierCursorController: onTouchEvent", @@ -6029,7 +6040,6 @@ public class Editor { } mDiscardNextActionUp = true; } - mGestureStayedInTapRegion = true; mHaventMovedEnoughToStartDrag = true; } break; @@ -6045,25 +6055,8 @@ public class Editor { break; case MotionEvent.ACTION_MOVE: - final ViewConfiguration viewConfig = ViewConfiguration.get( - mTextView.getContext()); - - if (mGestureStayedInTapRegion || mHaventMovedEnoughToStartDrag) { - final float deltaX = eventX - mTouchState.getLastDownX(); - final float deltaY = eventY - mTouchState.getLastDownY(); - final float distanceSquared = deltaX * deltaX + deltaY * deltaY; - - if (mGestureStayedInTapRegion) { - int doubleTapTouchSlop = viewConfig.getScaledDoubleTapTouchSlop(); - mGestureStayedInTapRegion = - distanceSquared <= doubleTapTouchSlop * doubleTapTouchSlop; - } - if (mHaventMovedEnoughToStartDrag) { - // We don't start dragging until the user has moved enough. - int touchSlop = viewConfig.getScaledTouchSlop(); - mHaventMovedEnoughToStartDrag = - distanceSquared <= touchSlop * touchSlop; - } + if (mHaventMovedEnoughToStartDrag) { + mHaventMovedEnoughToStartDrag = !mTouchState.isMovedEnoughForDrag(); } if (isMouse && !isDragAcceleratorActive()) { diff --git a/core/java/android/widget/EditorTouchState.java b/core/java/android/widget/EditorTouchState.java index d53099d44f6f..6277afe2f613 100644 --- a/core/java/android/widget/EditorTouchState.java +++ b/core/java/android/widget/EditorTouchState.java @@ -31,13 +31,15 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** - * Helper class used by {@link Editor} to track state for touch events. + * Helper class used by {@link Editor} to track state for touch events. Ideally the logic here + * should be replaced with {@link android.view.GestureDetector}. * * @hide */ @VisibleForTesting(visibility = PACKAGE) public class EditorTouchState { private float mLastDownX, mLastDownY; + private long mLastDownMillis; private float mLastUpX, mLastUpY; private long mLastUpMillis; @@ -106,9 +108,18 @@ public class EditorTouchState { final int action = event.getActionMasked(); if (action == MotionEvent.ACTION_DOWN) { final boolean isMouse = event.isFromSource(InputDevice.SOURCE_MOUSE); + + // We check both the time between the last up and current down event, as well as the + // time between the first down and up events. The latter check is necessary to handle + // the case when the user taps, drags/holds for some time, and then lifts up and + // quickly taps in the same area. This scenario should not be treated as a double-tap. + // This follows the behavior in GestureDetector. final long millisSinceLastUp = event.getEventTime() - mLastUpMillis; + final long millisBetweenLastDownAndLastUp = mLastUpMillis - mLastDownMillis; + // Detect double tap and triple click. if (millisSinceLastUp <= ViewConfiguration.getDoubleTapTimeout() + && millisBetweenLastDownAndLastUp <= ViewConfiguration.getDoubleTapTimeout() && (mMultiTapStatus == MultiTapStatus.FIRST_TAP || (mMultiTapStatus == MultiTapStatus.DOUBLE_TAP && isMouse))) { if (mMultiTapStatus == MultiTapStatus.FIRST_TAP) { @@ -133,6 +144,7 @@ public class EditorTouchState { } mLastDownX = event.getX(); mLastDownY = event.getY(); + mLastDownMillis = event.getEventTime(); mMovedEnoughForDrag = false; mIsDragCloseToVertical = false; } else if (action == MotionEvent.ACTION_UP) { diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index d86766ebdf58..01a0e9b15463 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -565,7 +565,8 @@ public class RemoteViews implements Parcelable, Filter { } private static void visitIconUri(Icon icon, @NonNull Consumer<Uri> visitor) { - if (icon != null && icon.getType() == Icon.TYPE_URI) { + if (icon != null && (icon.getType() == Icon.TYPE_URI + || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP)) { visitor.accept(icon.getUri()); } } diff --git a/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java index f9ba34ed1c3a..de204badfd0d 100644 --- a/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java +++ b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java @@ -16,6 +16,7 @@ package com.android.internal.app; import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON; +import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY; import static android.view.accessibility.AccessibilityManager.ShortcutType; import android.accessibilityservice.AccessibilityServiceInfo; @@ -24,13 +25,17 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Activity; import android.app.AlertDialog; +import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; +import android.os.UserHandle; import android.provider.Settings; +import android.text.TextUtils; +import android.util.ArraySet; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -49,7 +54,10 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Set; +import java.util.StringJoiner; /** * Activity used to display and persist a service or feature target for the Accessibility button. @@ -59,13 +67,46 @@ public class AccessibilityButtonChooserActivity extends Activity { private static final String MAGNIFICATION_COMPONENT_ID = "com.android.server.accessibility.MagnificationController"; + private static final char SERVICES_SEPARATOR = ':'; + private static final TextUtils.SimpleStringSplitter sStringColonSplitter = + new TextUtils.SimpleStringSplitter(SERVICES_SEPARATOR); + private static final int ACCESSIBILITY_BUTTON_USER_TYPE = convertToUserType( + ACCESSIBILITY_BUTTON); + private static final int ACCESSIBILITY_SHORTCUT_KEY_USER_TYPE = convertToUserType( + ACCESSIBILITY_SHORTCUT_KEY); + private int mShortcutType; private List<AccessibilityButtonTarget> mTargets = new ArrayList<>(); - private List<AccessibilityButtonTarget> mReadyToBeDisabledTargets = new ArrayList<>(); private AlertDialog mAlertDialog; private TargetAdapter mTargetAdapter; /** + * Annotation for different user shortcut type UI type. + * + * {@code DEFAULT} for displaying default value. + * {@code SOFTWARE} for displaying specifying the accessibility services or features which + * choose accessibility button in the navigation bar as preferred shortcut. + * {@code HARDWARE} for displaying specifying the accessibility services or features which + * choose accessibility shortcut as preferred shortcut. + * {@code TRIPLETAP} for displaying specifying magnification to be toggled via quickly + * tapping screen 3 times as preferred shortcut. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + UserShortcutType.DEFAULT, + UserShortcutType.SOFTWARE, + UserShortcutType.HARDWARE, + UserShortcutType.TRIPLETAP, + }) + /** Denotes the user shortcut type. */ + public @interface UserShortcutType { + int DEFAULT = 0; + int SOFTWARE = 1; // 1 << 0 + int HARDWARE = 2; // 1 << 1 + int TRIPLETAP = 4; // 1 << 2 + } + + /** * Annotation for different accessibilityService fragment UI type. * * {@code LEGACY} for displaying appearance aligned with sdk version Q accessibility service @@ -117,7 +158,6 @@ public class AccessibilityButtonChooserActivity extends Activity { ACCESSIBILITY_BUTTON); mTargets.addAll(getServiceTargets(this, mShortcutType)); - // TODO(b/146815548): Will add title to separate which one type mTargetAdapter = new TargetAdapter(mTargets); mAlertDialog = new AlertDialog.Builder(this) .setAdapter(mTargetAdapter, /* listener= */ null) @@ -269,8 +309,10 @@ public class AccessibilityButtonChooserActivity extends Activity { switch (target.getFragmentType()) { case AccessibilityServiceFragmentType.LEGACY: + updateLegacyActionItemVisibility(context, holder); + break; case AccessibilityServiceFragmentType.INVISIBLE: - updateLegacyOrInvisibleActionItemVisibility(context, holder); + updateInvisibleActionItemVisibility(context, holder); break; case AccessibilityServiceFragmentType.INTUITIVE: updateIntuitiveActionItemVisibility(context, holder, target); @@ -283,9 +325,21 @@ public class AccessibilityButtonChooserActivity extends Activity { } } - private void updateLegacyOrInvisibleActionItemVisibility(@NonNull Context context, + private void updateLegacyActionItemVisibility(@NonNull Context context, @NonNull ViewHolder holder) { - final boolean isEditMenuMode = mShortcutMenuMode == ShortcutMenuMode.EDIT; + final boolean isEditMenuMode = (mShortcutMenuMode == ShortcutMenuMode.EDIT); + + holder.mLabelView.setEnabled(!isEditMenuMode); + holder.mViewItem.setEnabled(!isEditMenuMode); + holder.mViewItem.setImageDrawable(context.getDrawable(R.drawable.ic_delete_item)); + holder.mViewItem.setVisibility(View.VISIBLE); + holder.mSwitchItem.setVisibility(View.GONE); + holder.mItemContainer.setVisibility(isEditMenuMode ? View.VISIBLE : View.GONE); + } + + private void updateInvisibleActionItemVisibility(@NonNull Context context, + @NonNull ViewHolder holder) { + final boolean isEditMenuMode = (mShortcutMenuMode == ShortcutMenuMode.EDIT); holder.mViewItem.setImageDrawable(context.getDrawable(R.drawable.ic_delete_item)); holder.mViewItem.setVisibility(View.VISIBLE); @@ -295,7 +349,7 @@ public class AccessibilityButtonChooserActivity extends Activity { private void updateIntuitiveActionItemVisibility(@NonNull Context context, @NonNull ViewHolder holder, AccessibilityButtonTarget target) { - final boolean isEditMenuMode = mShortcutMenuMode == ShortcutMenuMode.EDIT; + final boolean isEditMenuMode = (mShortcutMenuMode == ShortcutMenuMode.EDIT); holder.mViewItem.setImageDrawable(context.getDrawable(R.drawable.ic_delete_item)); holder.mViewItem.setVisibility(isEditMenuMode ? View.VISIBLE : View.GONE); @@ -306,7 +360,7 @@ public class AccessibilityButtonChooserActivity extends Activity { private void updateBounceActionItemVisibility(@NonNull Context context, @NonNull ViewHolder holder) { - final boolean isEditMenuMode = mShortcutMenuMode == ShortcutMenuMode.EDIT; + final boolean isEditMenuMode = (mShortcutMenuMode == ShortcutMenuMode.EDIT); holder.mViewItem.setImageDrawable( isEditMenuMode ? context.getDrawable(R.drawable.ic_delete_item) @@ -383,73 +437,252 @@ public class AccessibilityButtonChooserActivity extends Activity { } private void onTargetDeleted(AdapterView<?> parent, View view, int position, long id) { - // TODO(b/147027236): Will discuss with UX designer what UX behavior about deleting item - // is good for user. - mReadyToBeDisabledTargets.add(mTargets.get(position)); + final AccessibilityButtonTarget target = mTargets.get(position); + final ComponentName targetComponentName = + ComponentName.unflattenFromString(target.getId()); + + switch (target.getFragmentType()) { + case AccessibilityServiceFragmentType.INVISIBLE: + onInvisibleTargetDeleted(targetComponentName); + break; + case AccessibilityServiceFragmentType.INTUITIVE: + onIntuitiveTargetDeleted(targetComponentName); + break; + case AccessibilityServiceFragmentType.LEGACY: + case AccessibilityServiceFragmentType.BOUNCE: + // Do nothing + break; + default: + throw new IllegalStateException("Unexpected fragment type"); + } + mTargets.remove(position); mTargetAdapter.notifyDataSetChanged(); + + if (mTargets.isEmpty()) { + mAlertDialog.dismiss(); + } } - private void onCancelButtonClicked() { - resetAndUpdateTargets(); + private void onInvisibleTargetDeleted(ComponentName componentName) { + if (mShortcutType == ACCESSIBILITY_BUTTON) { + optOutValueFromSettings(this, ACCESSIBILITY_BUTTON_USER_TYPE, componentName); - mTargetAdapter.setShortcutMenuMode(ShortcutMenuMode.LAUNCH); - mTargetAdapter.notifyDataSetChanged(); + if (!hasValueInSettings(this, + ACCESSIBILITY_SHORTCUT_KEY_USER_TYPE, componentName)) { + setAccessibilityServiceState(this, componentName, /* enabled= */ false); + } + } else if (mShortcutType == ACCESSIBILITY_SHORTCUT_KEY) { + optOutValueFromSettings(this, ACCESSIBILITY_SHORTCUT_KEY_USER_TYPE, componentName); - mAlertDialog.getButton(DialogInterface.BUTTON_NEGATIVE).setVisibility(View.GONE); - mAlertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setText( - getString(R.string.edit_accessibility_shortcut_menu_button)); + if (!hasValueInSettings(this, + ACCESSIBILITY_BUTTON_USER_TYPE, componentName)) { + setAccessibilityServiceState(this, componentName, /* enabled= */ false); + } + } else { + throw new IllegalArgumentException("Unsupported shortcut type:" + mShortcutType); + } + } - updateDialogListeners(); + private void onIntuitiveTargetDeleted(ComponentName componentName) { + if (mShortcutType == ACCESSIBILITY_BUTTON) { + optOutValueFromSettings(this, ACCESSIBILITY_BUTTON_USER_TYPE, componentName); + } else if (mShortcutType == ACCESSIBILITY_SHORTCUT_KEY) { + optOutValueFromSettings(this, ACCESSIBILITY_SHORTCUT_KEY_USER_TYPE, componentName); + } else { + throw new IllegalArgumentException("Unsupported shortcut type:" + mShortcutType); + } } - private void onEditButtonClicked() { - mTargetAdapter.setShortcutMenuMode(ShortcutMenuMode.EDIT); + private void onCancelButtonClicked() { + mTargetAdapter.setShortcutMenuMode(ShortcutMenuMode.LAUNCH); mTargetAdapter.notifyDataSetChanged(); - mAlertDialog.getButton(DialogInterface.BUTTON_NEGATIVE).setText( - getString(R.string.cancel_accessibility_shortcut_menu_button)); - mAlertDialog.getButton(DialogInterface.BUTTON_NEGATIVE).setVisibility(View.VISIBLE); mAlertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setText( - getString(R.string.save_accessibility_shortcut_menu_button)); + getString(R.string.edit_accessibility_shortcut_menu_button)); updateDialogListeners(); } - private void onSaveButtonClicked() { - disableTargets(); - resetAndUpdateTargets(); - - mTargetAdapter.setShortcutMenuMode(ShortcutMenuMode.LAUNCH); + private void onEditButtonClicked() { + mTargetAdapter.setShortcutMenuMode(ShortcutMenuMode.EDIT); mTargetAdapter.notifyDataSetChanged(); - mAlertDialog.getButton(DialogInterface.BUTTON_NEGATIVE).setVisibility(View.GONE); mAlertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setText( - getString(R.string.edit_accessibility_shortcut_menu_button)); + getString(R.string.cancel_accessibility_shortcut_menu_button)); updateDialogListeners(); } private void updateDialogListeners() { final boolean isEditMenuMode = - mTargetAdapter.getShortcutMenuMode() == ShortcutMenuMode.EDIT; + (mTargetAdapter.getShortcutMenuMode() == ShortcutMenuMode.EDIT); - mAlertDialog.getButton(DialogInterface.BUTTON_NEGATIVE).setOnClickListener( - view -> onCancelButtonClicked()); mAlertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener( - isEditMenuMode ? view -> onSaveButtonClicked() : view -> onEditButtonClicked()); + isEditMenuMode ? view -> onCancelButtonClicked() : view -> onEditButtonClicked()); mAlertDialog.getListView().setOnItemClickListener( isEditMenuMode ? this::onTargetDeleted : this::onTargetSelected); } - private void disableTargets() { - for (AccessibilityButtonTarget service : mReadyToBeDisabledTargets) { - // TODO(b/146967898): disable services. + /** + * @return the set of enabled accessibility services for {@param userId}. If there are no + * services, it returns the unmodifiable {@link Collections#emptySet()}. + */ + private Set<ComponentName> getEnabledServicesFromSettings(Context context, int userId) { + final String enabledServicesSetting = Settings.Secure.getStringForUser( + context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, + userId); + if (TextUtils.isEmpty(enabledServicesSetting)) { + return Collections.emptySet(); } + + final Set<ComponentName> enabledServices = new HashSet<>(); + final TextUtils.StringSplitter colonSplitter = + new TextUtils.SimpleStringSplitter(SERVICES_SEPARATOR); + colonSplitter.setString(enabledServicesSetting); + + for (String componentNameString : colonSplitter) { + final ComponentName enabledService = ComponentName.unflattenFromString( + componentNameString); + if (enabledService != null) { + enabledServices.add(enabledService); + } + } + + return enabledServices; } - private void resetAndUpdateTargets() { - mTargets.clear(); - mTargets.addAll(getServiceTargets(this, mShortcutType)); + /** + * Changes an accessibility component's state. + */ + private void setAccessibilityServiceState(Context context, ComponentName componentName, + boolean enabled) { + setAccessibilityServiceState(context, componentName, enabled, UserHandle.myUserId()); + } + + /** + * Changes an accessibility component's state for {@param userId}. + */ + private void setAccessibilityServiceState(Context context, ComponentName componentName, + boolean enabled, int userId) { + Set<ComponentName> enabledServices = getEnabledServicesFromSettings( + context, userId); + + if (enabledServices.isEmpty()) { + enabledServices = new ArraySet<>(/* capacity= */ 1); + } + + if (enabled) { + enabledServices.add(componentName); + } else { + enabledServices.remove(componentName); + } + + final StringBuilder enabledServicesBuilder = new StringBuilder(); + for (ComponentName enabledService : enabledServices) { + enabledServicesBuilder.append(enabledService.flattenToString()); + enabledServicesBuilder.append( + SERVICES_SEPARATOR); + } + + final int enabledServicesBuilderLength = enabledServicesBuilder.length(); + if (enabledServicesBuilderLength > 0) { + enabledServicesBuilder.deleteCharAt(enabledServicesBuilderLength - 1); + } + + Settings.Secure.putStringForUser(context.getContentResolver(), + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, + enabledServicesBuilder.toString(), userId); + } + + /** + * Opts out component name into colon-separated {@code shortcutType} key's string in Settings. + * + * @param context The current context. + * @param shortcutType The preferred shortcut type user selected. + * @param componentName The component name that need to be opted out from Settings. + */ + private void optOutValueFromSettings( + Context context, int shortcutType, ComponentName componentName) { + final StringJoiner joiner = new StringJoiner(String.valueOf(SERVICES_SEPARATOR)); + final String targetsKey = convertToKey(shortcutType); + final String targetsValue = Settings.Secure.getString(context.getContentResolver(), + targetsKey); + + if (TextUtils.isEmpty(targetsValue)) { + return; + } + + sStringColonSplitter.setString(targetsValue); + while (sStringColonSplitter.hasNext()) { + final String name = sStringColonSplitter.next(); + if (TextUtils.isEmpty(name) || (componentName.flattenToString()).equals(name)) { + continue; + } + joiner.add(name); + } + + Settings.Secure.putString(context.getContentResolver(), targetsKey, joiner.toString()); + } + + /** + * Returns if component name existed in Settings. + * + * @param context The current context. + * @param shortcutType The preferred shortcut type user selected. + * @param componentName The component name that need to be checked existed in Settings. + * @return {@code true} if componentName existed in Settings. + */ + private boolean hasValueInSettings(Context context, @UserShortcutType int shortcutType, + @NonNull ComponentName componentName) { + final String targetKey = convertToKey(shortcutType); + final String targetString = Settings.Secure.getString(context.getContentResolver(), + targetKey); + + if (TextUtils.isEmpty(targetString)) { + return false; + } + + sStringColonSplitter.setString(targetString); + while (sStringColonSplitter.hasNext()) { + final String name = sStringColonSplitter.next(); + if ((componentName.flattenToString()).equals(name)) { + return true; + } + } + + return false; + } + + /** + * Converts {@link UserShortcutType} to key in Settings. + * + * @param type The shortcut type. + * @return Mapping key in Settings. + */ + private String convertToKey(@UserShortcutType int type) { + switch (type) { + case UserShortcutType.SOFTWARE: + return Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT; + case UserShortcutType.HARDWARE: + return Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE; + case UserShortcutType.TRIPLETAP: + return Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED; + default: + throw new IllegalArgumentException( + "Unsupported user shortcut type: " + type); + } + } + + private static @UserShortcutType int convertToUserType(@ShortcutType int type) { + switch (type) { + case ACCESSIBILITY_BUTTON: + return UserShortcutType.SOFTWARE; + case ACCESSIBILITY_SHORTCUT_KEY: + return UserShortcutType.HARDWARE; + default: + throw new IllegalArgumentException( + "Unsupported shortcut type:" + type); + } } } diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl index 4e764fcab7b7..dabaf5a8263c 100644 --- a/core/java/com/android/internal/app/IAppOpsService.aidl +++ b/core/java/com/android/internal/app/IAppOpsService.aidl @@ -58,10 +58,12 @@ interface IAppOpsService { List<AppOpsManager.PackageOps> getPackagesForOps(in int[] ops); @UnsupportedAppUsage List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName, in int[] ops); - void getHistoricalOps(int uid, String packageName, in List<String> ops, long beginTimeMillis, - long endTimeMillis, int flags, in RemoteCallback callback); - void getHistoricalOpsFromDiskRaw(int uid, String packageName, in List<String> ops, - long beginTimeMillis, long endTimeMillis, int flags, in RemoteCallback callback); + void getHistoricalOps(int uid, String packageName, String featureId, in List<String> ops, + int filter, long beginTimeMillis, long endTimeMillis, int flags, + in RemoteCallback callback); + void getHistoricalOpsFromDiskRaw(int uid, String packageName, String featureId, + in List<String> ops, int filter, long beginTimeMillis, long endTimeMillis, int flags, + in RemoteCallback callback); void offsetHistory(long duration); void setHistoryParameters(int mode, long baseSnapshotInterval, int compressionStep); void addHistoricalOps(in AppOpsManager.HistoricalOps ops); diff --git a/core/java/com/android/internal/app/ISoundTriggerService.aidl b/core/java/com/android/internal/app/ISoundTriggerService.aidl index d94294f0aa22..74bfb963c6b0 100644 --- a/core/java/com/android/internal/app/ISoundTriggerService.aidl +++ b/core/java/com/android/internal/app/ISoundTriggerService.aidl @@ -61,10 +61,6 @@ interface ISoundTriggerService { int setParameter(in ParcelUuid soundModelId, in ModelParams modelParam, int value); - /** - * @throws UnsupportedOperationException if hal or model do not support this API. - * @throws IllegalArgumentException if invalid model handle or parameter is passed. - */ int getParameter(in ParcelUuid soundModelId, in ModelParams modelParam); @nullable SoundTrigger.ModelParamRange queryParameter(in ParcelUuid soundModelId, diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl index f462f5d2571d..be2d1d60e9a2 100644 --- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl @@ -26,6 +26,7 @@ import com.android.internal.app.IVoiceInteractionSessionShowCallback; import com.android.internal.app.IVoiceInteractor; import com.android.internal.app.IVoiceInteractionSessionListener; import android.hardware.soundtrigger.IRecognitionStatusCallback; +import android.hardware.soundtrigger.ModelParams; import android.hardware.soundtrigger.SoundTrigger; import android.service.voice.IVoiceInteractionService; import android.service.voice.IVoiceInteractionSession; @@ -91,6 +92,49 @@ interface IVoiceInteractionManagerService { */ int stopRecognition(in IVoiceInteractionService service, int keyphraseId, in IRecognitionStatusCallback callback); + /** + * Set a model specific ModelParams with the given value. This + * parameter will keep its value for the duration the model is loaded regardless of starting and + * stopping recognition. Once the model is unloaded, the value will be lost. + * queryParameter should be checked first before calling this method. + * + * @param service The current VoiceInteractionService. + * @param keyphraseId The unique identifier for the keyphrase. + * @param modelParam ModelParams + * @param value Value to set + * @return - {@link SoundTrigger#STATUS_OK} in case of success + * - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached + * - {@link SoundTrigger#STATUS_BAD_VALUE} invalid input parameter + * - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or + * if API is not supported by HAL + */ + int setParameter(in IVoiceInteractionService service, int keyphraseId, + in ModelParams modelParam, int value); + /** + * Get a model specific ModelParams. This parameter will keep its value + * for the duration the model is loaded regardless of starting and stopping recognition. + * Once the model is unloaded, the value will be lost. If the value is not set, a default + * value is returned. See ModelParams for parameter default values. + * queryParameter should be checked first before calling this method. + * + * @param service The current VoiceInteractionService. + * @param keyphraseId The unique identifier for the keyphrase. + * @param modelParam ModelParams + * @return value of parameter + */ + int getParameter(in IVoiceInteractionService service, int keyphraseId, + in ModelParams modelParam); + /** + * Determine if parameter control is supported for the given model handle. + * This method should be checked prior to calling setParameter or getParameter. + * + * @param service The current VoiceInteractionService. + * @param keyphraseId The unique identifier for the keyphrase. + * @param modelParam ModelParams + * @return supported range of parameter, null if not supported + */ + @nullable SoundTrigger.ModelParamRange queryParameter(in IVoiceInteractionService service, + int keyphraseId, in ModelParams modelParam); /** * @return the component name for the currently active voice interaction service diff --git a/core/java/com/android/internal/compat/AndroidBuildClassifier.java b/core/java/com/android/internal/compat/AndroidBuildClassifier.java new file mode 100644 index 000000000000..0b937fad7df1 --- /dev/null +++ b/core/java/com/android/internal/compat/AndroidBuildClassifier.java @@ -0,0 +1,34 @@ +/* + * 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.internal.compat; + +import android.os.Build; + +/** + * Platform private class for determining the type of Android build installed. + * + */ +public class AndroidBuildClassifier { + + public boolean isDebuggableBuild() { + return Build.IS_DEBUGGABLE; + } + + public boolean isFinalBuild() { + return "REL".equals(Build.VERSION.CODENAME); + } +} diff --git a/core/java/com/android/internal/compat/IOverrideValidator.aidl b/core/java/com/android/internal/compat/IOverrideValidator.aidl new file mode 100644 index 000000000000..add4708863aa --- /dev/null +++ b/core/java/com/android/internal/compat/IOverrideValidator.aidl @@ -0,0 +1,38 @@ +/* + * 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.internal.compat; + +import android.content.pm.ApplicationInfo; + +import com.android.internal.compat.OverrideAllowedState; + +/** + * Platform private API for determining whether a changeId can be overridden. + * + * {@hide} + */ +interface IOverrideValidator +{ + /** + * Validation function. + * @param changeId id of the change to be toggled on or off. + * @param packageName package of the app for which the change should be overridden. + * @return {@link OverrideAllowedState} specifying whether the change can be overridden for + * the given package or a reason why not. + */ + OverrideAllowedState getOverrideAllowedState(long changeId, String packageName); +} diff --git a/core/java/com/android/internal/compat/IPlatformCompat.aidl b/core/java/com/android/internal/compat/IPlatformCompat.aidl index 7dcb12c9e72b..4c203d394759 100644 --- a/core/java/com/android/internal/compat/IPlatformCompat.aidl +++ b/core/java/com/android/internal/compat/IPlatformCompat.aidl @@ -17,6 +17,7 @@ package com.android.internal.compat; import android.content.pm.ApplicationInfo; +import com.android.internal.compat.IOverrideValidator; import java.util.Map; parcelable CompatibilityChangeConfig; @@ -195,4 +196,9 @@ interface IPlatformCompat * @return An array of {@link CompatChangeInfo} known to the service. */ CompatibilityChangeInfo[] listAllChanges(); + + /** + * Get an instance that can determine whether a changeid can be overridden for a package name. + */ + IOverrideValidator getOverrideValidator(); } diff --git a/core/java/com/android/internal/compat/OverrideAllowedState.aidl b/core/java/com/android/internal/compat/OverrideAllowedState.aidl new file mode 100644 index 000000000000..10ceac786841 --- /dev/null +++ b/core/java/com/android/internal/compat/OverrideAllowedState.aidl @@ -0,0 +1,19 @@ +/* + * 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.internal.compat; + +parcelable OverrideAllowedState;
\ No newline at end of file diff --git a/core/java/com/android/internal/compat/OverrideAllowedState.java b/core/java/com/android/internal/compat/OverrideAllowedState.java new file mode 100644 index 000000000000..56216c251070 --- /dev/null +++ b/core/java/com/android/internal/compat/OverrideAllowedState.java @@ -0,0 +1,153 @@ +/* + * 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.internal.compat; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * This class contains all the possible override allowed states. + */ +public final class OverrideAllowedState implements Parcelable { + @IntDef({ + ALLOWED, + DISABLED_NOT_DEBUGGABLE, + DISABLED_NON_TARGET_SDK, + DISABLED_TARGET_SDK_TOO_HIGH, + PACKAGE_DOES_NOT_EXIST + }) + @Retention(RetentionPolicy.SOURCE) + public @interface State { + } + + /** + * Change can be overridden. + */ + public static final int ALLOWED = 0; + /** + * Change cannot be overridden, due to the app not being debuggable. + */ + public static final int DISABLED_NOT_DEBUGGABLE = 1; + /** + * Change cannot be overridden, due to the build being non-debuggable and the change being + * non-targetSdk. + */ + public static final int DISABLED_NON_TARGET_SDK = 2; + /** + * Change cannot be overridden, due to the app's targetSdk being above the change's targetSdk. + */ + public static final int DISABLED_TARGET_SDK_TOO_HIGH = 3; + /** + * Package does not exist. + */ + public static final int PACKAGE_DOES_NOT_EXIST = 4; + + @State + public final int state; + public final int appTargetSdk; + public final int changeIdTargetSdk; + + private OverrideAllowedState(Parcel parcel) { + state = parcel.readInt(); + appTargetSdk = parcel.readInt(); + changeIdTargetSdk = parcel.readInt(); + } + + public OverrideAllowedState(@State int state, int appTargetSdk, int changeIdTargetSdk) { + this.state = state; + this.appTargetSdk = appTargetSdk; + this.changeIdTargetSdk = changeIdTargetSdk; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(state); + out.writeInt(appTargetSdk); + out.writeInt(changeIdTargetSdk); + } + + /** + * Enforces the policy for overriding compat changes. + * + * @param changeId the change id that was attempted to be overridden. + * @param packageName the package for which the attempt was made. + * @throws SecurityException if the policy forbids this operation. + */ + public void enforce(long changeId, String packageName) + throws SecurityException { + switch (state) { + case ALLOWED: + return; + case DISABLED_NOT_DEBUGGABLE: + throw new SecurityException( + "Cannot override a change on a non-debuggable app and user build."); + case DISABLED_NON_TARGET_SDK: + throw new SecurityException( + "Cannot override a default enabled/disabled change on a user build."); + case DISABLED_TARGET_SDK_TOO_HIGH: + throw new SecurityException(String.format( + "Cannot override %1$d for %2$s because the app's targetSdk (%3$d) is " + + "above the change's targetSdk threshold (%4$d)", + changeId, packageName, appTargetSdk, changeIdTargetSdk)); + case PACKAGE_DOES_NOT_EXIST: + throw new SecurityException(String.format( + "Cannot override %1$d for %2$s because the package does not exist, and " + + "the change is targetSdk gated.", + changeId, packageName)); + } + } + + public static final @NonNull + Parcelable.Creator<OverrideAllowedState> CREATOR = + new Parcelable.Creator<OverrideAllowedState>() { + public OverrideAllowedState createFromParcel(Parcel parcel) { + OverrideAllowedState info = new OverrideAllowedState(parcel); + return info; + } + + public OverrideAllowedState[] newArray(int size) { + return new OverrideAllowedState[size]; + } + }; + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof OverrideAllowedState)) { + return false; + } + OverrideAllowedState otherState = (OverrideAllowedState) obj; + return state == otherState.state + && appTargetSdk == otherState.appTargetSdk + && changeIdTargetSdk == otherState.changeIdTargetSdk; + } +} diff --git a/core/java/com/android/internal/infra/AndroidFuture.java b/core/java/com/android/internal/infra/AndroidFuture.java index b250578c8454..9f15d8991fa7 100644 --- a/core/java/com/android/internal/infra/AndroidFuture.java +++ b/core/java/com/android/internal/infra/AndroidFuture.java @@ -75,6 +75,7 @@ public class AndroidFuture<T> extends CompletableFuture<T> implements Parcelable private static final boolean DEBUG = false; private static final String LOG_TAG = AndroidFuture.class.getSimpleName(); + private static final StackTraceElement[] EMPTY_STACK_TRACE = new StackTraceElement[0]; private final @NonNull Object mLock = new Object(); @GuardedBy("mLock") @@ -95,15 +96,7 @@ public class AndroidFuture<T> extends CompletableFuture<T> implements Parcelable // Done if (in.readBoolean()) { // Failed - try { - in.readException(); - } catch (Throwable e) { - completeExceptionally(e); - } - if (!isCompletedExceptionally()) { - throw new IllegalStateException( - "Error unparceling AndroidFuture: exception expected"); - } + completeExceptionally(unparcelException(in)); } else { // Success complete((T) in.readValue(null)); @@ -512,14 +505,9 @@ public class AndroidFuture<T> extends CompletableFuture<T> implements Parcelable T result; try { result = get(); - } catch (Exception t) { - // Exceptions coming out of get() are wrapped in ExecutionException, which is not - // handled by Parcel. - if (t instanceof ExecutionException && t.getCause() instanceof Exception) { - t = (Exception) t.getCause(); - } + } catch (Throwable t) { dest.writeBoolean(true); - dest.writeException(t); + parcelException(dest, unwrapExecutionException(t)); return; } dest.writeBoolean(false); @@ -528,22 +516,76 @@ public class AndroidFuture<T> extends CompletableFuture<T> implements Parcelable dest.writeStrongBinder(new IAndroidFuture.Stub() { @Override public void complete(AndroidFuture resultContainer) { + boolean changed; try { - AndroidFuture.this.complete((T) resultContainer.get()); + changed = AndroidFuture.this.complete((T) resultContainer.get()); } catch (Throwable t) { - // If resultContainer was completed exceptionally, get() wraps the - // underlying exception in an ExecutionException. Unwrap it now to avoid - // double-layering ExecutionExceptions. - if (t instanceof ExecutionException && t.getCause() != null) { - t = t.getCause(); - } - completeExceptionally(t); + changed = completeExceptionally(unwrapExecutionException(t)); + } + if (!changed) { + Log.w(LOG_TAG, "Remote result " + resultContainer + + " ignored, as local future is already completed: " + + AndroidFuture.this); } } }.asBinder()); } } + /** + * Exceptions coming out of {@link #get} are wrapped in {@link ExecutionException} + */ + Throwable unwrapExecutionException(Throwable t) { + return t instanceof ExecutionException + ? t.getCause() + : t; + } + + /** + * Alternative to {@link Parcel#writeException} that stores the stack trace, in a + * way consistent with the binder IPC exception propagation behavior. + */ + private static void parcelException(Parcel p, @Nullable Throwable t) { + p.writeBoolean(t == null); + if (t == null) { + return; + } + + p.writeInt(Parcel.getExceptionCode(t)); + p.writeString(t.getClass().getName()); + p.writeString(t.getMessage()); + p.writeStackTrace(t); + parcelException(p, t.getCause()); + } + + /** + * @see #parcelException + */ + private static @Nullable Throwable unparcelException(Parcel p) { + if (p.readBoolean()) { + return null; + } + + int exCode = p.readInt(); + String cls = p.readString(); + String msg = p.readString(); + String stackTrace = p.readInt() > 0 ? p.readString() : "\t<stack trace unavailable>"; + msg += "\n" + stackTrace; + + Exception ex = p.createExceptionOrNull(exCode, msg); + if (ex == null) { + ex = new RuntimeException(cls + ": " + msg); + } + ex.setStackTrace(EMPTY_STACK_TRACE); + + Throwable cause = unparcelException(p); + if (cause != null) { + ex.initCause(ex); + } + + return ex; + } + @Override public int describeContents() { return 0; diff --git a/core/java/com/android/internal/logging/UiEventLogger.java b/core/java/com/android/internal/logging/UiEventLogger.java index cca97f689f4e..3a450de6c8e6 100644 --- a/core/java/com/android/internal/logging/UiEventLogger.java +++ b/core/java/com/android/internal/logging/UiEventLogger.java @@ -16,6 +16,9 @@ package com.android.internal.logging; +import android.annotation.NonNull; +import android.annotation.Nullable; + /** * Logging interface for UI events. Normal implementation is UiEventLoggerImpl. * For testing, use fake implementation UiEventLoggerFake. @@ -26,13 +29,24 @@ public interface UiEventLogger { /** Put your Event IDs in enums that implement this interface, and document them using the * UiEventEnum annotation. * Event IDs must be globally unique. This will be enforced by tooling (forthcoming). - * OEMs should use event IDs above 100000. + * OEMs should use event IDs above 100000 and below 1000000 (1 million). */ interface UiEventEnum { int getId(); } + + /** + * Log a simple event, with no package information. Does nothing if event.getId() <= 0. + * @param event an enum implementing UiEventEnum interface. + */ + void log(@NonNull UiEventEnum event); + /** - * Log a simple event, with no package or instance ID. + * 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. + * @param uid the uid of the relevant app, if known (0 otherwise). + * @param packageName the package name of the relevant app, if known (null otherwise). */ - void log(UiEventEnum eventID); + void log(@NonNull UiEventEnum event, int uid, @Nullable String packageName); } diff --git a/core/java/com/android/internal/logging/UiEventLoggerImpl.java b/core/java/com/android/internal/logging/UiEventLoggerImpl.java index e64fba2d2a31..bdf460c710c3 100644 --- a/core/java/com/android/internal/logging/UiEventLoggerImpl.java +++ b/core/java/com/android/internal/logging/UiEventLoggerImpl.java @@ -24,14 +24,16 @@ import android.util.StatsLog; * See UiEventReported atom in atoms.proto for more context. */ public class UiEventLoggerImpl implements UiEventLogger { - /** - * Log a simple event, with no package or instance ID. - */ @Override public void log(UiEventEnum event) { + log(event, 0, null); + } + + @Override + public void log(UiEventEnum event, int uid, String packageName) { final int eventID = event.getId(); if (eventID > 0) { - StatsLog.write(StatsLog.UI_EVENT_REPORTED, eventID, 0, null); + StatsLog.write(StatsLog.UI_EVENT_REPORTED, eventID, uid, packageName); } } } diff --git a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java index 92e9bbb77bd3..6be5b81afee2 100644 --- a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java +++ b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java @@ -30,7 +30,7 @@ public class UiEventLoggerFake implements UiEventLogger { /** * Immutable data class used to record fake log events. */ - public class FakeUiEvent { + public static class FakeUiEvent { public final int eventId; public final int uid; public final String packageName; @@ -44,15 +44,20 @@ public class UiEventLoggerFake implements UiEventLogger { private Queue<FakeUiEvent> mLogs = new LinkedList<FakeUiEvent>(); + public Queue<FakeUiEvent> getLogs() { + return mLogs; + } + @Override public void log(UiEventEnum event) { + log(event, 0, null); + } + + @Override + public void log(UiEventEnum event, int uid, String packageName) { final int eventId = event.getId(); if (eventId > 0) { - mLogs.offer(new FakeUiEvent(eventId, 0, null)); + mLogs.offer(new FakeUiEvent(eventId, uid, packageName)); } } - - public Queue<FakeUiEvent> getLogs() { - return mLogs; - } } diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java index a86d702ba323..179828c4b456 100644 --- a/core/java/com/android/internal/os/RuntimeInit.java +++ b/core/java/com/android/internal/os/RuntimeInit.java @@ -35,7 +35,6 @@ import com.android.internal.logging.AndroidConfig; import com.android.server.NetworkManagementSocketTagger; import dalvik.system.RuntimeHooks; -import dalvik.system.ThreadPrioritySetter; import dalvik.system.VMRuntime; import libcore.content.type.MimeMap; @@ -205,7 +204,6 @@ public class RuntimeInit { */ public static void preForkInit() { if (DEBUG) Slog.d(TAG, "Entered preForkInit."); - RuntimeHooks.setThreadPrioritySetter(new RuntimeThreadPrioritySetter()); RuntimeInit.enableDdms(); // TODO(b/142019040#comment13): Decide whether to load the default instance eagerly, i.e. // MimeMap.setDefault(DefaultMimeMapFactory.create()); @@ -218,35 +216,6 @@ public class RuntimeInit { MimeMap.setDefaultSupplier(DefaultMimeMapFactory::create); } - private static class RuntimeThreadPrioritySetter implements ThreadPrioritySetter { - // Should remain consistent with kNiceValues[] in system/libartpalette/palette_android.cc - private static final int[] NICE_VALUES = { - Process.THREAD_PRIORITY_LOWEST, // 1 (MIN_PRIORITY) - Process.THREAD_PRIORITY_BACKGROUND + 6, - Process.THREAD_PRIORITY_BACKGROUND + 3, - Process.THREAD_PRIORITY_BACKGROUND, - Process.THREAD_PRIORITY_DEFAULT, // 5 (NORM_PRIORITY) - Process.THREAD_PRIORITY_DEFAULT - 2, - Process.THREAD_PRIORITY_DEFAULT - 4, - Process.THREAD_PRIORITY_URGENT_DISPLAY + 3, - Process.THREAD_PRIORITY_URGENT_DISPLAY + 2, - Process.THREAD_PRIORITY_URGENT_DISPLAY // 10 (MAX_PRIORITY) - }; - - @Override - public void setPriority(int priority) { - // Check NICE_VALUES[] length first. - if (NICE_VALUES.length != (1 + Thread.MAX_PRIORITY - Thread.MIN_PRIORITY)) { - throw new AssertionError("Unexpected NICE_VALUES.length=" + NICE_VALUES.length); - } - // Priority should be in the range of MIN_PRIORITY (1) to MAX_PRIORITY (10). - if (priority < Thread.MIN_PRIORITY || priority > Thread.MAX_PRIORITY) { - throw new IllegalArgumentException("Priority out of range: " + priority); - } - Process.setThreadPriority(NICE_VALUES[priority - Thread.MIN_PRIORITY]); - } - } - @UnsupportedAppUsage protected static final void commonInit() { if (DEBUG) Slog.d(TAG, "Entered RuntimeInit!"); diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index 2248b8853f8c..f0a346ab25fd 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -145,6 +145,11 @@ public final class Zygote { /** The lower file system should be bind mounted directly on external storage */ public static final int MOUNT_EXTERNAL_PASS_THROUGH = IVold.REMOUNT_MODE_PASS_THROUGH; + /** Use the regular scoped storage filesystem, but Android/ should be writable. + * Used to support the applications hosting DownloadManager and the MTP server. + */ + public static final int MOUNT_EXTERNAL_ANDROID_WRITABLE = IVold.REMOUNT_MODE_ANDROID_WRITABLE; + /** Number of bytes sent to the Zygote over USAP pipes or the pool event FD */ static final int USAP_MANAGEMENT_MESSAGE_BYTES = 8; diff --git a/core/java/com/android/internal/os/ZygoteArguments.java b/core/java/com/android/internal/os/ZygoteArguments.java index d3499541a3a3..37f570bba238 100644 --- a/core/java/com/android/internal/os/ZygoteArguments.java +++ b/core/java/com/android/internal/os/ZygoteArguments.java @@ -376,6 +376,8 @@ class ZygoteArguments { mMountExternal = Zygote.MOUNT_EXTERNAL_LEGACY; } else if (arg.equals("--mount-external-pass-through")) { mMountExternal = Zygote.MOUNT_EXTERNAL_PASS_THROUGH; + } else if (arg.equals("--mount-external-android-writable")) { + mMountExternal = Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE; } else if (arg.equals("--query-abi-list")) { mAbiListQuery = true; } else if (arg.equals("--get-pid")) { diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl index 6933f166a06d..8e97ae1e4bd1 100644 --- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl +++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl @@ -63,7 +63,7 @@ interface ITelephonyRegistry { void notifyDataActivity(int state); void notifyDataActivityForSubscriber(in int subId, int state); void notifyDataConnectionForSubscriber( - int phoneId, int subId, String apnType, in PreciseDataConnectionState preciseState); + int phoneId, int subId, int apnType, in PreciseDataConnectionState preciseState); @UnsupportedAppUsage void notifyDataConnectionFailed(String apnType); // Uses CellIdentity which is Parcelable here; will convert to CellLocation in client. @@ -75,7 +75,7 @@ interface ITelephonyRegistry { int foregroundCallState, int backgroundCallState); void notifyDisconnectCause(int phoneId, int subId, int disconnectCause, int preciseDisconnectCause); - void notifyPreciseDataConnectionFailed(int phoneId, int subId, String apnType, String apn, + void notifyPreciseDataConnectionFailed(int phoneId, int subId, int apnType, String apn, int failCause); void notifyCellInfoForSubscriber(in int subId, in List<CellInfo> cellInfo); void notifySrvccStateChanged(in int subId, in int lteState); diff --git a/telephony/java/com/android/internal/telephony/IWapPushManager.aidl b/core/java/com/android/internal/telephony/IWapPushManager.aidl index 1c3df65336f8..9f6851b8c254 100644 --- a/telephony/java/com/android/internal/telephony/IWapPushManager.aidl +++ b/core/java/com/android/internal/telephony/IWapPushManager.aidl @@ -18,6 +18,7 @@ package com.android.internal.telephony; import android.content.Intent; +/** @hide */ interface IWapPushManager { /** * Processes WAP push message and triggers the receiver application registered @@ -26,11 +27,10 @@ interface IWapPushManager { int processMessage(String app_id, String content_type, in Intent intent); /** - * Add receiver application into the application ID table. - * Returns true if inserting the information is successfull. Inserting the duplicated + * Adds receiver application into the application ID table. + * Returns true if inserting the information is successful. Inserting duplicated * record in the application ID table is not allowed. Use update/delete method. */ - @UnsupportedAppUsage boolean addPackage(String x_app_id, String content_type, String package_name, String class_name, int app_type, boolean need_signature, boolean further_processing); @@ -39,17 +39,14 @@ interface IWapPushManager { * Updates receiver application that is last added. * Returns true if updating the information is successfull. */ - @UnsupportedAppUsage boolean updatePackage(String x_app_id, String content_type, String package_name, String class_name, int app_type, boolean need_signature, boolean further_processing); /** - * Delites receiver application information. + * Deletes receiver application information. * Returns true if deleting is successfull. */ - @UnsupportedAppUsage boolean deletePackage(String x_app_id, String content_type, - String package_name, String class_name); + String package_name, String class_name); } - diff --git a/core/java/com/android/internal/telephony/WapPushManagerParams.java b/core/java/com/android/internal/telephony/WapPushManagerParams.java new file mode 100644 index 000000000000..eafb8f1b423f --- /dev/null +++ b/core/java/com/android/internal/telephony/WapPushManagerParams.java @@ -0,0 +1,72 @@ +/* + * 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 com.android.internal.telephony; + +import android.telephony.WapPushManagerConnector; + +/** + * WapPushManager constant value definitions. + * @hide + */ +public class WapPushManagerParams { + /** + * Application type activity + */ + public static final int APP_TYPE_ACTIVITY = 0; + + /** + * Application type service + */ + public static final int APP_TYPE_SERVICE = 1; + + /** + * Process Message return value + * Message is handled + */ + public static final int MESSAGE_HANDLED = WapPushManagerConnector.RESULT_MESSAGE_HANDLED; + + /** + * Process Message return value + * Application ID or content type was not found in the application ID table + */ + public static final int APP_QUERY_FAILED = WapPushManagerConnector.RESULT_APP_QUERY_FAILED; + + /** + * Process Message return value + * Receiver application signature check failed + */ + public static final int SIGNATURE_NO_MATCH = WapPushManagerConnector.RESULT_SIGNATURE_NO_MATCH; + + /** + * Process Message return value + * Receiver application was not found + */ + public static final int INVALID_RECEIVER_NAME = + WapPushManagerConnector.RESULT_INVALID_RECEIVER_NAME; + + /** + * Process Message return value + * Unknown exception + */ + public static final int EXCEPTION_CAUGHT = WapPushManagerConnector.RESULT_EXCEPTION_CAUGHT; + + /** + * Process Message return value + * Need further processing after WapPushManager message processing + */ + public static final int FURTHER_PROCESSING = WapPushManagerConnector.RESULT_FURTHER_PROCESSING; +} diff --git a/core/java/com/android/internal/util/GrowingArrayUtils.java b/core/java/com/android/internal/util/GrowingArrayUtils.java index 9f563667e019..597fe6b53d11 100644 --- a/core/java/com/android/internal/util/GrowingArrayUtils.java +++ b/core/java/com/android/internal/util/GrowingArrayUtils.java @@ -16,7 +16,7 @@ package com.android.internal.util; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; /** * A helper class that aims to provide comparable growth performance to ArrayList, but on primitive diff --git a/core/java/com/android/internal/util/HexDump.java b/core/java/com/android/internal/util/HexDump.java index 6ffc92853b08..ad88dd6deec6 100644 --- a/core/java/com/android/internal/util/HexDump.java +++ b/core/java/com/android/internal/util/HexDump.java @@ -17,7 +17,7 @@ package com.android.internal.util; import android.annotation.Nullable; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; public class HexDump { diff --git a/core/java/com/android/internal/util/IState.java b/core/java/com/android/internal/util/IState.java index eb66e2ce94d7..07837bf8f587 100644 --- a/core/java/com/android/internal/util/IState.java +++ b/core/java/com/android/internal/util/IState.java @@ -16,7 +16,7 @@ package com.android.internal.util; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.os.Message; /** diff --git a/core/java/com/android/internal/util/IndentingPrintWriter.java b/core/java/com/android/internal/util/IndentingPrintWriter.java index 03a555edf4a8..34c6a055d5bd 100644 --- a/core/java/com/android/internal/util/IndentingPrintWriter.java +++ b/core/java/com/android/internal/util/IndentingPrintWriter.java @@ -16,7 +16,8 @@ package com.android.internal.util; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; + import java.io.PrintWriter; import java.io.Writer; import java.util.Arrays; diff --git a/core/java/com/android/internal/util/JournaledFile.java b/core/java/com/android/internal/util/JournaledFile.java index 065cc5b2416b..a9d8f7239d03 100644 --- a/core/java/com/android/internal/util/JournaledFile.java +++ b/core/java/com/android/internal/util/JournaledFile.java @@ -16,7 +16,7 @@ package com.android.internal.util; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; import java.io.File; diff --git a/core/java/com/android/internal/util/MemInfoReader.java b/core/java/com/android/internal/util/MemInfoReader.java index 580c2fa66de2..5de77d9b0545 100644 --- a/core/java/com/android/internal/util/MemInfoReader.java +++ b/core/java/com/android/internal/util/MemInfoReader.java @@ -16,7 +16,7 @@ package com.android.internal.util; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.os.Debug; import android.os.StrictMode; diff --git a/core/java/com/android/internal/util/Preconditions.java b/core/java/com/android/internal/util/Preconditions.java index 3fff5c233890..408a7a8e139a 100644 --- a/core/java/com/android/internal/util/Preconditions.java +++ b/core/java/com/android/internal/util/Preconditions.java @@ -18,7 +18,7 @@ package com.android.internal.util; import android.annotation.IntRange; import android.annotation.NonNull; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.text.TextUtils; import java.util.Collection; @@ -126,7 +126,9 @@ public class Preconditions { * @param reference an object reference * @return the non-null reference that was validated * @throws NullPointerException if {@code reference} is null + * @deprecated - use {@link java.util.Objects.requireNonNull} instead. */ + @Deprecated @UnsupportedAppUsage public static @NonNull <T> T checkNotNull(final T reference) { if (reference == null) { @@ -144,7 +146,9 @@ public class Preconditions { * be converted to a string using {@link String#valueOf(Object)} * @return the non-null reference that was validated * @throws NullPointerException if {@code reference} is null + * @deprecated - use {@link java.util.Objects.requireNonNull} instead. */ + @Deprecated @UnsupportedAppUsage public static @NonNull <T> T checkNotNull(final T reference, final Object errorMessage) { if (reference == null) { @@ -154,26 +158,6 @@ public class Preconditions { } /** - * Ensures that an object reference passed as a parameter to the calling - * method is not null. - * - * @param reference an object reference - * @param messageTemplate a printf-style message template to use if the check fails; will - * be converted to a string using {@link String#format(String, Object...)} - * @param messageArgs arguments for {@code messageTemplate} - * @return the non-null reference that was validated - * @throws NullPointerException if {@code reference} is null - */ - public static @NonNull <T> T checkNotNull(final T reference, - final String messageTemplate, - final Object... messageArgs) { - if (reference == null) { - throw new NullPointerException(String.format(messageTemplate, messageArgs)); - } - return reference; - } - - /** * Ensures the truth of an expression involving the state of the calling * instance, but not involving any parameters to the calling method. * diff --git a/core/java/com/android/internal/util/State.java b/core/java/com/android/internal/util/State.java index 3c61e035e886..636378e32091 100644 --- a/core/java/com/android/internal/util/State.java +++ b/core/java/com/android/internal/util/State.java @@ -16,7 +16,7 @@ package com.android.internal.util; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.os.Message; /** diff --git a/core/java/com/android/internal/util/StateMachine.java b/core/java/com/android/internal/util/StateMachine.java index 6c217e5a37bf..0c2406559dcc 100644 --- a/core/java/com/android/internal/util/StateMachine.java +++ b/core/java/com/android/internal/util/StateMachine.java @@ -16,7 +16,7 @@ package com.android.internal.util; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; diff --git a/core/java/com/android/internal/util/XmlUtils.java b/core/java/com/android/internal/util/XmlUtils.java index 8799e3d4c6bf..c1be33a215b8 100644 --- a/core/java/com/android/internal/util/XmlUtils.java +++ b/core/java/com/android/internal/util/XmlUtils.java @@ -16,10 +16,10 @@ package com.android.internal.util; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.graphics.Bitmap; -import android.graphics.BitmapFactory; import android.graphics.Bitmap.CompressFormat; +import android.graphics.BitmapFactory; import android.net.Uri; import android.text.TextUtils; import android.util.ArrayMap; diff --git a/core/java/com/android/internal/view/ActionBarPolicy.java b/core/java/com/android/internal/view/ActionBarPolicy.java index d18c35e703da..d16cb4362f1b 100644 --- a/core/java/com/android/internal/view/ActionBarPolicy.java +++ b/core/java/com/android/internal/view/ActionBarPolicy.java @@ -16,15 +16,15 @@ package com.android.internal.view; -import com.android.internal.R; - -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; import android.os.Build; +import com.android.internal.R; + /** * Allows components to query for various configuration policy decisions * about how the action bar should lay out and behave on the current device. diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java index 2ac2975d0a44..5dd3389b5d4c 100644 --- a/core/java/com/android/internal/view/BaseIWindow.java +++ b/core/java/com/android/internal/view/BaseIWindow.java @@ -16,6 +16,7 @@ package com.android.internal.view; +import android.compat.annotation.UnsupportedAppUsage; import android.graphics.Point; import android.graphics.Rect; import android.hardware.input.InputManager; @@ -34,8 +35,6 @@ import android.view.WindowInsets.Type.InsetsType; import com.android.internal.os.IResultReceiver; -import dalvik.annotation.compat.UnsupportedAppUsage; - import java.io.IOException; public class BaseIWindow extends IWindow.Stub { diff --git a/core/java/com/android/internal/view/IInputConnectionWrapper.java b/core/java/com/android/internal/view/IInputConnectionWrapper.java index ececba13c760..6278d4a35329 100644 --- a/core/java/com/android/internal/view/IInputConnectionWrapper.java +++ b/core/java/com/android/internal/view/IInputConnectionWrapper.java @@ -18,7 +18,7 @@ package com.android.internal.view; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; import android.os.Bundle; import android.os.Handler; diff --git a/core/java/com/android/internal/view/InputBindResult.java b/core/java/com/android/internal/view/InputBindResult.java index 1b133d2a8393..a5964b509b3c 100644 --- a/core/java/com/android/internal/view/InputBindResult.java +++ b/core/java/com/android/internal/view/InputBindResult.java @@ -20,7 +20,7 @@ import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.IntDef; import android.annotation.Nullable; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Intent; import android.content.ServiceConnection; diff --git a/core/java/com/android/internal/view/InputConnectionWrapper.java b/core/java/com/android/internal/view/InputConnectionWrapper.java index 0c057ea6df59..a41048c0f426 100644 --- a/core/java/com/android/internal/view/InputConnectionWrapper.java +++ b/core/java/com/android/internal/view/InputConnectionWrapper.java @@ -19,7 +19,7 @@ package com.android.internal.view; import android.annotation.AnyThread; import android.annotation.BinderThread; import android.annotation.NonNull; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.inputmethodservice.AbstractInputMethodService; import android.os.Bundle; import android.os.Handler; diff --git a/core/java/com/android/internal/view/WindowManagerPolicyThread.java b/core/java/com/android/internal/view/WindowManagerPolicyThread.java index b009a2d8ca30..6d691fce4fb0 100644 --- a/core/java/com/android/internal/view/WindowManagerPolicyThread.java +++ b/core/java/com/android/internal/view/WindowManagerPolicyThread.java @@ -16,7 +16,7 @@ package com.android.internal.view; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.os.Looper; /** diff --git a/core/java/com/android/internal/view/menu/ActionMenu.java b/core/java/com/android/internal/view/menu/ActionMenu.java index 977c1f6fda7b..648262965ab1 100644 --- a/core/java/com/android/internal/view/menu/ActionMenu.java +++ b/core/java/com/android/internal/view/menu/ActionMenu.java @@ -16,10 +16,7 @@ package com.android.internal.view.menu; -import java.util.ArrayList; -import java.util.List; - -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -30,6 +27,9 @@ import android.view.Menu; import android.view.MenuItem; import android.view.SubMenu; +import java.util.ArrayList; +import java.util.List; + /** * @hide */ diff --git a/core/java/com/android/internal/view/menu/ActionMenuItem.java b/core/java/com/android/internal/view/menu/ActionMenuItem.java index ed253d58fb82..bd8bcb9cf81e 100644 --- a/core/java/com/android/internal/view/menu/ActionMenuItem.java +++ b/core/java/com/android/internal/view/menu/ActionMenuItem.java @@ -17,7 +17,7 @@ package com.android.internal.view.menu; import android.annotation.Nullable; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.Intent; import android.content.res.ColorStateList; diff --git a/core/java/com/android/internal/view/menu/ActionMenuItemView.java b/core/java/com/android/internal/view/menu/ActionMenuItemView.java index eb94db33ba1a..7622b939b6eb 100644 --- a/core/java/com/android/internal/view/menu/ActionMenuItemView.java +++ b/core/java/com/android/internal/view/menu/ActionMenuItemView.java @@ -16,7 +16,7 @@ package com.android.internal.view.menu; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; diff --git a/core/java/com/android/internal/view/menu/ContextMenuBuilder.java b/core/java/com/android/internal/view/menu/ContextMenuBuilder.java index 3d3aceb4a85f..a9f5e47fc83f 100644 --- a/core/java/com/android/internal/view/menu/ContextMenuBuilder.java +++ b/core/java/com/android/internal/view/menu/ContextMenuBuilder.java @@ -16,7 +16,7 @@ package com.android.internal.view.menu; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.graphics.drawable.Drawable; import android.os.IBinder; diff --git a/core/java/com/android/internal/view/menu/IconMenuItemView.java b/core/java/com/android/internal/view/menu/IconMenuItemView.java index 3d888d347d65..539c71e5c473 100644 --- a/core/java/com/android/internal/view/menu/IconMenuItemView.java +++ b/core/java/com/android/internal/view/menu/IconMenuItemView.java @@ -16,13 +16,12 @@ package com.android.internal.view.menu; -import com.android.internal.view.menu.MenuBuilder.ItemInvoker; - -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.text.Layout; import android.text.TextUtils; import android.util.AttributeSet; import android.view.Gravity; @@ -30,7 +29,8 @@ import android.view.SoundEffectConstants; import android.view.View; import android.view.ViewDebug; import android.widget.TextView; -import android.text.Layout; + +import com.android.internal.view.menu.MenuBuilder.ItemInvoker; /** * The item view for each item in the {@link IconMenuView}. diff --git a/core/java/com/android/internal/view/menu/IconMenuView.java b/core/java/com/android/internal/view/menu/IconMenuView.java index 6f264341f7b4..9e240dbd8a21 100644 --- a/core/java/com/android/internal/view/menu/IconMenuView.java +++ b/core/java/com/android/internal/view/menu/IconMenuView.java @@ -16,9 +16,7 @@ package com.android.internal.view.menu; -import com.android.internal.view.menu.MenuBuilder.ItemInvoker; - -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; @@ -29,10 +27,12 @@ import android.os.Parcel; import android.os.Parcelable; import android.util.AttributeSet; import android.view.KeyEvent; +import android.view.LayoutInflater; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; -import android.view.LayoutInflater; + +import com.android.internal.view.menu.MenuBuilder.ItemInvoker; import java.util.ArrayList; diff --git a/core/java/com/android/internal/view/menu/MenuBuilder.java b/core/java/com/android/internal/view/menu/MenuBuilder.java index 0e07ca79faf7..b31ae38b4566 100644 --- a/core/java/com/android/internal/view/menu/MenuBuilder.java +++ b/core/java/com/android/internal/view/menu/MenuBuilder.java @@ -18,7 +18,7 @@ package com.android.internal.view.menu; import android.annotation.NonNull; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; import android.content.Intent; diff --git a/core/java/com/android/internal/view/menu/MenuDialogHelper.java b/core/java/com/android/internal/view/menu/MenuDialogHelper.java index 88d0a03bd55f..d02b8f6ceb63 100644 --- a/core/java/com/android/internal/view/menu/MenuDialogHelper.java +++ b/core/java/com/android/internal/view/menu/MenuDialogHelper.java @@ -16,9 +16,9 @@ package com.android.internal.view.menu; -import android.annotation.UnsupportedAppUsage; import android.app.AlertDialog; import android.app.Dialog; +import android.compat.annotation.UnsupportedAppUsage; import android.content.DialogInterface; import android.os.IBinder; import android.view.KeyEvent; diff --git a/core/java/com/android/internal/view/menu/MenuItemImpl.java b/core/java/com/android/internal/view/menu/MenuItemImpl.java index 994a9c117ce9..218f5185ec47 100644 --- a/core/java/com/android/internal/view/menu/MenuItemImpl.java +++ b/core/java/com/android/internal/view/menu/MenuItemImpl.java @@ -16,10 +16,8 @@ package com.android.internal.view.menu; -import com.android.internal.view.menu.MenuView.ItemView; - import android.annotation.Nullable; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; @@ -39,6 +37,8 @@ import android.view.ViewConfiguration; import android.view.ViewDebug; import android.widget.LinearLayout; +import com.android.internal.view.menu.MenuView.ItemView; + /** * @hide */ diff --git a/core/java/com/android/internal/view/menu/MenuPopupHelper.java b/core/java/com/android/internal/view/menu/MenuPopupHelper.java index d00108edefd0..bac602509148 100644 --- a/core/java/com/android/internal/view/menu/MenuPopupHelper.java +++ b/core/java/com/android/internal/view/menu/MenuPopupHelper.java @@ -20,7 +20,7 @@ import android.annotation.AttrRes; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StyleRes; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.graphics.Point; import android.graphics.Rect; diff --git a/core/java/com/android/internal/view/menu/MenuPresenter.java b/core/java/com/android/internal/view/menu/MenuPresenter.java index c5df8ad6edc6..35b8fefe75ab 100644 --- a/core/java/com/android/internal/view/menu/MenuPresenter.java +++ b/core/java/com/android/internal/view/menu/MenuPresenter.java @@ -18,7 +18,7 @@ package com.android.internal.view.menu; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.os.Parcelable; import android.view.ViewGroup; diff --git a/core/java/com/android/internal/view/menu/MenuView.java b/core/java/com/android/internal/view/menu/MenuView.java index 67a55308938d..a31c820cd2a6 100644 --- a/core/java/com/android/internal/view/menu/MenuView.java +++ b/core/java/com/android/internal/view/menu/MenuView.java @@ -16,10 +16,7 @@ package com.android.internal.view.menu; -import com.android.internal.view.menu.MenuBuilder; -import com.android.internal.view.menu.MenuItemImpl; - -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.graphics.drawable.Drawable; /** diff --git a/core/java/com/android/internal/view/menu/SubMenuBuilder.java b/core/java/com/android/internal/view/menu/SubMenuBuilder.java index cf6d9746bb93..6eb215e94093 100644 --- a/core/java/com/android/internal/view/menu/SubMenuBuilder.java +++ b/core/java/com/android/internal/view/menu/SubMenuBuilder.java @@ -16,7 +16,7 @@ package com.android.internal.view.menu; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.graphics.drawable.Drawable; import android.view.Menu; diff --git a/core/java/com/android/internal/widget/AbsActionBarView.java b/core/java/com/android/internal/widget/AbsActionBarView.java index 9ccee7fc32ff..0f0c1a3de3a2 100644 --- a/core/java/com/android/internal/widget/AbsActionBarView.java +++ b/core/java/com/android/internal/widget/AbsActionBarView.java @@ -15,26 +15,25 @@ */ package com.android.internal.widget; -import com.android.internal.R; - -import android.util.TypedValue; -import android.view.ContextThemeWrapper; -import android.view.MotionEvent; -import android.widget.ActionMenuPresenter; -import android.widget.ActionMenuView; - import android.animation.Animator; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.TimeInterpolator; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.res.Configuration; import android.content.res.TypedArray; import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.ContextThemeWrapper; +import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.animation.DecelerateInterpolator; +import android.widget.ActionMenuPresenter; +import android.widget.ActionMenuView; + +import com.android.internal.R; public abstract class AbsActionBarView extends ViewGroup { private static final TimeInterpolator sAlphaInterpolator = new DecelerateInterpolator(); diff --git a/core/java/com/android/internal/widget/ActionBarContextView.java b/core/java/com/android/internal/widget/ActionBarContextView.java index 78ed53fa918c..051526ef2da7 100644 --- a/core/java/com/android/internal/widget/ActionBarContextView.java +++ b/core/java/com/android/internal/widget/ActionBarContextView.java @@ -15,13 +15,7 @@ */ package com.android.internal.widget; -import com.android.internal.R; - -import android.widget.ActionMenuPresenter; -import android.widget.ActionMenuView; -import com.android.internal.view.menu.MenuBuilder; - -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; @@ -32,9 +26,14 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; +import android.widget.ActionMenuPresenter; +import android.widget.ActionMenuView; import android.widget.LinearLayout; import android.widget.TextView; +import com.android.internal.R; +import com.android.internal.view.menu.MenuBuilder; + /** * @hide */ diff --git a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java index e9e3cdab7a10..aca0b713686f 100644 --- a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java +++ b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java @@ -18,7 +18,7 @@ package com.android.internal.widget; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.res.Configuration; @@ -41,6 +41,7 @@ import android.view.Window; import android.view.WindowInsets; import android.widget.OverScroller; import android.widget.Toolbar; + import com.android.internal.view.menu.MenuPresenter; /** diff --git a/core/java/com/android/internal/widget/AlertDialogLayout.java b/core/java/com/android/internal/widget/AlertDialogLayout.java index 7a0174946671..d879b6d569f3 100644 --- a/core/java/com/android/internal/widget/AlertDialogLayout.java +++ b/core/java/com/android/internal/widget/AlertDialogLayout.java @@ -18,8 +18,8 @@ package com.android.internal.widget; import android.annotation.AttrRes; import android.annotation.Nullable; -import android.annotation.UnsupportedAppUsage; import android.annotation.StyleRes; +import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.graphics.drawable.Drawable; import android.util.AttributeSet; diff --git a/core/java/com/android/internal/widget/ButtonBarLayout.java b/core/java/com/android/internal/widget/ButtonBarLayout.java index 0ca67438c5c3..ff131071efef 100644 --- a/core/java/com/android/internal/widget/ButtonBarLayout.java +++ b/core/java/com/android/internal/widget/ButtonBarLayout.java @@ -16,7 +16,7 @@ package com.android.internal.widget; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.res.TypedArray; import android.util.AttributeSet; diff --git a/core/java/com/android/internal/widget/CachingIconView.java b/core/java/com/android/internal/widget/CachingIconView.java index 35bff6d7c430..74ad81566ef4 100644 --- a/core/java/com/android/internal/widget/CachingIconView.java +++ b/core/java/com/android/internal/widget/CachingIconView.java @@ -18,7 +18,7 @@ package com.android.internal.widget; import android.annotation.DrawableRes; import android.annotation.Nullable; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.res.Configuration; import android.graphics.Bitmap; diff --git a/core/java/com/android/internal/widget/DialogTitle.java b/core/java/com/android/internal/widget/DialogTitle.java index 405436c53ff0..0bfd684317fd 100644 --- a/core/java/com/android/internal/widget/DialogTitle.java +++ b/core/java/com/android/internal/widget/DialogTitle.java @@ -16,7 +16,7 @@ package com.android.internal.widget; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.res.TypedArray; import android.text.Layout; diff --git a/core/java/com/android/internal/widget/EditableInputConnection.java b/core/java/com/android/internal/widget/EditableInputConnection.java index 2b648e90f7dd..ff3543c837eb 100644 --- a/core/java/com/android/internal/widget/EditableInputConnection.java +++ b/core/java/com/android/internal/widget/EditableInputConnection.java @@ -16,7 +16,7 @@ package com.android.internal.widget; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.os.Bundle; import android.text.Editable; import android.text.method.KeyListener; diff --git a/core/java/com/android/internal/widget/LinearLayoutWithDefaultTouchRecepient.java b/core/java/com/android/internal/widget/LinearLayoutWithDefaultTouchRecepient.java index cc7911da0b96..9ef9f697c46c 100644 --- a/core/java/com/android/internal/widget/LinearLayoutWithDefaultTouchRecepient.java +++ b/core/java/com/android/internal/widget/LinearLayoutWithDefaultTouchRecepient.java @@ -16,12 +16,12 @@ package com.android.internal.widget; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.graphics.Rect; import android.util.AttributeSet; -import android.view.View; import android.view.MotionEvent; +import android.view.View; import android.widget.LinearLayout; diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index 4f4c8c3ec377..f37a46811b81 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -25,11 +25,11 @@ import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.UnsupportedAppUsage; import android.app.admin.DevicePolicyManager; import android.app.admin.PasswordMetrics; import android.app.trust.IStrongAuthTracker; import android.app.trust.TrustManager; +import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; @@ -55,10 +55,10 @@ import android.util.SparseLongArray; import com.android.internal.annotations.VisibleForTesting; import com.android.server.LocalServices; -import com.google.android.collect.Lists; - import libcore.util.HexEncoding; +import com.google.android.collect.Lists; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.security.MessageDigest; @@ -1599,6 +1599,11 @@ public class LockPatternUtils { public static final int STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN = 0x20; /** + * Strong authentication is required to prepare for unattended upgrade. + */ + public static final int STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE = 0x40; + + /** * Strong auth flags that do not prevent biometric methods from being accepted as auth. * If any other flags are set, biometric authentication is disabled. */ diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java index 74a0aa37dafb..4ddc782aacb4 100644 --- a/core/java/com/android/internal/widget/LockPatternView.java +++ b/core/java/com/android/internal/widget/LockPatternView.java @@ -19,7 +19,7 @@ package com.android.internal.widget; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; diff --git a/core/java/com/android/internal/widget/LockSettingsInternal.java b/core/java/com/android/internal/widget/LockSettingsInternal.java index dd05576338ef..90a18ef87ee2 100644 --- a/core/java/com/android/internal/widget/LockSettingsInternal.java +++ b/core/java/com/android/internal/widget/LockSettingsInternal.java @@ -77,4 +77,34 @@ public abstract class LockSettingsInternal { * @return the user password metrics. */ public abstract @Nullable PasswordMetrics getUserPasswordMetrics(int userHandle); + + /** + * Prepare for reboot escrow. This triggers the strong auth to be required. After the escrow + * is complete as indicated by calling to the listener registered with {@link + * #setRebootEscrowListener}, then {@link #armRebootEscrow()} should be called before + * rebooting to apply the update. + */ + public abstract void prepareRebootEscrow(); + + /** + * Registers a listener for when the RebootEscrow HAL has stored its data needed for rebooting + * for an OTA. + * + * @see RebootEscrowListener + * @param listener + */ + public abstract void setRebootEscrowListener(RebootEscrowListener listener); + + /** + * Requests that any data needed for rebooting is cleared from the RebootEscrow HAL. + */ + public abstract void clearRebootEscrow(); + + /** + * Should be called immediately before rebooting for an update. This depends on {@link + * #prepareRebootEscrow()} having been called and the escrow completing. + * + * @return true if the arming worked + */ + public abstract boolean armRebootEscrow(); } diff --git a/core/java/com/android/internal/widget/NumericTextView.java b/core/java/com/android/internal/widget/NumericTextView.java index d2156704a655..c8f901133be6 100644 --- a/core/java/com/android/internal/widget/NumericTextView.java +++ b/core/java/com/android/internal/widget/NumericTextView.java @@ -16,7 +16,7 @@ package com.android.internal.widget; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.graphics.Rect; import android.util.AttributeSet; diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java index 37046afaaa85..dc8d57ab709e 100644 --- a/core/java/com/android/internal/widget/PointerLocationView.java +++ b/core/java/com/android/internal/widget/PointerLocationView.java @@ -16,7 +16,7 @@ package com.android.internal.widget; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; diff --git a/core/java/com/android/internal/widget/PreferenceImageView.java b/core/java/com/android/internal/widget/PreferenceImageView.java index 02a0b8d436b9..43b6b5a169c5 100644 --- a/core/java/com/android/internal/widget/PreferenceImageView.java +++ b/core/java/com/android/internal/widget/PreferenceImageView.java @@ -16,7 +16,7 @@ package com.android.internal.widget; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.util.AttributeSet; import android.widget.ImageView; diff --git a/core/java/com/android/internal/widget/RebootEscrowListener.java b/core/java/com/android/internal/widget/RebootEscrowListener.java new file mode 100644 index 000000000000..165453200d8b --- /dev/null +++ b/core/java/com/android/internal/widget/RebootEscrowListener.java @@ -0,0 +1,32 @@ +/* + * 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.internal.widget; + +/** + * Private API to be notified about reboot escrow events. + * + * {@hide} + */ +public interface RebootEscrowListener { + /** + * Called when the preparation status has changed. When {@code prepared} is {@code true} the + * user has entered their lock screen knowledge factor (LSKF) and the HAL has confirmed that + * it is ready to retrieve the secret after a reboot. When {@code prepared} is {@code false} + * then those conditions are not true. + */ + void onPreparedForReboot(boolean prepared); +} diff --git a/core/java/com/android/internal/widget/RecyclerView.java b/core/java/com/android/internal/widget/RecyclerView.java index b66a7b44f05d..43a227a32346 100644 --- a/core/java/com/android/internal/widget/RecyclerView.java +++ b/core/java/com/android/internal/widget/RecyclerView.java @@ -20,7 +20,7 @@ import android.annotation.CallSuper; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.res.TypedArray; import android.database.Observable; diff --git a/core/java/com/android/internal/widget/ScrollBarUtils.java b/core/java/com/android/internal/widget/ScrollBarUtils.java index 982e3152fc7c..3e9d697a0ace 100644 --- a/core/java/com/android/internal/widget/ScrollBarUtils.java +++ b/core/java/com/android/internal/widget/ScrollBarUtils.java @@ -16,7 +16,7 @@ package com.android.internal.widget; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; public class ScrollBarUtils { diff --git a/core/java/com/android/internal/widget/ScrollingTabContainerView.java b/core/java/com/android/internal/widget/ScrollingTabContainerView.java index 5d48ab910439..aa0b0bbd4c19 100644 --- a/core/java/com/android/internal/widget/ScrollingTabContainerView.java +++ b/core/java/com/android/internal/widget/ScrollingTabContainerView.java @@ -15,13 +15,11 @@ */ package com.android.internal.widget; -import com.android.internal.view.ActionBarPolicy; - import android.animation.Animator; import android.animation.ObjectAnimator; import android.animation.TimeInterpolator; -import android.annotation.UnsupportedAppUsage; import android.app.ActionBar; +import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.res.Configuration; import android.graphics.drawable.Drawable; @@ -42,6 +40,8 @@ import android.widget.ListView; import android.widget.Spinner; import android.widget.TextView; +import com.android.internal.view.ActionBarPolicy; + /** * This widget implements the dynamic action bar tab behavior that can change * across different configurations or circumstances. diff --git a/core/java/com/android/internal/widget/SlidingTab.java b/core/java/com/android/internal/widget/SlidingTab.java index 4b5d62467af0..5e6f3a46de7d 100644 --- a/core/java/com/android/internal/widget/SlidingTab.java +++ b/core/java/com/android/internal/widget/SlidingTab.java @@ -16,7 +16,7 @@ package com.android.internal.widget; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; @@ -34,12 +34,12 @@ import android.view.View; import android.view.ViewGroup; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; +import android.view.animation.Animation.AnimationListener; import android.view.animation.LinearInterpolator; import android.view.animation.TranslateAnimation; -import android.view.animation.Animation.AnimationListener; import android.widget.ImageView; -import android.widget.TextView; import android.widget.ImageView.ScaleType; +import android.widget.TextView; import com.android.internal.R; diff --git a/core/java/com/android/internal/widget/TextViewInputDisabler.java b/core/java/com/android/internal/widget/TextViewInputDisabler.java index 8d8f0fe52d64..57806eb21dcf 100644 --- a/core/java/com/android/internal/widget/TextViewInputDisabler.java +++ b/core/java/com/android/internal/widget/TextViewInputDisabler.java @@ -16,7 +16,7 @@ package com.android.internal.widget; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.text.InputFilter; import android.text.Spanned; import android.widget.TextView; diff --git a/core/java/com/android/internal/widget/ViewPager.java b/core/java/com/android/internal/widget/ViewPager.java index 7d36b02d4157..c8a86d108134 100644 --- a/core/java/com/android/internal/widget/ViewPager.java +++ b/core/java/com/android/internal/widget/ViewPager.java @@ -18,7 +18,7 @@ package com.android.internal.widget; import android.annotation.DrawableRes; import android.annotation.NonNull; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; diff --git a/core/java/com/android/server/ResettableTimeout.java b/core/java/com/android/server/ResettableTimeout.java index 64083f72aff5..511af941bf76 100644 --- a/core/java/com/android/server/ResettableTimeout.java +++ b/core/java/com/android/server/ResettableTimeout.java @@ -16,10 +16,9 @@ package com.android.server; -import android.os.SystemClock; - -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.os.ConditionVariable; +import android.os.SystemClock; /** * Utility class that you can call on with a timeout, and get called back diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java index 8a59c998dacb..74b481c938c3 100644 --- a/core/java/com/android/server/SystemConfig.java +++ b/core/java/com/android/server/SystemConfig.java @@ -218,6 +218,7 @@ public class SystemConfig { final ArrayMap<String, ArraySet<String>> mAllowedAssociations = new ArrayMap<>(); private final ArraySet<String> mBugreportWhitelistedPackages = new ArraySet<>(); + private final ArraySet<String> mAppDataIsolationWhitelistedApps = new ArraySet<>(); // Map of packagesNames to userTypes. Stored temporarily until cleared by UserManagerService(). private ArrayMap<String, Set<String>> mPackageToUserTypeWhitelist = new ArrayMap<>(); @@ -389,6 +390,10 @@ public class SystemConfig { return mRollbackWhitelistedPackages; } + public ArraySet<String> getAppDataIsolationWhitelistedApps() { + return mAppDataIsolationWhitelistedApps; + } + /** * Gets map of packagesNames to userTypes, dictating on which user types each package should be * initially installed, and then removes this map from SystemConfig. @@ -1045,6 +1050,16 @@ public class SystemConfig { } XmlUtils.skipCurrentTag(parser); } break; + case "app-data-isolation-whitelisted-app": { + String pkgname = parser.getAttributeValue(null, "package"); + if (pkgname == null) { + Slog.w(TAG, "<" + name + "> without package in " + permFile + + " at " + parser.getPositionDescription()); + } else { + mAppDataIsolationWhitelistedApps.add(pkgname); + } + XmlUtils.skipCurrentTag(parser); + } break; case "bugreport-whitelisted": { String pkgname = parser.getAttributeValue(null, "package"); if (pkgname == null) { diff --git a/core/java/com/android/server/net/BaseNetworkObserver.java b/core/java/com/android/server/net/BaseNetworkObserver.java index e1a10a5805f5..2a9c0b44b45e 100644 --- a/core/java/com/android/server/net/BaseNetworkObserver.java +++ b/core/java/com/android/server/net/BaseNetworkObserver.java @@ -16,7 +16,7 @@ package com.android.server.net; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.net.INetworkManagementEventObserver; import android.net.LinkAddress; import android.net.RouteInfo; diff --git a/core/java/com/android/server/net/NetlinkTracker.java b/core/java/com/android/server/net/NetlinkTracker.java index 647fb5b9d079..b57397f46a7e 100644 --- a/core/java/com/android/server/net/NetlinkTracker.java +++ b/core/java/com/android/server/net/NetlinkTracker.java @@ -16,7 +16,7 @@ package com.android.server.net; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.net.LinkAddress; import android.net.LinkProperties; import android.net.RouteInfo; diff --git a/core/java/com/google/android/collect/Lists.java b/core/java/com/google/android/collect/Lists.java index 8f6594aefb0a..585847da566c 100644 --- a/core/java/com/google/android/collect/Lists.java +++ b/core/java/com/google/android/collect/Lists.java @@ -16,7 +16,8 @@ package com.google.android.collect; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; + import java.util.ArrayList; import java.util.Collections; diff --git a/core/java/com/google/android/collect/Maps.java b/core/java/com/google/android/collect/Maps.java index 6ba33207631a..cd4c1280545e 100644 --- a/core/java/com/google/android/collect/Maps.java +++ b/core/java/com/google/android/collect/Maps.java @@ -16,7 +16,7 @@ package com.google.android.collect; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.util.ArrayMap; import java.util.HashMap; diff --git a/core/java/com/google/android/collect/Sets.java b/core/java/com/google/android/collect/Sets.java index 09b5e51ae2c6..c67a88a19080 100644 --- a/core/java/com/google/android/collect/Sets.java +++ b/core/java/com/google/android/collect/Sets.java @@ -16,7 +16,7 @@ package com.google.android.collect; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.util.ArraySet; import java.util.Collections; diff --git a/core/java/com/google/android/util/AbstractMessageParser.java b/core/java/com/google/android/util/AbstractMessageParser.java index f11e6b2342b7..0da7607cba6e 100644 --- a/core/java/com/google/android/util/AbstractMessageParser.java +++ b/core/java/com/google/android/util/AbstractMessageParser.java @@ -16,7 +16,7 @@ package com.google.android.util; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import java.util.ArrayList; import java.util.HashMap; diff --git a/core/java/org/apache/http/conn/ssl/AbstractVerifier.java b/core/java/org/apache/http/conn/ssl/AbstractVerifier.java index 36d6e22ca847..2848ad7796af 100644 --- a/core/java/org/apache/http/conn/ssl/AbstractVerifier.java +++ b/core/java/org/apache/http/conn/ssl/AbstractVerifier.java @@ -31,7 +31,7 @@ package org.apache.http.conn.ssl; -import java.util.regex.Pattern; +import android.compat.annotation.UnsupportedAppUsage; import java.io.IOException; import java.security.cert.Certificate; @@ -43,10 +43,10 @@ import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Locale; -import java.util.logging.Logger; import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Pattern; -import android.annotation.UnsupportedAppUsage; import javax.net.ssl.SSLException; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; diff --git a/core/java/org/apache/http/conn/ssl/SSLSocketFactory.java b/core/java/org/apache/http/conn/ssl/SSLSocketFactory.java index b2e8b5e7af05..ffae7570ea79 100644 --- a/core/java/org/apache/http/conn/ssl/SSLSocketFactory.java +++ b/core/java/org/apache/http/conn/ssl/SSLSocketFactory.java @@ -31,20 +31,14 @@ package org.apache.http.conn.ssl; +import android.compat.annotation.UnsupportedAppUsage; +import android.os.Build; + import org.apache.http.conn.scheme.HostNameResolver; import org.apache.http.conn.scheme.LayeredSocketFactory; import org.apache.http.params.HttpConnectionParams; import org.apache.http.params.HttpParams; -import android.annotation.UnsupportedAppUsage; -import android.os.Build; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.KeyManager; -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSocket; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; @@ -57,6 +51,14 @@ import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.UnrecoverableKeyException; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; + /** * Layered socket factory for TLS/SSL connections, based on JSSE. *. diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index d17d0a4ebbe2..5039213954d7 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -319,7 +319,8 @@ enum MountExternalKind { MOUNT_EXTERNAL_INSTALLER = 5, MOUNT_EXTERNAL_FULL = 6, MOUNT_EXTERNAL_PASS_THROUGH = 7, - MOUNT_EXTERNAL_COUNT = 8 + MOUNT_EXTERNAL_ANDROID_WRITABLE = 8, + MOUNT_EXTERNAL_COUNT = 9 }; // The order of entries here must be kept in sync with MountExternalKind enum values. @@ -331,6 +332,8 @@ static const std::array<const std::string, MOUNT_EXTERNAL_COUNT> ExternalStorage "/mnt/runtime/write", // MOUNT_EXTERNAL_LEGACY "/mnt/runtime/write", // MOUNT_EXTERNAL_INSTALLER "/mnt/runtime/full", // MOUNT_EXTERNAL_FULL + "/mnt/runtime/full", // MOUNT_EXTERNAL_PASS_THROUGH (only used w/ FUSE) + "/mnt/runtime/full", // MOUNT_EXTERNAL_ANDROID_WRITABLE (only used w/ FUSE) }; // Must match values in com.android.internal.os.Zygote. @@ -755,12 +758,7 @@ static void MountEmulatedStorage(uid_t uid, jint mount_mode, multiuser_get_uid(user_id, AID_EVERYBODY), fail_fn); if (isFuse) { - if (mount_mode == MOUNT_EXTERNAL_PASS_THROUGH || mount_mode == - MOUNT_EXTERNAL_INSTALLER || mount_mode == MOUNT_EXTERNAL_FULL) { - // For now, MediaProvider, installers and "full" get the pass_through mount - // view, which is currently identical to the sdcardfs write view. - // - // TODO(b/146189163): scope down MOUNT_EXTERNAL_INSTALLER + if (mount_mode == MOUNT_EXTERNAL_PASS_THROUGH) { BindMount(pass_through_source, "/storage", fail_fn); } else { BindMount(user_source, "/storage", fail_fn); diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp index 5624f457a9b2..082a2892e34b 100644 --- a/core/jni/fd_utils.cpp +++ b/core/jni/fd_utils.cpp @@ -33,27 +33,27 @@ // Static whitelist of open paths that the zygote is allowed to keep open. static const char* kPathWhitelist[] = { - "/apex/com.android.appsearch/javalib/framework-appsearch.jar", - "/apex/com.android.conscrypt/javalib/conscrypt.jar", - "/apex/com.android.ipsec/javalib/ike.jar", - "/apex/com.android.media/javalib/updatable-media.jar", - "/apex/com.android.mediaprovider/javalib/framework-mediaprovider.jar", - "/apex/com.android.os.statsd/javalib/framework-statsd.jar", - "/apex/com.android.sdkext/javalib/framework-sdkext.jar", - "/apex/com.android.wifi/javalib/framework-wifi.jar", - "/apex/com.android.tethering/javalib/framework-tethering.jar", - "/dev/null", - "/dev/socket/zygote", - "/dev/socket/zygote_secondary", - "/dev/socket/usap_pool_primary", - "/dev/socket/usap_pool_secondary", - "/dev/socket/webview_zygote", - "/dev/socket/heapprofd", - "/sys/kernel/debug/tracing/trace_marker", - "/system/framework/framework-res.apk", - "/dev/urandom", - "/dev/ion", - "/dev/dri/renderD129", // Fixes b/31172436 + "/apex/com.android.appsearch/javalib/framework-appsearch.jar", + "/apex/com.android.conscrypt/javalib/conscrypt.jar", + "/apex/com.android.ipsec/javalib/ike.jar", + "/apex/com.android.media/javalib/updatable-media.jar", + "/apex/com.android.mediaprovider/javalib/framework-mediaprovider.jar", + "/apex/com.android.os.statsd/javalib/framework-statsd.jar", + "/apex/com.android.sdkext/javalib/framework-sdkextensions.jar", + "/apex/com.android.wifi/javalib/framework-wifi.jar", + "/apex/com.android.tethering/javalib/framework-tethering.jar", + "/dev/null", + "/dev/socket/zygote", + "/dev/socket/zygote_secondary", + "/dev/socket/usap_pool_primary", + "/dev/socket/usap_pool_secondary", + "/dev/socket/webview_zygote", + "/dev/socket/heapprofd", + "/sys/kernel/debug/tracing/trace_marker", + "/system/framework/framework-res.apk", + "/dev/urandom", + "/dev/ion", + "/dev/dri/renderD129", // Fixes b/31172436 }; static const char kFdPath[] = "/proc/self/fd"; diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto index d5a3b5e91151..b83b31c1303a 100644 --- a/core/proto/android/app/settings_enums.proto +++ b/core/proto/android/app/settings_enums.proto @@ -702,6 +702,12 @@ enum Action { // CATEGORY: SETTINGS // OS: R ACTION_DASHBOARD_VISIBLE_TIME = 1729; + + // ACTION: Allow "Access all files" for an app + APP_SPECIAL_PERMISSION_MANAGE_EXT_STRG_ALLOW = 1730; + + // ACTION: Deny "Access all files" for an app + APP_SPECIAL_PERMISSION_MANAGE_EXT_STRG_DENY = 1731; } /** @@ -2542,4 +2548,9 @@ enum PageId { // OS: R FUELGAUGE_BATTERY_SHARE = 1821; + // OPEN: Settings -> Apps & Notifications -> Special App Access + // CATEGORY: SETTINGS + // OS: R + MANAGE_EXTERNAL_STORAGE = 1822; + } diff --git a/core/proto/android/server/animationadapter.proto b/core/proto/android/server/animationadapter.proto index 70627edf2cb3..c6925f448a58 100644 --- a/core/proto/android/server/animationadapter.proto +++ b/core/proto/android/server/animationadapter.proto @@ -50,6 +50,7 @@ message AnimationSpecProto { optional WindowAnimationSpecProto window = 1; optional MoveAnimationSpecProto move = 2; optional AlphaAnimationSpecProto alpha = 3; + optional RotationAnimationSpecProto rotate = 4; } /* represents WindowAnimationSpec */ @@ -76,3 +77,12 @@ message AlphaAnimationSpecProto { optional float to = 2; optional int64 duration_ms = 3; } + +/* represents RotationAnimationSpec */ +message RotationAnimationSpecProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + optional float start_luma = 1; + optional float end_luma = 2; + optional int64 duration_ms = 3; +} diff --git a/core/proto/android/service/graphicsstats.proto b/core/proto/android/service/graphicsstats.proto index 557075cc5bfa..2de5b7fd45f6 100644 --- a/core/proto/android/service/graphicsstats.proto +++ b/core/proto/android/service/graphicsstats.proto @@ -32,6 +32,10 @@ message GraphicsStatsServiceDumpProto { } message GraphicsStatsProto { + enum PipelineType { + GL = 0; + VULKAN = 1; + } option (android.msg_privacy).dest = DEST_AUTOMATIC; // The package name of the app @@ -54,6 +58,9 @@ message GraphicsStatsProto { // The gpu frame time histogram for the package repeated GraphicsStatsHistogramBucketProto gpu_histogram = 7; + + // HWUI renders pipeline type: GL or Vulkan + optional PipelineType pipeline = 8; } message GraphicsStatsJankSummaryProto { diff --git a/core/proto/android/stats/devicepolicy/device_policy_enums.proto b/core/proto/android/stats/devicepolicy/device_policy_enums.proto index ee5144c0f91a..9054d5462da5 100644 --- a/core/proto/android/stats/devicepolicy/device_policy_enums.proto +++ b/core/proto/android/stats/devicepolicy/device_policy_enums.proto @@ -153,4 +153,5 @@ enum EventId { CROSS_PROFILE_APPS_START_ACTIVITY_AS_USER = 126; SET_AUTO_TIME = 127; SET_AUTO_TIME_ZONE = 128; + SET_PACKAGES_PROTECTED = 129; } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 31faff6bd314..d887032dbdbc 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -370,6 +370,7 @@ <protected-broadcast android:name="android.net.wifi.STATE_CHANGE" /> <protected-broadcast android:name="android.net.wifi.LINK_CONFIGURATION_CHANGED" /> <protected-broadcast android:name="android.net.wifi.CONFIGURED_NETWORKS_CHANGE" /> + <protected-broadcast android:name="android.net.wifi.action.NETWORK_SETTINGS_RESET" /> <protected-broadcast android:name="android.net.wifi.action.PASSPOINT_DEAUTH_IMMINENT" /> <protected-broadcast android:name="android.net.wifi.action.PASSPOINT_ICON" /> <protected-broadcast android:name="android.net.wifi.action.PASSPOINT_OSU_PROVIDERS_LIST" /> @@ -456,7 +457,6 @@ <protected-broadcast android:name="android.intent.action.internal_sim_state_changed" /> <protected-broadcast android:name="android.intent.action.LOCKED_BOOT_COMPLETED" /> <protected-broadcast android:name="android.intent.action.PRECISE_CALL_STATE" /> - <protected-broadcast android:name="android.intent.action.PRECISE_DATA_CONNECTION_STATE_CHANGED" /> <protected-broadcast android:name="android.intent.action.SUBSCRIPTION_PHONE_STATE" /> <protected-broadcast android:name="android.intent.action.USER_INFO_CHANGED" /> <protected-broadcast android:name="android.intent.action.USER_UNLOCKED" /> @@ -934,11 +934,10 @@ <!-- Allows an application a broad access to external storage in scoped storage. Intended to be used by few apps that need to manage files on behalf of the users. - <p>Protection level: signature|appop - <p>This protection level is temporary and will most likely be changed to |preinstalled --> + <p>Protection level: signature|appop|preinstalled --> <permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" android:permissionGroup="android.permission-group.UNDEFINED" - android:protectionLevel="signature|appop" /> + android:protectionLevel="signature|appop|preinstalled" /> <!-- ====================================================================== --> <!-- Permissions for accessing the device location --> @@ -1656,6 +1655,7 @@ <!-- Allows holder to request bluetooth/wifi scan bypassing global "use location" setting and location permissions. <p>Not for use by third-party or privileged applications. + @SystemApi @hide --> <permission android:name="android.permission.RADIO_SCAN_WITHOUT_LOCATION" @@ -4757,6 +4757,10 @@ <!-- Allows input events to be monitored. Very dangerous! @hide --> <permission android:name="android.permission.MONITOR_INPUT" android:protectionLevel="signature" /> + <!-- Allows the caller to change the associations between input devices and displays. + Very dangerous! @hide --> + <permission android:name="android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY_BY_PORT" + android:protectionLevel="signature" /> <!-- Allows query of any normal app on the device, regardless of manifest declarations. --> <permission android:name="android.permission.QUERY_ALL_PACKAGES" diff --git a/core/res/res/anim/screen_rotate_0_enter.xml b/core/res/res/anim/screen_rotate_0_enter.xml index 93cf3652d185..629be7ea2d30 100644 --- a/core/res/res/anim/screen_rotate_0_enter.xml +++ b/core/res/res/anim/screen_rotate_0_enter.xml @@ -1,25 +1,25 @@ <?xml version="1.0" encoding="utf-8"?> <!-- -/* -** Copyright 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. -*/ ---> + ~ 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. + --> <set xmlns:android="http://schemas.android.com/apk/res/android" - android:shareInterpolator="false"> - <alpha android:fromAlpha="1.0" android:toAlpha="1.0" - android:interpolator="@interpolator/decelerate_quint" - android:duration="@android:integer/config_shortAnimTime" /> + android:shareInterpolator="false"> + <alpha android:fromAlpha="0.0" android:toAlpha="1.0" + android:interpolator="@interpolator/screen_rotation_alpha_in" + android:fillEnabled="true" + android:fillBefore="true" android:fillAfter="true" + android:duration="@android:integer/config_screen_rotation_fade_in" /> </set> diff --git a/core/res/res/anim/screen_rotate_0_exit.xml b/core/res/res/anim/screen_rotate_0_exit.xml index 37d5a4115621..fa046a036855 100644 --- a/core/res/res/anim/screen_rotate_0_exit.xml +++ b/core/res/res/anim/screen_rotate_0_exit.xml @@ -1,22 +1,25 @@ <?xml version="1.0" encoding="utf-8"?> <!-- -/* -** Copyright 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. -*/ ---> + ~ 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. + --> <set xmlns:android="http://schemas.android.com/apk/res/android" - android:shareInterpolator="false"> + android:shareInterpolator="false"> + <alpha android:fromAlpha="1.0" android:toAlpha="0.0" + android:interpolator="@interpolator/screen_rotation_alpha_out" + android:fillEnabled="true" + android:fillBefore="true" android:fillAfter="true" + android:duration="@android:integer/config_screen_rotation_fade_out" /> </set> diff --git a/core/res/res/anim/screen_rotate_0_frame.xml b/core/res/res/anim/screen_rotate_0_frame.xml deleted file mode 100644 index 5ea9bf8205e3..000000000000 --- a/core/res/res/anim/screen_rotate_0_frame.xml +++ /dev/null @@ -1,25 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* -** Copyright 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. -*/ ---> - -<set xmlns:android="http://schemas.android.com/apk/res/android" - android:shareInterpolator="false"> - <alpha android:fromAlpha="1.0" android:toAlpha="1.0" - android:interpolator="@interpolator/decelerate_quint" - android:duration="@android:integer/config_shortAnimTime" /> -</set> diff --git a/core/res/res/anim/screen_rotate_180_enter.xml b/core/res/res/anim/screen_rotate_180_enter.xml index 688a8d5bb2aa..889a615e07f4 100644 --- a/core/res/res/anim/screen_rotate_180_enter.xml +++ b/core/res/res/anim/screen_rotate_180_enter.xml @@ -18,11 +18,11 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" - android:shareInterpolator="false"> + android:shareInterpolator="false"> <rotate android:fromDegrees="180" android:toDegrees="0" - android:pivotX="50%" android:pivotY="50%" - android:interpolator="@interpolator/decelerate_quint" - android:fillEnabled="true" - android:fillBefore="true" android:fillAfter="true" - android:duration="@android:integer/config_mediumAnimTime" /> + android:pivotX="50%" android:pivotY="50%" + android:fillEnabled="true" + android:fillBefore="true" android:fillAfter="true" + android:interpolator="@interpolator/fast_out_slow_in" + android:duration="@android:integer/config_screen_rotation_total_180" /> </set> diff --git a/core/res/res/anim/screen_rotate_180_exit.xml b/core/res/res/anim/screen_rotate_180_exit.xml index 58a1868bd398..766fcfae1f91 100644 --- a/core/res/res/anim/screen_rotate_180_exit.xml +++ b/core/res/res/anim/screen_rotate_180_exit.xml @@ -18,11 +18,11 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" - android:shareInterpolator="false"> + android:shareInterpolator="false"> <rotate android:fromDegrees="0" android:toDegrees="-180" - android:pivotX="50%" android:pivotY="50%" - android:interpolator="@interpolator/decelerate_quint" - android:fillEnabled="true" - android:fillBefore="true" android:fillAfter="true" - android:duration="@android:integer/config_mediumAnimTime" /> + android:pivotX="50%" android:pivotY="50%" + android:fillEnabled="true" + android:fillBefore="true" android:fillAfter="true" + android:interpolator="@interpolator/fast_out_slow_in" + android:duration="@android:integer/config_screen_rotation_total_180" /> </set>
\ No newline at end of file diff --git a/core/res/res/anim/screen_rotate_alpha.xml b/core/res/res/anim/screen_rotate_alpha.xml index c49ef9cafd39..2cac982e24b4 100644 --- a/core/res/res/anim/screen_rotate_alpha.xml +++ b/core/res/res/anim/screen_rotate_alpha.xml @@ -20,8 +20,8 @@ <set xmlns:android="http://schemas.android.com/apk/res/android" android:shareInterpolator="false"> <alpha android:fromAlpha="1.0" android:toAlpha="0.0" - android:interpolator="@interpolator/decelerate_quint" + android:interpolator="@interpolator/screen_rotation_alpha_out" android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true" - android:duration="@android:integer/config_mediumAnimTime" /> + android:duration="@android:integer/config_screen_rotation_fade_out" /> </set> diff --git a/core/res/res/anim/screen_rotate_minus_90_enter.xml b/core/res/res/anim/screen_rotate_minus_90_enter.xml index b16d5fc761ee..87fd25ea4603 100644 --- a/core/res/res/anim/screen_rotate_minus_90_enter.xml +++ b/core/res/res/anim/screen_rotate_minus_90_enter.xml @@ -18,19 +18,17 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" - android:shareInterpolator="false"> - <!-- Version for two-phase anim + android:shareInterpolator="false"> <rotate android:fromDegrees="-90" android:toDegrees="0" - android:pivotX="50%" android:pivotY="50%" - android:interpolator="@interpolator/decelerate_quint" - android:fillEnabled="true" - android:fillBefore="true" android:fillAfter="true" - android:duration="@android:integer/config_longAnimTime" /> - --> - <rotate android:fromDegrees="-90" android:toDegrees="0" - android:pivotX="50%" android:pivotY="50%" - android:fillEnabled="true" - android:fillBefore="true" android:fillAfter="true" - android:interpolator="@interpolator/decelerate_quint" - android:duration="@android:integer/config_mediumAnimTime" /> + android:pivotX="50%" android:pivotY="50%" + android:fillEnabled="true" + android:fillBefore="true" android:fillAfter="true" + android:interpolator="@interpolator/fast_out_slow_in" + android:duration="@android:integer/config_screen_rotation_total_90" /> + <alpha android:fromAlpha="0.0" android:toAlpha="1.0" + android:fillEnabled="true" + android:fillBefore="true" android:fillAfter="true" + android:interpolator="@interpolator/screen_rotation_alpha_in" + android:startOffset="@android:integer/config_screen_rotation_fade_in_delay" + android:duration="@android:integer/config_screen_rotation_fade_in" /> </set> diff --git a/core/res/res/anim/screen_rotate_minus_90_exit.xml b/core/res/res/anim/screen_rotate_minus_90_exit.xml index 0927dd30ceb3..c3aee14dc235 100644 --- a/core/res/res/anim/screen_rotate_minus_90_exit.xml +++ b/core/res/res/anim/screen_rotate_minus_90_exit.xml @@ -18,26 +18,16 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" - android:shareInterpolator="false"> - <!-- Version for two-phase animation + android:shareInterpolator="false"> <rotate android:fromDegrees="0" android:toDegrees="90" - android:pivotX="50%" android:pivotY="50%" - android:interpolator="@interpolator/decelerate_quint" - android:fillEnabled="true" - android:fillBefore="true" android:fillAfter="true" - android:duration="@android:integer/config_longAnimTime" /> - --> - <scale android:fromXScale="100%" android:toXScale="100%p" - android:fromYScale="100%" android:toYScale="100%p" - android:pivotX="50%" android:pivotY="50%" - android:interpolator="@interpolator/decelerate_quint" - android:fillEnabled="true" - android:fillBefore="true" android:fillAfter="true" - android:duration="@android:integer/config_mediumAnimTime" /> - <rotate android:fromDegrees="0" android:toDegrees="90" - android:pivotX="50%" android:pivotY="50%" - android:interpolator="@interpolator/decelerate_quint" - android:fillEnabled="true" - android:fillBefore="true" android:fillAfter="true" - android:duration="@android:integer/config_mediumAnimTime" /> + android:pivotX="50%" android:pivotY="50%" + android:fillEnabled="true" + android:fillBefore="true" android:fillAfter="true" + android:interpolator="@interpolator/fast_out_slow_in" + android:duration="@android:integer/config_screen_rotation_total_90" /> + <alpha android:fromAlpha="1.0" android:toAlpha="0.0" + android:fillEnabled="true" + android:fillBefore="true" android:fillAfter="true" + android:interpolator="@interpolator/screen_rotation_alpha_out" + android:duration="@android:integer/config_screen_rotation_fade_out" /> </set> diff --git a/core/res/res/anim/screen_rotate_plus_90_enter.xml b/core/res/res/anim/screen_rotate_plus_90_enter.xml index 86a8d24cbbcc..8849db421e75 100644 --- a/core/res/res/anim/screen_rotate_plus_90_enter.xml +++ b/core/res/res/anim/screen_rotate_plus_90_enter.xml @@ -18,19 +18,16 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" - android:shareInterpolator="false"> - <!-- Version for two-phase animation + android:shareInterpolator="false"> <rotate android:fromDegrees="90" android:toDegrees="0" - android:pivotX="50%" android:pivotY="50%" - android:interpolator="@interpolator/decelerate_quint" - android:fillEnabled="true" - android:fillBefore="true" android:fillAfter="true" - android:duration="@android:integer/config_longAnimTime" /> - --> - <rotate android:fromDegrees="90" android:toDegrees="0" - android:pivotX="50%" android:pivotY="50%" - android:interpolator="@interpolator/decelerate_quint" - android:fillEnabled="true" - android:fillBefore="true" android:fillAfter="true" - android:duration="@android:integer/config_mediumAnimTime" /> + android:pivotX="50%" android:pivotY="50%" + android:fillEnabled="true" + android:fillBefore="true" android:fillAfter="true" + android:interpolator="@interpolator/fast_out_slow_in" + android:duration="@android:integer/config_screen_rotation_total_90" /> + <alpha android:fromAlpha="0.0" android:toAlpha="1.0" + android:fillEnabled="true" + android:interpolator="@interpolator/screen_rotation_alpha_in" + android:startOffset="@android:integer/config_screen_rotation_fade_in_delay" + android:duration="@android:integer/config_screen_rotation_fade_in" /> </set> diff --git a/core/res/res/anim/screen_rotate_plus_90_exit.xml b/core/res/res/anim/screen_rotate_plus_90_exit.xml index fd786f9afce0..de84c3bd08fc 100644 --- a/core/res/res/anim/screen_rotate_plus_90_exit.xml +++ b/core/res/res/anim/screen_rotate_plus_90_exit.xml @@ -18,26 +18,16 @@ --> <set xmlns:android="http://schemas.android.com/apk/res/android" - android:shareInterpolator="false"> - <!-- Version for two-phase animation + android:shareInterpolator="false"> <rotate android:fromDegrees="0" android:toDegrees="-90" - android:pivotX="50%" android:pivotY="50%" - android:interpolator="@interpolator/decelerate_quint" - android:fillEnabled="true" - android:fillBefore="true" android:fillAfter="true" - android:duration="@android:integer/config_longAnimTime" /> - --> - <scale android:fromXScale="100%" android:toXScale="100%p" - android:fromYScale="100%" android:toYScale="100%p" - android:pivotX="50%" android:pivotY="50%" - android:interpolator="@interpolator/decelerate_quint" - android:fillEnabled="true" - android:fillBefore="true" android:fillAfter="true" - android:duration="@android:integer/config_mediumAnimTime" /> - <rotate android:fromDegrees="0" android:toDegrees="-90" - android:pivotX="50%" android:pivotY="50%" - android:interpolator="@interpolator/decelerate_quint" - android:fillEnabled="true" - android:fillBefore="true" android:fillAfter="true" - android:duration="@android:integer/config_mediumAnimTime" /> + android:pivotX="50%" android:pivotY="50%" + android:fillEnabled="true" + android:fillBefore="true" android:fillAfter="true" + android:interpolator="@interpolator/fast_out_slow_in" + android:duration="@android:integer/config_screen_rotation_total_90" /> + <alpha android:fromAlpha="1.0" android:toAlpha="0.0" + android:interpolator="@interpolator/screen_rotation_alpha_out" + android:fillEnabled="true" + android:fillBefore="true" android:fillAfter="true" + android:duration="@android:integer/config_screen_rotation_fade_out" /> </set> diff --git a/core/res/res/interpolator/screen_rotation_alpha_in.xml b/core/res/res/interpolator/screen_rotation_alpha_in.xml new file mode 100644 index 000000000000..9c566a7c8f23 --- /dev/null +++ b/core/res/res/interpolator/screen_rotation_alpha_in.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> + +<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android" + android:controlX1="0.15" + android:controlY1="0.45" + android:controlX2="0.33" + android:controlY2="1"/> diff --git a/core/res/res/interpolator/screen_rotation_alpha_out.xml b/core/res/res/interpolator/screen_rotation_alpha_out.xml new file mode 100644 index 000000000000..73a37d4f1aa5 --- /dev/null +++ b/core/res/res/interpolator/screen_rotation_alpha_out.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> + +<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android" + android:controlX1="0.57" + android:controlY1="0" + android:controlX2="0.71" + android:controlY2=".43"/> diff --git a/core/res/res/layout/accessibility_button_chooser_item.xml b/core/res/res/layout/accessibility_button_chooser_item.xml index 1edd2cdfcfe3..d19e313055ae 100644 --- a/core/res/res/layout/accessibility_button_chooser_item.xml +++ b/core/res/res/layout/accessibility_button_chooser_item.xml @@ -36,7 +36,7 @@ android:id="@+id/accessibility_button_target_label" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginStart="8dp" + android:layout_marginStart="14dp" android:layout_weight="1" android:textColor="?attr/textColorPrimary"/> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index e839709c9c88..245aed1484f7 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -147,6 +147,24 @@ <integer name="config_activityShortDur">150</integer> <integer name="config_activityDefaultDur">220</integer> + <!-- Fade out time for screen rotation --> + <integer name="config_screen_rotation_fade_out">116</integer> + + <!-- Fade in time for screen rotation --> + <integer name="config_screen_rotation_fade_in">233</integer> + + <!-- Fade in delay time for screen rotation --> + <integer name="config_screen_rotation_fade_in_delay">100</integer> + + <!-- Total time for 90 degree screen rotation animations --> + <integer name="config_screen_rotation_total_90">333</integer> + + <!-- Total time for 180 degree screen rotation animation --> + <integer name="config_screen_rotation_total_180">433</integer> + + <!-- Total time for the rotation background color transition --> + <integer name="config_screen_rotation_color_transition">200</integer> + <!-- The duration (in milliseconds) of the tooltip show/hide animations. --> <integer name="config_tooltipAnimTime">150</integer> @@ -4284,4 +4302,12 @@ <!-- Whether or not to use assistant stream volume separately from music volume --> <bool name="config_useAssistantVolume">false</bool> + + <!-- Whether to use a custom Bugreport handling. When true, ACTION_CUSTOM_BUGREPORT_REQUESTED + intent is broadcasted on bugreporting chord (instead of the default full bugreport + generation). --> + <bool name="config_customBugreport">false</bool> + + <!-- Class name of the custom country detector to be used. --> + <string name="config_customCountryDetector" translatable="false">com.android.server.location.ComprehensiveCountryDetector</string> </resources> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 66267d136e10..a0e40646ead9 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -4358,10 +4358,7 @@ <string name="accessibility_shortcut_menu_button">Empty</string> <!-- Text in button that edit the accessibility shortcut menu. [CHAR LIMIT=100] --> - <string name="edit_accessibility_shortcut_menu_button">Edit</string> - - <!-- Text in button that save the accessibility shortcut menu changed status. [CHAR LIMIT=100] --> - <string name="save_accessibility_shortcut_menu_button">Save</string> + <string name="edit_accessibility_shortcut_menu_button">Edit shortcuts</string> <!-- Text in button that cancel the accessibility shortcut menu changed status. [CHAR LIMIT=100] --> <string name="cancel_accessibility_shortcut_menu_button">Cancel</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 129c862c09e7..973d5f6392f4 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1805,7 +1805,6 @@ <!-- From services --> <java-symbol type="anim" name="screen_rotate_0_enter" /> <java-symbol type="anim" name="screen_rotate_0_exit" /> - <java-symbol type="anim" name="screen_rotate_0_frame" /> <java-symbol type="anim" name="screen_rotate_180_enter" /> <java-symbol type="anim" name="screen_rotate_180_exit" /> <java-symbol type="anim" name="screen_rotate_180_frame" /> @@ -1981,6 +1980,7 @@ <java-symbol type="integer" name="config_virtualKeyQuietTimeMillis" /> <java-symbol type="integer" name="config_brightness_ramp_rate_fast" /> <java-symbol type="integer" name="config_brightness_ramp_rate_slow" /> + <java-symbol type="integer" name="config_screen_rotation_color_transition" /> <java-symbol type="layout" name="am_compat_mode_dialog" /> <java-symbol type="layout" name="launch_warning" /> <java-symbol type="layout" name="safe_mode" /> @@ -3212,7 +3212,6 @@ <java-symbol type="id" name="accessibility_button_target_switch_item" /> <java-symbol type="string" name="accessibility_magnification_chooser_text" /> <java-symbol type="string" name="edit_accessibility_shortcut_menu_button" /> - <java-symbol type="string" name="save_accessibility_shortcut_menu_button" /> <java-symbol type="string" name="cancel_accessibility_shortcut_menu_button" /> <java-symbol type="drawable" name="ic_accessibility_magnification" /> @@ -3605,6 +3604,8 @@ <java-symbol type="bool" name="config_maskMainBuiltInDisplayCutout" /> + <java-symbol type="string" name="config_customCountryDetector" /> + <!-- For Foldables --> <java-symbol type="bool" name="config_lidControlsDisplayFold" /> <java-symbol type="string" name="config_foldedArea" /> diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/WifiConfigurationHelper.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/WifiConfigurationHelper.java index f0a83678f70b..a296ca27e268 100644 --- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/WifiConfigurationHelper.java +++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/WifiConfigurationHelper.java @@ -16,6 +16,7 @@ package com.android.connectivitymanagertest; +import android.net.IpConfiguration; import android.net.IpConfiguration.IpAssignment; import android.net.IpConfiguration.ProxySettings; import android.net.LinkAddress; @@ -136,7 +137,7 @@ public class WifiConfigurationHelper { config.enterpriseConfig.setPhase2Method(phase2); config.enterpriseConfig.setIdentity(identity); config.enterpriseConfig.setAnonymousIdentity(anonymousIdentity); - config.enterpriseConfig.setCaCertificateAlias(caCert); + config.enterpriseConfig.setCaCertificateAliases(new String[] {caCert}); config.enterpriseConfig.setClientCertificateAlias(clientCert); return config; } @@ -147,8 +148,12 @@ public class WifiConfigurationHelper { private static WifiConfiguration createGenericConfig(String ssid) { WifiConfiguration config = new WifiConfiguration(); config.SSID = quotedString(ssid); - config.setIpAssignment(IpAssignment.DHCP); - config.setProxySettings(ProxySettings.NONE); + + IpConfiguration ipConfiguration = config.getIpConfiguration(); + ipConfiguration.setIpAssignment(IpAssignment.DHCP); + ipConfiguration.setProxySettings(ProxySettings.NONE); + config.setIpConfiguration(ipConfiguration); + return config; } @@ -237,6 +242,7 @@ public class WifiConfigurationHelper { throw new IllegalArgumentException(); } + IpConfiguration ipConfiguration = config.getIpConfiguration(); if (jsonConfig.has("ip")) { StaticIpConfiguration staticIpConfig = new StaticIpConfiguration(); @@ -247,13 +253,14 @@ public class WifiConfigurationHelper { staticIpConfig.dnsServers.add(getInetAddress(jsonConfig.getString("dns1"))); staticIpConfig.dnsServers.add(getInetAddress(jsonConfig.getString("dns2"))); - config.setIpAssignment(IpAssignment.STATIC); - config.setStaticIpConfiguration(staticIpConfig); + ipConfiguration.setIpAssignment(IpAssignment.STATIC); + ipConfiguration.setStaticIpConfiguration(staticIpConfig); } else { - config.setIpAssignment(IpAssignment.DHCP); + ipConfiguration.setIpAssignment(IpAssignment.DHCP); } + ipConfiguration.setProxySettings(ProxySettings.NONE); + config.setIpConfiguration(ipConfiguration); - config.setProxySettings(ProxySettings.NONE); return config; } diff --git a/core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTest.java b/core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTest.java index 2989df83866c..5d42915fc82b 100644 --- a/core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTest.java +++ b/core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTest.java @@ -268,7 +268,7 @@ public class BandwidthTest extends InstrumentationTestCase { File snd_stat = new File (root_filepath + "tcp_snd"); int tx = BandwidthTestUtil.parseIntValueFromFile(snd_stat); NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 1); - stats.addValues(NetworkStats.IFACE_ALL, uid, NetworkStats.SET_DEFAULT, + stats.addEntry(NetworkStats.IFACE_ALL, uid, NetworkStats.SET_DEFAULT, NetworkStats.TAG_NONE, rx, 0, tx, 0, 0); return stats; } diff --git a/core/tests/benchmarks/src/android/net/NetworkStatsBenchmark.java b/core/tests/benchmarks/src/android/net/NetworkStatsBenchmark.java index 707d7b30e09b..239f971664e9 100644 --- a/core/tests/benchmarks/src/android/net/NetworkStatsBenchmark.java +++ b/core/tests/benchmarks/src/android/net/NetworkStatsBenchmark.java @@ -54,7 +54,7 @@ public class NetworkStatsBenchmark { recycle.txBytes = 150000; recycle.txPackets = 1500; recycle.operations = 0; - mNetworkStats.addValues(recycle); + mNetworkStats.addEntry(recycle); if (recycle.set == 1) { uid++; } @@ -70,7 +70,7 @@ public class NetworkStatsBenchmark { recycle.txBytes = 180000 * mSize; recycle.txPackets = 1200 * mSize; recycle.operations = 0; - mNetworkStats.addValues(recycle); + mNetworkStats.addEntry(recycle); } } diff --git a/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java b/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java index 68b9b0079761..f4709ff0bc00 100644 --- a/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java +++ b/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java @@ -33,14 +33,15 @@ import android.os.ParcelFileDescriptor.AutoCloseInputStream; import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; +import android.support.test.uiautomator.UiDevice; import android.test.InstrumentationTestCase; import android.util.Log; -import libcore.io.Streams; - import com.google.mockwebserver.MockResponse; import com.google.mockwebserver.MockWebServer; +import libcore.io.Streams; + import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; @@ -63,6 +64,7 @@ public class DownloadManagerBaseTest extends InstrumentationTestCase { private static final String TAG = "DownloadManagerBaseTest"; protected DownloadManager mDownloadManager = null; private MockWebServer mServer = null; + private UiDevice mUiDevice = null; protected String mFileType = "text/plain"; protected Context mContext = null; protected MultipleDownloadsCompletedReceiver mReceiver = null; @@ -234,6 +236,7 @@ public class DownloadManagerBaseTest extends InstrumentationTestCase { @Override public void setUp() throws Exception { mContext = getInstrumentation().getContext(); + mUiDevice = UiDevice.getInstance(getInstrumentation()); mDownloadManager = (DownloadManager)mContext.getSystemService(Context.DOWNLOAD_SERVICE); mServer = new MockWebServer(); mServer.play(); @@ -512,7 +515,7 @@ public class DownloadManagerBaseTest extends InstrumentationTestCase { Log.i(LOG_TAG, "Setting WiFi State to: " + enable); WifiManager manager = (WifiManager)mContext.getSystemService(Context.WIFI_SERVICE); - manager.setWifiEnabled(enable); + mUiDevice.executeShellCommand("svc wifi " + (enable ? "enable" : "disable")); String timeoutMessage = "Timed out waiting for Wifi to be " + (enable ? "enabled!" : "disabled!"); diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java index beaaa373b1a0..d8b527c8a11a 100644 --- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java +++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java @@ -438,8 +438,7 @@ public class ActivityThreadTest { } private static ClientTransaction newStopTransaction(Activity activity) { - final StopActivityItem stopStateRequest = - StopActivityItem.obtain(false /* showWindow */, 0 /* configChanges */); + final StopActivityItem stopStateRequest = StopActivityItem.obtain(0 /* configChanges */); final ClientTransaction transaction = newTransaction(activity); transaction.setLifecycleStateRequest(stopStateRequest); diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java index 37d21f0928be..4b29d59de332 100644 --- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java +++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java @@ -274,30 +274,15 @@ public class ObjectPoolTests { @Test public void testRecycleStopItem() { - StopActivityItem emptyItem = StopActivityItem.obtain(false, 0); - StopActivityItem item = StopActivityItem.obtain(true, 4); + StopActivityItem emptyItem = StopActivityItem.obtain(0); + StopActivityItem item = StopActivityItem.obtain(4); assertNotSame(item, emptyItem); assertFalse(item.equals(emptyItem)); item.recycle(); assertEquals(item, emptyItem); - StopActivityItem item2 = StopActivityItem.obtain(true, 3); - assertSame(item, item2); - assertFalse(item2.equals(emptyItem)); - } - - @Test - public void testRecycleWindowVisibleItem() { - WindowVisibilityItem emptyItem = WindowVisibilityItem.obtain(false); - WindowVisibilityItem item = WindowVisibilityItem.obtain(true); - assertNotSame(item, emptyItem); - assertFalse(item.equals(emptyItem)); - - item.recycle(); - assertEquals(item, emptyItem); - - WindowVisibilityItem item2 = WindowVisibilityItem.obtain(true); + StopActivityItem item2 = StopActivityItem.obtain(3); assertSame(item, item2); assertFalse(item2.equals(emptyItem)); } diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java index 39bf7421b15e..ecea9011e704 100644 --- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java +++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java @@ -61,6 +61,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -180,31 +181,6 @@ public class TransactionParcelTests { } @Test - public void testWindowVisibilityChange() { - // Write to parcel - WindowVisibilityItem item = WindowVisibilityItem.obtain(true /* showWindow */); - writeAndPrepareForReading(item); - - // Read from parcel and assert - WindowVisibilityItem result = WindowVisibilityItem.CREATOR.createFromParcel(mParcel); - - assertEquals(item.hashCode(), result.hashCode()); - assertTrue(item.equals(result)); - - // Check different value - item = WindowVisibilityItem.obtain(false); - - mParcel = Parcel.obtain(); - writeAndPrepareForReading(item); - - // Read from parcel and assert - result = WindowVisibilityItem.CREATOR.createFromParcel(mParcel); - - assertEquals(item.hashCode(), result.hashCode()); - assertTrue(item.equals(result)); - } - - @Test public void testDestroy() { DestroyActivityItem item = DestroyActivityItem.obtain(true /* finished */, 135 /* configChanges */); @@ -299,8 +275,7 @@ public class TransactionParcelTests { @Test public void testStop() { // Write to parcel - StopActivityItem item = StopActivityItem.obtain(true /* showWindow */, - 14 /* configChanges */); + StopActivityItem item = StopActivityItem.obtain(14 /* configChanges */); writeAndPrepareForReading(item); // Read from parcel and assert @@ -311,14 +286,26 @@ public class TransactionParcelTests { } @Test + public void testStart() { + // Write to parcel + StartActivityItem item = StartActivityItem.obtain(); + writeAndPrepareForReading(item); + + // Read from parcel and assert + StartActivityItem result = StartActivityItem.CREATOR.createFromParcel(mParcel); + + assertEquals(item.hashCode(), result.hashCode()); + assertEquals(item, result); + } + + @Test public void testClientTransaction() { // Write to parcel - WindowVisibilityItem callback1 = WindowVisibilityItem.obtain(true); + NewIntentItem callback1 = NewIntentItem.obtain(new ArrayList<>(), true); ActivityConfigurationChangeItem callback2 = ActivityConfigurationChangeItem.obtain( config()); - StopActivityItem lifecycleRequest = StopActivityItem.obtain(true /* showWindow */, - 78 /* configChanges */); + StopActivityItem lifecycleRequest = StopActivityItem.obtain(78 /* configChanges */); IApplicationThread appThread = new StubAppThread(); Binder activityToken = new Binder(); @@ -340,7 +327,7 @@ public class TransactionParcelTests { @Test public void testClientTransactionCallbacksOnly() { // Write to parcel - WindowVisibilityItem callback1 = WindowVisibilityItem.obtain(true); + NewIntentItem callback1 = NewIntentItem.obtain(new ArrayList<>(), true); ActivityConfigurationChangeItem callback2 = ActivityConfigurationChangeItem.obtain( config()); @@ -363,8 +350,7 @@ public class TransactionParcelTests { @Test public void testClientTransactionLifecycleOnly() { // Write to parcel - StopActivityItem lifecycleRequest = StopActivityItem.obtain(true /* showWindow */, - 78 /* configChanges */); + StopActivityItem lifecycleRequest = StopActivityItem.obtain(78 /* configChanges */); IApplicationThread appThread = new StubAppThread(); Binder activityToken = new Binder(); diff --git a/core/tests/coretests/src/android/app/timedetector/ManualTimeSuggestionTest.java b/core/tests/coretests/src/android/app/timedetector/ManualTimeSuggestionTest.java index de6f8f7231fa..750ffa1c9a54 100644 --- a/core/tests/coretests/src/android/app/timedetector/ManualTimeSuggestionTest.java +++ b/core/tests/coretests/src/android/app/timedetector/ManualTimeSuggestionTest.java @@ -22,7 +22,7 @@ import static android.app.timezonedetector.ParcelableTestSupport.roundTripParcel import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; -import android.util.TimestampedValue; +import android.os.TimestampedValue; import org.junit.Test; diff --git a/core/tests/coretests/src/android/app/timedetector/NetworkTimeSuggestionTest.java b/core/tests/coretests/src/android/app/timedetector/NetworkTimeSuggestionTest.java index 9b3d0c9eaff6..b88c36f20bc6 100644 --- a/core/tests/coretests/src/android/app/timedetector/NetworkTimeSuggestionTest.java +++ b/core/tests/coretests/src/android/app/timedetector/NetworkTimeSuggestionTest.java @@ -22,7 +22,7 @@ import static android.app.timezonedetector.ParcelableTestSupport.roundTripParcel import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; -import android.util.TimestampedValue; +import android.os.TimestampedValue; import org.junit.Test; diff --git a/core/tests/coretests/src/android/app/timedetector/PhoneTimeSuggestionTest.java b/core/tests/coretests/src/android/app/timedetector/PhoneTimeSuggestionTest.java index bee270e5f5c9..ba29a97b55ab 100644 --- a/core/tests/coretests/src/android/app/timedetector/PhoneTimeSuggestionTest.java +++ b/core/tests/coretests/src/android/app/timedetector/PhoneTimeSuggestionTest.java @@ -22,7 +22,7 @@ import static android.app.timezonedetector.ParcelableTestSupport.roundTripParcel import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; -import android.util.TimestampedValue; +import android.os.TimestampedValue; import org.junit.Test; diff --git a/core/tests/coretests/src/android/net/NetworkKeyTest.java b/core/tests/coretests/src/android/net/NetworkKeyTest.java index c6c0b46d0505..b13bcd1311f6 100644 --- a/core/tests/coretests/src/android/net/NetworkKeyTest.java +++ b/core/tests/coretests/src/android/net/NetworkKeyTest.java @@ -18,6 +18,7 @@ package android.net; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.net.wifi.ScanResult; @@ -107,7 +108,7 @@ public class NetworkKeyTest { @Test public void createFromScanResult_nullSsid() { - ScanResult scanResult = new ScanResult(); + ScanResult scanResult = mock(ScanResult.class); scanResult.BSSID = VALID_BSSID; assertNull(NetworkKey.createFromScanResult(scanResult)); @@ -115,7 +116,7 @@ public class NetworkKeyTest { @Test public void createFromScanResult_emptySsid() { - ScanResult scanResult = new ScanResult(); + ScanResult scanResult = mock(ScanResult.class); scanResult.SSID = ""; scanResult.BSSID = VALID_BSSID; @@ -124,7 +125,7 @@ public class NetworkKeyTest { @Test public void createFromScanResult_noneSsid() { - ScanResult scanResult = new ScanResult(); + ScanResult scanResult = mock(ScanResult.class); scanResult.SSID = WifiManager.UNKNOWN_SSID; scanResult.BSSID = VALID_BSSID; @@ -133,7 +134,7 @@ public class NetworkKeyTest { @Test public void createFromScanResult_nullBssid() { - ScanResult scanResult = new ScanResult(); + ScanResult scanResult = mock(ScanResult.class); scanResult.SSID = VALID_UNQUOTED_SSID; assertNull(NetworkKey.createFromScanResult(scanResult)); @@ -141,7 +142,7 @@ public class NetworkKeyTest { @Test public void createFromScanResult_emptyBssid() { - ScanResult scanResult = new ScanResult(); + ScanResult scanResult = mock(ScanResult.class); scanResult.SSID = VALID_UNQUOTED_SSID; scanResult.BSSID = ""; @@ -150,7 +151,7 @@ public class NetworkKeyTest { @Test public void createFromScanResult_invalidBssid() { - ScanResult scanResult = new ScanResult(); + ScanResult scanResult = mock(ScanResult.class); scanResult.SSID = VALID_UNQUOTED_SSID; scanResult.BSSID = INVALID_BSSID; @@ -159,7 +160,7 @@ public class NetworkKeyTest { @Test public void createFromScanResult_validSsid() { - ScanResult scanResult = new ScanResult(); + ScanResult scanResult = mock(ScanResult.class); scanResult.SSID = VALID_UNQUOTED_SSID; scanResult.BSSID = VALID_BSSID; diff --git a/core/tests/coretests/src/android/os/ExternalVibrationTest.java b/core/tests/coretests/src/android/os/ExternalVibrationTest.java new file mode 100644 index 000000000000..3b872d5a7ff1 --- /dev/null +++ b/core/tests/coretests/src/android/os/ExternalVibrationTest.java @@ -0,0 +1,47 @@ +/* + * 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.os; + +import static junit.framework.Assert.assertEquals; + +import static org.mockito.Mockito.mock; + +import android.media.AudioAttributes; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class ExternalVibrationTest { + @Test + public void testSerialization() { + AudioAttributes audio = new AudioAttributes.Builder().build(); + IExternalVibrationController controller = mock(IExternalVibrationController.class); + ExternalVibration original = new ExternalVibration( + 123, // uid + "pkg", + audio, + controller); + Parcel p = Parcel.obtain(); + original.writeToParcel(p, 0); + p.setDataPosition(0); + ExternalVibration restored = ExternalVibration.CREATOR.createFromParcel(p); + assertEquals(original, restored); + } +} + diff --git a/core/tests/coretests/src/android/util/TimestampedValueTest.java b/core/tests/coretests/src/android/os/TimestampedValueTest.java index 6fc2400316c2..f36d9e6b1eff 100644 --- a/core/tests/coretests/src/android/util/TimestampedValueTest.java +++ b/core/tests/coretests/src/android/os/TimestampedValueTest.java @@ -14,14 +14,12 @@ * limitations under the License. */ -package android.util; +package android.os; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.fail; -import android.os.Parcel; - import androidx.test.runner.AndroidJUnit4; import org.junit.Test; diff --git a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java index 0e19ca84d433..d0fd92a838c9 100644 --- a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java +++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java @@ -90,12 +90,12 @@ public class ImeInsetsSourceConsumerTest { mImeConsumer.onWindowFocusGained(); mImeConsumer.applyImeVisibility(true); mController.cancelExistingAnimation(); - assertTrue(mController.getSourceConsumer(ime.getType()).isVisible()); + assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); // test if setVisibility can hide IME mImeConsumer.applyImeVisibility(false); mController.cancelExistingAnimation(); - assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); }); } diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java index 179929f2aae0..fa61a0a0250b 100644 --- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java +++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java @@ -16,11 +16,11 @@ package android.view; +import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_STATUS_BAR; import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL; import static android.view.WindowInsets.Type.systemBars; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -39,8 +39,6 @@ import android.view.SurfaceControl.Transaction; import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams; import android.view.test.InsetsModeSession; -import androidx.test.runner.AndroidJUnit4; - import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; @@ -52,6 +50,8 @@ import org.mockito.MockitoAnnotations; import java.util.List; +import androidx.test.runner.AndroidJUnit4; + /** * Tests for {@link InsetsAnimationControlImpl}. * @@ -116,7 +116,7 @@ public class InsetsAnimationControlImplTest { mController = new InsetsAnimationControlImpl(controls, new Rect(0, 0, 500, 500), mInsetsState, mMockListener, systemBars(), mMockController, 10 /* durationMs */, - false /* fade */); + false /* fade */, LAYOUT_INSETS_DURING_ANIMATION_SHOWN); } @Test diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java index a89fc1e6315f..1db96b15f83a 100644 --- a/core/tests/coretests/src/android/view/InsetsControllerTest.java +++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java @@ -68,6 +68,7 @@ public class InsetsControllerTest { private InsetsController mController; private SurfaceSession mSession = new SurfaceSession(); private SurfaceControl mLeash; + private ViewRootImpl mViewRoot; @Before public void setup() { @@ -77,13 +78,13 @@ public class InsetsControllerTest { InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { Context context = InstrumentationRegistry.getTargetContext(); // cannot mock ViewRootImpl since it's final. - ViewRootImpl viewRootImpl = new ViewRootImpl(context, context.getDisplay()); + mViewRoot = new ViewRootImpl(context, context.getDisplay()); try { - viewRootImpl.setView(new TextView(context), new LayoutParams(), null); + mViewRoot.setView(new TextView(context), new LayoutParams(), null); } catch (BadTokenException e) { // activity isn't running, we will ignore BadTokenException. } - mController = new InsetsController(viewRootImpl); + mController = new InsetsController(mViewRoot); final Rect rect = new Rect(5, 5, 5, 5); mController.calculateInsets( false, @@ -117,16 +118,22 @@ public class InsetsControllerTest { @Test public void testControlsRevoked_duringAnim() { - InsetsSourceControl control = - new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, new Point()); - mController.onControlsChanged(new InsetsSourceControl[] { control }); + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + InsetsSourceControl control = + new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, new Point()); + mController.onControlsChanged(new InsetsSourceControl[] { control }); - WindowInsetsAnimationControlListener mockListener = - mock(WindowInsetsAnimationControlListener.class); - mController.controlWindowInsetsAnimation(statusBars(), 10 /* durationMs */, mockListener); - verify(mockListener).onReady(any(), anyInt()); - mController.onControlsChanged(new InsetsSourceControl[0]); - verify(mockListener).onCancelled(); + WindowInsetsAnimationControlListener mockListener = + mock(WindowInsetsAnimationControlListener.class); + mController.controlWindowInsetsAnimation(statusBars(), 10 /* durationMs */, + mockListener); + + // Ready gets deferred until next predraw + mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw(); + verify(mockListener).onReady(any(), anyInt()); + mController.onControlsChanged(new InsetsSourceControl[0]); + verify(mockListener).onCancelled(); + }); } @Test @@ -154,16 +161,16 @@ public class InsetsControllerTest { mController.show(Type.all()); // quickly jump to final state by cancelling it. mController.cancelExistingAnimation(); - assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible()); - assertTrue(mController.getSourceConsumer(statusBar.getType()).isVisible()); - assertTrue(mController.getSourceConsumer(ime.getType()).isVisible()); + assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); + assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); + assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); mController.applyImeVisibility(false /* setVisible */); mController.hide(Type.all()); mController.cancelExistingAnimation(); - assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); mController.getSourceConsumer(ITYPE_IME).onWindowFocusLost(); }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); @@ -180,10 +187,10 @@ public class InsetsControllerTest { mController.getSourceConsumer(ITYPE_IME).onWindowFocusGained(); mController.applyImeVisibility(true); mController.cancelExistingAnimation(); - assertTrue(mController.getSourceConsumer(ime.getType()).isVisible()); + assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); mController.applyImeVisibility(false); mController.cancelExistingAnimation(); - assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); mController.getSourceConsumer(ITYPE_IME).onWindowFocusLost(); }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); @@ -201,16 +208,16 @@ public class InsetsControllerTest { // test show select types. mController.show(types); mController.cancelExistingAnimation(); - assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible()); - assertTrue(mController.getSourceConsumer(statusBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); + assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); // test hide all mController.hide(types); mController.cancelExistingAnimation(); - assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); } @@ -227,29 +234,29 @@ public class InsetsControllerTest { // test show select types. mController.show(types); mController.cancelExistingAnimation(); - assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible()); - assertTrue(mController.getSourceConsumer(statusBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); + assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); // test hide all mController.hide(Type.all()); mController.cancelExistingAnimation(); - assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); // test single show mController.show(Type.navigationBars()); mController.cancelExistingAnimation(); - assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); // test single hide mController.hide(Type.navigationBars()); - assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); @@ -267,31 +274,31 @@ public class InsetsControllerTest { mController.show(Type.navigationBars()); mController.show(Type.systemBars()); mController.cancelExistingAnimation(); - assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible()); - assertTrue(mController.getSourceConsumer(statusBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); + assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); mController.hide(Type.navigationBars()); mController.hide(Type.systemBars()); mController.cancelExistingAnimation(); - assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); int types = Type.navigationBars() | Type.systemBars(); // show two at a time and hide one by one. mController.show(types); mController.hide(Type.navigationBars()); mController.cancelExistingAnimation(); - assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible()); - assertTrue(mController.getSourceConsumer(statusBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); + assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); mController.hide(Type.systemBars()); mController.cancelExistingAnimation(); - assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); } @@ -309,15 +316,15 @@ public class InsetsControllerTest { mController.show(types); mController.hide(Type.navigationBars()); mController.cancelExistingAnimation(); - assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible()); - assertTrue(mController.getSourceConsumer(statusBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); + assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); mController.hide(Type.systemBars()); mController.cancelExistingAnimation(); - assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible()); - assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible()); + assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible()); }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); } @@ -336,12 +343,16 @@ public class InsetsControllerTest { ArgumentCaptor<WindowInsetsAnimationController> controllerCaptor = ArgumentCaptor.forClass(WindowInsetsAnimationController.class); + + // Ready gets deferred until next predraw + mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw(); + verify(mockListener).onReady(controllerCaptor.capture(), anyInt()); controllerCaptor.getValue().finish(false /* shown */); }); waitUntilNextFrame(); InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { - assertFalse(mController.getSourceConsumer(ITYPE_STATUS_BAR).isVisible()); + assertFalse(mController.getSourceConsumer(ITYPE_STATUS_BAR).isRequestedVisible()); }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); } diff --git a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java index 7af833bfcba4..492c03653990 100644 --- a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java +++ b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java @@ -96,7 +96,7 @@ public class InsetsSourceConsumerTest { @Test public void testHide() { mConsumer.hide(); - assertFalse("Consumer should not be visible", mConsumer.isVisible()); + assertFalse("Consumer should not be visible", mConsumer.isRequestedVisible()); verify(mSpyInsetsSource).setVisible(eq(false)); } @@ -106,7 +106,7 @@ public class InsetsSourceConsumerTest { // Insets source starts out visible mConsumer.hide(); mConsumer.show(); - assertTrue("Consumer should be visible", mConsumer.isVisible()); + assertTrue("Consumer should be visible", mConsumer.isRequestedVisible()); verify(mSpyInsetsSource).setVisible(eq(false)); verify(mSpyInsetsSource).setVisible(eq(true)); } diff --git a/core/tests/coretests/src/android/view/InsetsSourceTest.java b/core/tests/coretests/src/android/view/InsetsSourceTest.java index d7f50bafdc0a..e3b08bb14e46 100644 --- a/core/tests/coretests/src/android/view/InsetsSourceTest.java +++ b/core/tests/coretests/src/android/view/InsetsSourceTest.java @@ -108,5 +108,30 @@ public class InsetsSourceTest { assertEquals(Insets.of(0, 100, 0, 0), insets); } + @Test + public void testCalculateVisibleInsets_default() { + mSource.setFrame(new Rect(0, 0, 500, 100)); + Insets insets = mSource.calculateVisibleInsets(new Rect(100, 0, 500, 500)); + assertEquals(Insets.of(0, 100, 0, 0), insets); + } + + @Test + public void testCalculateVisibleInsets_override() { + mSource.setFrame(new Rect(0, 0, 500, 100)); + mSource.setVisibleFrame(new Rect(0, 0, 500, 200)); + Insets insets = mSource.calculateVisibleInsets(new Rect(100, 0, 500, 500)); + assertEquals(Insets.of(0, 200, 0, 0), insets); + } + + @Test + public void testCalculateVisibleInsets_invisible() { + mSource.setFrame(new Rect(0, 0, 500, 100)); + mSource.setVisibleFrame(new Rect(0, 0, 500, 200)); + mSource.setVisible(false); + Insets insets = mSource.calculateVisibleInsets(new Rect(100, 0, 500, 500)); + assertEquals(Insets.of(0, 0, 0, 0), insets); + } + + // Parcel and equals already tested via InsetsStateTest } diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java index 60620881b6f2..4b76fee00496 100644 --- a/core/tests/coretests/src/android/view/InsetsStateTest.java +++ b/core/tests/coretests/src/android/view/InsetsStateTest.java @@ -18,11 +18,14 @@ package android.view; import static android.view.InsetsState.ISIDE_BOTTOM; import static android.view.InsetsState.ISIDE_TOP; +import static android.view.InsetsState.ITYPE_BOTTOM_GESTURES; import static android.view.InsetsState.ITYPE_CAPTION_BAR; import static android.view.InsetsState.ITYPE_IME; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_STATUS_BAR; import static android.view.WindowInsets.Type.ime; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; import static org.junit.Assert.assertEquals; @@ -116,14 +119,18 @@ public class InsetsStateTest { @Test public void testCalculateInsets_imeIgnoredWithoutAdjustResize() { - mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100)); - mState.getSource(ITYPE_STATUS_BAR).setVisible(true); - mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300)); - mState.getSource(ITYPE_IME).setVisible(true); - WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), false, false, - DisplayCutout.NO_CUTOUT, null, null, 0, null); - assertEquals(0, insets.getSystemWindowInsetBottom()); - assertTrue(insets.isVisible(ime())); + try (final InsetsModeSession session = + new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_FULL)) { + mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100)); + mState.getSource(ITYPE_STATUS_BAR).setVisible(true); + mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300)); + mState.getSource(ITYPE_IME).setVisible(true); + WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), false, false, + DisplayCutout.NO_CUTOUT, null, null, SOFT_INPUT_ADJUST_NOTHING, null); + assertEquals(0, insets.getSystemWindowInsetBottom()); + assertEquals(100, insets.getInsets(ime()).bottom); + assertTrue(insets.isVisible(ime())); + } } @Test @@ -204,6 +211,42 @@ public class InsetsStateTest { assertFalse(InsetsState.getDefaultVisibility(ITYPE_IME)); } + @Test + public void testCalculateVisibleInsets() throws Exception { + try (final InsetsModeSession session = + new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_FULL)) { + mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100)); + mState.getSource(ITYPE_STATUS_BAR).setVisible(true); + mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300)); + mState.getSource(ITYPE_IME).setVisible(true); + + // Make sure bottom gestures are ignored + mState.getSource(ITYPE_BOTTOM_GESTURES).setFrame(new Rect(0, 100, 100, 300)); + mState.getSource(ITYPE_BOTTOM_GESTURES).setVisible(true); + Rect visibleInsets = mState.calculateVisibleInsets( + new Rect(0, 0, 100, 300), new Rect(), SOFT_INPUT_ADJUST_PAN); + assertEquals(new Rect(0, 100, 0, 100), visibleInsets); + } + } + + @Test + public void testCalculateVisibleInsets_adjustNothing() throws Exception { + try (final InsetsModeSession session = + new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_FULL)) { + mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100)); + mState.getSource(ITYPE_STATUS_BAR).setVisible(true); + mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300)); + mState.getSource(ITYPE_IME).setVisible(true); + + // Make sure bottom gestures are ignored + mState.getSource(ITYPE_BOTTOM_GESTURES).setFrame(new Rect(0, 100, 100, 300)); + mState.getSource(ITYPE_BOTTOM_GESTURES).setVisible(true); + Rect visibleInsets = mState.calculateVisibleInsets( + new Rect(0, 0, 100, 300), new Rect(), SOFT_INPUT_ADJUST_NOTHING); + assertEquals(new Rect(0, 100, 0, 0), visibleInsets); + } + } + private void assertEqualsAndHashCode() { assertEquals(mState, mState2); assertEquals(mState.hashCode(), mState2.hashCode()); diff --git a/core/tests/coretests/src/android/view/WindowInsetsTest.java b/core/tests/coretests/src/android/view/WindowInsetsTest.java index 8c7b28af2e78..e5a4f6d5b3be 100644 --- a/core/tests/coretests/src/android/view/WindowInsetsTest.java +++ b/core/tests/coretests/src/android/view/WindowInsetsTest.java @@ -63,7 +63,8 @@ public class WindowInsetsTest { b.setInsets(navigationBars(), Insets.of(0, 0, 0, 100)); b.setInsets(ime(), Insets.of(0, 0, 0, 300)); WindowInsets insets = b.build(); - assertEquals(300, insets.getSystemWindowInsets().bottom); + assertEquals(100, insets.getSystemWindowInsets().bottom); + assertEquals(300, insets.getInsets(ime()).bottom); } // TODO: Move this to CTS once API made public diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java index b9b6d55b52d6..e23c51e66a02 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerTest.java +++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.accessibility; +package android.view.accessibility; import static junit.framework.TestCase.assertFalse; import static junit.framework.TestCase.assertSame; @@ -29,16 +29,17 @@ import static org.mockito.Mockito.when; import android.accessibilityservice.AccessibilityServiceInfo; import android.app.Instrumentation; +import android.app.PendingIntent; +import android.app.RemoteAction; +import android.content.Intent; +import android.graphics.drawable.Icon; import android.os.UserHandle; -import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityManager; -import android.view.accessibility.IAccessibilityManager; -import android.view.accessibility.IAccessibilityManagerClient; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; import com.android.internal.util.IntPair; +import com.android.server.accessibility.test.MessageCapturingHandler; import org.junit.After; import org.junit.Before; @@ -57,6 +58,16 @@ import java.util.List; public class AccessibilityManagerTest { private static final boolean WITH_A11Y_ENABLED = true; private static final boolean WITH_A11Y_DISABLED = false; + private static final String LABEL = "label"; + private static final String INTENT_ACTION = "TESTACTION"; + private static final String DESCRIPTION = "description"; + private static final PendingIntent TEST_PENDING_INTENT = PendingIntent.getBroadcast( + InstrumentationRegistry.getTargetContext(), 0, new Intent(INTENT_ACTION), 0); + private static final RemoteAction TEST_ACTION = new RemoteAction( + Icon.createWithContentUri("content://test"), + LABEL, + DESCRIPTION, + TEST_PENDING_INTENT); @Mock private IAccessibilityManager mMockService; private MessageCapturingHandler mHandler; @@ -122,6 +133,29 @@ public class AccessibilityManagerTest { } @Test + public void testRegisterSystemAction() throws Exception { + AccessibilityManager manager = createManager(WITH_A11Y_ENABLED); + RemoteAction action = new RemoteAction( + Icon.createWithContentUri("content://test"), + LABEL, + DESCRIPTION, + TEST_PENDING_INTENT); + final int actionId = 0; + manager.registerSystemAction(TEST_ACTION, actionId); + + verify(mMockService).registerSystemAction(TEST_ACTION, actionId); + } + + @Test + public void testUnregisterSystemAction() throws Exception { + AccessibilityManager manager = createManager(WITH_A11Y_ENABLED); + final int actionId = 0; + manager.unregisterSystemAction(actionId); + + verify(mMockService).unregisterSystemAction(actionId); + } + + @Test public void testIsEnabled() throws Exception { // Create manager with a11y enabled AccessibilityManager manager = createManager(WITH_A11Y_ENABLED); diff --git a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java index 25ac4008955f..89c237498e5c 100644 --- a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java +++ b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java @@ -27,9 +27,12 @@ import static androidx.test.espresso.matcher.ViewMatchers.withId; import static org.hamcrest.Matchers.emptyString; import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import android.app.Activity; import android.app.Instrumentation; +import android.view.MotionEvent; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; @@ -221,4 +224,147 @@ public class EditorCursorDragTest { onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("ccc"), text.indexOf("ddd"))); onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(initialCursorPosition)); } + + @Test + public void testEditor_onTouchEvent_quickTapAfterDrag() throws Throwable { + String text = "Hi world!"; + onView(withId(R.id.textview)).perform(replaceText(text)); + onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0)); + + TextView tv = mActivity.findViewById(R.id.textview); + Editor editor = tv.getEditorForTesting(); + + // Simulate a tap-and-drag gesture. + long event1Time = 1001; + MotionEvent event1 = downEvent(event1Time, event1Time, 5f, 10f); + mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event1)); + assertFalse(editor.getInsertionController().isCursorBeingModified()); + assertFalse(editor.getSelectionController().isCursorBeingModified()); + + long event2Time = 1002; + MotionEvent event2 = moveEvent(event1Time, event2Time, 50f, 10f); + mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event2)); + assertTrue(editor.getInsertionController().isCursorBeingModified()); + assertFalse(editor.getSelectionController().isCursorBeingModified()); + + long event3Time = 1003; + MotionEvent event3 = moveEvent(event1Time, event3Time, 100f, 10f); + mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event3)); + assertTrue(editor.getInsertionController().isCursorBeingModified()); + assertFalse(editor.getSelectionController().isCursorBeingModified()); + + long event4Time = 2004; + MotionEvent event4 = upEvent(event1Time, event4Time, 100f, 10f); + mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event4)); + assertFalse(editor.getInsertionController().isCursorBeingModified()); + assertFalse(editor.getSelectionController().isCursorBeingModified()); + + // Simulate a quick tap after the drag, near the location where the drag ended. + long event5Time = 2005; + MotionEvent event5 = downEvent(event5Time, event5Time, 90f, 10f); + mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event5)); + assertFalse(editor.getInsertionController().isCursorBeingModified()); + assertFalse(editor.getSelectionController().isCursorBeingModified()); + + long event6Time = 2006; + MotionEvent event6 = upEvent(event5Time, event6Time, 90f, 10f); + mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event6)); + assertFalse(editor.getInsertionController().isCursorBeingModified()); + assertFalse(editor.getSelectionController().isCursorBeingModified()); + + // Simulate another quick tap in the same location; now selection should be triggered. + long event7Time = 2007; + MotionEvent event7 = downEvent(event7Time, event7Time, 90f, 10f); + mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event7)); + assertFalse(editor.getInsertionController().isCursorBeingModified()); + assertTrue(editor.getSelectionController().isCursorBeingModified()); + } + + @Test + public void testEditor_onTouchEvent_cursorDrag() throws Throwable { + String text = "testEditor_onTouchEvent_cursorDrag"; + onView(withId(R.id.textview)).perform(replaceText(text)); + onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0)); + + TextView tv = mActivity.findViewById(R.id.textview); + Editor editor = tv.getEditorForTesting(); + + // Simulate a tap-and-drag gesture. This should trigger a cursor drag. + long event1Time = 1001; + MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f); + mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event1)); + assertFalse(editor.getInsertionController().isCursorBeingModified()); + assertFalse(editor.getSelectionController().isCursorBeingModified()); + + long event2Time = 1002; + MotionEvent event2 = moveEvent(event1Time, event2Time, 21f, 30f); + mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event2)); + assertFalse(editor.getInsertionController().isCursorBeingModified()); + assertFalse(editor.getSelectionController().isCursorBeingModified()); + + long event3Time = 1003; + MotionEvent event3 = moveEvent(event1Time, event3Time, 120f, 30f); + mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event3)); + assertTrue(editor.getInsertionController().isCursorBeingModified()); + assertFalse(editor.getSelectionController().isCursorBeingModified()); + + long event4Time = 1004; + MotionEvent event4 = upEvent(event1Time, event4Time, 120f, 30f); + mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event4)); + assertFalse(editor.getInsertionController().isCursorBeingModified()); + assertFalse(editor.getSelectionController().isCursorBeingModified()); + } + + @Test + public void testEditor_onTouchEvent_selectionDrag() throws Throwable { + String text = "testEditor_onTouchEvent_selectionDrag"; + onView(withId(R.id.textview)).perform(replaceText(text)); + onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0)); + + TextView tv = mActivity.findViewById(R.id.textview); + Editor editor = tv.getEditorForTesting(); + + // Simulate a double-tap followed by a drag. This should trigger a selection drag. + long event1Time = 1001; + MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f); + mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event1)); + assertFalse(editor.getInsertionController().isCursorBeingModified()); + assertFalse(editor.getSelectionController().isCursorBeingModified()); + + long event2Time = 1002; + MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f); + mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event2)); + assertFalse(editor.getInsertionController().isCursorBeingModified()); + assertFalse(editor.getSelectionController().isCursorBeingModified()); + + long event3Time = 1003; + MotionEvent event3 = downEvent(event3Time, event3Time, 20f, 30f); + mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event3)); + assertFalse(editor.getInsertionController().isCursorBeingModified()); + assertTrue(editor.getSelectionController().isCursorBeingModified()); + + long event4Time = 1004; + MotionEvent event4 = moveEvent(event3Time, event4Time, 120f, 30f); + mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event4)); + assertFalse(editor.getInsertionController().isCursorBeingModified()); + assertTrue(editor.getSelectionController().isCursorBeingModified()); + + long event5Time = 1005; + MotionEvent event5 = upEvent(event3Time, event5Time, 120f, 30f); + mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event5)); + assertFalse(editor.getInsertionController().isCursorBeingModified()); + assertFalse(editor.getSelectionController().isCursorBeingModified()); + } + + private static MotionEvent downEvent(long downTime, long eventTime, float x, float y) { + return MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_DOWN, x, y, 0); + } + + private static MotionEvent upEvent(long downTime, long eventTime, float x, float y) { + return MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0); + } + + private static MotionEvent moveEvent(long downTime, long eventTime, float x, float y) { + return MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, x, y, 0); + } } diff --git a/core/tests/coretests/src/android/widget/EditorTouchStateTest.java b/core/tests/coretests/src/android/widget/EditorTouchStateTest.java index 6adb1b8fa0d6..215d0b800074 100644 --- a/core/tests/coretests/src/android/widget/EditorTouchStateTest.java +++ b/core/tests/coretests/src/android/widget/EditorTouchStateTest.java @@ -120,6 +120,60 @@ public class EditorTouchStateTest { } @Test + public void testUpdate_doubleTap_delayAfterFirstDownEvent() throws Exception { + // Simulate an ACTION_DOWN event. + long event1Time = 1000; + MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f); + mTouchState.update(event1, mConfig); + assertSingleTap(mTouchState, 20f, 30f, 0, 0, false); + + // Simulate an ACTION_UP event with a delay that's longer than the double-tap timeout. + long event2Time = 1000 + ViewConfiguration.getDoubleTapTimeout() + 1; + MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f); + mTouchState.update(event2, mConfig); + assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false); + + // Generate an ACTION_DOWN event whose time is within the double-tap timeout when + // calculated from the last ACTION_UP event time. Even though the time between the last up + // and this down event is within the double-tap timeout, this should not be considered a + // double-tap (since the first down event had a longer delay). + long event3Time = event2Time + 1; + MotionEvent event3 = downEvent(event3Time, event3Time, 22f, 33f); + mTouchState.update(event3, mConfig); + assertSingleTap(mTouchState, 22f, 33f, 20f, 30f, false); + } + + @Test + public void testUpdate_quickTapAfterDrag() throws Exception { + // Simulate an ACTION_DOWN event. + long event1Time = 1000; + MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f); + mTouchState.update(event1, mConfig); + assertSingleTap(mTouchState, 20f, 30f, 0, 0, false); + + // Simulate an ACTION_MOVE event. + long event2Time = 1001; + MotionEvent event2 = moveEvent(event1Time, event2Time, 200f, 31f); + mTouchState.update(event2, mConfig); + assertSingleTap(mTouchState, 20f, 30f, 0, 0, true); + + // Simulate an ACTION_UP event with a delay that's longer than the double-tap timeout. + long event3Time = 5000; + MotionEvent event3 = upEvent(event1Time, event3Time, 200f, 31f); + mTouchState.update(event3, mConfig); + assertSingleTap(mTouchState, 20f, 30f, 200f, 31f, false); + + // Generate an ACTION_DOWN event whose time is within the double-tap timeout when + // calculated from the last ACTION_UP event time. Even though the time between the last up + // and this down event is within the double-tap timeout, this should not be considered a + // double-tap (since the first down event had a longer delay). + long event4Time = event3Time + 1; + MotionEvent event4 = downEvent(event4Time, event4Time, 200f, 31f); + mTouchState.update(event4, mConfig); + assertSingleTap(mTouchState, 200f, 31f, 200f, 31f, false); + } + + @Test public void testUpdate_tripleClick_mouse() throws Exception { // Simulate an ACTION_DOWN event. long event1Time = 1000; diff --git a/core/tests/coretests/src/com/android/internal/infra/AndroidFutureTest.java b/core/tests/coretests/src/com/android/internal/infra/AndroidFutureTest.java index ffc925ff82cd..f108eb8aeb0b 100644 --- a/core/tests/coretests/src/com/android/internal/infra/AndroidFutureTest.java +++ b/core/tests/coretests/src/com/android/internal/infra/AndroidFutureTest.java @@ -121,7 +121,12 @@ public class AndroidFutureTest { AndroidFuture future2 = AndroidFuture.CREATOR.createFromParcel(parcel); ExecutionException executionException = expectThrows(ExecutionException.class, future2::get); - assertThat(executionException.getCause()).isInstanceOf(UnsupportedOperationException.class); + + Throwable cause = executionException.getCause(); + String msg = cause.getMessage(); + assertThat(cause).isInstanceOf(UnsupportedOperationException.class); + assertThat(msg).contains(getClass().getName()); + assertThat(msg).contains("testWriteToParcel_Exception"); } @Test diff --git a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java index 66d84aaf35cd..9018320e479c 100644 --- a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java +++ b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java @@ -79,138 +79,6 @@ public class ActivityThreadClientTest { @Test @UiThreadTest - public void testWindowVisibilityChange_OnCreate() throws Exception { - try (ClientMockSession clientSession = new ClientMockSession()) { - ActivityClientRecord r = clientSession.stubActivityRecord(); - - clientSession.launchActivity(r); - assertEquals(ON_CREATE, r.getLifecycleState()); - - clientSession.changeVisibility(r, true); - assertEquals(ON_CREATE, r.getLifecycleState()); - - clientSession.changeVisibility(r, false); - assertEquals(ON_CREATE, r.getLifecycleState()); - } - } - - @Test - @UiThreadTest - public void testWindowVisibilityChange_OnCreate_Finished() throws Exception { - try (ClientMockSession clientSession = new ClientMockSession()) { - ActivityClientRecord r = clientSession.stubActivityRecord(); - - Activity activity = clientSession.launchActivity(r); - activity.finish(); - assertEquals(ON_CREATE, r.getLifecycleState()); - - clientSession.changeVisibility(r, true); - assertEquals(ON_CREATE, r.getLifecycleState()); - - clientSession.changeVisibility(r, false); - assertEquals(ON_CREATE, r.getLifecycleState()); - } - } - - @Test - @UiThreadTest - public void testWindowVisibilityChange_OnStart() throws Exception { - try (ClientMockSession clientSession = new ClientMockSession()) { - ActivityClientRecord r = clientSession.stubActivityRecord(); - - clientSession.launchActivity(r); - clientSession.startActivity(r); - assertEquals(ON_START, r.getLifecycleState()); - - clientSession.changeVisibility(r, false); - assertEquals(ON_STOP, r.getLifecycleState()); - - clientSession.changeVisibility(r, true); - assertEquals(ON_START, r.getLifecycleState()); - } - } - - @Test - @UiThreadTest - public void testWindowVisibilityChange_OnStart_Finished() throws Exception { - try (ClientMockSession clientSession = new ClientMockSession()) { - ActivityClientRecord r = clientSession.stubActivityRecord(); - - Activity activity = clientSession.launchActivity(r); - clientSession.startActivity(r); - activity.finish(); - assertEquals(ON_START, r.getLifecycleState()); - - clientSession.changeVisibility(r, false); - assertEquals(ON_STOP, r.getLifecycleState()); - - clientSession.changeVisibility(r, true); - assertEquals(ON_START, r.getLifecycleState()); - } - } - - @Test - @UiThreadTest - public void testWindowVisibilityChange_OnResume() throws Exception { - try (ClientMockSession clientSession = new ClientMockSession()) { - ActivityClientRecord r = clientSession.stubActivityRecord(); - - clientSession.launchActivity(r); - clientSession.startActivity(r); - clientSession.resumeActivity(r); - assertEquals(ON_RESUME, r.getLifecycleState()); - - clientSession.changeVisibility(r, false); - assertEquals(ON_STOP, r.getLifecycleState()); - - clientSession.changeVisibility(r, true); - assertEquals(ON_START, r.getLifecycleState()); - } - } - - @Test - @UiThreadTest - public void testWindowVisibilityChange_OnPause() throws Exception { - try (ClientMockSession clientSession = new ClientMockSession()) { - ActivityClientRecord r = clientSession.stubActivityRecord(); - - clientSession.launchActivity(r); - clientSession.startActivity(r); - clientSession.resumeActivity(r); - clientSession.pauseActivity(r); - assertEquals(ON_PAUSE, r.getLifecycleState()); - - clientSession.changeVisibility(r, false); - assertEquals(ON_STOP, r.getLifecycleState()); - - clientSession.changeVisibility(r, true); - assertEquals(ON_START, r.getLifecycleState()); - } - } - - @Test - @UiThreadTest - public void testWindowVisibilityChange_OnStop() throws Exception { - try (ClientMockSession clientSession = new ClientMockSession()) { - ActivityClientRecord r = clientSession.stubActivityRecord(); - - clientSession.launchActivity(r); - clientSession.startActivity(r); - clientSession.resumeActivity(r); - clientSession.pauseActivity(r); - clientSession.stopActivity(r); - assertEquals(ON_STOP, r.getLifecycleState()); - - clientSession.changeVisibility(r, true); - assertEquals(ON_START, r.getLifecycleState()); - - clientSession.changeVisibility(r, false); - assertEquals(ON_STOP, r.getLifecycleState()); - } - } - - @Test - @UiThreadTest public void testLifecycleAfterFinished_OnCreate() throws Exception { try (ClientMockSession clientSession = new ClientMockSession()) { ActivityClientRecord r = clientSession.stubActivityRecord(); @@ -308,7 +176,7 @@ public class ActivityThreadClientTest { } private void startActivity(ActivityClientRecord r) { - mThread.handleStartActivity(r, null /* pendingActions */); + mThread.handleStartActivity(r.token, null /* pendingActions */); } private void resumeActivity(ActivityClientRecord r) { @@ -323,7 +191,7 @@ public class ActivityThreadClientTest { } private void stopActivity(ActivityClientRecord r) { - mThread.handleStopActivity(r.token, false /* show */, 0 /* configChanges */, + mThread.handleStopActivity(r.token, 0 /* configChanges */, new PendingTransactionActions(), false /* finalStateRequest */, "test"); } @@ -332,10 +200,6 @@ public class ActivityThreadClientTest { false /* getNonConfigInstance */, "test"); } - private void changeVisibility(ActivityClientRecord r, boolean show) { - mThread.handleWindowVisibility(r.token, show); - } - private ActivityClientRecord stubActivityRecord() { ComponentName component = new ComponentName( InstrumentationRegistry.getInstrumentation().getContext(), TestActivity.class); diff --git a/core/xsd/permission.xsd b/core/xsd/permission.xsd index cc01a31224bc..543504764ee3 100644 --- a/core/xsd/permission.xsd +++ b/core/xsd/permission.xsd @@ -46,6 +46,7 @@ <xs:element name="hidden-api-whitelisted-app" type="hidden-api-whitelisted-app"/> <xs:element name="allow-association" type="allow-association"/> <xs:element name="bugreport-whitelisted" type="bugreport-whitelisted"/> + <xs:element name="app-data-isolation-whitelisted-app" type="app-data-isolation-whitelisted-app"/> </xs:choice> </xs:complexType> </xs:element> @@ -161,6 +162,9 @@ <xs:attribute name="target" type="xs:string"/> <xs:attribute name="allowed" type="xs:string"/> </xs:complexType> + <xs:complexType name="app-data-isolation-whitelisted-app"> + <xs:attribute name="package" type="xs:string"/> + </xs:complexType> <xs:complexType name="bugreport-whitelisted"> <xs:attribute name="package" type="xs:string"/> </xs:complexType> diff --git a/core/xsd/schema/current.txt b/core/xsd/schema/current.txt index 771c1dffb909..c36c422a852d 100644 --- a/core/xsd/schema/current.txt +++ b/core/xsd/schema/current.txt @@ -45,6 +45,12 @@ package com.android.xml.permission.configfile { method public void set_package(String); } + public class AppDataIsolationWhitelistedApp { + ctor public AppDataIsolationWhitelistedApp(); + method public String get_package(); + method public void set_package(String); + } + public class AppLink { ctor public AppLink(); method public String get_package(); @@ -160,6 +166,7 @@ package com.android.xml.permission.configfile { method public java.util.List<com.android.xml.permission.configfile.AllowInPowerSaveExceptIdle> getAllowInPowerSaveExceptIdle_optional(); method public java.util.List<com.android.xml.permission.configfile.AllowInPowerSave> getAllowInPowerSave_optional(); method public java.util.List<com.android.xml.permission.configfile.AllowUnthrottledLocation> getAllowUnthrottledLocation_optional(); + method public java.util.List<com.android.xml.permission.configfile.AppDataIsolationWhitelistedApp> getAppDataIsolationWhitelistedApp_optional(); method public java.util.List<com.android.xml.permission.configfile.AppLink> getAppLink_optional(); method public java.util.List<com.android.xml.permission.configfile.AssignPermission> getAssignPermission_optional(); method public java.util.List<com.android.xml.permission.configfile.BackupTransportWhitelistedService> getBackupTransportWhitelistedService_optional(); diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index eb1d1ab1089c..9930ea262b32 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -243,6 +243,7 @@ applications that come with the platform <permission name="android.permission.MANAGE_USB"/> <permission name="android.permission.MODIFY_PHONE_STATE"/> <permission name="android.permission.READ_NETWORK_USAGE_HISTORY"/> + <permission name="android.permission.TETHER_PRIVILEGED"/> <permission name="android.permission.UPDATE_APP_OPS_STATS"/> </privapp-permissions> diff --git a/graphics/java/android/graphics/BaseCanvas.java b/graphics/java/android/graphics/BaseCanvas.java index 6e7f286d19a7..bee8d5efc933 100644 --- a/graphics/java/android/graphics/BaseCanvas.java +++ b/graphics/java/android/graphics/BaseCanvas.java @@ -21,7 +21,7 @@ import android.annotation.ColorLong; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Size; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.graphics.Canvas.VertexMode; import android.graphics.text.MeasuredText; import android.text.GraphicsOperations; diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java index d900a42b1e66..ac094ba5d5d2 100644 --- a/graphics/java/android/graphics/Bitmap.java +++ b/graphics/java/android/graphics/Bitmap.java @@ -21,8 +21,8 @@ import android.annotation.ColorInt; import android.annotation.ColorLong; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.UnsupportedAppUsage; import android.annotation.WorkerThread; +import android.compat.annotation.UnsupportedAppUsage; import android.content.res.ResourcesImpl; import android.hardware.HardwareBuffer; import android.os.Build; diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java index 5623a8a49b35..bad487b47682 100644 --- a/graphics/java/android/graphics/BitmapFactory.java +++ b/graphics/java/android/graphics/BitmapFactory.java @@ -20,7 +20,7 @@ import static android.graphics.BitmapFactory.Options.validate; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.res.AssetManager; import android.content.res.Resources; import android.os.Trace; diff --git a/graphics/java/android/graphics/BitmapRegionDecoder.java b/graphics/java/android/graphics/BitmapRegionDecoder.java index 629d8c131b68..34eba97819aa 100644 --- a/graphics/java/android/graphics/BitmapRegionDecoder.java +++ b/graphics/java/android/graphics/BitmapRegionDecoder.java @@ -15,7 +15,7 @@ package android.graphics; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.res.AssetManager; import android.os.Build; diff --git a/graphics/java/android/graphics/BitmapShader.java b/graphics/java/android/graphics/BitmapShader.java index 198d1e7bc956..edf53c491311 100644 --- a/graphics/java/android/graphics/BitmapShader.java +++ b/graphics/java/android/graphics/BitmapShader.java @@ -17,7 +17,7 @@ package android.graphics; import android.annotation.NonNull; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; /** * Shader used to draw a bitmap as a texture. The bitmap can be repeated or diff --git a/graphics/java/android/graphics/Camera.java b/graphics/java/android/graphics/Camera.java index cbd4eadca30a..80a3740d2f4e 100644 --- a/graphics/java/android/graphics/Camera.java +++ b/graphics/java/android/graphics/Camera.java @@ -16,7 +16,7 @@ package android.graphics; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; /** * A camera instance can be used to compute 3D transformations and diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java index a815f20293c5..9a0ca3e4ad9b 100644 --- a/graphics/java/android/graphics/Canvas.java +++ b/graphics/java/android/graphics/Canvas.java @@ -22,7 +22,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Size; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.graphics.text.MeasuredText; import android.os.Build; diff --git a/graphics/java/android/graphics/CanvasProperty.java b/graphics/java/android/graphics/CanvasProperty.java index 1275e0827580..4263772c1c2c 100644 --- a/graphics/java/android/graphics/CanvasProperty.java +++ b/graphics/java/android/graphics/CanvasProperty.java @@ -16,7 +16,8 @@ package android.graphics; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; + import com.android.internal.util.VirtualRefBasePtr; /** diff --git a/graphics/java/android/graphics/ColorMatrixColorFilter.java b/graphics/java/android/graphics/ColorMatrixColorFilter.java index 0f7980cc32e4..a8b18a9fcb1f 100644 --- a/graphics/java/android/graphics/ColorMatrixColorFilter.java +++ b/graphics/java/android/graphics/ColorMatrixColorFilter.java @@ -18,7 +18,7 @@ package android.graphics; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; /** * A color filter that transforms colors through a 4x5 color matrix. This filter diff --git a/graphics/java/android/graphics/FontFamily.java b/graphics/java/android/graphics/FontFamily.java index 5ad93f411393..ae90995573dc 100644 --- a/graphics/java/android/graphics/FontFamily.java +++ b/graphics/java/android/graphics/FontFamily.java @@ -17,7 +17,7 @@ package android.graphics; import android.annotation.Nullable; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.res.AssetManager; import android.graphics.fonts.FontVariationAxis; import android.text.TextUtils; diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java index 21cc3757a40e..c146bbd4441b 100644 --- a/graphics/java/android/graphics/FontListParser.java +++ b/graphics/java/android/graphics/FontListParser.java @@ -16,7 +16,7 @@ package android.graphics; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.graphics.fonts.FontVariationAxis; import android.text.FontConfig; import android.util.Xml; diff --git a/graphics/java/android/graphics/GraphicBuffer.java b/graphics/java/android/graphics/GraphicBuffer.java index 3b1fc70397ea..99fa5eef7bbd 100644 --- a/graphics/java/android/graphics/GraphicBuffer.java +++ b/graphics/java/android/graphics/GraphicBuffer.java @@ -16,7 +16,7 @@ package android.graphics; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.os.Parcelable; diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java index bcb313e1c227..83432c362672 100644 --- a/graphics/java/android/graphics/ImageDecoder.java +++ b/graphics/java/android/graphics/ImageDecoder.java @@ -28,8 +28,8 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Px; import android.annotation.TestApi; -import android.annotation.UnsupportedAppUsage; import android.annotation.WorkerThread; +import android.compat.annotation.UnsupportedAppUsage; import android.content.ContentResolver; import android.content.res.AssetFileDescriptor; import android.content.res.AssetManager; diff --git a/graphics/java/android/graphics/LightingColorFilter.java b/graphics/java/android/graphics/LightingColorFilter.java index 62a890ff4f0b..221dfa192795 100644 --- a/graphics/java/android/graphics/LightingColorFilter.java +++ b/graphics/java/android/graphics/LightingColorFilter.java @@ -22,7 +22,7 @@ package android.graphics; import android.annotation.ColorInt; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; /** * A color filter that can be used to simulate simple lighting effects. diff --git a/graphics/java/android/graphics/LinearGradient.java b/graphics/java/android/graphics/LinearGradient.java index 12e63c09d76b..3f3ad967fe97 100644 --- a/graphics/java/android/graphics/LinearGradient.java +++ b/graphics/java/android/graphics/LinearGradient.java @@ -20,7 +20,7 @@ import android.annotation.ColorInt; import android.annotation.ColorLong; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; public class LinearGradient extends Shader { diff --git a/graphics/java/android/graphics/Matrix.java b/graphics/java/android/graphics/Matrix.java index 22b6401fdc2e..cf914c2c3eae 100644 --- a/graphics/java/android/graphics/Matrix.java +++ b/graphics/java/android/graphics/Matrix.java @@ -16,7 +16,7 @@ package android.graphics; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import dalvik.annotation.optimization.CriticalNative; import dalvik.annotation.optimization.FastNative; diff --git a/graphics/java/android/graphics/Movie.java b/graphics/java/android/graphics/Movie.java index 6f030ffac2df..4b3924f0d55f 100644 --- a/graphics/java/android/graphics/Movie.java +++ b/graphics/java/android/graphics/Movie.java @@ -16,7 +16,7 @@ package android.graphics; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.res.AssetManager; import android.os.Build; diff --git a/graphics/java/android/graphics/NinePatch.java b/graphics/java/android/graphics/NinePatch.java index c4c1eaceb4fc..ff3239348240 100644 --- a/graphics/java/android/graphics/NinePatch.java +++ b/graphics/java/android/graphics/NinePatch.java @@ -16,7 +16,7 @@ package android.graphics; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; /** * The NinePatch class permits drawing a bitmap in nine or more sections. diff --git a/graphics/java/android/graphics/Outline.java b/graphics/java/android/graphics/Outline.java index 1fc056c3652f..91a60c327bf0 100644 --- a/graphics/java/android/graphics/Outline.java +++ b/graphics/java/android/graphics/Outline.java @@ -19,7 +19,7 @@ package android.graphics; import android.annotation.FloatRange; import android.annotation.IntDef; import android.annotation.NonNull; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.graphics.drawable.Drawable; import java.lang.annotation.Retention; diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java index 109d8631284d..3b586242e5b1 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -24,7 +24,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Px; import android.annotation.Size; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.graphics.fonts.FontVariationAxis; import android.os.Build; import android.os.LocaleList; diff --git a/graphics/java/android/graphics/Path.java b/graphics/java/android/graphics/Path.java index 7282d52d6e23..1362fd864d29 100644 --- a/graphics/java/android/graphics/Path.java +++ b/graphics/java/android/graphics/Path.java @@ -20,7 +20,7 @@ import android.annotation.FloatRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Size; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import dalvik.annotation.optimization.CriticalNative; import dalvik.annotation.optimization.FastNative; diff --git a/graphics/java/android/graphics/Picture.java b/graphics/java/android/graphics/Picture.java index 8d12cbffc793..390d3d414346 100644 --- a/graphics/java/android/graphics/Picture.java +++ b/graphics/java/android/graphics/Picture.java @@ -17,7 +17,7 @@ package android.graphics; import android.annotation.NonNull; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import java.io.InputStream; import java.io.OutputStream; diff --git a/graphics/java/android/graphics/PorterDuff.java b/graphics/java/android/graphics/PorterDuff.java index bc1f66fdd5c0..1275cb9ca4f9 100644 --- a/graphics/java/android/graphics/PorterDuff.java +++ b/graphics/java/android/graphics/PorterDuff.java @@ -16,7 +16,7 @@ package android.graphics; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; /** * <p>This class contains the list of alpha compositing and blending modes diff --git a/graphics/java/android/graphics/PorterDuffColorFilter.java b/graphics/java/android/graphics/PorterDuffColorFilter.java index cc2d3a8969fc..50ecb62e7fcc 100644 --- a/graphics/java/android/graphics/PorterDuffColorFilter.java +++ b/graphics/java/android/graphics/PorterDuffColorFilter.java @@ -18,7 +18,7 @@ package android.graphics; import android.annotation.ColorInt; import android.annotation.NonNull; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; /** * A color filter that can be used to tint the source pixels using a single diff --git a/graphics/java/android/graphics/RadialGradient.java b/graphics/java/android/graphics/RadialGradient.java index acbe3da75247..96b7b9a78ba8 100644 --- a/graphics/java/android/graphics/RadialGradient.java +++ b/graphics/java/android/graphics/RadialGradient.java @@ -20,7 +20,7 @@ import android.annotation.ColorInt; import android.annotation.ColorLong; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; public class RadialGradient extends Shader { @UnsupportedAppUsage diff --git a/graphics/java/android/graphics/Rect.java b/graphics/java/android/graphics/Rect.java index 9e1946c557d2..081b851d1333 100644 --- a/graphics/java/android/graphics/Rect.java +++ b/graphics/java/android/graphics/Rect.java @@ -19,7 +19,7 @@ package android.graphics; import android.annotation.CheckResult; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; diff --git a/graphics/java/android/graphics/Region.java b/graphics/java/android/graphics/Region.java index ec7f7a05b685..d8d96413a93d 100644 --- a/graphics/java/android/graphics/Region.java +++ b/graphics/java/android/graphics/Region.java @@ -17,7 +17,7 @@ package android.graphics; import android.annotation.NonNull; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.os.Parcelable; import android.util.Pools.SynchronizedPool; diff --git a/graphics/java/android/graphics/Shader.java b/graphics/java/android/graphics/Shader.java index 3050d1dae5e4..5335aa4725ad 100644 --- a/graphics/java/android/graphics/Shader.java +++ b/graphics/java/android/graphics/Shader.java @@ -20,7 +20,7 @@ import android.annotation.ColorInt; import android.annotation.ColorLong; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import libcore.util.NativeAllocationRegistry; diff --git a/graphics/java/android/graphics/SurfaceTexture.java b/graphics/java/android/graphics/SurfaceTexture.java index 99f440d599cb..697daa8b7b70 100644 --- a/graphics/java/android/graphics/SurfaceTexture.java +++ b/graphics/java/android/graphics/SurfaceTexture.java @@ -17,7 +17,7 @@ package android.graphics; import android.annotation.Nullable; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.os.Handler; import android.os.Looper; import android.os.Message; diff --git a/graphics/java/android/graphics/SweepGradient.java b/graphics/java/android/graphics/SweepGradient.java index 667f45afe500..08520048b787 100644 --- a/graphics/java/android/graphics/SweepGradient.java +++ b/graphics/java/android/graphics/SweepGradient.java @@ -20,7 +20,7 @@ import android.annotation.ColorInt; import android.annotation.ColorLong; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; public class SweepGradient extends Shader { @UnsupportedAppUsage diff --git a/graphics/java/android/graphics/TableMaskFilter.java b/graphics/java/android/graphics/TableMaskFilter.java index d81c491e07e0..204f9705852a 100644 --- a/graphics/java/android/graphics/TableMaskFilter.java +++ b/graphics/java/android/graphics/TableMaskFilter.java @@ -16,7 +16,7 @@ package android.graphics; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; /** * @hide diff --git a/graphics/java/android/graphics/TemporaryBuffer.java b/graphics/java/android/graphics/TemporaryBuffer.java index 0ae2c703c21c..ef3f7f704e0d 100644 --- a/graphics/java/android/graphics/TemporaryBuffer.java +++ b/graphics/java/android/graphics/TemporaryBuffer.java @@ -16,7 +16,8 @@ package android.graphics; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; + import com.android.internal.util.ArrayUtils; /** diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index 6d20ec32cdc4..a2dd9a8322b6 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -25,7 +25,7 @@ import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.res.AssetManager; import android.graphics.fonts.Font; import android.graphics.fonts.FontFamily; diff --git a/graphics/java/android/graphics/Xfermode.java b/graphics/java/android/graphics/Xfermode.java index 6f4adfde7ff9..e79fb76d806e 100644 --- a/graphics/java/android/graphics/Xfermode.java +++ b/graphics/java/android/graphics/Xfermode.java @@ -21,7 +21,7 @@ package android.graphics; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; /** * Xfermode is the base class for objects that are called to implement custom diff --git a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java index 82f587086428..d8946009483c 100644 --- a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java @@ -19,7 +19,7 @@ package android.graphics.drawable; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.res.AssetFileDescriptor; import android.content.res.Resources; import android.content.res.Resources.Theme; diff --git a/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java b/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java index b29fd4db5803..686f146e9c18 100644 --- a/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java @@ -18,23 +18,23 @@ package android.graphics.drawable; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.UnsupportedAppUsage; -import android.graphics.Canvas; -import android.graphics.Rect; +import android.compat.annotation.UnsupportedAppUsage; import android.content.res.Resources; -import android.content.res.TypedArray; import android.content.res.Resources.Theme; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.os.SystemClock; import android.util.AttributeSet; import android.util.TypedValue; -import android.os.SystemClock; + +import com.android.internal.R; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; -import com.android.internal.R; - /** * @hide */ diff --git a/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java b/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java index 11a46c4ba9b9..06159d8a0558 100644 --- a/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java @@ -20,7 +20,7 @@ import android.animation.ObjectAnimator; import android.animation.TimeInterpolator; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.res.Resources; import android.content.res.Resources.Theme; import android.content.res.TypedArray; diff --git a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java index 66947da9166f..1acf6c512fbd 100644 --- a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java @@ -25,9 +25,9 @@ import android.animation.TimeInterpolator; import android.animation.ValueAnimator; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.UnsupportedAppUsage; import android.app.ActivityThread; import android.app.Application; +import android.compat.annotation.UnsupportedAppUsage; import android.content.pm.ActivityInfo.Config; import android.content.res.ColorStateList; import android.content.res.Resources; diff --git a/graphics/java/android/graphics/drawable/AnimationDrawable.java b/graphics/java/android/graphics/drawable/AnimationDrawable.java index 57764c2cb693..8c3fa441cbb0 100644 --- a/graphics/java/android/graphics/drawable/AnimationDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimationDrawable.java @@ -16,21 +16,21 @@ package android.graphics.drawable; -import com.android.internal.R; - -import java.io.IOException; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - import android.annotation.NonNull; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.res.Resources; -import android.content.res.TypedArray; import android.content.res.Resources.Theme; +import android.content.res.TypedArray; import android.os.SystemClock; import android.util.AttributeSet; +import com.android.internal.R; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; + /** * An object used to create frame-by-frame animations, defined by a series of * Drawable objects, which can be used as a View object's background. diff --git a/graphics/java/android/graphics/drawable/BitmapDrawable.java b/graphics/java/android/graphics/drawable/BitmapDrawable.java index e4aa774fd434..4e768c9eddfb 100644 --- a/graphics/java/android/graphics/drawable/BitmapDrawable.java +++ b/graphics/java/android/graphics/drawable/BitmapDrawable.java @@ -17,7 +17,7 @@ package android.graphics.drawable; import android.annotation.NonNull; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.pm.ActivityInfo.Config; import android.content.res.ColorStateList; import android.content.res.Resources; diff --git a/graphics/java/android/graphics/drawable/ClipDrawable.java b/graphics/java/android/graphics/drawable/ClipDrawable.java index 31fdb025bbc5..69ed9b423d48 100644 --- a/graphics/java/android/graphics/drawable/ClipDrawable.java +++ b/graphics/java/android/graphics/drawable/ClipDrawable.java @@ -16,20 +16,22 @@ package android.graphics.drawable; -import com.android.internal.R; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.res.Resources; -import android.content.res.TypedArray; import android.content.res.Resources.Theme; -import android.graphics.*; -import android.view.Gravity; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.PixelFormat; +import android.graphics.Rect; import android.util.AttributeSet; +import android.view.Gravity; + +import com.android.internal.R; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index d945fc49ec88..a7f8cc4688ef 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -215,6 +215,7 @@ cc_defaults { android: { srcs: [ + "pipeline/skia/ATraceMemoryDump.cpp", "pipeline/skia/GLFunctorDrawable.cpp", "pipeline/skia/LayerDrawable.cpp", "pipeline/skia/ShaderCache.cpp", @@ -244,7 +245,6 @@ cc_defaults { "DeviceInfo.cpp", "FrameInfo.cpp", "FrameInfoVisualizer.cpp", - "GpuMemoryTracker.cpp", "HardwareBitmapUploader.cpp", "HWUIProperties.sysprop", "JankTracker.cpp", @@ -325,7 +325,6 @@ cc_test { "tests/unit/DamageAccumulatorTests.cpp", "tests/unit/DeferredLayerUpdaterTests.cpp", "tests/unit/FatVectorTests.cpp", - "tests/unit/GpuMemoryTrackerTests.cpp", "tests/unit/GraphicsStatsServiceTests.cpp", "tests/unit/LayerUpdateQueueTests.cpp", "tests/unit/LinearAllocatorTests.cpp", diff --git a/libs/hwui/DeviceInfo.cpp b/libs/hwui/DeviceInfo.cpp index e53f3db08538..6d4a0c6421d9 100644 --- a/libs/hwui/DeviceInfo.cpp +++ b/libs/hwui/DeviceInfo.cpp @@ -52,8 +52,8 @@ void DeviceInfo::setMaxTextureSize(int maxTextureSize) { DeviceInfo::get()->mMaxTextureSize = maxTextureSize; } -void DeviceInfo::onDisplayConfigChanged() { - updateDisplayInfo(); +void DeviceInfo::onRefreshRateChanged(int64_t vsyncPeriod) { + mVsyncPeriod = vsyncPeriod; } void DeviceInfo::updateDisplayInfo() { @@ -113,10 +113,11 @@ void DeviceInfo::updateDisplayInfo() { ADisplay* primaryDisplay = mDisplays[mPhysicalDisplayIndex]; status_t status = ADisplay_getCurrentConfig(primaryDisplay, &mCurrentConfig); LOG_ALWAYS_FATAL_IF(status, "Failed to get display config, error %d", status); + mWidth = ADisplayConfig_getWidth(mCurrentConfig); mHeight = ADisplayConfig_getHeight(mCurrentConfig); mDensity = ADisplayConfig_getDensity(mCurrentConfig); - mRefreshRate = ADisplayConfig_getFps(mCurrentConfig); + mVsyncPeriod = static_cast<int64_t>(1000000000 / ADisplayConfig_getFps(mCurrentConfig)); mCompositorOffset = ADisplayConfig_getCompositorOffsetNanos(mCurrentConfig); mAppOffset = ADisplayConfig_getAppVsyncOffsetNanos(mCurrentConfig); } diff --git a/libs/hwui/DeviceInfo.h b/libs/hwui/DeviceInfo.h index a4207460883e..16a22f4706f5 100644 --- a/libs/hwui/DeviceInfo.h +++ b/libs/hwui/DeviceInfo.h @@ -37,7 +37,7 @@ public: static int32_t getWidth() { return get()->mWidth; } static int32_t getHeight() { return get()->mHeight; } static float getDensity() { return get()->mDensity; } - static float getRefreshRate() { return get()->mRefreshRate; } + static int64_t getVsyncPeriod() { return get()->mVsyncPeriod; } static int64_t getCompositorOffset() { return get()->mCompositorOffset; } static int64_t getAppOffset() { return get()->mAppOffset; } @@ -47,7 +47,8 @@ public: sk_sp<SkColorSpace> getWideColorSpace() const { return mWideColorSpace; } SkColorType getWideColorType() const { return mWideColorType; } - void onDisplayConfigChanged(); + // This method should be called whenever the display refresh rate changes. + void onRefreshRateChanged(int64_t vsyncPeriod); private: friend class renderthread::RenderThread; @@ -68,7 +69,7 @@ private: int32_t mWidth = 1080; int32_t mHeight = 1920; float mDensity = 2.0; - float mRefreshRate = 60.0; + int64_t mVsyncPeriod = 16666666; int64_t mCompositorOffset = 0; int64_t mAppOffset = 0; }; diff --git a/libs/hwui/GpuMemoryTracker.cpp b/libs/hwui/GpuMemoryTracker.cpp deleted file mode 100644 index a9a7af8f22f3..000000000000 --- a/libs/hwui/GpuMemoryTracker.cpp +++ /dev/null @@ -1,122 +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. - */ - -#include "utils/StringUtils.h" - -#include <GpuMemoryTracker.h> -#include <cutils/compiler.h> -#include <utils/Trace.h> -#include <array> -#include <sstream> -#include <unordered_set> -#include <vector> - -namespace android { -namespace uirenderer { - -pthread_t gGpuThread = 0; - -#define NUM_TYPES static_cast<int>(GpuObjectType::TypeCount) - -const char* TYPE_NAMES[] = { - "Texture", "OffscreenBuffer", "Layer", -}; - -struct TypeStats { - int totalSize = 0; - int count = 0; -}; - -static std::array<TypeStats, NUM_TYPES> gObjectStats; -static std::unordered_set<GpuMemoryTracker*> gObjectSet; - -void GpuMemoryTracker::notifySizeChanged(int newSize) { - int delta = newSize - mSize; - mSize = newSize; - gObjectStats[static_cast<int>(mType)].totalSize += delta; -} - -void GpuMemoryTracker::startTrackingObject() { - auto result = gObjectSet.insert(this); - LOG_ALWAYS_FATAL_IF(!result.second, - "startTrackingObject() on %p failed, already being tracked!", this); - gObjectStats[static_cast<int>(mType)].count++; -} - -void GpuMemoryTracker::stopTrackingObject() { - size_t removed = gObjectSet.erase(this); - LOG_ALWAYS_FATAL_IF(removed != 1, "stopTrackingObject removed %zd, is %p not being tracked?", - removed, this); - gObjectStats[static_cast<int>(mType)].count--; -} - -void GpuMemoryTracker::onGpuContextCreated() { - LOG_ALWAYS_FATAL_IF(gGpuThread != 0, - "We already have a gpu thread? " - "current = %lu, gpu thread = %lu", - pthread_self(), gGpuThread); - gGpuThread = pthread_self(); -} - -void GpuMemoryTracker::onGpuContextDestroyed() { - gGpuThread = 0; - if (CC_UNLIKELY(gObjectSet.size() > 0)) { - std::stringstream os; - dump(os); - ALOGE("%s", os.str().c_str()); - LOG_ALWAYS_FATAL("Leaked %zd GPU objects!", gObjectSet.size()); - } -} - -void GpuMemoryTracker::dump() { - std::stringstream strout; - dump(strout); - ALOGD("%s", strout.str().c_str()); -} - -void GpuMemoryTracker::dump(std::ostream& stream) { - for (int type = 0; type < NUM_TYPES; type++) { - const TypeStats& stats = gObjectStats[type]; - stream << TYPE_NAMES[type]; - stream << " is using " << SizePrinter{stats.totalSize}; - stream << ", count = " << stats.count; - stream << std::endl; - } -} - -int GpuMemoryTracker::getInstanceCount(GpuObjectType type) { - return gObjectStats[static_cast<int>(type)].count; -} - -int GpuMemoryTracker::getTotalSize(GpuObjectType type) { - return gObjectStats[static_cast<int>(type)].totalSize; -} - -void GpuMemoryTracker::onFrameCompleted() { - if (ATRACE_ENABLED()) { - char buf[128]; - for (int type = 0; type < NUM_TYPES; type++) { - snprintf(buf, 128, "hwui_%s", TYPE_NAMES[type]); - const TypeStats& stats = gObjectStats[type]; - ATRACE_INT(buf, stats.totalSize); - snprintf(buf, 128, "hwui_%s_count", TYPE_NAMES[type]); - ATRACE_INT(buf, stats.count); - } - } -} - -} // namespace uirenderer -} // namespace android; diff --git a/libs/hwui/GpuMemoryTracker.h b/libs/hwui/GpuMemoryTracker.h deleted file mode 100644 index de3ca99ef14b..000000000000 --- a/libs/hwui/GpuMemoryTracker.h +++ /dev/null @@ -1,77 +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. - */ -#pragma once - -#include <pthread.h> -#include <ostream> - -#include <log/log.h> - -namespace android { -namespace uirenderer { - -extern pthread_t gGpuThread; - -#define ASSERT_GPU_THREAD() \ - LOG_ALWAYS_FATAL_IF(!pthread_equal(gGpuThread, pthread_self()), \ - "Error, %p of type %d (size=%d) used on wrong thread! cur thread %lu " \ - "!= gpu thread %lu", \ - this, static_cast<int>(mType), mSize, pthread_self(), gGpuThread) - -enum class GpuObjectType { - Texture = 0, - OffscreenBuffer, - Layer, - - TypeCount, -}; - -class GpuMemoryTracker { -public: - GpuObjectType objectType() { return mType; } - int objectSize() { return mSize; } - - static void onGpuContextCreated(); - static void onGpuContextDestroyed(); - static void dump(); - static void dump(std::ostream& stream); - static int getInstanceCount(GpuObjectType type); - static int getTotalSize(GpuObjectType type); - static void onFrameCompleted(); - -protected: - explicit GpuMemoryTracker(GpuObjectType type) : mType(type) { - ASSERT_GPU_THREAD(); - startTrackingObject(); - } - - ~GpuMemoryTracker() { - notifySizeChanged(0); - stopTrackingObject(); - } - - void notifySizeChanged(int newSize); - -private: - void startTrackingObject(); - void stopTrackingObject(); - - int mSize = 0; - GpuObjectType mType; -}; - -} // namespace uirenderer -} // namespace android; diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp index 10e7160e7069..d25fc4b0b03e 100644 --- a/libs/hwui/JankTracker.cpp +++ b/libs/hwui/JankTracker.cpp @@ -81,7 +81,7 @@ static FrameInfoIndex sFrameStart = FrameInfoIndex::IntendedVsync; JankTracker::JankTracker(ProfileDataContainer* globalData) { mGlobalData = globalData; - nsecs_t frameIntervalNanos = static_cast<nsecs_t>(1_s / DeviceInfo::getRefreshRate()); + nsecs_t frameIntervalNanos = DeviceInfo::getVsyncPeriod(); nsecs_t sfOffset = DeviceInfo::getCompositorOffset(); nsecs_t offsetDelta = sfOffset - DeviceInfo::getAppOffset(); // There are two different offset cases. If the offsetDelta is positive diff --git a/libs/hwui/ProfileData.cpp b/libs/hwui/ProfileData.cpp index 7921662b213c..a8e36e37905d 100644 --- a/libs/hwui/ProfileData.cpp +++ b/libs/hwui/ProfileData.cpp @@ -15,6 +15,7 @@ */ #include "ProfileData.h" +#include "Properties.h" #include <cinttypes> @@ -102,6 +103,7 @@ void ProfileData::mergeWith(const ProfileData& other) { mGPUFrameCounts[i] >>= divider; mGPUFrameCounts[i] += other.mGPUFrameCounts[i]; } + mPipelineType = other.mPipelineType; } void ProfileData::dump(int fd) const { @@ -157,6 +159,7 @@ void ProfileData::reset() { mTotalFrameCount = 0; mJankFrameCount = 0; mStatStartTime = systemTime(SYSTEM_TIME_MONOTONIC); + mPipelineType = Properties::getRenderPipelineType(); } void ProfileData::reportFrame(int64_t duration) { diff --git a/libs/hwui/ProfileData.h b/libs/hwui/ProfileData.h index ccbffc6f136e..dd3ba661dd29 100644 --- a/libs/hwui/ProfileData.h +++ b/libs/hwui/ProfileData.h @@ -16,6 +16,7 @@ #pragma once +#include "Properties.h" #include "utils/Macros.h" #include <utils/Timers.h> @@ -65,6 +66,7 @@ public: uint32_t jankFrameCount() const { return mJankFrameCount; } nsecs_t statsStartTime() const { return mStatStartTime; } uint32_t jankTypeCount(JankType type) const { return mJankTypeCounts[static_cast<int>(type)]; } + RenderPipelineType pipelineType() const { return mPipelineType; } struct HistogramEntry { uint32_t renderTimeMs; @@ -103,6 +105,9 @@ private: uint32_t mTotalFrameCount; uint32_t mJankFrameCount; nsecs_t mStatStartTime; + + // true if HWUI renders with Vulkan pipeline + RenderPipelineType mPipelineType; }; // For testing diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp index f4149b9b4d7d..84549e8ce6e4 100644 --- a/libs/hwui/hwui/Bitmap.cpp +++ b/libs/hwui/hwui/Bitmap.cpp @@ -152,7 +152,9 @@ sk_sp<Bitmap> Bitmap::createFrom(AHardwareBuffer* hardwareBuffer, sk_sp<SkColorS AHardwareBuffer_describe(hardwareBuffer, &bufferDesc); SkImageInfo info = uirenderer::BufferDescriptionToImageInfo(bufferDesc, colorSpace); - const size_t rowBytes = info.bytesPerPixel() * bufferDesc.stride; + // If the stride is 0 we have to use the width as an approximation (eg, compressed buffer) + const auto bufferStride = bufferDesc.stride > 0 ? bufferDesc.stride : bufferDesc.width; + const size_t rowBytes = info.bytesPerPixel() * bufferStride; return sk_sp<Bitmap>(new Bitmap(hardwareBuffer, info, rowBytes, palette)); } diff --git a/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp b/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp new file mode 100644 index 000000000000..2c78b024536b --- /dev/null +++ b/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp @@ -0,0 +1,175 @@ +/* + * 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. + */ + +#include "ATraceMemoryDump.h" + +#include <utils/Trace.h> + +#include <cstring> + +namespace android { +namespace uirenderer { +namespace skiapipeline { + +// When purgeable is INVALID_TIME it won't be logged at all. +#define INVALID_TIME -1 + +/** + * Skia invokes the following SkTraceMemoryDump functions: + * 1. dumpNumericValue (dumpName, units="bytes", valueName="size") + * 2. dumpStringValue (dumpName, valueName="type") [optional -> for example CPU memory does not + * invoke dumpStringValue] + * 3. dumpNumericValue (dumpName, units="bytes", valueName="purgeable_size") [optional] + * 4. setMemoryBacking(dumpName, backingType) [optional -> for example Vulkan GPU resources do not + * invoke setMemoryBacking] + * + * ATraceMemoryDump calculates memory category first by looking at the "type" string passed to + * dumpStringValue and then by looking at "backingType" passed to setMemoryBacking. + * Only GPU Texture memory is tracked separately and everything else is grouped as one + * "GPU Memory" category. + */ +static std::unordered_map<const char*, const char*> sResourceMap = { + {"malloc", "Graphics CPU Memory"}, // taken from setMemoryBacking(backingType) + {"gl_texture", "Graphics Texture Memory"}, // taken from setMemoryBacking(backingType) + {"Texture", + "Graphics Texture Memory"}, // taken from dumpStringValue(value, valueName="type") + // Uncomment categories below to split "GPU Memory" into more brackets for debugging. + /*{"vk_buffer", "vk_buffer"}, + {"gl_renderbuffer", "gl_renderbuffer"}, + {"gl_buffer", "gl_buffer"}, + {"RenderTarget", "RenderTarget"}, + {"Stencil", "Stencil"}, + {"Path Data", "Path Data"}, + {"Buffer Object", "Buffer Object"}, + {"Surface", "Surface"},*/ +}; + +ATraceMemoryDump::ATraceMemoryDump() { + mLastDumpName.reserve(100); + mCategory.reserve(100); +} + +void ATraceMemoryDump::dumpNumericValue(const char* dumpName, const char* valueName, + const char* units, uint64_t value) { + if (!strcmp(units, "bytes")) { + recordAndResetCountersIfNeeded(dumpName); + if (!strcmp(valueName, "size")) { + mLastDumpValue = value; + } else if (!strcmp(valueName, "purgeable_size")) { + mLastPurgeableDumpValue = value; + } + } +} + +void ATraceMemoryDump::dumpStringValue(const char* dumpName, const char* valueName, + const char* value) { + if (!strcmp(valueName, "type")) { + recordAndResetCountersIfNeeded(dumpName); + auto categoryIt = sResourceMap.find(value); + if (categoryIt != sResourceMap.end()) { + mCategory = categoryIt->second; + } + } +} + +void ATraceMemoryDump::setMemoryBacking(const char* dumpName, const char* backingType, + const char* backingObjectId) { + recordAndResetCountersIfNeeded(dumpName); + auto categoryIt = sResourceMap.find(backingType); + if (categoryIt != sResourceMap.end()) { + mCategory = categoryIt->second; + } +} + +/** + * startFrame is invoked before dumping anything. It resets counters from the previous frame. + * This is important, because if there is no new data for a given category trace would assume + * usage has not changed (instead of reporting 0). + */ +void ATraceMemoryDump::startFrame() { + resetCurrentCounter(""); + for (auto& it : mCurrentValues) { + // Once a category is observed in at least one frame, it is always reported in subsequent + // frames (even if it is 0). Not logging a category to ATRACE would mean its value has not + // changed since the previous frame, which is not what we want. + it.second.time = 0; + // If purgeableTime is INVALID_TIME, then logTraces won't log it at all. + if (it.second.purgeableTime != INVALID_TIME) { + it.second.purgeableTime = 0; + } + } +} + +/** + * logTraces reads from mCurrentValues and logs the counters with ATRACE. + */ +void ATraceMemoryDump::logTraces() { + // Accumulate data from last dumpName + recordAndResetCountersIfNeeded(""); + for (auto& it : mCurrentValues) { + ATRACE_INT64(it.first.c_str(), it.second.time); + if (it.second.purgeableTime != INVALID_TIME) { + ATRACE_INT64((std::string("Purgeable ") + it.first).c_str(), it.second.purgeableTime); + } + } +} + +/** + * recordAndResetCountersIfNeeded reads memory usage from mLastDumpValue/mLastPurgeableDumpValue and + * accumulates in mCurrentValues[category]. It makes provision to create a new category and track + * purgeable memory only if there is at least one observation. + * recordAndResetCountersIfNeeded won't do anything until all the information for a given dumpName + * is received. + */ +void ATraceMemoryDump::recordAndResetCountersIfNeeded(const char* dumpName) { + if (!mLastDumpName.compare(dumpName)) { + // Still waiting for more data for current dumpName. + return; + } + + // First invocation will have an empty mLastDumpName. + if (!mLastDumpName.empty()) { + // A new dumpName observed -> store the data already collected. + auto memoryCounter = mCurrentValues.find(mCategory); + if (memoryCounter != mCurrentValues.end()) { + memoryCounter->second.time += mLastDumpValue; + if (mLastPurgeableDumpValue != INVALID_TIME) { + if (memoryCounter->second.purgeableTime == INVALID_TIME) { + memoryCounter->second.purgeableTime = mLastPurgeableDumpValue; + } else { + memoryCounter->second.purgeableTime += mLastPurgeableDumpValue; + } + } + } else { + mCurrentValues[mCategory] = {mLastDumpValue, mLastPurgeableDumpValue}; + } + } + + // Reset counters and default category for the newly observed "dumpName". + resetCurrentCounter(dumpName); +} + +void ATraceMemoryDump::resetCurrentCounter(const char* dumpName) { + mLastDumpValue = 0; + mLastPurgeableDumpValue = INVALID_TIME; + mLastDumpName = dumpName; + // Categories not listed in sResourceMap are reported as "GPU memory" + mCategory = "GPU Memory"; +} + +} /* namespace skiapipeline */ +} /* namespace uirenderer */ +} /* namespace android */ diff --git a/libs/hwui/pipeline/skia/ATraceMemoryDump.h b/libs/hwui/pipeline/skia/ATraceMemoryDump.h new file mode 100644 index 000000000000..aa5c401ad1ca --- /dev/null +++ b/libs/hwui/pipeline/skia/ATraceMemoryDump.h @@ -0,0 +1,79 @@ +/* + * 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. + */ + +#pragma once + +#include <SkString.h> +#include <SkTraceMemoryDump.h> + +#include <string> +#include <unordered_map> +#include <utility> + +namespace android { +namespace uirenderer { +namespace skiapipeline { + +class ATraceMemoryDump : public SkTraceMemoryDump { +public: + ATraceMemoryDump(); + ~ATraceMemoryDump() override {} + + void dumpNumericValue(const char* dumpName, const char* valueName, const char* units, + uint64_t value) override; + + void dumpStringValue(const char* dumpName, const char* valueName, const char* value) override; + + LevelOfDetail getRequestedDetails() const override { + return SkTraceMemoryDump::kLight_LevelOfDetail; + } + + bool shouldDumpWrappedObjects() const override { return false; } + + void setMemoryBacking(const char* dumpName, const char* backingType, + const char* backingObjectId) override; + + void setDiscardableMemoryBacking(const char*, const SkDiscardableMemory&) override {} + + void startFrame(); + + void logTraces(); + +private: + std::string mLastDumpName; + + uint64_t mLastDumpValue; + + uint64_t mLastPurgeableDumpValue; + + std::string mCategory; + + struct TraceValue { + uint64_t time; + uint64_t purgeableTime; + }; + + // keys are define in sResourceMap + std::unordered_map<std::string, TraceValue> mCurrentValues; + + void recordAndResetCountersIfNeeded(const char* dumpName); + + void resetCurrentCounter(const char* dumpName); +}; + +} /* namespace skiapipeline */ +} /* namespace uirenderer */ +} /* namespace android */
\ No newline at end of file diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp index 11dc013af6bc..35a885f46919 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp @@ -457,7 +457,10 @@ void SkiaPipeline::renderFrameImpl(const SkRect& clip, const Rect& contentDrawBounds, SkCanvas* canvas, const SkMatrix& preTransform) { SkAutoCanvasRestore saver(canvas, true); - canvas->androidFramework_setDeviceClipRestriction(preTransform.mapRect(clip).roundOut()); + auto clipRestriction = preTransform.mapRect(clip).roundOut(); + canvas->androidFramework_setDeviceClipRestriction(clipRestriction); + canvas->drawAnnotation(SkRect::Make(clipRestriction), "AndroidDeviceClipRestriction", + nullptr); canvas->concat(preTransform); // STOPSHIP: Revert, temporary workaround to clear always F16 frame buffer for b/74976293 diff --git a/libs/hwui/protos/graphicsstats.proto b/libs/hwui/protos/graphicsstats.proto index 0cd5c6228504..dd5676c9c961 100644 --- a/libs/hwui/protos/graphicsstats.proto +++ b/libs/hwui/protos/graphicsstats.proto @@ -29,6 +29,11 @@ message GraphicsStatsServiceDumpProto { } message GraphicsStatsProto { + enum PipelineType { + GL = 0; + VULKAN = 1; + } + // The package name of the app optional string package_name = 1; @@ -49,6 +54,9 @@ message GraphicsStatsProto { // The gpu frame time histogram for the package repeated GraphicsStatsHistogramBucketProto gpu_histogram = 7; + + // HWUI renders pipeline type: GL or Vulkan + optional PipelineType pipeline = 8; } message GraphicsStatsJankSummaryProto { diff --git a/libs/hwui/renderstate/RenderState.cpp b/libs/hwui/renderstate/RenderState.cpp index fad9440be73f..7e8c96d96860 100644 --- a/libs/hwui/renderstate/RenderState.cpp +++ b/libs/hwui/renderstate/RenderState.cpp @@ -16,7 +16,6 @@ #include "renderstate/RenderState.h" #include "renderthread/RenderThread.h" -#include "GpuMemoryTracker.h" namespace android { namespace uirenderer { @@ -25,15 +24,10 @@ RenderState::RenderState(renderthread::RenderThread& thread) : mRenderThread(thr mThreadId = pthread_self(); } -void RenderState::onContextCreated() { - GpuMemoryTracker::onGpuContextCreated(); -} - void RenderState::onContextDestroyed() { for(auto callback : mContextCallbacks) { callback->onContextDestroyed(); } - GpuMemoryTracker::onGpuContextDestroyed(); } void RenderState::postDecStrong(VirtualLightRefBase* object) { diff --git a/libs/hwui/renderstate/RenderState.h b/libs/hwui/renderstate/RenderState.h index ff5d02fe359a..e08d32a7735c 100644 --- a/libs/hwui/renderstate/RenderState.h +++ b/libs/hwui/renderstate/RenderState.h @@ -62,7 +62,6 @@ private: ~RenderState() {} // Context notifications are only to be triggered by renderthread::RenderThread - void onContextCreated(); void onContextDestroyed(); std::set<IGpuContextCallback*> mContextCallbacks; diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp index eaed46c44e5d..d177855e5a7d 100644 --- a/libs/hwui/renderthread/CacheManager.cpp +++ b/libs/hwui/renderthread/CacheManager.cpp @@ -20,10 +20,12 @@ #include "Layer.h" #include "Properties.h" #include "RenderThread.h" +#include "pipeline/skia/ATraceMemoryDump.h" #include "pipeline/skia/ShaderCache.h" #include "pipeline/skia/SkiaMemoryTracer.h" #include "renderstate/RenderState.h" #include "thread/CommonPool.h" +#include <utils/Trace.h> #include <GrContextOptions.h> #include <SkExecutor.h> @@ -184,6 +186,18 @@ void CacheManager::dumpMemoryUsage(String8& log, const RenderState* renderState) gpuTracer.logTotals(log); } +void CacheManager::onFrameCompleted() { + if (ATRACE_ENABLED()) { + static skiapipeline::ATraceMemoryDump tracer; + tracer.startFrame(); + SkGraphics::DumpMemoryStatistics(&tracer); + if (mGrContext) { + mGrContext->dumpMemoryStatistics(&tracer); + } + tracer.logTraces(); + } +} + } /* namespace renderthread */ } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/renderthread/CacheManager.h b/libs/hwui/renderthread/CacheManager.h index 968251e9f467..b009cc4f48f2 100644 --- a/libs/hwui/renderthread/CacheManager.h +++ b/libs/hwui/renderthread/CacheManager.h @@ -50,6 +50,7 @@ public: size_t getCacheSize() const { return mMaxResourceBytes; } size_t getBackgroundCacheSize() const { return mBackgroundResourceBytes; } + void onFrameCompleted(); private: friend class RenderThread; diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 84902210a751..5993e176f0b8 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -16,7 +16,6 @@ #include "CanvasContext.h" -#include <GpuMemoryTracker.h> #include <apex/window.h> #include <fcntl.h> #include <strings.h> @@ -558,7 +557,7 @@ void CanvasContext::draw() { mJankTracker.finishGpuDraw(*forthBehind); } - GpuMemoryTracker::onFrameCompleted(); + mRenderThread.cacheManager().onFrameCompleted(); } // Called by choreographer to do an RT-driven animation diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp index a446858ca565..cae3e3b5188c 100644 --- a/libs/hwui/renderthread/RenderThread.cpp +++ b/libs/hwui/renderthread/RenderThread.cpp @@ -34,7 +34,6 @@ #include <GrContextOptions.h> #include <gl/GrGLInterface.h> -#include <gui/DisplayEventReceiver.h> #include <sys/resource.h> #include <utils/Condition.h> #include <utils/Log.h> @@ -45,53 +44,43 @@ namespace android { namespace uirenderer { namespace renderthread { -// Number of events to read at a time from the DisplayEventReceiver pipe. -// The value should be large enough that we can quickly drain the pipe -// using just a few large reads. -static const size_t EVENT_BUFFER_SIZE = 100; - static bool gHasRenderThreadInstance = false; static JVMAttachHook gOnStartHook = nullptr; -class DisplayEventReceiverWrapper : public VsyncSource { +void RenderThread::frameCallback(int64_t frameTimeNanos, void* data) { + RenderThread* rt = reinterpret_cast<RenderThread*>(data); + rt->mVsyncRequested = false; + if (rt->timeLord().vsyncReceived(frameTimeNanos) && !rt->mFrameCallbackTaskPending) { + ATRACE_NAME("queue mFrameCallbackTask"); + rt->mFrameCallbackTaskPending = true; + nsecs_t runAt = (frameTimeNanos + rt->mDispatchFrameDelay); + rt->queue().postAt(runAt, [=]() { rt->dispatchFrameCallbacks(); }); + } +} + +void RenderThread::refreshRateCallback(int64_t vsyncPeriod, void* data) { + ATRACE_NAME("refreshRateCallback"); + RenderThread* rt = reinterpret_cast<RenderThread*>(data); + DeviceInfo::get()->onRefreshRateChanged(vsyncPeriod); + rt->setupFrameInterval(); +} + +class ChoreographerSource : public VsyncSource { public: - DisplayEventReceiverWrapper(std::unique_ptr<DisplayEventReceiver>&& receiver, - const std::function<void()>& onDisplayConfigChanged) - : mDisplayEventReceiver(std::move(receiver)) - , mOnDisplayConfigChanged(onDisplayConfigChanged) {} + ChoreographerSource(RenderThread* renderThread) : mRenderThread(renderThread) {} virtual void requestNextVsync() override { - status_t status = mDisplayEventReceiver->requestNextVsync(); - LOG_ALWAYS_FATAL_IF(status != NO_ERROR, "requestNextVsync failed with status: %d", status); + AChoreographer_postFrameCallback64(mRenderThread->mChoreographer, + RenderThread::frameCallback, mRenderThread); } - virtual nsecs_t latestVsyncEvent() override { - DisplayEventReceiver::Event buf[EVENT_BUFFER_SIZE]; - nsecs_t latest = 0; - ssize_t n; - while ((n = mDisplayEventReceiver->getEvents(buf, EVENT_BUFFER_SIZE)) > 0) { - for (ssize_t i = 0; i < n; i++) { - const DisplayEventReceiver::Event& ev = buf[i]; - switch (ev.header.type) { - case DisplayEventReceiver::DISPLAY_EVENT_VSYNC: - latest = ev.header.timestamp; - break; - case DisplayEventReceiver::DISPLAY_EVENT_CONFIG_CHANGED: - mOnDisplayConfigChanged(); - break; - } - } - } - if (n < 0) { - ALOGW("Failed to get events from display event receiver, status=%d", status_t(n)); - } - return latest; + virtual void drainPendingEvents() override { + AChoreographer_handlePendingEvents(mRenderThread->mChoreographer, mRenderThread); } private: - std::unique_ptr<DisplayEventReceiver> mDisplayEventReceiver; - std::function<void()> mOnDisplayConfigChanged; + RenderThread* mRenderThread; }; class DummyVsyncSource : public VsyncSource { @@ -99,11 +88,14 @@ public: DummyVsyncSource(RenderThread* renderThread) : mRenderThread(renderThread) {} virtual void requestNextVsync() override { - mRenderThread->queue().postDelayed(16_ms, - [this]() { mRenderThread->drainDisplayEventQueue(); }); + mRenderThread->queue().postDelayed(16_ms, [this]() { + RenderThread::frameCallback(systemTime(SYSTEM_TIME_MONOTONIC), mRenderThread); + }); } - virtual nsecs_t latestVsyncEvent() override { return systemTime(SYSTEM_TIME_MONOTONIC); } + virtual void drainPendingEvents() override { + RenderThread::frameCallback(systemTime(SYSTEM_TIME_MONOTONIC), mRenderThread); + } private: RenderThread* mRenderThread; @@ -145,29 +137,24 @@ RenderThread::RenderThread() } RenderThread::~RenderThread() { + // Note that if this fatal assertion is removed then member variables must + // be properly destroyed. LOG_ALWAYS_FATAL("Can't destroy the render thread"); } -void RenderThread::initializeDisplayEventReceiver() { - LOG_ALWAYS_FATAL_IF(mVsyncSource, "Initializing a second DisplayEventReceiver?"); +void RenderThread::initializeChoreographer() { + LOG_ALWAYS_FATAL_IF(mVsyncSource, "Initializing a second Choreographer?"); if (!Properties::isolatedProcess) { - auto receiver = std::make_unique<DisplayEventReceiver>( - ISurfaceComposer::eVsyncSourceApp, - ISurfaceComposer::eConfigChangedDispatch); - status_t status = receiver->initCheck(); - LOG_ALWAYS_FATAL_IF(status != NO_ERROR, - "Initialization of DisplayEventReceiver " - "failed with status: %d", - status); + mChoreographer = AChoreographer_create(); + LOG_ALWAYS_FATAL_IF(mChoreographer == nullptr, "Initialization of Choreographer failed"); + AChoreographer_registerRefreshRateCallback(mChoreographer, + RenderThread::refreshRateCallback, this); // Register the FD - mLooper->addFd(receiver->getFd(), 0, Looper::EVENT_INPUT, - RenderThread::displayEventReceiverCallback, this); - mVsyncSource = new DisplayEventReceiverWrapper(std::move(receiver), [this] { - DeviceInfo::get()->onDisplayConfigChanged(); - setupFrameInterval(); - }); + mLooper->addFd(AChoreographer_getFd(mChoreographer), 0, Looper::EVENT_INPUT, + RenderThread::choreographerCallback, this); + mVsyncSource = new ChoreographerSource(this); } else { mVsyncSource = new DummyVsyncSource(this); } @@ -175,7 +162,7 @@ void RenderThread::initializeDisplayEventReceiver() { void RenderThread::initThreadLocals() { setupFrameInterval(); - initializeDisplayEventReceiver(); + initializeChoreographer(); mEglManager = new EglManager(); mRenderState = new RenderState(*this); mVkManager = new VulkanManager(); @@ -183,7 +170,7 @@ void RenderThread::initThreadLocals() { } void RenderThread::setupFrameInterval() { - nsecs_t frameIntervalNanos = static_cast<nsecs_t>(1000000000 / DeviceInfo::getRefreshRate()); + nsecs_t frameIntervalNanos = DeviceInfo::getVsyncPeriod(); mTimeLord.setFrameInterval(frameIntervalNanos); mDispatchFrameDelay = static_cast<nsecs_t>(frameIntervalNanos * .25f); } @@ -283,12 +270,11 @@ void RenderThread::setGrContext(sk_sp<GrContext> context) { } mGrContext = std::move(context); if (mGrContext) { - mRenderState->onContextCreated(); DeviceInfo::setMaxTextureSize(mGrContext->maxRenderTargetSize()); } } -int RenderThread::displayEventReceiverCallback(int fd, int events, void* data) { +int RenderThread::choreographerCallback(int fd, int events, void* data) { if (events & (Looper::EVENT_ERROR | Looper::EVENT_HANGUP)) { ALOGE("Display event receiver pipe was closed or an error occurred. " "events=0x%x", @@ -302,24 +288,10 @@ int RenderThread::displayEventReceiverCallback(int fd, int events, void* data) { events); return 1; // keep the callback } + RenderThread* rt = reinterpret_cast<RenderThread*>(data); + AChoreographer_handlePendingEvents(rt->mChoreographer, data); - reinterpret_cast<RenderThread*>(data)->drainDisplayEventQueue(); - - return 1; // keep the callback -} - -void RenderThread::drainDisplayEventQueue() { - ATRACE_CALL(); - nsecs_t vsyncEvent = mVsyncSource->latestVsyncEvent(); - if (vsyncEvent > 0) { - mVsyncRequested = false; - if (mTimeLord.vsyncReceived(vsyncEvent) && !mFrameCallbackTaskPending) { - ATRACE_NAME("queue mFrameCallbackTask"); - mFrameCallbackTaskPending = true; - nsecs_t runAt = (vsyncEvent + mDispatchFrameDelay); - queue().postAt(runAt, [this]() { dispatchFrameCallbacks(); }); - } - } + return 1; } void RenderThread::dispatchFrameCallbacks() { @@ -360,7 +332,7 @@ bool RenderThread::threadLoop() { processQueue(); if (mPendingRegistrationFrameCallbacks.size() && !mFrameCallbackTaskPending) { - drainDisplayEventQueue(); + mVsyncSource->drainPendingEvents(); mFrameCallbacks.insert(mPendingRegistrationFrameCallbacks.begin(), mPendingRegistrationFrameCallbacks.end()); mPendingRegistrationFrameCallbacks.clear(); diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h index da79e97a6ceb..8be46a6d16e1 100644 --- a/libs/hwui/renderthread/RenderThread.h +++ b/libs/hwui/renderthread/RenderThread.h @@ -19,6 +19,7 @@ #include <GrContext.h> #include <SkBitmap.h> +#include <apex/choreographer.h> #include <cutils/compiler.h> #include <thread/ThreadBase.h> #include <utils/Looper.h> @@ -73,10 +74,11 @@ protected: struct VsyncSource { virtual void requestNextVsync() = 0; - virtual nsecs_t latestVsyncEvent() = 0; + virtual void drainPendingEvents() = 0; virtual ~VsyncSource() {} }; +class ChoreographerSource; class DummyVsyncSource; typedef void (*JVMAttachHook)(const char* name); @@ -136,6 +138,7 @@ private: friend class DispatchFrameCallbacks; friend class RenderProxy; friend class DummyVsyncSource; + friend class ChoreographerSource; friend class android::uirenderer::AutoBackendTextureRelease; friend class android::uirenderer::TestUtils; friend class android::uirenderer::WebViewFunctor; @@ -149,13 +152,21 @@ private: static RenderThread& getInstance(); void initThreadLocals(); - void initializeDisplayEventReceiver(); + void initializeChoreographer(); void setupFrameInterval(); - static int displayEventReceiverCallback(int fd, int events, void* data); + // Callbacks for choreographer events: + // choreographerCallback will call AChoreograper_handleEvent to call the + // corresponding callbacks for each display event type + static int choreographerCallback(int fd, int events, void* data); + // Callback that will be run on vsync ticks. + static void frameCallback(int64_t frameTimeNanos, void* data); + // Callback that will be run whenver there is a refresh rate change. + static void refreshRateCallback(int64_t vsyncPeriod, void* data); void drainDisplayEventQueue(); void dispatchFrameCallbacks(); void requestVsync(); + AChoreographer* mChoreographer; VsyncSource* mVsyncSource; bool mVsyncRequested; std::set<IFrameCallback*> mFrameCallbacks; diff --git a/libs/hwui/service/GraphicsStatsService.cpp b/libs/hwui/service/GraphicsStatsService.cpp index 12c5b836f711..c4186174b637 100644 --- a/libs/hwui/service/GraphicsStatsService.cpp +++ b/libs/hwui/service/GraphicsStatsService.cpp @@ -16,24 +16,28 @@ #include "GraphicsStatsService.h" -#include "JankTracker.h" -#include "protos/graphicsstats.pb.h" - -#include <google/protobuf/io/zero_copy_stream_impl_lite.h> -#include <log/log.h> - #include <errno.h> #include <fcntl.h> +#include <google/protobuf/io/zero_copy_stream_impl_lite.h> #include <inttypes.h> +#include <log/log.h> #include <sys/mman.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> +#include <algorithm> +#include <map> +#include <vector> + +#include "JankTracker.h" +#include "protos/graphicsstats.pb.h" + namespace android { namespace uirenderer { using namespace google::protobuf; +using namespace uirenderer::protos; constexpr int32_t sCurrentFileVersion = 1; constexpr int32_t sHeaderSize = 4; @@ -42,9 +46,9 @@ static_assert(sizeof(sCurrentFileVersion) == sHeaderSize, "Header size is wrong" constexpr int sHistogramSize = ProfileData::HistogramSize(); constexpr int sGPUHistogramSize = ProfileData::GPUHistogramSize(); -static bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto, - const std::string& package, int64_t versionCode, - int64_t startTime, int64_t endTime, const ProfileData* data); +static bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto, const std::string& package, + int64_t versionCode, int64_t startTime, int64_t endTime, + const ProfileData* data); static void dumpAsTextToFd(protos::GraphicsStatsProto* proto, int outFd); class FileDescriptor { @@ -57,7 +61,7 @@ public: } } bool valid() { return mFd != -1; } - operator int() { return mFd; } // NOLINT(google-explicit-constructor) + operator int() { return mFd; } // NOLINT(google-explicit-constructor) private: int mFd; @@ -167,6 +171,8 @@ bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto, const std::str } proto->set_package_name(package); proto->set_version_code(versionCode); + proto->set_pipeline(data->pipelineType() == RenderPipelineType::SkiaGL ? + GraphicsStatsProto_PipelineType_GL : GraphicsStatsProto_PipelineType_VULKAN); auto summary = proto->mutable_summary(); summary->set_total_frames(summary->total_frames() + data->totalFrameCount()); summary->set_janky_frames(summary->janky_frames() + data->jankFrameCount()); @@ -179,8 +185,8 @@ bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto, const std::str summary->set_slow_bitmap_upload_count(summary->slow_bitmap_upload_count() + data->jankTypeCount(kSlowSync)); summary->set_slow_draw_count(summary->slow_draw_count() + data->jankTypeCount(kSlowRT)); - summary->set_missed_deadline_count(summary->missed_deadline_count() - + data->jankTypeCount(kMissedDeadline)); + summary->set_missed_deadline_count(summary->missed_deadline_count() + + data->jankTypeCount(kMissedDeadline)); bool creatingHistogram = false; if (proto->histogram_size() == 0) { @@ -365,17 +371,69 @@ void GraphicsStatsService::saveBuffer(const std::string& path, const std::string class GraphicsStatsService::Dump { public: - Dump(int outFd, DumpType type) : mFd(outFd), mType(type) {} + Dump(int outFd, DumpType type) : mFd(outFd), mType(type) { + if (mFd == -1 && mType == DumpType::Protobuf) { + mType = DumpType::ProtobufStatsd; + } + } int fd() { return mFd; } DumpType type() { return mType; } protos::GraphicsStatsServiceDumpProto& proto() { return mProto; } + void mergeStat(const protos::GraphicsStatsProto& stat); + void updateProto(); private: + // use package name and app version for a key + typedef std::pair<std::string, int64_t> DumpKey; + + std::map<DumpKey, protos::GraphicsStatsProto> mStats; int mFd; DumpType mType; protos::GraphicsStatsServiceDumpProto mProto; }; +void GraphicsStatsService::Dump::mergeStat(const protos::GraphicsStatsProto& stat) { + auto dumpKey = std::make_pair(stat.package_name(), stat.version_code()); + auto findIt = mStats.find(dumpKey); + if (findIt == mStats.end()) { + mStats[dumpKey] = stat; + } else { + auto summary = findIt->second.mutable_summary(); + summary->set_total_frames(summary->total_frames() + stat.summary().total_frames()); + summary->set_janky_frames(summary->janky_frames() + stat.summary().janky_frames()); + summary->set_missed_vsync_count(summary->missed_vsync_count() + + stat.summary().missed_vsync_count()); + summary->set_high_input_latency_count(summary->high_input_latency_count() + + stat.summary().high_input_latency_count()); + summary->set_slow_ui_thread_count(summary->slow_ui_thread_count() + + stat.summary().slow_ui_thread_count()); + summary->set_slow_bitmap_upload_count(summary->slow_bitmap_upload_count() + + stat.summary().slow_bitmap_upload_count()); + summary->set_slow_draw_count(summary->slow_draw_count() + stat.summary().slow_draw_count()); + summary->set_missed_deadline_count(summary->missed_deadline_count() + + stat.summary().missed_deadline_count()); + for (int bucketIndex = 0; bucketIndex < findIt->second.histogram_size(); bucketIndex++) { + auto bucket = findIt->second.mutable_histogram(bucketIndex); + bucket->set_frame_count(bucket->frame_count() + + stat.histogram(bucketIndex).frame_count()); + } + for (int bucketIndex = 0; bucketIndex < findIt->second.gpu_histogram_size(); + bucketIndex++) { + auto bucket = findIt->second.mutable_gpu_histogram(bucketIndex); + bucket->set_frame_count(bucket->frame_count() + + stat.gpu_histogram(bucketIndex).frame_count()); + } + findIt->second.set_stats_start(std::min(findIt->second.stats_start(), stat.stats_start())); + findIt->second.set_stats_end(std::max(findIt->second.stats_end(), stat.stats_end())); + } +} + +void GraphicsStatsService::Dump::updateProto() { + for (auto& stat : mStats) { + mProto.add_stats()->CopyFrom(stat.second); + } +} + GraphicsStatsService::Dump* GraphicsStatsService::createDump(int outFd, DumpType type) { return new Dump(outFd, type); } @@ -396,8 +454,9 @@ void GraphicsStatsService::addToDump(Dump* dump, const std::string& path, path.empty() ? "<empty>" : path.c_str(), data); return; } - - if (dump->type() == DumpType::Protobuf) { + if (dump->type() == DumpType::ProtobufStatsd) { + dump->mergeStat(statsProto); + } else if (dump->type() == DumpType::Protobuf) { dump->proto().add_stats()->CopyFrom(statsProto); } else { dumpAsTextToFd(&statsProto, dump->fd()); @@ -409,7 +468,9 @@ void GraphicsStatsService::addToDump(Dump* dump, const std::string& path) { if (!parseFromFile(path, &statsProto)) { return; } - if (dump->type() == DumpType::Protobuf) { + if (dump->type() == DumpType::ProtobufStatsd) { + dump->mergeStat(statsProto); + } else if (dump->type() == DumpType::Protobuf) { dump->proto().add_stats()->CopyFrom(statsProto); } else { dumpAsTextToFd(&statsProto, dump->fd()); @@ -424,5 +485,79 @@ void GraphicsStatsService::finishDump(Dump* dump) { delete dump; } +class MemOutputStreamLite : public io::ZeroCopyOutputStream { +public: + explicit MemOutputStreamLite() : mCopyAdapter(), mImpl(&mCopyAdapter) {} + virtual ~MemOutputStreamLite() {} + + virtual bool Next(void** data, int* size) override { return mImpl.Next(data, size); } + + virtual void BackUp(int count) override { mImpl.BackUp(count); } + + virtual int64 ByteCount() const override { return mImpl.ByteCount(); } + + bool Flush() { return mImpl.Flush(); } + + void copyData(const DumpMemoryFn& reader, void* param1, void* param2) { + int bufferOffset = 0; + int totalSize = mCopyAdapter.mBuffersSize - mCopyAdapter.mCurrentBufferUnusedSize; + int totalDataLeft = totalSize; + for (auto& it : mCopyAdapter.mBuffers) { + int bufferSize = std::min(totalDataLeft, (int)it.size()); // last buffer is not full + reader(it.data(), bufferOffset, bufferSize, totalSize, param1, param2); + bufferOffset += bufferSize; + totalDataLeft -= bufferSize; + } + } + +private: + struct MemAdapter : public io::CopyingOutputStream { + // Data is stored in an array of buffers. + // JNI SetByteArrayRegion assembles data in one continuous Java byte[] buffer. + std::vector<std::vector<unsigned char>> mBuffers; + int mBuffersSize = 0; // total bytes allocated in mBuffers + int mCurrentBufferUnusedSize = 0; // unused bytes in the last buffer mBuffers.back() + unsigned char* mCurrentBuffer = nullptr; // pointer to next free byte in mBuffers.back() + + explicit MemAdapter() {} + virtual ~MemAdapter() {} + + virtual bool Write(const void* buffer, int size) override { + while (size > 0) { + if (0 == mCurrentBufferUnusedSize) { + mCurrentBufferUnusedSize = + std::max(size, mBuffersSize ? 2 * mBuffersSize : 10000); + mBuffers.emplace_back(); + mBuffers.back().resize(mCurrentBufferUnusedSize); + mCurrentBuffer = mBuffers.back().data(); + mBuffersSize += mCurrentBufferUnusedSize; + } + int dataMoved = std::min(mCurrentBufferUnusedSize, size); + memcpy(mCurrentBuffer, buffer, dataMoved); + mCurrentBufferUnusedSize -= dataMoved; + mCurrentBuffer += dataMoved; + buffer = reinterpret_cast<const unsigned char*>(buffer) + dataMoved; + size -= dataMoved; + } + return true; + } + }; + + MemOutputStreamLite::MemAdapter mCopyAdapter; + io::CopyingOutputStreamAdaptor mImpl; +}; + +void GraphicsStatsService::finishDumpInMemory(Dump* dump, const DumpMemoryFn& reader, void* param1, + void* param2) { + MemOutputStreamLite stream; + dump->updateProto(); + bool success = dump->proto().SerializeToZeroCopyStream(&stream) && stream.Flush(); + delete dump; + if (!success) { + return; + } + stream.copyData(reader, param1, param2); +} + } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/service/GraphicsStatsService.h b/libs/hwui/service/GraphicsStatsService.h index 389f599f439f..4bed96330a52 100644 --- a/libs/hwui/service/GraphicsStatsService.h +++ b/libs/hwui/service/GraphicsStatsService.h @@ -27,6 +27,9 @@ namespace protos { class GraphicsStatsProto; } +typedef void (*DumpMemoryFn)(void* buffer, int bufferOffset, int bufferSize, int totalSize, + void* param1, void* param2); + /* * The exported entry points used by GraphicsStatsService.java in f/b/services/core * @@ -40,6 +43,7 @@ public: enum class DumpType { Text, Protobuf, + ProtobufStatsd, }; ANDROID_API static void saveBuffer(const std::string& path, const std::string& package, @@ -52,6 +56,8 @@ public: int64_t startTime, int64_t endTime, const ProfileData* data); ANDROID_API static void addToDump(Dump* dump, const std::string& path); ANDROID_API static void finishDump(Dump* dump); + ANDROID_API static void finishDumpInMemory(Dump* dump, const DumpMemoryFn& reader, void* param1, + void* param2); // Visible for testing static bool parseFromFile(const std::string& path, protos::GraphicsStatsProto* output); diff --git a/libs/hwui/tests/unit/GpuMemoryTrackerTests.cpp b/libs/hwui/tests/unit/GpuMemoryTrackerTests.cpp deleted file mode 100644 index dac888cd79ca..000000000000 --- a/libs/hwui/tests/unit/GpuMemoryTrackerTests.cpp +++ /dev/null @@ -1,65 +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. - */ - -#include <GpuMemoryTracker.h> -#include <gtest/gtest.h> - -#include "renderthread/EglManager.h" -#include "renderthread/RenderThread.h" -#include "tests/common/TestUtils.h" - -#include <utils/StrongPointer.h> - -using namespace android; -using namespace android::uirenderer; -using namespace android::uirenderer::renderthread; - -class TestGPUObject : public GpuMemoryTracker { -public: - TestGPUObject() : GpuMemoryTracker(GpuObjectType::Texture) {} - - void changeSize(int newSize) { notifySizeChanged(newSize); } -}; - -// Other tests may have created a renderthread and EGL context. -// This will destroy the EGLContext on RenderThread if it exists so that the -// current thread can spoof being a GPU thread -static void destroyEglContext() { - if (TestUtils::isRenderThreadRunning()) { - TestUtils::runOnRenderThread([](RenderThread& thread) { thread.destroyRenderingContext(); }); - } -} - -TEST(GpuMemoryTracker, sizeCheck) { - destroyEglContext(); - - GpuMemoryTracker::onGpuContextCreated(); - ASSERT_EQ(0, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture)); - ASSERT_EQ(0, GpuMemoryTracker::getInstanceCount(GpuObjectType::Texture)); - { - TestGPUObject myObj; - ASSERT_EQ(1, GpuMemoryTracker::getInstanceCount(GpuObjectType::Texture)); - myObj.changeSize(500); - ASSERT_EQ(500, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture)); - myObj.changeSize(1000); - ASSERT_EQ(1000, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture)); - myObj.changeSize(300); - ASSERT_EQ(300, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture)); - } - ASSERT_EQ(0, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture)); - ASSERT_EQ(0, GpuMemoryTracker::getInstanceCount(GpuObjectType::Texture)); - GpuMemoryTracker::onGpuContextDestroyed(); -} diff --git a/location/java/android/location/Criteria.java b/location/java/android/location/Criteria.java index 1370b1095ae1..26f73f784879 100644 --- a/location/java/android/location/Criteria.java +++ b/location/java/android/location/Criteria.java @@ -16,9 +16,16 @@ package android.location; +import android.annotation.IntDef; +import android.annotation.NonNull; import android.os.Parcel; import android.os.Parcelable; +import com.android.internal.util.Preconditions; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * A class indicating the application criteria for selecting a * location provider. Providers may be ordered according to accuracy, @@ -26,6 +33,25 @@ import android.os.Parcelable; * cost. */ public class Criteria implements Parcelable { + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({NO_REQUIREMENT, POWER_LOW, POWER_MEDIUM, POWER_HIGH}) + public @interface PowerRequirement { + } + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({NO_REQUIREMENT, ACCURACY_LOW, ACCURACY_MEDIUM, ACCURACY_HIGH}) + public @interface AccuracyRequirement { + } + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({NO_REQUIREMENT, ACCURACY_FINE, ACCURACY_COARSE}) + public @interface LocationAccuracyRequirement { + } + /** * A constant indicating that the application does not choose to * place requirement on a particular feature. @@ -81,15 +107,15 @@ public class Criteria implements Parcelable { */ public static final int ACCURACY_HIGH = 3; - private int mHorizontalAccuracy = NO_REQUIREMENT; - private int mVerticalAccuracy = NO_REQUIREMENT; - private int mSpeedAccuracy = NO_REQUIREMENT; - private int mBearingAccuracy = NO_REQUIREMENT; - private int mPowerRequirement = NO_REQUIREMENT; - private boolean mAltitudeRequired = false; - private boolean mBearingRequired = false; - private boolean mSpeedRequired = false; - private boolean mCostAllowed = false; + private int mHorizontalAccuracy = NO_REQUIREMENT; + private int mVerticalAccuracy = NO_REQUIREMENT; + private int mSpeedAccuracy = NO_REQUIREMENT; + private int mBearingAccuracy = NO_REQUIREMENT; + private int mPowerRequirement = NO_REQUIREMENT; + private boolean mAltitudeRequired = false; + private boolean mBearingRequired = false; + private boolean mSpeedRequired = false; + private boolean mCostAllowed = false; /** * Constructs a new Criteria object. The new object will have no @@ -97,7 +123,8 @@ public class Criteria implements Parcelable { * require altitude, speed, or bearing; and will not allow monetary * cost. */ - public Criteria() {} + public Criteria() { + } /** * Constructs a new Criteria object that is a copy of the given criteria. @@ -115,125 +142,121 @@ public class Criteria implements Parcelable { } /** - * Indicates the desired horizontal accuracy (latitude and longitude). - * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_MEDIUM}, - * {@link #ACCURACY_HIGH} or {@link #NO_REQUIREMENT}. - * More accurate location may consume more power and may take longer. + * Indicates the desired horizontal accuracy (latitude and longitude). Accuracy may be + * {@link #ACCURACY_LOW}, {@link #ACCURACY_MEDIUM}, {@link #ACCURACY_HIGH} or + * {@link #NO_REQUIREMENT}. More accurate location may consume more power and may take longer. * * @throws IllegalArgumentException if accuracy is not one of the supported constants */ - public void setHorizontalAccuracy(int accuracy) { - if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_HIGH) { - throw new IllegalArgumentException("accuracy=" + accuracy); - } - mHorizontalAccuracy = accuracy; + public void setHorizontalAccuracy(@AccuracyRequirement int accuracy) { + mHorizontalAccuracy = Preconditions.checkArgumentInRange(accuracy, NO_REQUIREMENT, + ACCURACY_HIGH, "accuracy"); } /** * Returns a constant indicating the desired horizontal accuracy (latitude and longitude). - * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_MEDIUM}, - * {@link #ACCURACY_HIGH} or {@link #NO_REQUIREMENT}. + * + * @see #setHorizontalAccuracy(int) */ + @AccuracyRequirement public int getHorizontalAccuracy() { return mHorizontalAccuracy; } /** - * Indicates the desired vertical accuracy (altitude). - * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_MEDIUM}, - * {@link #ACCURACY_HIGH} or {@link #NO_REQUIREMENT}. - * More accurate location may consume more power and may take longer. + * Indicates the desired vertical accuracy (altitude). Accuracy may be {@link #ACCURACY_LOW}, + * {@link #ACCURACY_MEDIUM}, {@link #ACCURACY_HIGH} or {@link #NO_REQUIREMENT}. More accurate + * location may consume more power and may take longer. * * @throws IllegalArgumentException if accuracy is not one of the supported constants */ - public void setVerticalAccuracy(int accuracy) { - if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_HIGH) { - throw new IllegalArgumentException("accuracy=" + accuracy); - } - mVerticalAccuracy = accuracy; + public void setVerticalAccuracy(@AccuracyRequirement int accuracy) { + mVerticalAccuracy = Preconditions.checkArgumentInRange(accuracy, NO_REQUIREMENT, + ACCURACY_HIGH, "accuracy"); } /** * Returns a constant indicating the desired vertical accuracy (altitude). - * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_HIGH}, - * or {@link #NO_REQUIREMENT}. + * + * @see #setVerticalAccuracy(int) */ + @AccuracyRequirement public int getVerticalAccuracy() { return mVerticalAccuracy; } /** - * Indicates the desired speed accuracy. - * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_HIGH}, - * or {@link #NO_REQUIREMENT}. - * More accurate location may consume more power and may take longer. + * Indicates the desired speed accuracy. Accuracy may be {@link #ACCURACY_LOW}, + * {@link #ACCURACY_MEDIUM}, {@link #ACCURACY_HIGH}, or {@link #NO_REQUIREMENT}. More accurate + * location may consume more power and may take longer. * * @throws IllegalArgumentException if accuracy is not one of the supported constants */ - public void setSpeedAccuracy(int accuracy) { - if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_HIGH) { - throw new IllegalArgumentException("accuracy=" + accuracy); - } - mSpeedAccuracy = accuracy; + public void setSpeedAccuracy(@AccuracyRequirement int accuracy) { + mSpeedAccuracy = Preconditions.checkArgumentInRange(accuracy, NO_REQUIREMENT, ACCURACY_HIGH, + "accuracy"); } /** - * Returns a constant indicating the desired speed accuracy - * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_HIGH}, - * or {@link #NO_REQUIREMENT}. + * Returns a constant indicating the desired speed accuracy. + * + * @see #setSpeedAccuracy(int) */ + @AccuracyRequirement public int getSpeedAccuracy() { return mSpeedAccuracy; } /** - * Indicates the desired bearing accuracy. - * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_HIGH}, - * or {@link #NO_REQUIREMENT}. - * More accurate location may consume more power and may take longer. + * Indicates the desired bearing accuracy. Accuracy may be {@link #ACCURACY_LOW}, + * {@link #ACCURACY_MEDIUM}, {@link #ACCURACY_HIGH}, or {@link #NO_REQUIREMENT}. More accurate + * location may consume more power and may take longer. * * @throws IllegalArgumentException if accuracy is not one of the supported constants */ - public void setBearingAccuracy(int accuracy) { - if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_HIGH) { - throw new IllegalArgumentException("accuracy=" + accuracy); - } - mBearingAccuracy = accuracy; + public void setBearingAccuracy(@AccuracyRequirement int accuracy) { + mBearingAccuracy = Preconditions.checkArgumentInRange(accuracy, NO_REQUIREMENT, + ACCURACY_HIGH, "accuracy"); } /** * Returns a constant indicating the desired bearing accuracy. - * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_HIGH}, - * or {@link #NO_REQUIREMENT}. + * + * @see #setBearingAccuracy(int) */ + @AccuracyRequirement public int getBearingAccuracy() { return mBearingAccuracy; } /** - * Indicates the desired accuracy for latitude and longitude. Accuracy - * may be {@link #ACCURACY_FINE} if desired location - * is fine, else it can be {@link #ACCURACY_COARSE}. - * More accurate location may consume more power and may take longer. + * Indicates the desired accuracy for latitude and longitude. Accuracy may be + * {@link #ACCURACY_FINE} or {@link #ACCURACY_COARSE}. More accurate location may consume more + * power and may take longer. * * @throws IllegalArgumentException if accuracy is not one of the supported constants */ - public void setAccuracy(int accuracy) { - if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_COARSE) { - throw new IllegalArgumentException("accuracy=" + accuracy); - } - if (accuracy == ACCURACY_FINE) { - mHorizontalAccuracy = ACCURACY_HIGH; - } else { - mHorizontalAccuracy = ACCURACY_LOW; + public void setAccuracy(@LocationAccuracyRequirement int accuracy) { + Preconditions.checkArgumentInRange(accuracy, NO_REQUIREMENT, ACCURACY_COARSE, "accuracy"); + switch (accuracy) { + case NO_REQUIREMENT: + setHorizontalAccuracy(NO_REQUIREMENT); + break; + case ACCURACY_FINE: + setHorizontalAccuracy(ACCURACY_HIGH); + break; + case ACCURACY_COARSE: + setHorizontalAccuracy(ACCURACY_LOW); + break; } } /** - * Returns a constant indicating desired accuracy of location - * Accuracy may be {@link #ACCURACY_FINE} if desired location - * is fine, else it can be {@link #ACCURACY_COARSE}. + * Returns a constant indicating desired accuracy of location. + * + * @see #setAccuracy(int) */ + @LocationAccuracyRequirement public int getAccuracy() { if (mHorizontalAccuracy >= ACCURACY_HIGH) { return ACCURACY_FINE; @@ -243,21 +266,20 @@ public class Criteria implements Parcelable { } /** - * Indicates the desired maximum power level. The level parameter - * must be one of NO_REQUIREMENT, POWER_LOW, POWER_MEDIUM, or - * POWER_HIGH. + * Indicates the desired maximum power requirement. The power requirement parameter may be + * {@link #NO_REQUIREMENT}, {@link #POWER_LOW}, {@link #POWER_MEDIUM}, or {@link #POWER_HIGH}. */ - public void setPowerRequirement(int level) { - if (level < NO_REQUIREMENT || level > POWER_HIGH) { - throw new IllegalArgumentException("level=" + level); - } - mPowerRequirement = level; + public void setPowerRequirement(@PowerRequirement int powerRequirement) { + mPowerRequirement = Preconditions.checkArgumentInRange(powerRequirement, NO_REQUIREMENT, + POWER_HIGH, "powerRequirement"); } /** - * Returns a constant indicating the desired power requirement. The - * returned + * Returns a constant indicating the desired maximum power requirement. + * + * @see #setPowerRequirement(int) */ + @PowerRequirement public int getPowerRequirement() { return mPowerRequirement; } @@ -277,8 +299,8 @@ public class Criteria implements Parcelable { } /** - * Indicates whether the provider must provide altitude information. - * Not all fixes are guaranteed to contain such information. + * Indicates whether the provider must provide altitude information. Not all fixes are + * guaranteed to contain such information. */ public void setAltitudeRequired(boolean altitudeRequired) { mAltitudeRequired = altitudeRequired; @@ -286,15 +308,16 @@ public class Criteria implements Parcelable { /** * Returns whether the provider must provide altitude information. - * Not all fixes are guaranteed to contain such information. + * + * @see #setAltitudeRequired(boolean) */ public boolean isAltitudeRequired() { return mAltitudeRequired; } /** - * Indicates whether the provider must provide speed information. - * Not all fixes are guaranteed to contain such information. + * Indicates whether the provider must provide speed information. Not all fixes are guaranteed + * to contain such information. */ public void setSpeedRequired(boolean speedRequired) { mSpeedRequired = speedRequired; @@ -302,15 +325,16 @@ public class Criteria implements Parcelable { /** * Returns whether the provider must provide speed information. - * Not all fixes are guaranteed to contain such information. + * + * @see #setSpeedRequired(boolean) */ public boolean isSpeedRequired() { return mSpeedRequired; } /** - * Indicates whether the provider must provide bearing information. - * Not all fixes are guaranteed to contain such information. + * Indicates whether the provider must provide bearing information. Not all fixes are guaranteed + * to contain such information. */ public void setBearingRequired(boolean bearingRequired) { mBearingRequired = bearingRequired; @@ -318,34 +342,36 @@ public class Criteria implements Parcelable { /** * Returns whether the provider must provide bearing information. - * Not all fixes are guaranteed to contain such information. + * + * @see #setBearingRequired(boolean) */ public boolean isBearingRequired() { return mBearingRequired; } - public static final @android.annotation.NonNull Parcelable.Creator<Criteria> CREATOR = - new Parcelable.Creator<Criteria>() { - @Override - public Criteria createFromParcel(Parcel in) { - Criteria c = new Criteria(); - c.mHorizontalAccuracy = in.readInt(); - c.mVerticalAccuracy = in.readInt(); - c.mSpeedAccuracy = in.readInt(); - c.mBearingAccuracy = in.readInt(); - c.mPowerRequirement = in.readInt(); - c.mAltitudeRequired = in.readInt() != 0; - c.mBearingRequired = in.readInt() != 0; - c.mSpeedRequired = in.readInt() != 0; - c.mCostAllowed = in.readInt() != 0; - return c; - } - - @Override - public Criteria[] newArray(int size) { - return new Criteria[size]; - } - }; + @NonNull + public static final Parcelable.Creator<Criteria> CREATOR = + new Parcelable.Creator<Criteria>() { + @Override + public Criteria createFromParcel(Parcel in) { + Criteria c = new Criteria(); + c.mHorizontalAccuracy = in.readInt(); + c.mVerticalAccuracy = in.readInt(); + c.mSpeedAccuracy = in.readInt(); + c.mBearingAccuracy = in.readInt(); + c.mPowerRequirement = in.readInt(); + c.mAltitudeRequired = in.readInt() != 0; + c.mBearingRequired = in.readInt() != 0; + c.mSpeedRequired = in.readInt() != 0; + c.mCostAllowed = in.readInt() != 0; + return c; + } + + @Override + public Criteria[] newArray(int size) { + return new Criteria[size]; + } + }; @Override public int describeContents() { @@ -365,42 +391,57 @@ public class Criteria implements Parcelable { parcel.writeInt(mCostAllowed ? 1 : 0); } - private static String powerToString(int power) { + @Override + public String toString() { + StringBuilder s = new StringBuilder(); + s.append("Criteria["); + s.append("power=").append(requirementToString(mPowerRequirement)).append(", "); + s.append("accuracy=").append(requirementToString(mHorizontalAccuracy)); + if (mVerticalAccuracy != NO_REQUIREMENT) { + s.append(", verticalAccuracy=").append(requirementToString(mVerticalAccuracy)); + } + if (mSpeedAccuracy != NO_REQUIREMENT) { + s.append(", speedAccuracy=").append(requirementToString(mSpeedAccuracy)); + } + if (mBearingAccuracy != NO_REQUIREMENT) { + s.append(", bearingAccuracy=").append(requirementToString(mBearingAccuracy)); + } + if (mAltitudeRequired || mBearingRequired || mSpeedRequired) { + s.append(", required=["); + if (mAltitudeRequired) { + s.append("altitude, "); + } + if (mBearingRequired) { + s.append("bearing, "); + } + if (mSpeedRequired) { + s.append("speed, "); + } + s.setLength(s.length() - 2); + s.append("]"); + } + if (mCostAllowed) { + s.append(", costAllowed"); + } + s.append(']'); + return s.toString(); + } + + private static String requirementToString(int power) { switch (power) { case NO_REQUIREMENT: - return "NO_REQ"; + return "None"; + //case ACCURACY_LOW: case POWER_LOW: - return "LOW"; + return "Low"; + //case ACCURACY_MEDIUM: case POWER_MEDIUM: - return "MEDIUM"; + return "Medium"; + //case ACCURACY_HIGH: case POWER_HIGH: - return "HIGH"; + return "High"; default: return "???"; } } - - private static String accuracyToString(int accuracy) { - switch (accuracy) { - case NO_REQUIREMENT: - return "---"; - case ACCURACY_HIGH: - return "HIGH"; - case ACCURACY_MEDIUM: - return "MEDIUM"; - case ACCURACY_LOW: - return "LOW"; - default: - return "???"; - } - } - - @Override - public String toString() { - StringBuilder s = new StringBuilder(); - s.append("Criteria[power=").append(powerToString(mPowerRequirement)); - s.append(" acc=").append(accuracyToString(mHorizontalAccuracy)); - s.append(']'); - return s.toString(); - } } diff --git a/location/java/com/android/internal/location/ProviderProperties.java b/location/java/com/android/internal/location/ProviderProperties.java index def96f0fb674..68f9ec3c530b 100644 --- a/location/java/com/android/internal/location/ProviderProperties.java +++ b/location/java/com/android/internal/location/ProviderProperties.java @@ -16,15 +16,36 @@ package com.android.internal.location; +import android.annotation.IntDef; +import android.location.Criteria; import android.os.Parcel; import android.os.Parcelable; +import com.android.internal.util.Preconditions; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * A Parcelable containing (legacy) location provider properties. * This object is just used inside the framework and system services. + * * @hide */ public final class ProviderProperties implements Parcelable { + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({Criteria.POWER_LOW, Criteria.POWER_MEDIUM, Criteria.POWER_HIGH}) + public @interface PowerRequirement { + } + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({Criteria.ACCURACY_FINE, Criteria.ACCURACY_COARSE}) + public @interface Accuracy { + } + /** * True if provider requires access to a * data network (e.g., the Internet), false otherwise. @@ -79,58 +100,58 @@ public final class ProviderProperties implements Parcelable { /** * Power requirement for this provider. - * - * @return the power requirement for this provider, as one of the - * constants Criteria.POWER_*. */ + @PowerRequirement public final int mPowerRequirement; /** * Constant describing the horizontal accuracy returned * by this provider. - * - * @return the horizontal accuracy for this provider, as one of the - * constants Criteria.ACCURACY_COARSE or Criteria.ACCURACY_FINE */ + @Accuracy public final int mAccuracy; - public ProviderProperties(boolean mRequiresNetwork, - boolean mRequiresSatellite, boolean mRequiresCell, boolean mHasMonetaryCost, - boolean mSupportsAltitude, boolean mSupportsSpeed, boolean mSupportsBearing, - int mPowerRequirement, int mAccuracy) { - this.mRequiresNetwork = mRequiresNetwork; - this.mRequiresSatellite = mRequiresSatellite; - this.mRequiresCell = mRequiresCell; - this.mHasMonetaryCost = mHasMonetaryCost; - this.mSupportsAltitude = mSupportsAltitude; - this.mSupportsSpeed = mSupportsSpeed; - this.mSupportsBearing = mSupportsBearing; - this.mPowerRequirement = mPowerRequirement; - this.mAccuracy = mAccuracy; + public ProviderProperties(boolean requiresNetwork, boolean requiresSatellite, + boolean requiresCell, boolean hasMonetaryCost, boolean supportsAltitude, + boolean supportsSpeed, boolean supportsBearing, @PowerRequirement int powerRequirement, + @Accuracy int accuracy) { + mRequiresNetwork = requiresNetwork; + mRequiresSatellite = requiresSatellite; + mRequiresCell = requiresCell; + mHasMonetaryCost = hasMonetaryCost; + mSupportsAltitude = supportsAltitude; + mSupportsSpeed = supportsSpeed; + mSupportsBearing = supportsBearing; + mPowerRequirement = Preconditions.checkArgumentInRange(powerRequirement, Criteria.POWER_LOW, + Criteria.POWER_HIGH, "powerRequirement"); + mAccuracy = Preconditions.checkArgumentInRange(accuracy, Criteria.ACCURACY_FINE, + Criteria.ACCURACY_COARSE, "accuracy"); } public static final Parcelable.Creator<ProviderProperties> CREATOR = new Parcelable.Creator<ProviderProperties>() { - @Override - public ProviderProperties createFromParcel(Parcel in) { - boolean requiresNetwork = in.readInt() == 1; - boolean requiresSatellite = in.readInt() == 1; - boolean requiresCell = in.readInt() == 1; - boolean hasMonetaryCost = in.readInt() == 1; - boolean supportsAltitude = in.readInt() == 1; - boolean supportsSpeed = in.readInt() == 1; - boolean supportsBearing = in.readInt() == 1; - int powerRequirement = in.readInt(); - int accuracy = in.readInt(); - return new ProviderProperties(requiresNetwork, requiresSatellite, - requiresCell, hasMonetaryCost, supportsAltitude, supportsSpeed, supportsBearing, - powerRequirement, accuracy); - } - @Override - public ProviderProperties[] newArray(int size) { - return new ProviderProperties[size]; - } - }; + @Override + public ProviderProperties createFromParcel(Parcel in) { + boolean requiresNetwork = in.readInt() == 1; + boolean requiresSatellite = in.readInt() == 1; + boolean requiresCell = in.readInt() == 1; + boolean hasMonetaryCost = in.readInt() == 1; + boolean supportsAltitude = in.readInt() == 1; + boolean supportsSpeed = in.readInt() == 1; + boolean supportsBearing = in.readInt() == 1; + int powerRequirement = in.readInt(); + int accuracy = in.readInt(); + return new ProviderProperties(requiresNetwork, requiresSatellite, + requiresCell, hasMonetaryCost, supportsAltitude, supportsSpeed, + supportsBearing, + powerRequirement, accuracy); + } + + @Override + public ProviderProperties[] newArray(int size) { + return new ProviderProperties[size]; + } + }; @Override public int describeContents() { @@ -149,4 +170,67 @@ public final class ProviderProperties implements Parcelable { parcel.writeInt(mPowerRequirement); parcel.writeInt(mAccuracy); } + + @Override + public String toString() { + StringBuilder b = new StringBuilder("ProviderProperties["); + b.append("power=").append(powerToString(mPowerRequirement)).append(", "); + b.append("accuracy=").append(accuracyToString(mAccuracy)); + if (mRequiresNetwork || mRequiresSatellite || mRequiresCell) { + b.append(", requires="); + if (mRequiresNetwork) { + b.append("network,"); + } + if (mRequiresSatellite) { + b.append("satellite,"); + } + if (mRequiresCell) { + b.append("cell,"); + } + b.setLength(b.length() - 1); + } + if (mHasMonetaryCost) { + b.append(", hasMonetaryCost"); + } + if (mSupportsBearing || mSupportsSpeed || mSupportsAltitude) { + b.append(", supports=["); + if (mSupportsBearing) { + b.append("bearing, "); + } + if (mSupportsSpeed) { + b.append("speed, "); + } + if (mSupportsAltitude) { + b.append("altitude, "); + } + b.setLength(b.length() - 2); + b.append("]"); + } + b.append("]"); + return b.toString(); + } + + private static String powerToString(@PowerRequirement int power) { + switch (power) { + case Criteria.POWER_LOW: + return "Low"; + case Criteria.POWER_MEDIUM: + return "Medium"; + case Criteria.POWER_HIGH: + return "High"; + default: + return "???"; + } + } + + private static String accuracyToString(@Accuracy int accuracy) { + switch (accuracy) { + case Criteria.ACCURACY_COARSE: + return "Coarse"; + case Criteria.ACCURACY_FINE: + return "Fine"; + default: + return "???"; + } + } } diff --git a/location/java/com/android/internal/location/ProviderRequest.java b/location/java/com/android/internal/location/ProviderRequest.java index c23f49976799..8d8df4533ebe 100644 --- a/location/java/com/android/internal/location/ProviderRequest.java +++ b/location/java/com/android/internal/location/ProviderRequest.java @@ -20,33 +20,42 @@ import android.compat.annotation.UnsupportedAppUsage; import android.location.LocationRequest; import android.os.Parcel; import android.os.Parcelable; +import android.os.WorkSource; import android.util.TimeUtils; +import com.android.internal.util.Preconditions; + import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @hide */ public final class ProviderRequest implements Parcelable { + + public static final ProviderRequest EMPTY_REQUEST = new ProviderRequest(false, Long.MAX_VALUE, + false, false, + Collections.emptyList(), new WorkSource()); + /** Location reporting is requested (true) */ @UnsupportedAppUsage - public boolean reportLocation = false; + public final boolean reportLocation; /** The smallest requested interval */ @UnsupportedAppUsage - public long interval = Long.MAX_VALUE; + public final long interval; /** - * When this flag is true, providers should ignore all location settings, user consents, power - * restrictions or any other restricting factors and always satisfy this request to the best of - * their ability. This flag should only be used in event of an emergency. + * Whether provider shall make stronger than normal tradeoffs to substantially restrict power + * use. */ - public boolean locationSettingsIgnored = false; + public final boolean lowPowerMode; /** - * Whether provider shall make stronger than normal tradeoffs to substantially restrict power - * use. + * When this flag is true, providers should ignore all location settings, user consents, power + * restrictions or any other restricting factors and always satisfy this request to the best of + * their ability. This flag should only be used in event of an emergency. */ - public boolean lowPowerMode = false; + public final boolean locationSettingsIgnored; /** * A more detailed set of requests. @@ -56,26 +65,37 @@ public final class ProviderRequest implements Parcelable { * low power fast interval request. */ @UnsupportedAppUsage - public final List<LocationRequest> locationRequests = new ArrayList<>(); + public final List<LocationRequest> locationRequests; - @UnsupportedAppUsage - public ProviderRequest() { + public final WorkSource workSource; + + private ProviderRequest(boolean reportLocation, long interval, boolean lowPowerMode, + boolean locationSettingsIgnored, List<LocationRequest> locationRequests, + WorkSource workSource) { + this.reportLocation = reportLocation; + this.interval = interval; + this.lowPowerMode = lowPowerMode; + this.locationSettingsIgnored = locationSettingsIgnored; + this.locationRequests = Preconditions.checkNotNull(locationRequests); + this.workSource = Preconditions.checkNotNull(workSource); } public static final Parcelable.Creator<ProviderRequest> CREATOR = new Parcelable.Creator<ProviderRequest>() { @Override public ProviderRequest createFromParcel(Parcel in) { - ProviderRequest request = new ProviderRequest(); - request.reportLocation = in.readInt() == 1; - request.interval = in.readLong(); - request.lowPowerMode = in.readBoolean(); - request.locationSettingsIgnored = in.readBoolean(); + boolean reportLocation = in.readInt() == 1; + long interval = in.readLong(); + boolean lowPowerMode = in.readBoolean(); + boolean locationSettingsIgnored = in.readBoolean(); int count = in.readInt(); + ArrayList<LocationRequest> locationRequests = new ArrayList<>(count); for (int i = 0; i < count; i++) { - request.locationRequests.add(LocationRequest.CREATOR.createFromParcel(in)); + locationRequests.add(LocationRequest.CREATOR.createFromParcel(in)); } - return request; + WorkSource workSource = in.readParcelable(null); + return new ProviderRequest(reportLocation, interval, lowPowerMode, + locationSettingsIgnored, locationRequests, workSource); } @Override @@ -106,14 +126,13 @@ public final class ProviderRequest implements Parcelable { StringBuilder s = new StringBuilder(); s.append("ProviderRequest["); if (reportLocation) { - s.append("ON"); - s.append(" interval="); + s.append("interval="); TimeUtils.formatDuration(interval, s); if (lowPowerMode) { - s.append(" lowPowerMode"); + s.append(", lowPowerMode"); } if (locationSettingsIgnored) { - s.append(" locationSettingsIgnored"); + s.append(", locationSettingsIgnored"); } } else { s.append("OFF"); @@ -121,4 +140,67 @@ public final class ProviderRequest implements Parcelable { s.append(']'); return s.toString(); } + + /** + * A Builder for {@link ProviderRequest}s. + */ + public static class Builder { + private long mInterval = Long.MAX_VALUE; + private boolean mLowPowerMode; + private boolean mLocationSettingsIgnored; + private List<LocationRequest> mLocationRequests = Collections.emptyList(); + private WorkSource mWorkSource = new WorkSource(); + + public long getInterval() { + return mInterval; + } + + public void setInterval(long interval) { + this.mInterval = interval; + } + + public boolean isLowPowerMode() { + return mLowPowerMode; + } + + public void setLowPowerMode(boolean lowPowerMode) { + this.mLowPowerMode = lowPowerMode; + } + + public boolean isLocationSettingsIgnored() { + return mLocationSettingsIgnored; + } + + public void setLocationSettingsIgnored(boolean locationSettingsIgnored) { + this.mLocationSettingsIgnored = locationSettingsIgnored; + } + + public List<LocationRequest> getLocationRequests() { + return mLocationRequests; + } + + public void setLocationRequests(List<LocationRequest> locationRequests) { + this.mLocationRequests = Preconditions.checkNotNull(locationRequests); + } + + public WorkSource getWorkSource() { + return mWorkSource; + } + + public void setWorkSource(WorkSource workSource) { + mWorkSource = Preconditions.checkNotNull(workSource); + } + + /** + * Builds a ProviderRequest object with the set information. + */ + public ProviderRequest build() { + if (mInterval == Long.MAX_VALUE) { + return EMPTY_REQUEST; + } else { + return new ProviderRequest(true, mInterval, mLowPowerMode, + mLocationSettingsIgnored, mLocationRequests, mWorkSource); + } + } + } } diff --git a/media/java/android/media/IMediaRoute2Provider.aidl b/media/java/android/media/IMediaRoute2Provider.aidl index 02a381669893..28bf84d6e079 100644 --- a/media/java/android/media/IMediaRoute2Provider.aidl +++ b/media/java/android/media/IMediaRoute2Provider.aidl @@ -24,13 +24,12 @@ import android.media.IMediaRoute2ProviderClient; */ oneway interface IMediaRoute2Provider { void setClient(IMediaRoute2ProviderClient client); - void requestCreateSession(String packageName, String routeId, - String controlCategory, long requestId); - void releaseSession(int sessionId); + void requestCreateSession(String packageName, String routeId, String routeType, long requestId); + void releaseSession(String sessionId); - void selectRoute(int sessionId, String routeId); - void deselectRoute(int sessionId, String routeId); - void transferToRoute(int sessionId, String routeId); + void selectRoute(String sessionId, String routeId); + void deselectRoute(String sessionId, String routeId); + void transferToRoute(String sessionId, String routeId); void notifyControlRequestSent(String id, in Intent request); void requestSetVolume(String id, int volume); diff --git a/media/java/android/media/IMediaRouter2Manager.aidl b/media/java/android/media/IMediaRouter2Manager.aidl index b7cb7059ce3d..e8af21e59cc1 100644 --- a/media/java/android/media/IMediaRouter2Manager.aidl +++ b/media/java/android/media/IMediaRouter2Manager.aidl @@ -24,7 +24,7 @@ import android.media.MediaRoute2Info; */ oneway interface IMediaRouter2Manager { void notifyRouteSelected(String packageName, in MediaRoute2Info route); - void notifyControlCategoriesChanged(String packageName, in List<String> categories); + void notifyRouteTypesChanged(String packageName, in List<String> routeTypes); void notifyRoutesAdded(in List<MediaRoute2Info> routes); void notifyRoutesRemoved(in List<MediaRoute2Info> routes); void notifyRoutesChanged(in List<MediaRoute2Info> routes); diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl index e5b62ff10aa6..b573f64da51d 100644 --- a/media/java/android/media/IMediaRouterService.aidl +++ b/media/java/android/media/IMediaRouterService.aidl @@ -22,6 +22,7 @@ import android.media.IMediaRouter2Manager; import android.media.IMediaRouterClient; import android.media.MediaRoute2Info; import android.media.MediaRouterClientState; +import android.media.RouteDiscoveryRequest; import android.media.RouteSessionInfo; /** @@ -51,8 +52,8 @@ interface IMediaRouterService { void requestUpdateVolume2(IMediaRouter2Client client, in MediaRoute2Info route, int direction); void requestCreateSession(IMediaRouter2Client client, in MediaRoute2Info route, - String controlCategory, int requestId); - void setControlCategories(IMediaRouter2Client client, in List<String> categories); + String routeType, int requestId); + void setDiscoveryRequest2(IMediaRouter2Client client, in RouteDiscoveryRequest request); void selectRoute(IMediaRouter2Client client, String sessionId, in MediaRoute2Info route); void deselectRoute(IMediaRouter2Client client, String sessionId, in MediaRoute2Info route); void transferToRoute(IMediaRouter2Client client, String sessionId, in MediaRoute2Info route); diff --git a/media/java/android/media/MediaMetadataRetriever.java b/media/java/android/media/MediaMetadataRetriever.java index 7fca03cdfd57..4cd581b6628c 100644 --- a/media/java/android/media/MediaMetadataRetriever.java +++ b/media/java/android/media/MediaMetadataRetriever.java @@ -17,6 +17,7 @@ package android.media; import android.annotation.IntDef; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; @@ -354,8 +355,8 @@ public class MediaMetadataRetriever implements AutoCloseable { * is less than or equal to 0. * @see {@link #getScaledFrameAtTime(long, int, int, int, BitmapParams)} */ - public @Nullable Bitmap getScaledFrameAtTime( - long timeUs, @Option int option, int dstWidth, int dstHeight) { + public @Nullable Bitmap getScaledFrameAtTime(long timeUs, @Option int option, + @IntRange(from=1) int dstWidth, @IntRange(from=1) int dstHeight) { validate(option, dstWidth, dstHeight); return _getFrameAtTime(timeUs, option, dstWidth, dstHeight, null); } @@ -400,7 +401,8 @@ public class MediaMetadataRetriever implements AutoCloseable { * @see {@link #getScaledFrameAtTime(long, int, int, int)} */ public @Nullable Bitmap getScaledFrameAtTime(long timeUs, @Option int option, - int dstWidth, int dstHeight, @NonNull BitmapParams params) { + @IntRange(from=1) int dstWidth, @IntRange(from=1) int dstHeight, + @NonNull BitmapParams params) { validate(option, dstWidth, dstHeight); return _getFrameAtTime(timeUs, option, dstWidth, dstHeight, params); } diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java index 506d6165bbfa..1ed53d942b63 100644 --- a/media/java/android/media/MediaRoute2Info.java +++ b/media/java/android/media/MediaRoute2Info.java @@ -16,6 +16,8 @@ package android.media; +import static android.media.MediaRouter2Utils.toUniqueId; + import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -83,12 +85,14 @@ public final class MediaRoute2Info implements Parcelable { * controlled from this object. An example of fixed playback volume is a remote player, * playing over HDMI where the user prefers to control the volume on the HDMI sink, rather * than attenuate at the source. + * * @see #getVolumeHandling() */ public static final int PLAYBACK_VOLUME_FIXED = 0; /** * Playback information indicating the playback volume is variable and can be controlled * from this object. + * * @see #getVolumeHandling() */ public static final int PLAYBACK_VOLUME_VARIABLE = 1; @@ -148,7 +152,7 @@ public final class MediaRoute2Info implements Parcelable { @Nullable final String mClientPackageName; @NonNull - final List<String> mSupportedCategories; + final List<String> mRouteTypes; final int mVolume; final int mVolumeMax; final int mVolumeHandling; @@ -156,8 +160,6 @@ public final class MediaRoute2Info implements Parcelable { @Nullable final Bundle mExtras; - private final String mUniqueId; - MediaRoute2Info(@NonNull Builder builder) { mId = builder.mId; mProviderId = builder.mProviderId; @@ -166,13 +168,12 @@ public final class MediaRoute2Info implements Parcelable { mConnectionState = builder.mConnectionState; mIconUri = builder.mIconUri; mClientPackageName = builder.mClientPackageName; - mSupportedCategories = builder.mSupportedCategories; + mRouteTypes = builder.mRouteTypes; mVolume = builder.mVolume; mVolumeMax = builder.mVolumeMax; mVolumeHandling = builder.mVolumeHandling; mDeviceType = builder.mDeviceType; mExtras = builder.mExtras; - mUniqueId = createUniqueId(); } MediaRoute2Info(@NonNull Parcel in) { @@ -183,25 +184,12 @@ public final class MediaRoute2Info implements Parcelable { mConnectionState = in.readInt(); mIconUri = in.readParcelable(null); mClientPackageName = in.readString(); - mSupportedCategories = in.createStringArrayList(); + mRouteTypes = in.createStringArrayList(); mVolume = in.readInt(); mVolumeMax = in.readInt(); mVolumeHandling = in.readInt(); mDeviceType = in.readInt(); mExtras = in.readBundle(); - mUniqueId = createUniqueId(); - } - - private String createUniqueId() { - String uniqueId = null; - if (mProviderId != null) { - uniqueId = toUniqueId(mProviderId, mId); - } - return uniqueId; - } - - static String toUniqueId(String providerId, String routeId) { - return providerId + ":" + routeId; } /** @@ -235,7 +223,7 @@ public final class MediaRoute2Info implements Parcelable { && (mConnectionState == other.mConnectionState) && Objects.equals(mIconUri, other.mIconUri) && Objects.equals(mClientPackageName, other.mClientPackageName) - && Objects.equals(mSupportedCategories, other.mSupportedCategories) + && Objects.equals(mRouteTypes, other.mRouteTypes) && (mVolume == other.mVolume) && (mVolumeMax == other.mVolumeMax) && (mVolumeHandling == other.mVolumeHandling) @@ -247,29 +235,34 @@ public final class MediaRoute2Info implements Parcelable { @Override public int hashCode() { return Objects.hash(mId, mName, mDescription, mConnectionState, mIconUri, - mSupportedCategories, mVolume, mVolumeMax, mVolumeHandling, mDeviceType); + mRouteTypes, mVolume, mVolumeMax, mVolumeHandling, mDeviceType); } /** - * Gets the id of the route. - * Use {@link #getUniqueId()} if you need a unique identifier. + * Gets the id of the route. The routes which are given by {@link MediaRouter2} will have + * unique IDs. + * <p> + * In order to ensure uniqueness in {@link MediaRouter2} side, the value of this method + * can be different from what was set in {@link MediaRoute2ProviderService}. * - * @see #getUniqueId() + * @see Builder#setId(String) */ @NonNull public String getId() { - return mId; + if (mProviderId != null) { + return toUniqueId(mProviderId, mId); + } else { + return mId; + } } /** - * Gets the unique id of the route. A route obtained from - * {@link com.android.server.media.MediaRouterService} always has a unique id. - * - * @return unique id of the route or null if it has no unique id. + * Gets the original id set by {@link Builder#setId(String)}. + * @hide */ - @Nullable - public String getUniqueId() { - return mUniqueId; + @NonNull + public String getOriginalId() { + return mId; } /** @@ -331,8 +324,8 @@ public final class MediaRoute2Info implements Parcelable { * Gets the supported categories of the route. */ @NonNull - public List<String> getSupportedCategories() { - return mSupportedCategories; + public List<String> getRouteTypes() { + return mRouteTypes; } //TODO: once device types are confirmed, reflect those into the comment. @@ -376,32 +369,15 @@ public final class MediaRoute2Info implements Parcelable { } /** - * Returns if the route supports the specified control category + * Returns if the route contains at least one of the specified route types. * - * @param controlCategory control category to consider - * @return true if the route supports at the category + * @param routeTypes the list of route types to consider + * @return true if the route contains at least one type in the list */ - public boolean supportsControlCategory(@NonNull String controlCategory) { - Objects.requireNonNull(controlCategory, "control category must not be null"); - for (String supportedCategory : getSupportedCategories()) { - if (TextUtils.equals(controlCategory, supportedCategory)) { - return true; - } - } - return false; - } - - //TODO: Move this if we re-define control category / selector things. - /** - * Returns if the route supports at least one of the specified control categories - * - * @param controlCategories the list of control categories to consider - * @return true if the route supports at least one category - */ - public boolean supportsControlCategories(@NonNull Collection<String> controlCategories) { - Objects.requireNonNull(controlCategories, "control categories must not be null"); - for (String controlCategory : controlCategories) { - if (supportsControlCategory(controlCategory)) { + public boolean containsRouteTypes(@NonNull Collection<String> routeTypes) { + Objects.requireNonNull(routeTypes, "routeTypes must not be null"); + for (String routeType : routeTypes) { + if (getRouteTypes().contains(routeType)) { return true; } } @@ -422,7 +398,7 @@ public final class MediaRoute2Info implements Parcelable { dest.writeInt(mConnectionState); dest.writeParcelable(mIconUri, flags); dest.writeString(mClientPackageName); - dest.writeStringList(mSupportedCategories); + dest.writeStringList(mRouteTypes); dest.writeInt(mVolume); dest.writeInt(mVolumeMax); dest.writeInt(mVolumeHandling); @@ -460,7 +436,7 @@ public final class MediaRoute2Info implements Parcelable { int mConnectionState; Uri mIconUri; String mClientPackageName; - List<String> mSupportedCategories; + List<String> mRouteTypes; int mVolume; int mVolumeMax; int mVolumeHandling = PLAYBACK_VOLUME_FIXED; @@ -471,7 +447,7 @@ public final class MediaRoute2Info implements Parcelable { public Builder(@NonNull String id, @NonNull CharSequence name) { setId(id); setName(name); - mSupportedCategories = new ArrayList<>(); + mRouteTypes = new ArrayList<>(); } public Builder(@NonNull MediaRoute2Info routeInfo) { @@ -488,7 +464,7 @@ public final class MediaRoute2Info implements Parcelable { mConnectionState = routeInfo.mConnectionState; mIconUri = routeInfo.mIconUri; setClientPackageName(routeInfo.mClientPackageName); - setSupportedCategories(routeInfo.mSupportedCategories); + setRouteTypes(routeInfo.mRouteTypes); setVolume(routeInfo.mVolume); setVolumeMax(routeInfo.mVolumeMax); setVolumeHandling(routeInfo.mVolumeHandling); @@ -499,7 +475,15 @@ public final class MediaRoute2Info implements Parcelable { } /** - * Sets the unique id of the route. + * Sets the unique id of the route. The value given here must be unique for each of your + * route. + * <p> + * In order to ensure uniqueness in {@link MediaRouter2} side, the value of + * {@link MediaRoute2Info#getId()} can be different from what was set in + * {@link MediaRoute2ProviderService}. + * </p> + * + * @see MediaRoute2Info#getId() */ @NonNull public Builder setId(@NonNull String id) { @@ -585,35 +569,35 @@ public final class MediaRoute2Info implements Parcelable { } /** - * Sets the supported categories of the route. + * Sets the types of the route. */ @NonNull - public Builder setSupportedCategories(@NonNull Collection<String> categories) { - mSupportedCategories = new ArrayList<>(); - return addSupportedCategories(categories); + public Builder setRouteTypes(@NonNull Collection<String> routeTypes) { + mRouteTypes = new ArrayList<>(); + return addRouteTypes(routeTypes); } /** - * Adds supported categories for the route. + * Adds types for the route. */ @NonNull - public Builder addSupportedCategories(@NonNull Collection<String> categories) { - Objects.requireNonNull(categories, "categories must not be null"); - for (String category: categories) { - addSupportedCategory(category); + public Builder addRouteTypes(@NonNull Collection<String> routeTypes) { + Objects.requireNonNull(routeTypes, "routeTypes must not be null"); + for (String routeType: routeTypes) { + addRouteType(routeType); } return this; } /** - * Add a supported category for the route. + * Add a type for the route. */ @NonNull - public Builder addSupportedCategory(@NonNull String category) { - if (TextUtils.isEmpty(category)) { - throw new IllegalArgumentException("category must not be null or empty"); + public Builder addRouteType(@NonNull String routeType) { + if (TextUtils.isEmpty(routeType)) { + throw new IllegalArgumentException("routeType must not be null or empty"); } - mSupportedCategories.add(category); + mRouteTypes.add(routeType); return this; } diff --git a/media/java/android/media/MediaRoute2ProviderInfo.java b/media/java/android/media/MediaRoute2ProviderInfo.java index 7078d4a0b568..e2f246cae8c2 100644 --- a/media/java/android/media/MediaRoute2ProviderInfo.java +++ b/media/java/android/media/MediaRoute2ProviderInfo.java @@ -25,6 +25,7 @@ import android.util.ArrayMap; import java.util.Arrays; import java.util.Collection; +import java.util.Map; import java.util.Objects; /** @@ -161,14 +162,17 @@ public final class MediaRoute2ProviderInfo implements Parcelable { return this; } mUniqueId = uniqueId; - final int count = mRoutes.size(); - for (int i = 0; i < count; i++) { - MediaRoute2Info route = mRoutes.valueAt(i); - mRoutes.setValueAt(i, new MediaRoute2Info.Builder(route) + + final ArrayMap<String, MediaRoute2Info> newRoutes = new ArrayMap<>(); + for (Map.Entry<String, MediaRoute2Info> entry : mRoutes.entrySet()) { + MediaRoute2Info routeWithProviderId = new MediaRoute2Info.Builder(entry.getValue()) .setProviderId(mUniqueId) - .build()); + .build(); + newRoutes.put(routeWithProviderId.getId(), routeWithProviderId); } + mRoutes.clear(); + mRoutes.putAll(newRoutes); return this; } diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java index 99bd1dcde3bb..24b65baebcbd 100644 --- a/media/java/android/media/MediaRoute2ProviderService.java +++ b/media/java/android/media/MediaRoute2ProviderService.java @@ -29,6 +29,7 @@ import android.os.IBinder; import android.os.Looper; import android.os.Process; import android.os.RemoteException; +import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; @@ -55,7 +56,7 @@ public abstract class MediaRoute2ProviderService extends Service { private MediaRoute2ProviderInfo mProviderInfo; @GuardedBy("mSessionLock") - private ArrayMap<Integer, RouteSessionInfo> mSessionInfo = new ArrayMap<>(); + private ArrayMap<String, RouteSessionInfo> mSessionInfo = new ArrayMap<>(); public MediaRoute2ProviderService() { mHandler = new Handler(Looper.getMainLooper()); @@ -106,7 +107,10 @@ public abstract class MediaRoute2ProviderService extends Service { * null if the session is destroyed or id is not valid. */ @Nullable - public final RouteSessionInfo getSessionInfo(int sessionId) { + public final RouteSessionInfo getSessionInfo(@NonNull String sessionId) { + if (TextUtils.isEmpty(sessionId)) { + throw new IllegalArgumentException("sessionId must not be empty"); + } synchronized (mSessionLock) { return mSessionInfo.get(sessionId); } @@ -134,7 +138,7 @@ public abstract class MediaRoute2ProviderService extends Service { */ public final void updateSessionInfo(@NonNull RouteSessionInfo sessionInfo) { Objects.requireNonNull(sessionInfo, "sessionInfo must not be null"); - int sessionId = sessionInfo.getSessionId(); + String sessionId = sessionInfo.getId(); if (sessionInfo.getSelectedRoutes().isEmpty()) { releaseSession(sessionId); return; @@ -160,7 +164,7 @@ public abstract class MediaRoute2ProviderService extends Service { public final void notifySessionInfoChanged(@NonNull RouteSessionInfo sessionInfo) { Objects.requireNonNull(sessionInfo, "sessionInfo must not be null"); - int sessionId = sessionInfo.getSessionId(); + String sessionId = sessionInfo.getId(); synchronized (mSessionLock) { if (mSessionInfo.containsKey(sessionId)) { mSessionInfo.put(sessionId, sessionInfo); @@ -185,7 +189,7 @@ public abstract class MediaRoute2ProviderService extends Service { * controlled, pass a {@link Bundle} that contains how to control it. * * @param sessionInfo information of the new session. - * The {@link RouteSessionInfo#getSessionId() id} of the session must be + * The {@link RouteSessionInfo#getId() id} of the session must be * unique. Pass {@code null} to reject the request or inform clients that * session creation is failed. * @param requestId id of the previous request to create this session @@ -194,13 +198,13 @@ public abstract class MediaRoute2ProviderService extends Service { // TODO: Maybe better to create notifySessionCreationFailed? public final void notifySessionCreated(@Nullable RouteSessionInfo sessionInfo, long requestId) { if (sessionInfo != null) { - int sessionId = sessionInfo.getSessionId(); + String sessionId = sessionInfo.getId(); synchronized (mSessionLock) { if (mSessionInfo.containsKey(sessionId)) { Log.w(TAG, "Ignoring duplicate session id."); return; } - mSessionInfo.put(sessionInfo.getSessionId(), sessionInfo); + mSessionInfo.put(sessionInfo.getId(), sessionInfo); } schedulePublishState(); } @@ -220,9 +224,12 @@ public abstract class MediaRoute2ProviderService extends Service { * {@link #onDestroySession} is called if the session is released. * * @param sessionId id of the session to be released - * @see #onDestroySession(int, RouteSessionInfo) + * @see #onDestroySession(String, RouteSessionInfo) */ - public final void releaseSession(int sessionId) { + public final void releaseSession(@NonNull String sessionId) { + if (TextUtils.isEmpty(sessionId)) { + throw new IllegalArgumentException("sessionId must not be empty"); + } //TODO: notify media router service of release. RouteSessionInfo sessionInfo; synchronized (mSessionLock) { @@ -246,11 +253,11 @@ public abstract class MediaRoute2ProviderService extends Service { * * @param packageName the package name of the application that selected the route * @param routeId the id of the route initially being connected - * @param controlCategory the control category of the new session + * @param routeType the route type of the new session * @param requestId the id of this session creation request */ public abstract void onCreateSession(@NonNull String packageName, @NonNull String routeId, - @NonNull String controlCategory, long requestId); + @NonNull String routeType, long requestId); /** * Called when a session is about to be destroyed. @@ -259,9 +266,10 @@ public abstract class MediaRoute2ProviderService extends Service { * * @param sessionId id of the session being destroyed. * @param lastSessionInfo information of the session being destroyed. - * @see #releaseSession(int) + * @see #releaseSession(String) */ - public abstract void onDestroySession(int sessionId, @NonNull RouteSessionInfo lastSessionInfo); + public abstract void onDestroySession(@NonNull String sessionId, + @NonNull RouteSessionInfo lastSessionInfo); //TODO: make a way to reject the request /** @@ -274,7 +282,7 @@ public abstract class MediaRoute2ProviderService extends Service { * @param routeId id of the route * @see #updateSessionInfo(RouteSessionInfo) */ - public abstract void onSelectRoute(int sessionId, @NonNull String routeId); + public abstract void onSelectRoute(@NonNull String sessionId, @NonNull String routeId); //TODO: make a way to reject the request /** @@ -286,7 +294,7 @@ public abstract class MediaRoute2ProviderService extends Service { * @param sessionId id of the session * @param routeId id of the route */ - public abstract void onDeselectRoute(int sessionId, @NonNull String routeId); + public abstract void onDeselectRoute(@NonNull String sessionId, @NonNull String routeId); //TODO: make a way to reject the request /** @@ -298,7 +306,26 @@ public abstract class MediaRoute2ProviderService extends Service { * @param sessionId id of the session * @param routeId id of the route */ - public abstract void onTransferToRoute(int sessionId, @NonNull String routeId); + public abstract void onTransferToRoute(@NonNull String sessionId, @NonNull String routeId); + + /** + * Called when the {@link RouteDiscoveryRequest discovery request} has changed. + * <p> + * Whenever an application registers a {@link MediaRouter2.RouteCallback callback}, + * it also provides a discovery request to specify types of routes that it is interested in. + * The media router combines all of these discovery request into a single discovery request + * and notifies each provider. + * </p><p> + * The provider should examine {@link RouteDiscoveryRequest#getRouteTypes() route types} + * in the discovery request to determine what kind of routes it should try to discover + * and whether it should perform active or passive scans. In many cases, the provider may be + * able to save power by not performing any scans when the request doesn't have any matching + * route types. + * </p> + * + * @param request the new discovery request + */ + public void onDiscoveryRequestChanged(@NonNull RouteDiscoveryRequest request) {} /** * Updates provider info and publishes routes and session info. @@ -357,46 +384,62 @@ public abstract class MediaRoute2ProviderService extends Service { @Override public void requestCreateSession(String packageName, String routeId, - String controlCategory, long requestId) { + String routeType, long requestId) { if (!checkCallerisSystem()) { return; } mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onCreateSession, - MediaRoute2ProviderService.this, packageName, routeId, controlCategory, + MediaRoute2ProviderService.this, packageName, routeId, routeType, requestId)); } @Override - public void releaseSession(int sessionId) { + public void releaseSession(@NonNull String sessionId) { if (!checkCallerisSystem()) { return; } + if (TextUtils.isEmpty(sessionId)) { + Log.w(TAG, "releaseSession: Ignoring empty sessionId from system service."); + return; + } mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::releaseSession, MediaRoute2ProviderService.this, sessionId)); } @Override - public void selectRoute(int sessionId, String routeId) { + public void selectRoute(@NonNull String sessionId, String routeId) { if (!checkCallerisSystem()) { return; } + if (TextUtils.isEmpty(sessionId)) { + Log.w(TAG, "selectRoute: Ignoring empty sessionId from system service."); + return; + } mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSelectRoute, MediaRoute2ProviderService.this, sessionId, routeId)); } @Override - public void deselectRoute(int sessionId, String routeId) { + public void deselectRoute(@NonNull String sessionId, String routeId) { if (!checkCallerisSystem()) { return; } + if (TextUtils.isEmpty(sessionId)) { + Log.w(TAG, "deselectRoute: Ignoring empty sessionId from system service."); + return; + } mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onDeselectRoute, MediaRoute2ProviderService.this, sessionId, routeId)); } @Override - public void transferToRoute(int sessionId, String routeId) { + public void transferToRoute(@NonNull String sessionId, String routeId) { if (!checkCallerisSystem()) { return; } + if (TextUtils.isEmpty(sessionId)) { + Log.w(TAG, "transferToRoute: Ignoring empty sessionId from system service."); + return; + } mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onTransferToRoute, MediaRoute2ProviderService.this, sessionId, routeId)); } diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index f5cfde4b968b..8ebf6174cabf 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -39,7 +39,6 @@ import com.android.internal.annotations.GuardedBy; import java.lang.annotation.Retention; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -48,6 +47,7 @@ import java.util.Objects; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; /** * A new Media Router @@ -118,7 +118,7 @@ public class MediaRouter2 { final Map<String, MediaRoute2Info> mRoutes = new HashMap<>(); @GuardedBy("sLock") - private List<String> mControlCategories = Collections.emptyList(); + private RouteDiscoveryRequest mDiscoveryRequest = RouteDiscoveryRequest.EMPTY; // TODO: Make MediaRouter2 is always connected to the MediaRouterService. @GuardedBy("sLock") @@ -152,7 +152,6 @@ public class MediaRouter2 { mMediaRouterService = IMediaRouterService.Stub.asInterface( ServiceManager.getService(Context.MEDIA_ROUTER_SERVICE)); mPackageName = mContext.getPackageName(); - //TODO: read control categories from the manifest mHandler = new Handler(Looper.getMainLooper()); List<MediaRoute2Info> currentSystemRoutes = null; @@ -177,9 +176,9 @@ public class MediaRouter2 { * @hide */ public static boolean checkRouteListContainsRouteId(@NonNull List<MediaRoute2Info> routeList, - @NonNull String uniqueRouteId) { + @NonNull String routeId) { for (MediaRoute2Info info : routeList) { - if (TextUtils.equals(uniqueRouteId, info.getUniqueId())) { + if (TextUtils.equals(routeId, info.getId())) { return true; } } @@ -188,24 +187,18 @@ public class MediaRouter2 { /** * Registers a callback to discover routes and to receive events when they change. - */ - public void registerRouteCallback(@NonNull @CallbackExecutor Executor executor, - @NonNull RouteCallback routeCallback) { - registerRouteCallback(executor, routeCallback, 0); - } - - /** - * Registers a callback to discover routes and to receive events when they change. * <p> * If you register the same callback twice or more, it will be ignored. * </p> */ public void registerRouteCallback(@NonNull @CallbackExecutor Executor executor, - @NonNull RouteCallback routeCallback, int flags) { + @NonNull RouteCallback routeCallback, + @NonNull RouteDiscoveryRequest request) { Objects.requireNonNull(executor, "executor must not be null"); Objects.requireNonNull(routeCallback, "callback must not be null"); + Objects.requireNonNull(request, "request must not be null"); - RouteCallbackRecord record = new RouteCallbackRecord(executor, routeCallback, flags); + RouteCallbackRecord record = new RouteCallbackRecord(executor, routeCallback, request); if (!mRouteCallbackRecords.addIfAbsent(record)) { Log.w(TAG, "Ignoring the same callback"); return; @@ -216,7 +209,8 @@ public class MediaRouter2 { Client2 client = new Client2(); try { mMediaRouterService.registerClient2(client, mPackageName); - mMediaRouterService.setControlCategories(client, mControlCategories); + updateDiscoveryRequestLocked(); + mMediaRouterService.setDiscoveryRequest2(client, mDiscoveryRequest); mClient = client; } catch (RemoteException ex) { Log.e(TAG, "Unable to register media router.", ex); @@ -238,7 +232,7 @@ public class MediaRouter2 { Objects.requireNonNull(routeCallback, "callback must not be null"); if (!mRouteCallbackRecords.remove( - new RouteCallbackRecord(null, routeCallback, 0))) { + new RouteCallbackRecord(null, routeCallback, null))) { Log.w(TAG, "Ignoring unknown callback"); return; } @@ -256,30 +250,10 @@ public class MediaRouter2 { } } - //TODO(b/139033746): Rename "Control Category" when it's finalized. - /** - * Sets the control categories of the application. - * Routes that support at least one of the given control categories are handled - * by the media router. - */ - public void setControlCategories(@NonNull Collection<String> controlCategories) { - Objects.requireNonNull(controlCategories, "control categories must not be null"); - - List<String> newControlCategories = new ArrayList<>(controlCategories); - - synchronized (sRouterLock) { - mShouldUpdateRoutes = true; - - // invoke callbacks due to control categories change - handleControlCategoriesChangedLocked(newControlCategories); - if (mClient != null) { - try { - mMediaRouterService.setControlCategories(mClient, mControlCategories); - } catch (RemoteException ex) { - Log.e(TAG, "Unable to set control categories.", ex); - } - } - } + private void updateDiscoveryRequestLocked() { + mDiscoveryRequest = new RouteDiscoveryRequest.Builder( + mRouteCallbackRecords.stream().map(record -> record.mRequest).collect( + Collectors.toList())).build(); } /** @@ -287,8 +261,8 @@ public class MediaRouter2 { * known to the media router. * Please note that the list can be changed before callbacks are invoked. * - * @return the list of routes that support at least one of the control categories set by - * the application + * @return the list of routes that contains at least one of the route types in discovery + * requests registered by the application */ @NonNull public List<MediaRoute2Info> getRoutes() { @@ -298,7 +272,7 @@ public class MediaRouter2 { List<MediaRoute2Info> filteredRoutes = new ArrayList<>(); for (MediaRoute2Info route : mRoutes.values()) { - if (route.supportsControlCategories(mControlCategories)) { + if (route.containsRouteTypes(mDiscoveryRequest.getRouteTypes())) { filteredRoutes.add(route); } } @@ -350,26 +324,25 @@ public class MediaRouter2 { * Requests the media route provider service to create a session with the given route. * * @param route the route you want to create a session with. - * @param controlCategory the control category of the session. Should not be empty + * @param routeType the route type of the session. Should not be empty * * @see SessionCallback#onSessionCreated * @see SessionCallback#onSessionCreationFailed */ @NonNull public void requestCreateSession(@NonNull MediaRoute2Info route, - @NonNull String controlCategory) { + @NonNull String routeType) { Objects.requireNonNull(route, "route must not be null"); - if (TextUtils.isEmpty(controlCategory)) { - throw new IllegalArgumentException("controlCategory must not be empty"); + if (TextUtils.isEmpty(routeType)) { + throw new IllegalArgumentException("routeType must not be empty"); } // TODO: Check the given route exists - // TODO: Check the route supports the given controlCategory + // TODO: Check the route supports the given routeType final int requestId; requestId = mSessionCreationRequestCnt.getAndIncrement(); - SessionCreationRequest request = new SessionCreationRequest( - requestId, route, controlCategory); + SessionCreationRequest request = new SessionCreationRequest(requestId, route, routeType); mSessionCreationRequests.add(request); Client2 client; @@ -378,8 +351,7 @@ public class MediaRouter2 { } if (client != null) { try { - mMediaRouterService.requestCreateSession( - client, route, controlCategory, requestId); + mMediaRouterService.requestCreateSession(client, route, routeType, requestId); } catch (RemoteException ex) { Log.e(TAG, "Unable to request to create session.", ex); mHandler.sendMessage(obtainMessage(MediaRouter2::createControllerOnHandler, @@ -461,36 +433,6 @@ public class MediaRouter2 { } } - private void handleControlCategoriesChangedLocked(List<String> newControlCategories) { - List<MediaRoute2Info> addedRoutes = new ArrayList<>(); - List<MediaRoute2Info> removedRoutes = new ArrayList<>(); - - List<String> prevControlCategories = mControlCategories; - mControlCategories = newControlCategories; - - for (MediaRoute2Info route : mRoutes.values()) { - boolean preSupported = route.supportsControlCategories(prevControlCategories); - boolean postSupported = route.supportsControlCategories(newControlCategories); - if (preSupported == postSupported) { - continue; - } - if (preSupported) { - removedRoutes.add(route); - } else { - addedRoutes.add(route); - } - } - - if (removedRoutes.size() > 0) { - mHandler.sendMessage(obtainMessage(MediaRouter2::notifyRoutesRemoved, - MediaRouter2.this, removedRoutes)); - } - if (addedRoutes.size() > 0) { - mHandler.sendMessage(obtainMessage(MediaRouter2::notifyRoutesAdded, - MediaRouter2.this, addedRoutes)); - } - } - void addRoutesOnHandler(List<MediaRoute2Info> routes) { // TODO: When onRoutesAdded is first called, // 1) clear mRoutes before adding the routes @@ -499,8 +441,8 @@ public class MediaRouter2 { List<MediaRoute2Info> addedRoutes = new ArrayList<>(); synchronized (sRouterLock) { for (MediaRoute2Info route : routes) { - mRoutes.put(route.getUniqueId(), route); - if (route.supportsControlCategories(mControlCategories)) { + mRoutes.put(route.getId(), route); + if (route.containsRouteTypes(mDiscoveryRequest.getRouteTypes())) { addedRoutes.add(route); } } @@ -515,8 +457,8 @@ public class MediaRouter2 { List<MediaRoute2Info> removedRoutes = new ArrayList<>(); synchronized (sRouterLock) { for (MediaRoute2Info route : routes) { - mRoutes.remove(route.getUniqueId()); - if (route.supportsControlCategories(mControlCategories)) { + mRoutes.remove(route.getId()); + if (route.containsRouteTypes(mDiscoveryRequest.getRouteTypes())) { removedRoutes.add(route); } } @@ -531,8 +473,8 @@ public class MediaRouter2 { List<MediaRoute2Info> changedRoutes = new ArrayList<>(); synchronized (sRouterLock) { for (MediaRoute2Info route : routes) { - mRoutes.put(route.getUniqueId(), route); - if (route.supportsControlCategories(mControlCategories)) { + mRoutes.put(route.getId(), route); + if (route.containsRouteTypes(mDiscoveryRequest.getRouteTypes())) { changedRoutes.add(route); } } @@ -562,27 +504,27 @@ public class MediaRouter2 { mSessionCreationRequests.remove(matchingRequest); MediaRoute2Info requestedRoute = matchingRequest.mRoute; - String requestedControlCategory = matchingRequest.mControlCategory; + String requestedRouteType = matchingRequest.mRouteType; if (sessionInfo == null) { // TODO: We may need to distinguish between failure and rejection. // One way can be introducing 'reason'. - notifySessionCreationFailed(requestedRoute, requestedControlCategory); + notifySessionCreationFailed(requestedRoute, requestedRouteType); return; - } else if (!TextUtils.equals(requestedControlCategory, - sessionInfo.getControlCategory())) { - Log.w(TAG, "The session has different control category from what we requested. " - + "(requested=" + requestedControlCategory - + ", actual=" + sessionInfo.getControlCategory() + } else if (!TextUtils.equals(requestedRouteType, + sessionInfo.getRouteType())) { + Log.w(TAG, "The session has different route type from what we requested. " + + "(requested=" + requestedRouteType + + ", actual=" + sessionInfo.getRouteType() + ")"); - notifySessionCreationFailed(requestedRoute, requestedControlCategory); + notifySessionCreationFailed(requestedRoute, requestedRouteType); return; } else if (!sessionInfo.getSelectedRoutes().contains(requestedRoute.getId())) { Log.w(TAG, "The session does not contain the requested route. " + "(requestedRouteId=" + requestedRoute.getId() + ", actualRoutes=" + sessionInfo.getSelectedRoutes() + ")"); - notifySessionCreationFailed(requestedRoute, requestedControlCategory); + notifySessionCreationFailed(requestedRoute, requestedRouteType); return; } else if (!TextUtils.equals(requestedRoute.getProviderId(), sessionInfo.getProviderId())) { @@ -590,7 +532,7 @@ public class MediaRouter2 { + "(requested route's providerId=" + requestedRoute.getProviderId() + ", actual providerId=" + sessionInfo.getProviderId() + ")"); - notifySessionCreationFailed(requestedRoute, requestedControlCategory); + notifySessionCreationFailed(requestedRoute, requestedRouteType); return; } } @@ -598,7 +540,7 @@ public class MediaRouter2 { if (sessionInfo != null) { RouteSessionController controller = new RouteSessionController(sessionInfo); synchronized (sRouterLock) { - mSessionControllers.put(controller.getUniqueSessionId(), controller); + mSessionControllers.put(controller.getSessionId(), controller); } notifySessionCreated(controller); } @@ -612,12 +554,12 @@ public class MediaRouter2 { RouteSessionController matchingController; synchronized (sRouterLock) { - matchingController = mSessionControllers.get(sessionInfo.getUniqueSessionId()); + matchingController = mSessionControllers.get(sessionInfo.getId()); } if (matchingController == null) { Log.w(TAG, "changeSessionInfoOnHandler: Matching controller not found. uniqueSessionId=" - + sessionInfo.getUniqueSessionId()); + + sessionInfo.getId()); return; } @@ -638,7 +580,7 @@ public class MediaRouter2 { return; } - final String uniqueSessionId = sessionInfo.getUniqueSessionId(); + final String uniqueSessionId = sessionInfo.getId(); RouteSessionController matchingController; synchronized (sRouterLock) { matchingController = mSessionControllers.get(uniqueSessionId); @@ -647,7 +589,7 @@ public class MediaRouter2 { if (matchingController == null) { if (DEBUG) { Log.d(TAG, "releaseControllerOnHandler: Matching controller not found. " - + "uniqueSessionId=" + sessionInfo.getUniqueSessionId()); + + "uniqueSessionId=" + sessionInfo.getId()); } return; } @@ -666,24 +608,41 @@ public class MediaRouter2 { notifyControllerReleased(matchingController); } + private List<MediaRoute2Info> filterRoutes(List<MediaRoute2Info> routes, + RouteDiscoveryRequest discoveryRequest) { + return routes.stream() + .filter( + route -> route.containsRouteTypes(discoveryRequest.getRouteTypes())) + .collect(Collectors.toList()); + } + private void notifyRoutesAdded(List<MediaRoute2Info> routes) { for (RouteCallbackRecord record: mRouteCallbackRecords) { - record.mExecutor.execute( - () -> record.mRouteCallback.onRoutesAdded(routes)); + List<MediaRoute2Info> filteredRoutes = filterRoutes(routes, record.mRequest); + if (!filteredRoutes.isEmpty()) { + record.mExecutor.execute( + () -> record.mRouteCallback.onRoutesAdded(filteredRoutes)); + } } } private void notifyRoutesRemoved(List<MediaRoute2Info> routes) { for (RouteCallbackRecord record: mRouteCallbackRecords) { - record.mExecutor.execute( - () -> record.mRouteCallback.onRoutesRemoved(routes)); + List<MediaRoute2Info> filteredRoutes = filterRoutes(routes, record.mRequest); + if (!filteredRoutes.isEmpty()) { + record.mExecutor.execute( + () -> record.mRouteCallback.onRoutesRemoved(filteredRoutes)); + } } } private void notifyRoutesChanged(List<MediaRoute2Info> routes) { for (RouteCallbackRecord record: mRouteCallbackRecords) { - record.mExecutor.execute( - () -> record.mRouteCallback.onRoutesChanged(routes)); + List<MediaRoute2Info> filteredRoutes = filterRoutes(routes, record.mRequest); + if (!filteredRoutes.isEmpty()) { + record.mExecutor.execute( + () -> record.mRouteCallback.onRoutesChanged(filteredRoutes)); + } } } @@ -694,10 +653,10 @@ public class MediaRouter2 { } } - private void notifySessionCreationFailed(MediaRoute2Info route, String controlCategory) { + private void notifySessionCreationFailed(MediaRoute2Info route, String routeType) { for (SessionCallbackRecord record: mSessionCallbackRecords) { record.mExecutor.execute( - () -> record.mSessionCallback.onSessionCreationFailed(route, controlCategory)); + () -> record.mSessionCallback.onSessionCreationFailed(route, routeType)); } } @@ -764,10 +723,10 @@ public class MediaRouter2 { * Called when the session creation request failed. * * @param requestedRoute the route info which was used for the request - * @param requestedControlCategory the control category which was used for the request + * @param requestedRouteType the route type which was used for the request */ public void onSessionCreationFailed(@NonNull MediaRoute2Info requestedRoute, - @NonNull String requestedControlCategory) {} + @NonNull String requestedRouteType) {} /** * Called when the session info has changed. @@ -822,30 +781,19 @@ public class MediaRouter2 { /** * @return the ID of the session */ - public int getSessionId() { + public String getSessionId() { synchronized (mControllerLock) { - return mSessionInfo.getSessionId(); + return mSessionInfo.getId(); } } /** - * @return the unique ID of the session - * @hide + * @return the type of routes that the session includes. */ @NonNull - public String getUniqueSessionId() { + public String getRouteType() { synchronized (mControllerLock) { - return mSessionInfo.getUniqueSessionId(); - } - } - - /** - * @return the category of routes that the session includes. - */ - @NonNull - public String getControlCategory() { - synchronized (mControllerLock) { - return mSessionInfo.getControlCategory(); + return mSessionInfo.getRouteType(); } } @@ -935,13 +883,13 @@ public class MediaRouter2 { } List<MediaRoute2Info> selectedRoutes = getSelectedRoutes(); - if (checkRouteListContainsRouteId(selectedRoutes, route.getUniqueId())) { + if (checkRouteListContainsRouteId(selectedRoutes, route.getId())) { Log.w(TAG, "Ignoring selecting a route that is already selected. route=" + route); return; } List<MediaRoute2Info> selectableRoutes = getSelectableRoutes(); - if (!checkRouteListContainsRouteId(selectableRoutes, route.getUniqueId())) { + if (!checkRouteListContainsRouteId(selectableRoutes, route.getId())) { Log.w(TAG, "Ignoring selecting a non-selectable route=" + route); return; } @@ -952,7 +900,7 @@ public class MediaRouter2 { } if (client != null) { try { - mMediaRouterService.selectRoute(client, getUniqueSessionId(), route); + mMediaRouterService.selectRoute(client, getSessionId(), route); } catch (RemoteException ex) { Log.e(TAG, "Unable to select route for session.", ex); } @@ -982,13 +930,13 @@ public class MediaRouter2 { } List<MediaRoute2Info> selectedRoutes = getSelectedRoutes(); - if (!checkRouteListContainsRouteId(selectedRoutes, route.getUniqueId())) { + if (!checkRouteListContainsRouteId(selectedRoutes, route.getId())) { Log.w(TAG, "Ignoring deselecting a route that is not selected. route=" + route); return; } List<MediaRoute2Info> deselectableRoutes = getDeselectableRoutes(); - if (!checkRouteListContainsRouteId(deselectableRoutes, route.getUniqueId())) { + if (!checkRouteListContainsRouteId(deselectableRoutes, route.getId())) { Log.w(TAG, "Ignoring deselecting a non-deselectable route=" + route); return; } @@ -999,7 +947,7 @@ public class MediaRouter2 { } if (client != null) { try { - mMediaRouterService.deselectRoute(client, getUniqueSessionId(), route); + mMediaRouterService.deselectRoute(client, getSessionId(), route); } catch (RemoteException ex) { Log.e(TAG, "Unable to remove route from session.", ex); } @@ -1029,14 +977,14 @@ public class MediaRouter2 { } List<MediaRoute2Info> selectedRoutes = getSelectedRoutes(); - if (checkRouteListContainsRouteId(selectedRoutes, route.getUniqueId())) { + if (checkRouteListContainsRouteId(selectedRoutes, route.getId())) { Log.w(TAG, "Ignoring transferring to a route that is already added. route=" + route); return; } List<MediaRoute2Info> transferrableRoutes = getTransferrableRoutes(); - if (!checkRouteListContainsRouteId(transferrableRoutes, route.getUniqueId())) { + if (!checkRouteListContainsRouteId(transferrableRoutes, route.getId())) { Log.w(TAG, "Ignoring transferring to a non-transferrable route=" + route); return; } @@ -1047,7 +995,7 @@ public class MediaRouter2 { } if (client != null) { try { - mMediaRouterService.transferToRoute(client, getUniqueSessionId(), route); + mMediaRouterService.transferToRoute(client, getSessionId(), route); } catch (RemoteException ex) { Log.e(TAG, "Unable to transfer to route for session.", ex); } @@ -1072,20 +1020,24 @@ public class MediaRouter2 { Client2 client; synchronized (sRouterLock) { - mSessionControllers.remove(getUniqueSessionId(), this); + mSessionControllers.remove(getSessionId(), this); client = mClient; } if (client != null) { try { - mMediaRouterService.releaseSession(client, getUniqueSessionId()); + mMediaRouterService.releaseSession(client, getSessionId()); } catch (RemoteException ex) { Log.e(TAG, "Unable to notify of controller release", ex); } } } + /** + * TODO: Change this to package private. (Hidden for debugging purposes) + * @hide + */ @NonNull - RouteSessionInfo getRouteSessionInfo() { + public RouteSessionInfo getRouteSessionInfo() { synchronized (mControllerLock) { return mSessionInfo; } @@ -1100,11 +1052,12 @@ public class MediaRouter2 { // TODO: This method uses two locks (mLock outside, sLock inside). // Check if there is any possiblity of deadlock. private List<MediaRoute2Info> getRoutesWithIdsLocked(List<String> routeIds) { + List<MediaRoute2Info> routes = new ArrayList<>(); synchronized (sRouterLock) { + // TODO: Maybe able to change using Collection.stream()? for (String routeId : routeIds) { - MediaRoute2Info route = mRoutes.get( - MediaRoute2Info.toUniqueId(mSessionInfo.mProviderId, routeId)); + MediaRoute2Info route = mRoutes.get(routeId); if (route != null) { routes.add(route); } @@ -1117,13 +1070,13 @@ public class MediaRouter2 { final class RouteCallbackRecord { public final Executor mExecutor; public final RouteCallback mRouteCallback; - public final int mFlags; + public final RouteDiscoveryRequest mRequest; RouteCallbackRecord(@Nullable Executor executor, @NonNull RouteCallback routeCallback, - int flags) { + @Nullable RouteDiscoveryRequest request) { mRouteCallback = routeCallback; mExecutor = executor; - mFlags = flags; + mRequest = request; } @Override @@ -1172,13 +1125,13 @@ public class MediaRouter2 { final class SessionCreationRequest { public final MediaRoute2Info mRoute; - public final String mControlCategory; + public final String mRouteType; public final int mRequestId; SessionCreationRequest(int requestId, @NonNull MediaRoute2Info route, - @NonNull String controlCategory) { + @NonNull String routeType) { mRoute = route; - mControlCategory = controlCategory; + mRouteType = routeType; mRequestId = requestId; } } diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java index 2e68e42e714f..1e6ec51442d6 100644 --- a/media/java/android/media/MediaRouter2Manager.java +++ b/media/java/android/media/MediaRouter2Manager.java @@ -65,7 +65,7 @@ public class MediaRouter2Manager { @GuardedBy("mRoutesLock") private final Map<String, MediaRoute2Info> mRoutes = new HashMap<>(); @NonNull - final ConcurrentMap<String, List<String>> mControlCategoryMap = new ConcurrentHashMap<>(); + final ConcurrentMap<String, List<String>> mRouteTypeMap = new ConcurrentHashMap<>(); private AtomicInteger mNextRequestId = new AtomicInteger(1); @@ -144,7 +144,7 @@ public class MediaRouter2Manager { } //TODO: clear mRoutes? mClient = null; - mControlCategoryMap.clear(); + mRouteTypeMap.clear(); } } } @@ -160,14 +160,14 @@ public class MediaRouter2Manager { public List<MediaRoute2Info> getAvailableRoutes(@NonNull String packageName) { Objects.requireNonNull(packageName, "packageName must not be null"); - List<String> controlCategories = mControlCategoryMap.get(packageName); - if (controlCategories == null) { + List<String> routeTypes = mRouteTypeMap.get(packageName); + if (routeTypes == null) { return Collections.emptyList(); } List<MediaRoute2Info> routes = new ArrayList<>(); synchronized (mRoutesLock) { for (MediaRoute2Info route : mRoutes.values()) { - if (route.supportsControlCategories(controlCategories)) { + if (route.containsRouteTypes(routeTypes)) { routes.add(route); } } @@ -295,7 +295,7 @@ public class MediaRouter2Manager { void addRoutesOnHandler(List<MediaRoute2Info> routes) { synchronized (mRoutesLock) { for (MediaRoute2Info route : routes) { - mRoutes.put(route.getUniqueId(), route); + mRoutes.put(route.getId(), route); } } if (routes.size() > 0) { @@ -306,7 +306,7 @@ public class MediaRouter2Manager { void removeRoutesOnHandler(List<MediaRoute2Info> routes) { synchronized (mRoutesLock) { for (MediaRoute2Info route : routes) { - mRoutes.remove(route.getUniqueId()); + mRoutes.remove(route.getId()); } } if (routes.size() > 0) { @@ -317,7 +317,7 @@ public class MediaRouter2Manager { void changeRoutesOnHandler(List<MediaRoute2Info> routes) { synchronized (mRoutesLock) { for (MediaRoute2Info route : routes) { - mRoutes.put(route.getUniqueId(), route); + mRoutes.put(route.getId(), route); } } if (routes.size() > 0) { @@ -352,15 +352,15 @@ public class MediaRouter2Manager { } } - void updateControlCategories(String packageName, List<String> categories) { - List<String> prevCategories = mControlCategoryMap.put(packageName, categories); - if ((prevCategories == null && categories.size() == 0) - || Objects.equals(categories, prevCategories)) { + void updateRouteTypes(String packageName, List<String> routeTypes) { + List<String> prevTypes = mRouteTypeMap.put(packageName, routeTypes); + if ((prevTypes == null && routeTypes.size() == 0) + || Objects.equals(routeTypes, prevTypes)) { return; } for (CallbackRecord record : mCallbackRecords) { record.mExecutor.execute( - () -> record.mCallback.onControlCategoriesChanged(packageName, categories)); + () -> record.mCallback.onControlCategoriesChanged(packageName, routeTypes)); } } @@ -398,13 +398,13 @@ public class MediaRouter2Manager { /** - * Called when the control categories of an app is changed. + * Called when the route types of an app is changed. * * @param packageName the package name of the application - * @param controlCategories the list of control categories set by an application. + * @param routeTypes the list of route types set by an application. */ public void onControlCategoriesChanged(@NonNull String packageName, - @NonNull List<String> controlCategories) {} + @NonNull List<String> routeTypes) {} } final class CallbackRecord { @@ -440,10 +440,9 @@ public class MediaRouter2Manager { MediaRouter2Manager.this, packageName, route)); } - @Override - public void notifyControlCategoriesChanged(String packageName, List<String> categories) { - mHandler.sendMessage(obtainMessage(MediaRouter2Manager::updateControlCategories, - MediaRouter2Manager.this, packageName, categories)); + public void notifyRouteTypesChanged(String packageName, List<String> routeTypes) { + mHandler.sendMessage(obtainMessage(MediaRouter2Manager::updateRouteTypes, + MediaRouter2Manager.this, packageName, routeTypes)); } @Override diff --git a/media/java/android/media/MediaRouter2Utils.java b/media/java/android/media/MediaRouter2Utils.java new file mode 100644 index 000000000000..49045828dbe8 --- /dev/null +++ b/media/java/android/media/MediaRouter2Utils.java @@ -0,0 +1,100 @@ +/* + * 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 android.media; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.text.TextUtils; +import android.util.Log; + +/** + * @hide + */ +public class MediaRouter2Utils { + + static final String TAG = "MR2Utils"; + static final String SEPARATOR = ":"; + + /** + * @hide + */ + @NonNull + public static String toUniqueId(@NonNull String providerId, @NonNull String id) { + if (TextUtils.isEmpty(providerId)) { + Log.w(TAG, "toUniqueId: providerId shouldn't be empty"); + return null; + } + if (TextUtils.isEmpty(id)) { + Log.w(TAG, "toUniqueId: id shouldn't be null"); + return null; + } + + return providerId + SEPARATOR + id; + } + + /** + * Gets provider ID from unique ID. + * If the corresponding provider ID could not be generated, it will return null. + * + * @hide + */ + @Nullable + public static String getProviderId(@NonNull String uniqueId) { + if (TextUtils.isEmpty(uniqueId)) { + Log.w(TAG, "getProviderId: uniqueId shouldn't be empty"); + return null; + } + + int firstIndexOfSeparator = uniqueId.indexOf(SEPARATOR); + if (firstIndexOfSeparator == -1) { + return null; + } + + String providerId = uniqueId.substring(0, firstIndexOfSeparator); + if (TextUtils.isEmpty(providerId)) { + return null; + } + + return providerId; + } + + /** + * Gets the original ID (i.e. non-unique route/session ID) from unique ID. + * If the corresponding ID could not be generated, it will return null. + * + * @hide + */ + @Nullable + public static String getOriginalId(@NonNull String uniqueId) { + if (TextUtils.isEmpty(uniqueId)) { + Log.w(TAG, "getOriginalId: uniqueId shouldn't be empty"); + return null; + } + + int firstIndexOfSeparator = uniqueId.indexOf(SEPARATOR); + if (firstIndexOfSeparator == -1 || firstIndexOfSeparator + 1 >= uniqueId.length()) { + return null; + } + + String providerId = uniqueId.substring(firstIndexOfSeparator + 1); + if (TextUtils.isEmpty(providerId)) { + return null; + } + + return providerId; + } +} diff --git a/media/java/android/media/MediaScannerConnection.java b/media/java/android/media/MediaScannerConnection.java index 40e90731f2a2..05fa511fc81a 100644 --- a/media/java/android/media/MediaScannerConnection.java +++ b/media/java/android/media/MediaScannerConnection.java @@ -16,7 +16,7 @@ package android.media; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.ContentProviderClient; import android.content.ContentResolver; diff --git a/media/java/android/media/RouteDiscoveryRequest.aidl b/media/java/android/media/RouteDiscoveryRequest.aidl new file mode 100644 index 000000000000..744f6569325d --- /dev/null +++ b/media/java/android/media/RouteDiscoveryRequest.aidl @@ -0,0 +1,19 @@ +/* + * 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.media; + +parcelable RouteDiscoveryRequest; diff --git a/media/java/android/media/RouteDiscoveryRequest.java b/media/java/android/media/RouteDiscoveryRequest.java new file mode 100644 index 000000000000..88b31fb30ffc --- /dev/null +++ b/media/java/android/media/RouteDiscoveryRequest.java @@ -0,0 +1,200 @@ +/* + * 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 android.media; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + + +/** + * @hide + */ +public final class RouteDiscoveryRequest implements Parcelable { + @NonNull + public static final Creator<RouteDiscoveryRequest> CREATOR = + new Creator<RouteDiscoveryRequest>() { + @Override + public RouteDiscoveryRequest createFromParcel(Parcel in) { + return new RouteDiscoveryRequest(in); + } + + @Override + public RouteDiscoveryRequest[] newArray(int size) { + return new RouteDiscoveryRequest[size]; + } + }; + + @NonNull + private final List<String> mRouteTypes; + private final boolean mActiveScan; + @Nullable + private final Bundle mExtras; + + /** + * @hide + */ + public static final RouteDiscoveryRequest EMPTY = + new Builder(Collections.emptyList(), false).build(); + + RouteDiscoveryRequest(@NonNull Builder builder) { + mRouteTypes = builder.mRouteTypes; + mActiveScan = builder.mActiveScan; + mExtras = builder.mExtras; + } + + RouteDiscoveryRequest(@NonNull Parcel in) { + mRouteTypes = in.createStringArrayList(); + mActiveScan = in.readBoolean(); + mExtras = in.readBundle(); + } + + @NonNull + public List<String> getRouteTypes() { + return mRouteTypes; + } + + public boolean isActiveScan() { + return mActiveScan; + } + + /** + * @hide + */ + public Bundle getExtras() { + return mExtras; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeStringList(mRouteTypes); + dest.writeBoolean(mActiveScan); + dest.writeBundle(mExtras); + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder() + .append("RouteDiscoveryRequest{ ") + .append("routeTypes={") + .append(String.join(", ", mRouteTypes)) + .append("}") + .append(", activeScan=") + .append(mActiveScan) + .append(" }"); + + return result.toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof RouteDiscoveryRequest)) { + return false; + } + RouteDiscoveryRequest other = (RouteDiscoveryRequest) o; + return Objects.equals(mRouteTypes, other.mRouteTypes) + && mActiveScan == other.mActiveScan; + } + + /** + * Builder for {@link RouteDiscoveryRequest}. + */ + public static final class Builder { + List<String> mRouteTypes; + boolean mActiveScan; + Bundle mExtras; + + public Builder(@NonNull List<String> routeTypes, boolean activeScan) { + mRouteTypes = new ArrayList<>( + Objects.requireNonNull(routeTypes, "routeTypes must not be null")); + mActiveScan = activeScan; + } + + public Builder(@NonNull RouteDiscoveryRequest request) { + Objects.requireNonNull(request, "request must not be null"); + + mRouteTypes = request.getRouteTypes(); + mActiveScan = request.isActiveScan(); + mExtras = request.getExtras(); + } + + /** + * A constructor to combine all of the requests into a single request. + * It ignores extras of requests. + */ + Builder(@NonNull Collection<RouteDiscoveryRequest> requests) { + Set<String> routeTypeSet = new HashSet<>(); + mActiveScan = false; + for (RouteDiscoveryRequest request : requests) { + routeTypeSet.addAll(request.mRouteTypes); + mActiveScan |= request.mActiveScan; + } + mRouteTypes = new ArrayList<>(routeTypeSet); + } + + /** + * Sets route types to discover. + */ + public Builder setRouteTypes(@NonNull List<String> routeTypes) { + mRouteTypes = new ArrayList<>( + Objects.requireNonNull(routeTypes, "routeTypes must not be null")); + return this; + } + + /** + * Sets if active scanning should be performed. + */ + public Builder setActiveScan(boolean activeScan) { + mActiveScan = activeScan; + return this; + } + + /** + * Sets the extras of the route. + * @hide + */ + public Builder setExtras(@Nullable Bundle extras) { + mExtras = extras; + return this; + } + + /** + * Builds the {@link RouteDiscoveryRequest}. + */ + public RouteDiscoveryRequest build() { + return new RouteDiscoveryRequest(this); + } + } +} diff --git a/media/java/android/media/RouteSessionInfo.java b/media/java/android/media/RouteSessionInfo.java index 2d7bc24ae7a2..5330630ef3a9 100644 --- a/media/java/android/media/RouteSessionInfo.java +++ b/media/java/android/media/RouteSessionInfo.java @@ -22,6 +22,7 @@ import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; +import android.util.Log; import java.util.ArrayList; import java.util.Collections; @@ -33,6 +34,7 @@ import java.util.Objects; * @hide */ public class RouteSessionInfo implements Parcelable { + @NonNull public static final Creator<RouteSessionInfo> CREATOR = new Creator<RouteSessionInfo>() { @@ -46,9 +48,11 @@ public class RouteSessionInfo implements Parcelable { } }; - final int mSessionId; - final String mPackageName; - final String mControlCategory; + public static final String TAG = "RouteSessionInfo"; + + final String mId; + final String mClientPackageName; + final String mRouteType; @Nullable final String mProviderId; final List<String> mSelectedRoutes; @@ -61,15 +65,19 @@ public class RouteSessionInfo implements Parcelable { RouteSessionInfo(@NonNull Builder builder) { Objects.requireNonNull(builder, "builder must not be null."); - mSessionId = builder.mSessionId; - mPackageName = builder.mPackageName; - mControlCategory = builder.mControlCategory; + mId = builder.mId; + mClientPackageName = builder.mClientPackageName; + mRouteType = builder.mRouteType; mProviderId = builder.mProviderId; - mSelectedRoutes = Collections.unmodifiableList(builder.mSelectedRoutes); - mSelectableRoutes = Collections.unmodifiableList(builder.mSelectableRoutes); - mDeselectableRoutes = Collections.unmodifiableList(builder.mDeselectableRoutes); - mTransferrableRoutes = Collections.unmodifiableList(builder.mTransferrableRoutes); + mSelectedRoutes = Collections.unmodifiableList( + convertToUniqueRouteIds(builder.mSelectedRoutes)); + mSelectableRoutes = Collections.unmodifiableList( + convertToUniqueRouteIds(builder.mSelectableRoutes)); + mDeselectableRoutes = Collections.unmodifiableList( + convertToUniqueRouteIds(builder.mDeselectableRoutes)); + mTransferrableRoutes = Collections.unmodifiableList( + convertToUniqueRouteIds(builder.mTransferrableRoutes)); mControlHints = builder.mControlHints; } @@ -77,9 +85,9 @@ public class RouteSessionInfo implements Parcelable { RouteSessionInfo(@NonNull Parcel src) { Objects.requireNonNull(src, "src must not be null."); - mSessionId = src.readInt(); - mPackageName = ensureString(src.readString()); - mControlCategory = ensureString(src.readString()); + mId = ensureString(src.readString()); + mClientPackageName = ensureString(src.readString()); + mRouteType = ensureString(src.readString()); mProviderId = src.readString(); mSelectedRoutes = ensureList(src.createStringArrayList()); @@ -105,82 +113,59 @@ public class RouteSessionInfo implements Parcelable { } /** - * Gets non-unique session id (int) from unique session id (string). - * If the corresponding session id could not be generated, it will return null. - * @hide + * Returns whether the session info is valid or not + * + * TODO in this CL: Remove this method. */ - @Nullable - public static Integer getSessionId(@NonNull String uniqueSessionId) { - int lastIndexOfSeparator = uniqueSessionId.lastIndexOf("/"); - if (lastIndexOfSeparator == -1 || lastIndexOfSeparator + 1 >= uniqueSessionId.length()) { - return null; - } - - String integerString = uniqueSessionId.substring(lastIndexOfSeparator + 1); - if (TextUtils.isEmpty(integerString)) { - return null; - } - - try { - return Integer.parseInt(integerString); - } catch (NumberFormatException ex) { - return null; - } + public boolean isValid() { + return !TextUtils.isEmpty(mId) + && !TextUtils.isEmpty(mClientPackageName) + && !TextUtils.isEmpty(mRouteType) + && mSelectedRoutes.size() > 0; } /** - * Gets provider ID (string) from unique session id (string). - * If the corresponding provider ID could not be generated, it will return null. - * @hide + * Gets the id of the session. The sessions which are given by {@link MediaRouter2} will have + * unique IDs. + * <p> + * In order to ensure uniqueness in {@link MediaRouter2} side, the value of this method + * can be different from what was set in {@link MediaRoute2ProviderService}. * - * TODO: This logic seems error-prone. Consider to use long uniqueId. + * @see Builder#Builder(String, String, String) */ - @Nullable - public static String getProviderId(@NonNull String uniqueSessionId) { - int lastIndexOfSeparator = uniqueSessionId.lastIndexOf("/"); - if (lastIndexOfSeparator == -1) { - return null; - } - - String result = uniqueSessionId.substring(0, lastIndexOfSeparator); - if (TextUtils.isEmpty(result)) { - return null; + @NonNull + public String getId() { + if (mProviderId != null) { + return MediaRouter2Utils.toUniqueId(mProviderId, mId); + } else { + return mId; } - return result; } /** - * Returns whether the session info is valid or not - */ - public boolean isValid() { - return !TextUtils.isEmpty(mPackageName) - && !TextUtils.isEmpty(mControlCategory) - && mSelectedRoutes.size() > 0; - } - - /** - * Gets the id of the session + * Gets the original id set by {@link Builder#Builder(String, String, String)}. + * @hide */ @NonNull - public int getSessionId() { - return mSessionId; + public String getOriginalId() { + return mId; } /** * Gets the client package name of the session */ @NonNull - public String getPackageName() { - return mPackageName; + public String getClientPackageName() { + return mClientPackageName; } /** - * Gets the control category of the session. - * Routes that don't support the category can't be added to the session. + * Gets the route type of the session. + * Routes that don't have the type can't be added to the session. */ @NonNull - public String getControlCategory() { - return mControlCategory; + public String getRouteType() { + return mRouteType; } /** @@ -193,19 +178,6 @@ public class RouteSessionInfo implements Parcelable { } /** - * Gets the unique id of the session. - * @hide - */ - @NonNull - public String getUniqueSessionId() { - StringBuilder sessionIdBuilder = new StringBuilder() - .append(mProviderId) - .append("/") - .append(mSessionId); - return sessionIdBuilder.toString(); - } - - /** * Gets the list of ids of selected routes for the session. It shouldn't be empty. */ @NonNull @@ -252,9 +224,9 @@ public class RouteSessionInfo implements Parcelable { @Override public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeInt(mSessionId); - dest.writeString(mPackageName); - dest.writeString(mControlCategory); + dest.writeString(mId); + dest.writeString(mClientPackageName); + dest.writeString(mRouteType); dest.writeString(mProviderId); dest.writeStringList(mSelectedRoutes); dest.writeStringList(mSelectableRoutes); @@ -267,8 +239,8 @@ public class RouteSessionInfo implements Parcelable { public String toString() { StringBuilder result = new StringBuilder() .append("RouteSessionInfo{ ") - .append("sessionId=").append(mSessionId) - .append(", controlCategory=").append(mControlCategory) + .append("sessionId=").append(mId) + .append(", routeType=").append(mRouteType) .append(", selectedRoutes={") .append(String.join(",", mSelectedRoutes)) .append("}") @@ -285,13 +257,31 @@ public class RouteSessionInfo implements Parcelable { return result.toString(); } + private List<String> convertToUniqueRouteIds(@NonNull List<String> routeIds) { + if (routeIds == null) { + Log.w(TAG, "routeIds is null. Returning an empty list"); + return Collections.emptyList(); + } + + // mProviderId can be null if not set. Return the original list for this case. + if (mProviderId == null) { + return routeIds; + } + + List<String> result = new ArrayList<>(); + for (String routeId : routeIds) { + result.add(MediaRouter2Utils.toUniqueId(mProviderId, routeId)); + } + return result; + } + /** * Builder class for {@link RouteSessionInfo}. */ public static final class Builder { - final String mPackageName; - final int mSessionId; - final String mControlCategory; + final String mId; + final String mClientPackageName; + final String mRouteType; String mProviderId; final List<String> mSelectedRoutes; final List<String> mSelectableRoutes; @@ -299,23 +289,43 @@ public class RouteSessionInfo implements Parcelable { final List<String> mTransferrableRoutes; Bundle mControlHints; - public Builder(int sessionId, @NonNull String packageName, - @NonNull String controlCategory) { - mSessionId = sessionId; - mPackageName = Objects.requireNonNull(packageName, "packageName must not be null"); - mControlCategory = Objects.requireNonNull(controlCategory, - "controlCategory must not be null"); - + /** + * Constructor for builder to create {@link RouteSessionInfo}. + * <p> + * In order to ensure ID uniqueness in {@link MediaRouter2} side, the value of + * {@link RouteSessionInfo#getId()} can be different from what was set in + * {@link MediaRoute2ProviderService}. + * </p> + * + * @see MediaRoute2Info#getId() + */ + public Builder(@NonNull String id, @NonNull String clientPackageName, + @NonNull String routeType) { + if (TextUtils.isEmpty(id)) { + throw new IllegalArgumentException("id must not be empty"); + } + mId = id; + mClientPackageName = Objects.requireNonNull( + clientPackageName, "clientPackageName must not be null"); + mRouteType = Objects.requireNonNull(routeType, "routeType must not be null"); mSelectedRoutes = new ArrayList<>(); mSelectableRoutes = new ArrayList<>(); mDeselectableRoutes = new ArrayList<>(); mTransferrableRoutes = new ArrayList<>(); } - public Builder(RouteSessionInfo sessionInfo) { - mSessionId = sessionInfo.mSessionId; - mPackageName = sessionInfo.mPackageName; - mControlCategory = sessionInfo.mControlCategory; + /** + * Constructor for builder to create {@link RouteSessionInfo} with + * existing {@link RouteSessionInfo} instance. + * + * @param sessionInfo the existing instance to copy data from. + */ + public Builder(@NonNull RouteSessionInfo sessionInfo) { + Objects.requireNonNull(sessionInfo, "sessionInfo must not be null"); + + mId = sessionInfo.mId; + mClientPackageName = sessionInfo.mClientPackageName; + mRouteType = sessionInfo.mRouteType; mProviderId = sessionInfo.mProviderId; mSelectedRoutes = new ArrayList<>(sessionInfo.mSelectedRoutes); @@ -327,10 +337,17 @@ public class RouteSessionInfo implements Parcelable { } /** - * Sets the provider id of the session. + * Sets the provider ID of the session. + * Also, calling this method will make all type of route IDs be unique by adding + * {@code providerId:} as a prefix. So do NOT call this method twice on same instance. + * + * @hide */ @NonNull - public Builder setProviderId(String providerId) { + public Builder setProviderId(@NonNull String providerId) { + if (TextUtils.isEmpty(providerId)) { + throw new IllegalArgumentException("providerId must not be empty"); + } mProviderId = providerId; return this; } diff --git a/media/java/android/media/audiofx/AudioEffect.java b/media/java/android/media/audiofx/AudioEffect.java index 4a40c627ecb0..cad5aa6aaa3c 100644 --- a/media/java/android/media/audiofx/AudioEffect.java +++ b/media/java/android/media/audiofx/AudioEffect.java @@ -16,11 +16,18 @@ package android.media.audiofx; +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.annotation.TestApi; import android.app.ActivityThread; import android.compat.annotation.UnsupportedAppUsage; +import android.media.AudioDeviceAddress; +import android.media.AudioDeviceInfo; +import android.media.AudioSystem; import android.os.Build; import android.os.Handler; import android.os.Looper; @@ -448,12 +455,46 @@ public class AudioEffect { public AudioEffect(UUID type, UUID uuid, int priority, int audioSession) throws IllegalArgumentException, UnsupportedOperationException, RuntimeException { + this(type, uuid, priority, audioSession, null); + } + + /** + * Constructs an AudioEffect attached to a particular audio device. + * The device does not have to be attached when the effect is created. The effect will only + * be applied when the device is actually selected for playback or capture. + * @param uuid unique identifier of a particular effect implementation. + * @param device the device the effect must be attached to. + * + * @throws java.lang.IllegalArgumentException + * @throws java.lang.UnsupportedOperationException + * @throws java.lang.RuntimeException + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + public AudioEffect(@NonNull UUID uuid, @NonNull AudioDeviceAddress device) { + this(EFFECT_TYPE_NULL, Objects.requireNonNull(uuid), 0, -2, Objects.requireNonNull(device)); + } + + private AudioEffect(UUID type, UUID uuid, int priority, + int audioSession, @Nullable AudioDeviceAddress device) + throws IllegalArgumentException, UnsupportedOperationException, + RuntimeException { int[] id = new int[1]; Descriptor[] desc = new Descriptor[1]; + + int deviceType = AudioSystem.DEVICE_NONE; + String deviceAddress = ""; + if (device != null) { + deviceType = AudioDeviceInfo.convertDeviceTypeToInternalDevice(device.getType()); + deviceAddress = device.getAddress(); + } + // native initialization int initResult = native_setup(new WeakReference<AudioEffect>(this), - type.toString(), uuid.toString(), priority, audioSession, id, - desc, ActivityThread.currentOpPackageName()); + type.toString(), uuid.toString(), priority, audioSession, + deviceType, deviceAddress, + id, desc, ActivityThread.currentOpPackageName()); if (initResult != SUCCESS && initResult != ALREADY_EXISTS) { Log.e(TAG, "Error code " + initResult + " when initializing AudioEffect."); @@ -1293,7 +1334,8 @@ public class AudioEffect { private static native final void native_init(); private native final int native_setup(Object audioeffect_this, String type, - String uuid, int priority, int audioSession, int[] id, Object[] desc, + String uuid, int priority, int audioSession, + int deviceType, String deviceAddress, int[] id, Object[] desc, String opPackageName); private native final void native_finalize(); diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java index aff725734ee1..aece39d78694 100644 --- a/media/java/android/media/session/MediaSessionManager.java +++ b/media/java/android/media/session/MediaSessionManager.java @@ -940,16 +940,15 @@ public final class MediaSessionManager { /** * Called when a media key event is dispatched through the media session service. The * session token can be {@link null} if the framework has sent the media key event to the - * media button receiver to revive the media app's playback. - * - * the session is dead when , but the framework sent + * media button receiver to revive the media app's playback after the corresponding session + * is released. * * @param event Dispatched media key event. * @param packageName Package * @param sessionToken The media session's token. Can be {@code null}. */ default void onMediaKeyEventDispatched(@NonNull KeyEvent event, @NonNull String packageName, - @NonNull MediaSession.Token sessionToken) { } + @Nullable MediaSession.Token sessionToken) { } } /** diff --git a/media/java/android/media/soundtrigger/SoundTriggerDetector.java b/media/java/android/media/soundtrigger/SoundTriggerDetector.java index 56e5566df29c..77596a5de815 100644 --- a/media/java/android/media/soundtrigger/SoundTriggerDetector.java +++ b/media/java/android/media/soundtrigger/SoundTriggerDetector.java @@ -22,7 +22,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.hardware.soundtrigger.IRecognitionStatusCallback; import android.hardware.soundtrigger.SoundTrigger; import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig; diff --git a/media/java/android/media/soundtrigger/SoundTriggerManager.java b/media/java/android/media/soundtrigger/SoundTriggerManager.java index 61b3e76e7cee..938ffcd5f731 100644 --- a/media/java/android/media/soundtrigger/SoundTriggerManager.java +++ b/media/java/android/media/soundtrigger/SoundTriggerManager.java @@ -23,7 +23,7 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; import android.hardware.soundtrigger.ModelParams; @@ -428,8 +428,7 @@ public final class SoundTriggerManager { */ @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int setParameter(@Nullable UUID soundModelId, - @ModelParams int modelParam, int value) - throws UnsupportedOperationException, IllegalArgumentException { + @ModelParams int modelParam, int value) { try { return mSoundTriggerService.setParameter(new ParcelUuid(soundModelId), modelParam, value); @@ -449,15 +448,10 @@ public final class SoundTriggerManager { * @param soundModelId UUID of model to get parameter * @param modelParam {@link ModelParams} * @return value of parameter - * @throws UnsupportedOperationException if hal or model do not support this API. - * {@link SoundTriggerManager#queryParameter} should be checked first. - * @throws IllegalArgumentException if invalid model handle or parameter is passed. - * {@link SoundTriggerManager#queryParameter} should be checked first. */ @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int getParameter(@NonNull UUID soundModelId, - @ModelParams int modelParam) - throws UnsupportedOperationException, IllegalArgumentException { + @ModelParams int modelParam) { try { return mSoundTriggerService.getParameter(new ParcelUuid(soundModelId), modelParam); } catch (RemoteException e) { @@ -479,8 +473,7 @@ public final class SoundTriggerManager { public ModelParamRange queryParameter(@Nullable UUID soundModelId, @ModelParams int modelParam) { try { - return mSoundTriggerService.queryParameter(new ParcelUuid(soundModelId), - modelParam); + return mSoundTriggerService.queryParameter(new ParcelUuid(soundModelId), modelParam); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl b/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl index 1a3b40261a62..909f1a00006f 100644 --- a/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl +++ b/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl @@ -30,6 +30,14 @@ parcelable SoundTriggerModuleProperties { * Unique implementation ID. The UUID must change with each version of the engine implementation */ String uuid; + /** + * String naming the architecture used for running the supported models. + * (eg. a platform running models on a DSP could implement this string to convey the DSP + * architecture used) + * This property is supported for soundtrigger HAL v2.3 and above. + * If running a previous version, the string will be empty. + */ + String supportedModelArch; /** Maximum number of concurrent sound models loaded */ int maxSoundModels; /** Maximum number of key phrases */ diff --git a/media/java/android/media/soundtrigger_middleware/Status.aidl b/media/java/android/media/soundtrigger_middleware/Status.aidl index d8f9d8f7e891..5d082e272295 100644 --- a/media/java/android/media/soundtrigger_middleware/Status.aidl +++ b/media/java/android/media/soundtrigger_middleware/Status.aidl @@ -26,4 +26,6 @@ enum Status { RESOURCE_CONTENTION = 1, /** Operation is not supported in this implementation. This is a permanent condition. */ OPERATION_NOT_SUPPORTED = 2, + /** Temporary lack of permission. */ + TEMPORARY_PERMISSION_DENIED = 3, } diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java index 1b9cac0c8c99..377b2bc19c6b 100644 --- a/media/java/android/media/tv/TvInputInfo.java +++ b/media/java/android/media/tv/TvInputInfo.java @@ -20,7 +20,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.StringRes; import android.annotation.SystemApi; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; import android.content.Intent; diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java index 5c11ed9bb7b4..7fbb3376d5fb 100755 --- a/media/java/android/media/tv/TvInputService.java +++ b/media/java/android/media/tv/TvInputService.java @@ -22,9 +22,9 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.annotation.SystemApi; -import android.annotation.UnsupportedAppUsage; import android.app.ActivityManager; import android.app.Service; +import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.Intent; import android.graphics.PixelFormat; diff --git a/media/java/android/media/tv/TvTrackInfo.java b/media/java/android/media/tv/TvTrackInfo.java index ea00d6eff12e..4318a0ae7d06 100644 --- a/media/java/android/media/tv/TvTrackInfo.java +++ b/media/java/android/media/tv/TvTrackInfo.java @@ -61,6 +61,9 @@ public final class TvTrackInfo implements Parcelable { private final boolean mEncrypted; private final int mAudioChannelCount; private final int mAudioSampleRate; + private final boolean mAudioDescription; + private final boolean mHardOfHearing; + private final boolean mSpokenSubtitle; private final int mVideoWidth; private final int mVideoHeight; private final float mVideoFrameRate; @@ -70,9 +73,10 @@ public final class TvTrackInfo implements Parcelable { private final Bundle mExtra; private TvTrackInfo(int type, String id, String language, CharSequence description, - boolean encrypted, int audioChannelCount, int audioSampleRate, int videoWidth, - int videoHeight, float videoFrameRate, float videoPixelAspectRatio, - byte videoActiveFormatDescription, Bundle extra) { + boolean encrypted, int audioChannelCount, int audioSampleRate, boolean audioDescription, + boolean hardOfHearing, boolean spokenSubtitle, int videoWidth, int videoHeight, + float videoFrameRate, float videoPixelAspectRatio, byte videoActiveFormatDescription, + Bundle extra) { mType = type; mId = id; mLanguage = language; @@ -80,6 +84,9 @@ public final class TvTrackInfo implements Parcelable { mEncrypted = encrypted; mAudioChannelCount = audioChannelCount; mAudioSampleRate = audioSampleRate; + mAudioDescription = audioDescription; + mHardOfHearing = hardOfHearing; + mSpokenSubtitle = spokenSubtitle; mVideoWidth = videoWidth; mVideoHeight = videoHeight; mVideoFrameRate = videoFrameRate; @@ -96,6 +103,9 @@ public final class TvTrackInfo implements Parcelable { mEncrypted = in.readInt() != 0; mAudioChannelCount = in.readInt(); mAudioSampleRate = in.readInt(); + mAudioDescription = in.readInt() != 0; + mHardOfHearing = in.readInt() != 0; + mSpokenSubtitle = in.readInt() != 0; mVideoWidth = in.readInt(); mVideoHeight = in.readInt(); mVideoFrameRate = in.readFloat(); @@ -172,6 +182,56 @@ public final class TvTrackInfo implements Parcelable { } /** + * Returns {@code true} if the track is an audio description intended for people with visual + * impairment, {@code false} otherwise. Valid only for {@link #TYPE_AUDIO} tracks. + * + * <p>For example of broadcast, audio description information may be referred to broadcast + * standard (e.g. ISO 639 Language Descriptor of ISO/IEC 13818-1, Supplementary Audio Language + * Descriptor, AC-3 Descriptor, Enhanced AC-3 Descriptor, AAC Descriptor of ETSI EN 300 468). + * + * @throws IllegalStateException if not called on an audio track + */ + public boolean isAudioDescription() { + if (mType != TYPE_AUDIO) { + throw new IllegalStateException("Not an audio track"); + } + return mAudioDescription; + } + + /** + * Returns {@code true} if the track is intended for people with hearing impairment, {@code + * false} otherwise. Valid only for {@link #TYPE_AUDIO} and {@link #TYPE_SUBTITLE} tracks. + * + * <p>For example of broadcast, hard of hearing information may be referred to broadcast + * standard (e.g. ISO 639 Language Descriptor of ISO/IEC 13818-1, Supplementary Audio Language + * Descriptor, AC-3 Descriptor, Enhanced AC-3 Descriptor, AAC Descriptor of ETSI EN 300 468). + * + * @throws IllegalStateException if not called on an audio track or a subtitle track + */ + public boolean isHardOfHearing() { + if (mType != TYPE_AUDIO && mType != TYPE_SUBTITLE) { + throw new IllegalStateException("Not an audio or a subtitle track"); + } + return mHardOfHearing; + } + + /** + * Returns {@code true} if the track is a spoken subtitle for people with visual impairment, + * {@code false} otherwise. Valid only for {@link #TYPE_AUDIO} tracks. + * + * <p>For example of broadcast, spoken subtitle information may be referred to broadcast + * standard (e.g. Supplementary Audio Language Descriptor of ETSI EN 300 468). + * + * @throws IllegalStateException if not called on an audio track + */ + public boolean isSpokenSubtitle() { + if (mType != TYPE_AUDIO) { + throw new IllegalStateException("Not an audio track"); + } + return mSpokenSubtitle; + } + + /** * Returns the width of the video, in the unit of pixels. Valid only for {@link #TYPE_VIDEO} * tracks. * @@ -266,6 +326,9 @@ public final class TvTrackInfo implements Parcelable { dest.writeInt(mEncrypted ? 1 : 0); dest.writeInt(mAudioChannelCount); dest.writeInt(mAudioSampleRate); + dest.writeInt(mAudioDescription ? 1 : 0); + dest.writeInt(mHardOfHearing ? 1 : 0); + dest.writeInt(mSpokenSubtitle ? 1 : 0); dest.writeInt(mVideoWidth); dest.writeInt(mVideoHeight); dest.writeFloat(mVideoFrameRate); @@ -289,6 +352,7 @@ public final class TvTrackInfo implements Parcelable { if (!TextUtils.equals(mId, obj.mId) || mType != obj.mType || !TextUtils.equals(mLanguage, obj.mLanguage) || !TextUtils.equals(mDescription, obj.mDescription) + || mEncrypted != obj.mEncrypted || !Objects.equals(mExtra, obj.mExtra)) { return false; } @@ -296,7 +360,10 @@ public final class TvTrackInfo implements Parcelable { switch (mType) { case TYPE_AUDIO: return mAudioChannelCount == obj.mAudioChannelCount - && mAudioSampleRate == obj.mAudioSampleRate; + && mAudioSampleRate == obj.mAudioSampleRate + && mAudioDescription == obj.mAudioDescription + && mHardOfHearing == obj.mHardOfHearing + && mSpokenSubtitle == obj.mSpokenSubtitle; case TYPE_VIDEO: return mVideoWidth == obj.mVideoWidth @@ -304,6 +371,9 @@ public final class TvTrackInfo implements Parcelable { && mVideoFrameRate == obj.mVideoFrameRate && mVideoPixelAspectRatio == obj.mVideoPixelAspectRatio && mVideoActiveFormatDescription == obj.mVideoActiveFormatDescription; + + case TYPE_SUBTITLE: + return mHardOfHearing == obj.mHardOfHearing; } return true; @@ -338,6 +408,9 @@ public final class TvTrackInfo implements Parcelable { private boolean mEncrypted; private int mAudioChannelCount; private int mAudioSampleRate; + private boolean mAudioDescription; + private boolean mHardOfHearing; + private boolean mSpokenSubtitle; private int mVideoWidth; private int mVideoHeight; private float mVideoFrameRate; @@ -430,6 +503,67 @@ public final class TvTrackInfo implements Parcelable { } /** + * Sets the audio description attribute of the audio. Valid only for {@link #TYPE_AUDIO} + * tracks. + * + * <p>For example of broadcast, audio description information may be referred to broadcast + * standard (e.g. ISO 639 Language Descriptor of ISO/IEC 13818-1, Supplementary Audio + * Language Descriptor, AC-3 Descriptor, Enhanced AC-3 Descriptor, AAC Descriptor of ETSI EN + * 300 468). + * + * @param audioDescription The audio description attribute of the audio. + * @throws IllegalStateException if not called on an audio track + */ + @NonNull + public Builder setAudioDescription(boolean audioDescription) { + if (mType != TYPE_AUDIO) { + throw new IllegalStateException("Not an audio track"); + } + mAudioDescription = audioDescription; + return this; + } + + /** + * Sets the hard of hearing attribute of the track. Valid only for {@link #TYPE_AUDIO} and + * {@link #TYPE_SUBTITLE} tracks. + * + * <p>For example of broadcast, hard of hearing information may be referred to broadcast + * standard (e.g. ISO 639 Language Descriptor of ISO/IEC 13818-1, Supplementary Audio + * Language Descriptor, AC-3 Descriptor, Enhanced AC-3 Descriptor, AAC Descriptor of ETSI EN + * 300 468). + * + * @param hardOfHearing The hard of hearing attribute of the track. + * @throws IllegalStateException if not called on an audio track or a subtitle track + */ + @NonNull + public Builder setHardOfHearing(boolean hardOfHearing) { + if (mType != TYPE_AUDIO && mType != TYPE_SUBTITLE) { + throw new IllegalStateException("Not an audio track or a subtitle track"); + } + mHardOfHearing = hardOfHearing; + return this; + } + + /** + * Sets the spoken subtitle attribute of the audio. Valid only for {@link #TYPE_AUDIO} + * tracks. + * + * <p>For example of broadcast, spoken subtitle information may be referred to broadcast + * standard (e.g. Supplementary Audio Language Descriptor of ETSI EN 300 468). + * + * @param spokenSubtitle The spoken subtitle attribute of the audio. + * @throws IllegalStateException if not called on an audio track + */ + @NonNull + public Builder setSpokenSubtitle(boolean spokenSubtitle) { + if (mType != TYPE_AUDIO) { + throw new IllegalStateException("Not an audio track"); + } + mSpokenSubtitle = spokenSubtitle; + return this; + } + + /** * Sets the width of the video, in the unit of pixels. Valid only for {@link #TYPE_VIDEO} * tracks. * @@ -531,8 +665,9 @@ public final class TvTrackInfo implements Parcelable { */ public TvTrackInfo build() { return new TvTrackInfo(mType, mId, mLanguage, mDescription, mEncrypted, - mAudioChannelCount, mAudioSampleRate, mVideoWidth, mVideoHeight, - mVideoFrameRate, mVideoPixelAspectRatio, mVideoActiveFormatDescription, mExtra); + mAudioChannelCount, mAudioSampleRate, mAudioDescription, mHardOfHearing, + mSpokenSubtitle, mVideoWidth, mVideoHeight, mVideoFrameRate, + mVideoPixelAspectRatio, mVideoActiveFormatDescription, mExtra); } } } diff --git a/media/java/android/media/tv/tuner/DemuxCapabilities.java b/media/java/android/media/tv/tuner/DemuxCapabilities.java new file mode 100644 index 000000000000..bda166ec9d14 --- /dev/null +++ b/media/java/android/media/tv/tuner/DemuxCapabilities.java @@ -0,0 +1,114 @@ +/* + * 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.media.tv.tuner; + +/** + * Capabilities info for Demux. + * @hide + */ +public class DemuxCapabilities { + private final int mNumDemux; + private final int mNumRecord; + private final int mNumPlayback; + private final int mNumTsFilter; + private final int mNumSectionFilter; + private final int mNumAudioFilter; + private final int mNumVideoFilter; + private final int mNumPesFilter; + private final int mNumPcrFilter; + private final int mNumBytesInSectionFilter; + private final int mFilterCaps; + private final int[] mLinkCaps; + + DemuxCapabilities(int numDemux, int numRecord, int numPlayback, int numTsFilter, + int numSectionFilter, int numAudioFilter, int numVideoFilter, int numPesFilter, + int numPcrFilter, int numBytesInSectionFilter, int filterCaps, int[] linkCaps) { + mNumDemux = numDemux; + mNumRecord = numRecord; + mNumPlayback = numPlayback; + mNumTsFilter = numTsFilter; + mNumSectionFilter = numSectionFilter; + mNumAudioFilter = numAudioFilter; + mNumVideoFilter = numVideoFilter; + mNumPesFilter = numPesFilter; + mNumPcrFilter = numPcrFilter; + mNumBytesInSectionFilter = numBytesInSectionFilter; + mFilterCaps = filterCaps; + mLinkCaps = linkCaps; + } + + /** Gets total number of demuxes. */ + public int getNumDemux() { + return mNumDemux; + } + /** Gets max number of recordings at a time. */ + public int getNumRecord() { + return mNumRecord; + } + /** Gets max number of playbacks at a time. */ + public int getNumPlayback() { + return mNumPlayback; + } + /** Gets number of TS filters. */ + public int getNumTsFilter() { + return mNumTsFilter; + } + /** Gets number of section filters. */ + public int getNumSectionFilter() { + return mNumSectionFilter; + } + /** Gets number of audio filters. */ + public int getNumAudioFilter() { + return mNumAudioFilter; + } + /** Gets number of video filters. */ + public int getNumVideoFilter() { + return mNumVideoFilter; + } + /** Gets number of PES filters. */ + public int getNumPesFilter() { + return mNumPesFilter; + } + /** Gets number of PCR filters. */ + public int getNumPcrFilter() { + return mNumPcrFilter; + } + /** Gets number of bytes in the mask of a section filter. */ + public int getNumBytesInSectionFilter() { + return mNumBytesInSectionFilter; + } + /** + * Gets filter capabilities in bit field. + * + * The bits of the returned value is corresponding to the types in + * {@link TunerConstants.FilterType}. + */ + public int getFilterCapabilities() { + return mFilterCaps; + } + + /** + * Gets link capabilities. + * + * The returned array contains the same elements as the number of types in + * {@link TunerConstants.FilterType}. + * The ith element represents the filter's capability as the source for the ith type + */ + public int[] getLinkCapabilities() { + return mLinkCaps; + } +} diff --git a/media/java/android/media/tv/tuner/Descrambler.java b/media/java/android/media/tv/tuner/Descrambler.java new file mode 100644 index 000000000000..f9f7a22c3de8 --- /dev/null +++ b/media/java/android/media/tv/tuner/Descrambler.java @@ -0,0 +1,103 @@ +/* + * 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.media.tv.tuner; + +import android.annotation.Nullable; +import android.media.tv.tuner.Tuner.Filter; +import android.media.tv.tuner.TunerConstants.DemuxPidType; + +/** + * This class is used to interact with descramblers. + * + * <p> Descrambler is a hardware component used to descramble data. + * + * <p> This class controls the TIS interaction with Tuner HAL. + * @hide + */ +public class Descrambler implements AutoCloseable { + private long mNativeContext; + + private native int nativeAddPid(int pidType, int pid, Filter filter); + private native int nativeRemovePid(int pidType, int pid, Filter filter); + private native int nativeSetKeyToken(byte[] keyToken); + private native int nativeClose(); + + private Descrambler() {} + + /** + * Add packets' PID to the descrambler for descrambling. + * + * The descrambler will start descrambling packets with this PID. Multiple PIDs can be added + * into one descrambler instance because descambling can happen simultaneously on packets + * from different PIDs. + * + * If the Descrambler previously contained a filter for the PID, the old filter is replaced + * by the specified filter. + * + * @param pidType the type of the PID. + * @param pid the PID of packets to start to be descrambled. + * @param filter an optional filter instance to identify upper stream. + * @return result status of the operation. + * + * @hide + */ + public int addPid(@DemuxPidType int pidType, int pid, @Nullable Filter filter) { + return nativeAddPid(pidType, pid, filter); + } + + /** + * Remove packets' PID from the descrambler + * + * The descrambler will stop descrambling packets with this PID. + * + * @param pidType the type of the PID. + * @param pid the PID of packets to stop to be descrambled. + * @param filter an optional filter instance to identify upper stream. + * @return result status of the operation. + * + * @hide + */ + public int removePid(@DemuxPidType int pidType, int pid, @Nullable Filter filter) { + return nativeRemovePid(pidType, pid, filter); + } + + /** + * Set a key token to link descrambler to a key slot + * + * A descrambler instance can have only one key slot to link, but a key slot can hold a few + * keys for different purposes. + * + * @param keyToken the token to be used to link the key slot. + * @return result status of the operation. + * + * @hide + */ + public int setKeyToken(byte[] keyToken) { + return nativeSetKeyToken(keyToken); + } + + /** + * Release the descrambler instance. + * + * @hide + */ + @Override + public void close() { + nativeClose(); + } + +} diff --git a/media/java/android/media/tv/tuner/Dvr.java b/media/java/android/media/tv/tuner/Dvr.java new file mode 100644 index 000000000000..0bfba8f9d4f3 --- /dev/null +++ b/media/java/android/media/tv/tuner/Dvr.java @@ -0,0 +1,152 @@ +/* + * 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.media.tv.tuner; + +import android.annotation.NonNull; +import android.media.tv.tuner.Tuner.DvrCallback; +import android.media.tv.tuner.Tuner.Filter; +import android.os.ParcelFileDescriptor; + +/** @hide */ +public class Dvr { + private long mNativeContext; + private DvrCallback mCallback; + + private native int nativeAttachFilter(Filter filter); + private native int nativeDetachFilter(Filter filter); + private native int nativeConfigureDvr(DvrSettings settings); + private native int nativeStartDvr(); + private native int nativeStopDvr(); + private native int nativeFlushDvr(); + private native int nativeClose(); + private native void nativeSetFileDescriptor(int fd); + private native int nativeRead(int size); + private native int nativeRead(byte[] bytes, int offset, int size); + private native int nativeWrite(int size); + private native int nativeWrite(byte[] bytes, int offset, int size); + + private Dvr() {} + + /** + * Attaches a filter to DVR interface for recording. + * + * @param filter the filter to be attached. + * @return result status of the operation. + */ + public int attachFilter(Filter filter) { + return nativeAttachFilter(filter); + } + + /** + * Detaches a filter from DVR interface. + * + * @param filter the filter to be detached. + * @return result status of the operation. + */ + public int detachFilter(Filter filter) { + return nativeDetachFilter(filter); + } + + /** + * Configures the DVR. + * + * @param settings the settings of the DVR interface. + * @return result status of the operation. + */ + public int configure(DvrSettings settings) { + return nativeConfigureDvr(settings); + } + + /** + * Starts DVR. + * + * Starts consuming playback data or producing data for recording. + * + * @return result status of the operation. + */ + public int start() { + return nativeStartDvr(); + } + + /** + * Stops DVR. + * + * Stops consuming playback data or producing data for recording. + * + * @return result status of the operation. + */ + public int stop() { + return nativeStopDvr(); + } + + /** + * Flushed DVR data. + * + * @return result status of the operation. + */ + public int flush() { + return nativeFlushDvr(); + } + + /** + * closes the DVR instance to release resources. + * + * @return result status of the operation. + */ + public int close() { + return nativeClose(); + } + + /** + * Sets file descriptor to read/write data. + */ + public void setFileDescriptor(ParcelFileDescriptor fd) { + nativeSetFileDescriptor(fd.getFd()); + } + + /** + * Reads data from the file for DVR playback. + */ + public int read(int size) { + return nativeRead(size); + } + + /** + * Reads data from the buffer for DVR playback. + */ + public int read(@NonNull byte[] bytes, int offset, int size) { + if (size + offset > bytes.length) { + throw new ArrayIndexOutOfBoundsException( + "Array length=" + bytes.length + ", offset=" + offset + ", size=" + size); + } + return nativeRead(bytes, offset, size); + } + + /** + * Writes recording data to file. + */ + public int write(int size) { + return nativeWrite(size); + } + + /** + * Writes recording data to buffer. + */ + public int write(@NonNull byte[] bytes, int offset, int size) { + return nativeWrite(bytes, offset, size); + } +} diff --git a/media/java/android/media/tv/tuner/Filter.java b/media/java/android/media/tv/tuner/Filter.java new file mode 100644 index 000000000000..db3b97afb1da --- /dev/null +++ b/media/java/android/media/tv/tuner/Filter.java @@ -0,0 +1,142 @@ +/* + * 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.media.tv.tuner; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.media.tv.tuner.Tuner.FilterCallback; +import android.media.tv.tuner.filter.FilterConfiguration; +import android.media.tv.tuner.filter.Settings; + +/** + * Tuner data filter. + * + * <p> This class is used to filter wanted data according to the filter's configuration. + * @hide + */ +public class Filter implements AutoCloseable { + private long mNativeContext; + private FilterCallback mCallback; + int mId; + + private native int nativeConfigureFilter( + int type, int subType, FilterConfiguration settings); + private native int nativeGetId(); + private native int nativeSetDataSource(Tuner.Filter source); + private native int nativeStartFilter(); + private native int nativeStopFilter(); + private native int nativeFlushFilter(); + private native int nativeRead(byte[] buffer, int offset, int size); + private native int nativeClose(); + + private Filter(int id) { + mId = id; + } + + private void onFilterStatus(int status) { + } + + /** + * Configures the filter. + * + * @param settings the settings of the filter. + * @return result status of the operation. + * @hide + */ + public int configure(FilterConfiguration settings) { + int subType = -1; + Settings s = settings.getSettings(); + if (s != null) { + subType = s.getType(); + } + return nativeConfigureFilter(settings.getType(), subType, settings); + } + + /** + * Gets the filter Id. + * + * @return the hardware resource Id for the filter. + * @hide + */ + public int getId() { + return nativeGetId(); + } + + /** + * Sets the filter's data source. + * + * A filter uses demux as data source by default. If the data was packetized + * by multiple protocols, multiple filters may need to work together to + * extract all protocols' header. Then a filter's data source can be output + * from another filter. + * + * @param source the filter instance which provides data input. Switch to + * use demux as data source if the filter instance is NULL. + * @return result status of the operation. + * @hide + */ + public int setDataSource(@Nullable Tuner.Filter source) { + return nativeSetDataSource(source); + } + + /** + * Starts the filter. + * + * @return result status of the operation. + * @hide + */ + public int start() { + return nativeStartFilter(); + } + + + /** + * Stops the filter. + * + * @return result status of the operation. + * @hide + */ + public int stop() { + return nativeStopFilter(); + } + + /** + * Flushes the filter. + * + * @return result status of the operation. + * @hide + */ + public int flush() { + return nativeFlushFilter(); + } + + /** @hide */ + public int read(@NonNull byte[] buffer, int offset, int size) { + size = Math.min(size, buffer.length - offset); + return nativeRead(buffer, offset, size); + } + + /** + * Release the Filter instance. + * + * @hide + */ + @Override + public void close() { + nativeClose(); + } +} diff --git a/media/java/android/media/tv/tuner/FilterSettings.java b/media/java/android/media/tv/tuner/FilterSettings.java deleted file mode 100644 index 4ee61a8feee8..000000000000 --- a/media/java/android/media/tv/tuner/FilterSettings.java +++ /dev/null @@ -1,329 +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.media.tv.tuner; - -import android.annotation.Nullable; -import android.media.tv.tuner.TunerConstants.FilterType; - -import java.util.List; - -/** - * Demux Filter settings. - * - * @hide - */ -public abstract class FilterSettings { - @Nullable - protected final Settings mSettings; - - protected FilterSettings(Settings settings) { - mSettings = settings; - } - - /** - * Gets filter settings type - */ - @FilterType - public abstract int getType(); - - public Settings getSettings() { - return mSettings; - } - - // TODO: more builders and getters - - /** - * Filter Settings for a TS filter. - */ - public static class TsFilterSettings extends FilterSettings { - private int mTpid; - - private TsFilterSettings(Settings settings, int tpid) { - super(settings); - mTpid = tpid; - } - - @Override - public int getType() { - return TunerConstants.FILTER_TYPE_TS; - } - - /** - * Creates a new builder. - */ - public static Builder newBuilder() { - return new Builder(); - } - - /** - * Builder for TsFilterSettings. - */ - public static class Builder { - private Settings mSettings; - private int mTpid; - - /** - * Sets settings. - */ - public Builder setSettings(Settings settings) { - mSettings = settings; - return this; - } - - /** - * Sets TPID. - */ - public Builder setTpid(int tpid) { - mTpid = tpid; - return this; - } - - /** - * Builds a TsFilterSettings instance. - */ - public TsFilterSettings build() { - return new TsFilterSettings(mSettings, mTpid); - } - } - } - - /** - * Filter Settings for a MMTP filter. - */ - public static class MmtpFilterSettings extends FilterSettings { - private int mMmtpPid; - - public MmtpFilterSettings(Settings settings) { - super(settings); - } - - @Override - public int getType() { - return TunerConstants.FILTER_TYPE_MMTP; - } - } - - - /** - * Filter Settings for a IP filter. - */ - public static class IpFilterSettings extends FilterSettings { - private byte[] mSrcIpAddress; - private byte[] mDstIpAddress; - private int mSrcPort; - private int mDstPort; - private boolean mPassthrough; - - public IpFilterSettings(Settings settings) { - super(settings); - } - - @Override - public int getType() { - return TunerConstants.FILTER_TYPE_IP; - } - } - - - /** - * Filter Settings for a TLV filter. - */ - public static class TlvFilterSettings extends FilterSettings { - private int mPacketType; - private boolean mIsCompressedIpPacket; - private boolean mPassthrough; - - public TlvFilterSettings(Settings settings) { - super(settings); - } - - @Override - public int getType() { - return TunerConstants.FILTER_TYPE_TLV; - } - } - - - /** - * Filter Settings for a ALP filter. - */ - public static class AlpFilterSettings extends FilterSettings { - private int mPacketType; - private int mLengthType; - - public AlpFilterSettings(Settings settings) { - super(settings); - } - - @Override - public int getType() { - return TunerConstants.FILTER_TYPE_ALP; - } - } - - - /** - * Settings for filters of different subtypes. - */ - public abstract static class Settings { - protected final int mType; - - protected Settings(int type) { - mType = type; - } - - /** - * Gets filter settings type. - * @return - */ - int getType() { - return mType; - } - } - - /** - * Filter Settings for Section data according to ISO/IEC 13818-1. - */ - public static class SectionSettings extends Settings { - - private SectionSettings(int mainType) { - super(TunerUtils.getFilterSubtype(mainType, TunerConstants.FILTER_SUBTYPE_SECTION)); - } - } - - /** - * Bits Settings for Section Filter. - */ - public static class SectionSettingsWithSectionBits extends SectionSettings { - private List<Byte> mFilter; - private List<Byte> mMask; - private List<Byte> mMode; - - private SectionSettingsWithSectionBits(int mainType) { - super(mainType); - } - } - - /** - * Table information for Section Filter. - */ - public static class SectionSettingsWithTableInfo extends SectionSettings { - private int mTableId; - private int mVersion; - - private SectionSettingsWithTableInfo(int mainType) { - super(mainType); - } - } - - /** - * Filter Settings for a PES Data. - */ - public static class PesSettings extends Settings { - private int mStreamId; - private boolean mIsRaw; - - private PesSettings(int mainType, int streamId, boolean isRaw) { - super(TunerUtils.getFilterSubtype(mainType, TunerConstants.FILTER_SUBTYPE_PES)); - mStreamId = streamId; - mIsRaw = isRaw; - } - - /** - * Creates a builder for PesSettings. - */ - public static Builder newBuilder(int mainType) { - return new Builder(mainType); - } - - /** - * Builder for PesSettings. - */ - public static class Builder { - private final int mMainType; - private int mStreamId; - private boolean mIsRaw; - - public Builder(int mainType) { - mMainType = mainType; - } - - /** - * Sets stream ID. - */ - public Builder setStreamId(int streamId) { - mStreamId = streamId; - return this; - } - - /** - * Sets whether it's raw. - * true if the filter send onFilterStatus instead of onFilterEvent. - */ - public Builder setIsRaw(boolean isRaw) { - mIsRaw = isRaw; - return this; - } - - /** - * Builds a PesSettings instance. - */ - public PesSettings build() { - return new PesSettings(mMainType, mStreamId, mIsRaw); - } - } - } - - /** - * Filter Settings for a Video and Audio. - */ - public static class AvSettings extends Settings { - private boolean mIsPassthrough; - - private AvSettings(int mainType, boolean isAudio) { - super(TunerUtils.getFilterSubtype( - mainType, - isAudio - ? TunerConstants.FILTER_SUBTYPE_AUDIO - : TunerConstants.FILTER_SUBTYPE_VIDEO)); - } - } - - /** - * Filter Settings for a Download. - */ - public static class DownloadSettings extends Settings { - private int mDownloadId; - - public DownloadSettings(int mainType) { - super(TunerUtils.getFilterSubtype(mainType, TunerConstants.FILTER_SUBTYPE_DOWNLOAD)); - } - } - - /** - * The Settings for the record in DVR. - */ - public static class RecordSettings extends Settings { - private int mIndexType; - private int mIndexMask; - - public RecordSettings(int mainType) { - super(TunerUtils.getFilterSubtype(mainType, TunerConstants.FILTER_SUBTYPE_RECORD)); - } - } - -} diff --git a/media/java/android/media/tv/tuner/FrontendCapabilities.java b/media/java/android/media/tv/tuner/FrontendCapabilities.java deleted file mode 100644 index fcfd7c8c8639..000000000000 --- a/media/java/android/media/tv/tuner/FrontendCapabilities.java +++ /dev/null @@ -1,291 +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.media.tv.tuner; - -/** - * Frontend Capabilities. - * @hide - */ -public class FrontendCapabilities { - /** Analog Capabilities. */ - public class Analog extends FrontendCapabilities { - private final int mTypeCap; - private final int mSifStandardCap; - - Analog(int typeCap, int sifStandardCap) { - mTypeCap = typeCap; - mSifStandardCap = sifStandardCap; - } - /** - * Gets type capability. - */ - public int getTypeCapability() { - return mTypeCap; - } - /** Gets SIF standard capability. */ - public int getSifStandardCapability() { - return mSifStandardCap; - } - } - - /** ATSC Capabilities. */ - public class Atsc extends FrontendCapabilities { - private final int mModulationCap; - - Atsc(int modulationCap) { - mModulationCap = modulationCap; - } - /** Gets modulation capability. */ - public int getModulationCapability() { - return mModulationCap; - } - } - - /** ATSC-3 Capabilities. */ - public class Atsc3 extends FrontendCapabilities { - private final int mBandwidthCap; - private final int mModulationCap; - private final int mTimeInterleaveModeCap; - private final int mCodeRateCap; - private final int mFecCap; - private final int mDemodOutputFormatCap; - - Atsc3(int bandwidthCap, int modulationCap, int timeInterleaveModeCap, int codeRateCap, - int fecCap, int demodOutputFormatCap) { - mBandwidthCap = bandwidthCap; - mModulationCap = modulationCap; - mTimeInterleaveModeCap = timeInterleaveModeCap; - mCodeRateCap = codeRateCap; - mFecCap = fecCap; - mDemodOutputFormatCap = demodOutputFormatCap; - } - - /** Gets bandwidth capability. */ - public int getBandwidthCapability() { - return mBandwidthCap; - } - /** Gets modulation capability. */ - public int getModulationCapability() { - return mModulationCap; - } - /** Gets time interleave mod capability. */ - public int getTimeInterleaveModeCapability() { - return mTimeInterleaveModeCap; - } - /** Gets code rate capability. */ - public int getCodeRateCapability() { - return mCodeRateCap; - } - /** Gets FEC capability. */ - public int getFecCapability() { - return mFecCap; - } - /** Gets demodulator output format capability. */ - public int getDemodOutputFormatCapability() { - return mDemodOutputFormatCap; - } - } - - /** DVBS Capabilities. */ - public class Dvbs extends FrontendCapabilities { - private final int mModulationCap; - private final long mInnerFecCap; - private final int mStandard; - - Dvbs(int modulationCap, long innerFecCap, int standard) { - mModulationCap = modulationCap; - mInnerFecCap = innerFecCap; - mStandard = standard; - } - - /** Gets modulation capability. */ - public int getModulationCapability() { - return mModulationCap; - } - /** Gets inner FEC capability. */ - public long getInnerFecCapability() { - return mInnerFecCap; - } - /** Gets DVBS standard capability. */ - public int getStandardCapability() { - return mStandard; - } - } - - /** DVBC Capabilities. */ - public class Dvbc extends FrontendCapabilities { - private final int mModulationCap; - private final int mFecCap; - private final int mAnnexCap; - - Dvbc(int modulationCap, int fecCap, int annexCap) { - mModulationCap = modulationCap; - mFecCap = fecCap; - mAnnexCap = annexCap; - } - - /** Gets modulation capability. */ - public int getModulationCapability() { - return mModulationCap; - } - /** Gets FEC capability. */ - public int getFecCapability() { - return mFecCap; - } - /** Gets annex capability. */ - public int getAnnexCapability() { - return mAnnexCap; - } - } - - /** DVBT Capabilities. */ - public class Dvbt extends FrontendCapabilities { - private final int mTransmissionModeCap; - private final int mBandwidthCap; - private final int mConstellationCap; - private final int mCoderateCap; - private final int mHierarchyCap; - private final int mGuardIntervalCap; - private final boolean mIsT2Supported; - private final boolean mIsMisoSupported; - - Dvbt(int transmissionModeCap, int bandwidthCap, int constellationCap, int coderateCap, - int hierarchyCap, int guardIntervalCap, boolean isT2Supported, - boolean isMisoSupported) { - mTransmissionModeCap = transmissionModeCap; - mBandwidthCap = bandwidthCap; - mConstellationCap = constellationCap; - mCoderateCap = coderateCap; - mHierarchyCap = hierarchyCap; - mGuardIntervalCap = guardIntervalCap; - mIsT2Supported = isT2Supported; - mIsMisoSupported = isMisoSupported; - } - - /** Gets transmission mode capability. */ - public int getTransmissionModeCapability() { - return mTransmissionModeCap; - } - /** Gets bandwidth capability. */ - public int getBandwidthCapability() { - return mBandwidthCap; - } - /** Gets constellation capability. */ - public int getConstellationCapability() { - return mConstellationCap; - } - /** Gets code rate capability. */ - public int getCodeRateCapability() { - return mCoderateCap; - } - /** Gets hierarchy capability. */ - public int getHierarchyCapability() { - return mHierarchyCap; - } - /** Gets guard interval capability. */ - public int getGuardIntervalCapability() { - return mGuardIntervalCap; - } - /** Returns whether T2 is supported. */ - public boolean getIsT2Supported() { - return mIsT2Supported; - } - /** Returns whether MISO is supported. */ - public boolean getIsMisoSupported() { - return mIsMisoSupported; - } - } - - /** ISDBS Capabilities. */ - public class Isdbs extends FrontendCapabilities { - private final int mModulationCap; - private final int mCoderateCap; - - Isdbs(int modulationCap, int coderateCap) { - mModulationCap = modulationCap; - mCoderateCap = coderateCap; - } - - /** Gets modulation capability. */ - public int getModulationCapability() { - return mModulationCap; - } - /** Gets code rate capability. */ - public int getCodeRateCapability() { - return mCoderateCap; - } - } - - /** ISDBS-3 Capabilities. */ - public class Isdbs3 extends FrontendCapabilities { - private final int mModulationCap; - private final int mCoderateCap; - - Isdbs3(int modulationCap, int coderateCap) { - mModulationCap = modulationCap; - mCoderateCap = coderateCap; - } - - /** Gets modulation capability. */ - public int getModulationCapability() { - return mModulationCap; - } - /** Gets code rate capability. */ - public int getCodeRateCapability() { - return mCoderateCap; - } - } - - /** ISDBC Capabilities. */ - public class Isdbc extends FrontendCapabilities { - private final int mModeCap; - private final int mBandwidthCap; - private final int mModulationCap; - private final int mCoderateCap; - private final int mGuardIntervalCap; - - Isdbc(int modeCap, int bandwidthCap, int modulationCap, int coderateCap, - int guardIntervalCap) { - mModeCap = modeCap; - mBandwidthCap = bandwidthCap; - mModulationCap = modulationCap; - mCoderateCap = coderateCap; - mGuardIntervalCap = guardIntervalCap; - } - - /** Gets mode capability. */ - public int getModeCapability() { - return mModeCap; - } - /** Gets bandwidth capability. */ - public int getBandwidthCapability() { - return mBandwidthCap; - } - /** Gets modulation capability. */ - public int getModulationCapability() { - return mModulationCap; - } - /** Gets code rate capability. */ - public int getCodeRateCapability() { - return mCoderateCap; - } - /** Gets guard interval capability. */ - public int getGuardIntervalCapability() { - return mGuardIntervalCap; - } - } -} diff --git a/media/java/android/media/tv/tuner/FrontendSettings.java b/media/java/android/media/tv/tuner/FrontendSettings.java index 531e21038bfc..e2e9910f53ee 100644 --- a/media/java/android/media/tv/tuner/FrontendSettings.java +++ b/media/java/android/media/tv/tuner/FrontendSettings.java @@ -19,8 +19,6 @@ package android.media.tv.tuner; import android.annotation.SystemApi; import android.media.tv.tuner.TunerConstants.FrontendSettingsType; -import java.util.List; - /** * Frontend settings for tune and scan operations. * @hide @@ -29,7 +27,8 @@ import java.util.List; public abstract class FrontendSettings { private final int mFrequency; - FrontendSettings(int frequency) { + /** @hide */ + public FrontendSettings(int frequency) { mFrequency = frequency; } @@ -48,282 +47,4 @@ public abstract class FrontendSettings { return mFrequency; } - // TODO: use hal constants for enum fields - // TODO: javaDoc - // TODO: add builders and getters for other settings type - - /** - * Frontend settings for analog. - * @hide - */ - public static class FrontendAnalogSettings extends FrontendSettings { - private int mAnalogType; - private int mSifStandard; - - @Override - public int getType() { - return TunerConstants.FRONTEND_TYPE_ANALOG; - } - - public int getAnalogType() { - return mAnalogType; - } - - public int getSifStandard() { - return mSifStandard; - } - - /** - * Creates a new builder object. - */ - public static Builder newBuilder() { - return new Builder(); - } - - private FrontendAnalogSettings(int frequency, int analogType, int sifStandard) { - super(frequency); - mAnalogType = analogType; - mSifStandard = sifStandard; - } - - /** - * Builder for FrontendAnalogSettings. - */ - public static class Builder { - private int mFrequency; - private int mAnalogType; - private int mSifStandard; - - private Builder() {} - - /** - * Sets frequency. - */ - public Builder setFrequency(int frequency) { - mFrequency = frequency; - return this; - } - - /** - * Sets analog type. - */ - public Builder setAnalogType(int analogType) { - mAnalogType = analogType; - return this; - } - - /** - * Sets sif standard. - */ - public Builder setSifStandard(int sifStandard) { - mSifStandard = sifStandard; - return this; - } - - /** - * Builds a FrontendAnalogSettings instance. - */ - public FrontendAnalogSettings build() { - return new FrontendAnalogSettings(mFrequency, mAnalogType, mSifStandard); - } - } - } - - /** - * Frontend settings for ATSC. - * @hide - */ - public static class FrontendAtscSettings extends FrontendSettings { - public int modulation; - - FrontendAtscSettings(int frequency) { - super(frequency); - } - - @Override - public int getType() { - return TunerConstants.FRONTEND_TYPE_ATSC; - } - } - - /** - * Frontend settings for ATSC-3. - * @hide - */ - public static class FrontendAtsc3Settings extends FrontendSettings { - public int bandwidth; - public byte demodOutputFormat; - public List<FrontendAtsc3PlpSettings> plpSettings; - - FrontendAtsc3Settings(int frequency) { - super(frequency); - } - - @Override - public int getType() { - return TunerConstants.FRONTEND_TYPE_ATSC3; - } - } - - /** - * Frontend settings for DVBS. - * @hide - */ - public static class FrontendDvbsSettings extends FrontendSettings { - public int modulation; - public FrontendDvbsCodeRate coderate; - public int symbolRate; - public int rolloff; - public int pilot; - public int inputStreamId; - public byte standard; - - FrontendDvbsSettings(int frequency) { - super(frequency); - } - - @Override - public int getType() { - return TunerConstants.FRONTEND_TYPE_DVBS; - } - } - - /** - * Frontend settings for DVBC. - * @hide - */ - public static class FrontendDvbcSettings extends FrontendSettings { - public int modulation; - public long fec; - public int symbolRate; - public int outerFec; - public byte annex; - public int spectralInversion; - - FrontendDvbcSettings(int frequency) { - super(frequency); - } - - @Override - public int getType() { - return TunerConstants.FRONTEND_TYPE_DVBC; - } - } - - /** - * Frontend settings for DVBT. - * @hide - */ - public static class FrontendDvbtSettings extends FrontendSettings { - public int transmissionMode; - public int bandwidth; - public int constellation; - public int hierarchy; - public int hpCoderate; - public int lpCoderate; - public int guardInterval; - public boolean isHighPriority; - public byte standard; - public boolean isMiso; - public int plpMode; - public byte plpId; - public byte plpGroupId; - - FrontendDvbtSettings(int frequency) { - super(frequency); - } - - @Override - public int getType() { - return TunerConstants.FRONTEND_TYPE_DVBT; - } - } - - /** - * Frontend settings for ISDBS. - * @hide - */ - public static class FrontendIsdbsSettings extends FrontendSettings { - public int streamId; - public int streamIdType; - public int modulation; - public int coderate; - public int symbolRate; - public int rolloff; - - FrontendIsdbsSettings(int frequency) { - super(frequency); - } - - @Override - public int getType() { - return TunerConstants.FRONTEND_TYPE_ISDBS; - } - } - - /** - * Frontend settings for ISDBS-3. - * @hide - */ - public static class FrontendIsdbs3Settings extends FrontendSettings { - public int streamId; - public int streamIdType; - public int modulation; - public int coderate; - public int symbolRate; - public int rolloff; - - FrontendIsdbs3Settings(int frequency) { - super(frequency); - } - - @Override - public int getType() { - return TunerConstants.FRONTEND_TYPE_ISDBS3; - } - } - - /** - * Frontend settings for ISDBT. - * @hide - */ - public static class FrontendIsdbtSettings extends FrontendSettings { - public int modulation; - public int bandwidth; - public int coderate; - public int guardInterval; - public int serviceAreaId; - - FrontendIsdbtSettings(int frequency) { - super(frequency); - } - - @Override - public int getType() { - return TunerConstants.FRONTEND_TYPE_ISDBT; - } - } - - /** - * PLP settings for ATSC-3. - * @hide - */ - public static class FrontendAtsc3PlpSettings { - public byte plpId; - public int modulation; - public int interleaveMode; - public int codeRate; - public int fec; - } - - /** - * Code rate for DVBS. - * @hide - */ - public static class FrontendDvbsCodeRate { - public long fec; - public boolean isLinear; - public boolean isShortFrames; - public int bitsPer1000Symbol; - } } diff --git a/media/java/android/media/tv/tuner/Lnb.java b/media/java/android/media/tv/tuner/Lnb.java new file mode 100644 index 000000000000..f181b49239d7 --- /dev/null +++ b/media/java/android/media/tv/tuner/Lnb.java @@ -0,0 +1,202 @@ +/* + * 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.media.tv.tuner; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.content.Context; +import android.hardware.tv.tuner.V1_0.Constants; +import android.media.tv.tuner.Tuner.LnbCallback; +import android.media.tv.tuner.TunerConstants.Result; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * LNB (low-noise block downconverter) for satellite tuner. + * + * A Tuner LNB (low-noise block downconverter) is used by satellite frontend to receive the + * microwave signal from the satellite, amplify it, and downconvert the frequency to a lower + * frequency. + * + * @hide + */ +public class Lnb implements AutoCloseable { + /** @hide */ + @IntDef({VOLTAGE_NONE, VOLTAGE_5V, VOLTAGE_11V, VOLTAGE_12V, VOLTAGE_13V, VOLTAGE_14V, + VOLTAGE_15V, VOLTAGE_18V, VOLTAGE_19V}) + @Retention(RetentionPolicy.SOURCE) + public @interface Voltage {} + + /** + * LNB power voltage not set. + */ + public static final int VOLTAGE_NONE = Constants.LnbVoltage.NONE; + /** + * LNB power voltage 5V. + */ + public static final int VOLTAGE_5V = Constants.LnbVoltage.VOLTAGE_5V; + /** + * LNB power voltage 11V. + */ + public static final int VOLTAGE_11V = Constants.LnbVoltage.VOLTAGE_11V; + /** + * LNB power voltage 12V. + */ + public static final int VOLTAGE_12V = Constants.LnbVoltage.VOLTAGE_12V; + /** + * LNB power voltage 13V. + */ + public static final int VOLTAGE_13V = Constants.LnbVoltage.VOLTAGE_13V; + /** + * LNB power voltage 14V. + */ + public static final int VOLTAGE_14V = Constants.LnbVoltage.VOLTAGE_14V; + /** + * LNB power voltage 15V. + */ + public static final int VOLTAGE_15V = Constants.LnbVoltage.VOLTAGE_15V; + /** + * LNB power voltage 18V. + */ + public static final int VOLTAGE_18V = Constants.LnbVoltage.VOLTAGE_18V; + /** + * LNB power voltage 19V. + */ + public static final int VOLTAGE_19V = Constants.LnbVoltage.VOLTAGE_19V; + + /** @hide */ + @IntDef({TONE_NONE, TONE_CONTINUOUS}) + @Retention(RetentionPolicy.SOURCE) + public @interface Tone {} + + /** + * LNB tone mode not set. + */ + public static final int TONE_NONE = Constants.LnbTone.NONE; + /** + * LNB continuous tone mode. + */ + public static final int TONE_CONTINUOUS = Constants.LnbTone.CONTINUOUS; + + /** @hide */ + @IntDef({POSITION_UNDEFINED, POSITION_A, POSITION_B}) + @Retention(RetentionPolicy.SOURCE) + public @interface Position {} + + /** + * LNB position is not defined. + */ + public static final int POSITION_UNDEFINED = Constants.LnbPosition.UNDEFINED; + /** + * Position A of two-band LNBs + */ + public static final int POSITION_A = Constants.LnbPosition.POSITION_A; + /** + * Position B of two-band LNBs + */ + public static final int POSITION_B = Constants.LnbPosition.POSITION_B; + + int mId; + LnbCallback mCallback; + Context mContext; + + private native int nativeSetVoltage(int voltage); + private native int nativeSetTone(int tone); + private native int nativeSetSatellitePosition(int position); + private native int nativeSendDiseqcMessage(byte[] message); + private native int nativeClose(); + + Lnb(int id) { + mId = id; + } + + /** @hide */ + public void setCallback(@Nullable LnbCallback callback) { + mCallback = callback; + if (mCallback == null) { + return; + } + } + + /** + * Sets the LNB's power voltage. + * + * @param voltage the power voltage constant the Lnb to use. + * @return result status of the operation. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) + @Result + public int setVoltage(@Voltage int voltage) { + TunerUtils.checkTunerPermission(mContext); + return nativeSetVoltage(voltage); + } + + /** + * Sets the LNB's tone mode. + * + * @param tone the tone mode the Lnb to use. + * @return result status of the operation. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) + @Result + public int setTone(@Tone int tone) { + TunerUtils.checkTunerPermission(mContext); + return nativeSetTone(tone); + } + + /** + * Selects the LNB's position. + * + * @param position the position the Lnb to use. + * @return result status of the operation. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) + @Result + public int setSatellitePosition(@Position int position) { + TunerUtils.checkTunerPermission(mContext); + return nativeSetSatellitePosition(position); + } + + /** + * Sends DiSEqC (Digital Satellite Equipment Control) message. + * + * The response message from the device comes back through callback onDiseqcMessage. + * + * @param message a byte array of data for DiSEqC message which is specified by EUTELSAT Bus + * Functional Specification Version 4.2. + * + * @return result status of the operation. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) + @Result + public int sendDiseqcMessage(@NonNull byte[] message) { + TunerUtils.checkTunerPermission(mContext); + return nativeSendDiseqcMessage(message); + } + + /** + * Releases the LNB instance. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) + public void close() { + TunerUtils.checkTunerPermission(mContext); + nativeClose(); + } +} diff --git a/media/java/android/media/tv/tuner/TimeFilter.java b/media/java/android/media/tv/tuner/TimeFilter.java new file mode 100644 index 000000000000..8bd0d26ce951 --- /dev/null +++ b/media/java/android/media/tv/tuner/TimeFilter.java @@ -0,0 +1,127 @@ +/* + * 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.media.tv.tuner; + +import android.annotation.Nullable; +import android.media.tv.tuner.TunerConstants.Result; + +/** + * A timer filter is used to filter data based on timestamps. + * + * <p> If the timestamp is set, data is discarded if its timestamp is smaller than the + * timestamp in this time filter. + * + * <p> The format of the timestamps is the same as PTS defined in ISO/IEC 13818-1:2019. The + * timestamps may or may not be related to PTS or DTS. + * + * @hide + */ +public class TimeFilter implements AutoCloseable { + private native int nativeSetTimestamp(long timestamp); + private native int nativeClearTimestamp(); + private native Long nativeGetTimestamp(); + private native Long nativeGetSourceTime(); + private native int nativeClose(); + + private boolean mEnable = false; + + /** + * Set timestamp for time based filter. + * + * It is used to set initial timestamp and enable time filtering. Once set, the time will be + * increased automatically like a clock. Contents are discarded if their timestamps are + * older than the time in the time filter. + * + * This method can be called more than once to reset the initial timestamp. + * + * @param timestamp initial timestamp for the time filter before it's increased. It's + * based on the 90KHz counter, and it's the same format as PTS (Presentation Time Stamp) + * defined in ISO/IEC 13818-1:2019. The timestamps may or may not be related to PTS or DTS. + * @return result status of the operation. + */ + @Result + public int setCurrentTimestamp(long timestamp) { + int res = nativeSetTimestamp(timestamp); + // TODO: use a constant for SUCCESS + if (res == 0) { + mEnable = true; + } + return res; + } + + /** + * Clear the timestamp in the time filter. + * + * It is used to clear the time value of the time filter. Time filtering is disabled then. + * + * @return result status of the operation. + */ + @Result + public int clearTimestamp() { + int res = nativeClearTimestamp(); + if (res == 0) { + mEnable = false; + } + return res; + } + + /** + * Get the current time in the time filter. + * + * It is used to inquiry current time in the time filter. + * + * @return current timestamp in the time filter. It's based on the 90KHz counter, and it's + * the same format as PTS (Presentation Time Stamp) defined in ISO/IEC 13818-1:2019. The + * timestamps may or may not be related to PTS or DTS. {@code null} if the timestamp is + * never set. + */ + @Nullable + public Long getTimeStamp() { + if (!mEnable) { + return null; + } + return nativeGetTimestamp(); + } + + /** + * Get the timestamp from the beginning of incoming data stream. + * + * It is used to inquiry the timestamp from the beginning of incoming data stream. + * + * @return first timestamp of incoming data stream. It's based on the 90KHz counter, and + * it's the same format as PTS (Presentation Time Stamp) defined in ISO/IEC 13818-1:2019. + * The timestamps may or may not be related to PTS or DTS. + */ + @Nullable + public Long getSourceTime() { + if (!mEnable) { + return null; + } + return nativeGetSourceTime(); + } + + /** + * Close the Time Filter instance + * + * It is to release the TimeFilter instance. Resources are reclaimed so the instance must + * not be accessed after this method is called. + */ + @Override + public void close() { + nativeClose(); + } +} diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java index 63ef4b8c178b..f8b46b305216 100644 --- a/media/java/android/media/tv/tuner/Tuner.java +++ b/media/java/android/media/tv/tuner/Tuner.java @@ -21,21 +21,19 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.content.Context; -import android.media.tv.tuner.FilterSettings.Settings; -import android.media.tv.tuner.TunerConstants.DemuxPidType; +import android.media.tv.tuner.TunerConstants.FilterStatus; import android.media.tv.tuner.TunerConstants.FilterSubtype; -import android.media.tv.tuner.TunerConstants.FilterType; import android.media.tv.tuner.TunerConstants.FrontendScanType; -import android.media.tv.tuner.TunerConstants.LnbPosition; -import android.media.tv.tuner.TunerConstants.LnbTone; -import android.media.tv.tuner.TunerConstants.LnbVoltage; import android.media.tv.tuner.TunerConstants.Result; +import android.media.tv.tuner.filter.FilterConfiguration.FilterType; import android.media.tv.tuner.filter.FilterEvent; +import android.media.tv.tuner.frontend.FrontendCallback; +import android.media.tv.tuner.frontend.FrontendInfo; +import android.media.tv.tuner.frontend.FrontendStatus; import android.os.Handler; import android.os.Looper; import android.os.Message; -import java.io.FileDescriptor; import java.util.List; /** @@ -119,6 +117,7 @@ public final class Tuner implements AutoCloseable { private native int nativeDisconnectCiCam(); private native FrontendInfo nativeGetFrontendInfo(int id); private native Filter nativeOpenFilter(int type, int subType, int bufferSize); + private native TimeFilter nativeOpenTimeFilter(); private native List<Integer> nativeGetLnbIds(); private native Lnb nativeOpenLnbById(int id); @@ -127,24 +126,7 @@ public final class Tuner implements AutoCloseable { private native Dvr nativeOpenDvr(int type, int bufferSize); - /** - * Frontend Callback. - * - * @hide - */ - public interface FrontendCallback { - - /** - * Invoked when there is a frontend event. - */ - void onEvent(int frontendEventType); - - /** - * Invoked when there is a scan message. - * @param msg - */ - void onScanMessage(ScanMessage msg); - } + private static native DemuxCapabilities nativeGetDemuxCapabilities(); /** * LNB Callback. @@ -168,19 +150,23 @@ public final class Tuner implements AutoCloseable { } /** - * Frontend Callback. - * - * @hide + * Callback interface for receiving information from the corresponding filters. */ public interface FilterCallback { /** * Invoked when there are filter events. + * + * @param filter the corresponding filter which sent the events. + * @param events the filter events sent from the filter. */ - void onFilterEvent(FilterEvent[] events); + void onFilterEvent(@NonNull Filter filter, @NonNull FilterEvent[] events); /** * Invoked when filter status changed. + * + * @param filter the corresponding filter whose status is changed. + * @param status the new status of the filter. */ - void onFilterStatus(int status); + void onFilterStatusChanged(@NonNull Filter filter, @FilterStatus int status); } /** @@ -226,15 +212,10 @@ public final class Tuner implements AutoCloseable { case MSG_ON_FILTER_STATUS: { Filter filter = (Filter) msg.obj; if (filter.mCallback != null) { - filter.mCallback.onFilterStatus(msg.arg1); + filter.mCallback.onFilterStatusChanged(filter, msg.arg1); } break; } - case MSG_ON_LNB_EVENT: { - if (mLnb != null && mLnb.mCallback != null) { - mLnb.mCallback.onEvent(msg.arg1); - } - } default: // fall through } @@ -434,6 +415,11 @@ public final class Tuner implements AutoCloseable { return mFrontend.mId; } + /** @hide */ + private static DemuxCapabilities getDemuxCapabilities() { + return nativeGetDemuxCapabilities(); + } + private List<Integer> getFrontendIds() { mFrontendIds = nativeGetFrontendIds(); return mFrontendIds; @@ -456,113 +442,15 @@ public final class Tuner implements AutoCloseable { } } - /** @hide */ + /** + * Tuner data filter. + * + * <p> This class is used to filter wanted data according to the filter's configuration. + * TODO: remove + */ public class Filter { - private long mNativeContext; - private FilterCallback mCallback; - int mId; - - private native int nativeConfigureFilter(int type, int subType, FilterSettings settings); - private native int nativeGetId(); - private native int nativeSetDataSource(Filter source); - private native int nativeStartFilter(); - private native int nativeStopFilter(); - private native int nativeFlushFilter(); - private native int nativeRead(byte[] buffer, int offset, int size); - private native int nativeClose(); - - private Filter(int id) { - mId = id; - } - - private void onFilterStatus(int status) { - if (mHandler != null) { - mHandler.sendMessage( - mHandler.obtainMessage(MSG_ON_FILTER_STATUS, status, 0, this)); - } - } - - /** - * Configures the filter. - * - * @param settings the settings of the filter. - * @return result status of the operation. - */ - public int configure(FilterSettings settings) { - int subType = -1; - Settings s = settings.getSettings(); - if (s != null) { - subType = s.getType(); - } - return nativeConfigureFilter(settings.getType(), subType, settings); - } - - /** - * Gets the filter Id. - * - * @return the hardware resource Id for the filter. - */ - public int getId() { - return nativeGetId(); - } - - /** - * Sets the filter's data source. - * - * A filter uses demux as data source by default. If the data was packetized - * by multiple protocols, multiple filters may need to work together to - * extract all protocols' header. Then a filter's data source can be output - * from another filter. - * - * @param source the filter instance which provides data input. Switch to - * use demux as data source if the filter instance is NULL. - * @return result status of the operation. - */ - public int setDataSource(@Nullable Filter source) { - return nativeSetDataSource(source); - } - - /** - * Starts the filter. - * - * @return result status of the operation. - */ - public int start() { - return nativeStartFilter(); - } - - - /** - * Stops the filter. - * - * @return result status of the operation. - */ - public int stop() { - return nativeStopFilter(); - } - - /** - * Flushes the filter. - * - * @return result status of the operation. - */ - public int flush() { - return nativeFlushFilter(); - } - - public int read(@NonNull byte[] buffer, int offset, int size) { - size = Math.min(size, buffer.length - offset); - return nativeRead(buffer, offset, size); - } - - /** - * Release the Filter instance. - * - * @return result status of the operation. - */ - public int close() { - return nativeClose(); - } + FilterCallback mCallback; + private Filter() {} } private Filter openFilter(@FilterType int mainType, @FilterSubtype int subType, int bufferSize, @@ -578,90 +466,6 @@ public final class Tuner implements AutoCloseable { return filter; } - /** @hide */ - public class Lnb { - private int mId; - private LnbCallback mCallback; - - private native int nativeSetVoltage(int voltage); - private native int nativeSetTone(int tone); - private native int nativeSetSatellitePosition(int position); - private native int nativeSendDiseqcMessage(byte[] message); - private native int nativeClose(); - - private Lnb(int id) { - mId = id; - } - - public void setCallback(@Nullable LnbCallback callback) { - mCallback = callback; - if (mCallback == null) { - return; - } - if (mHandler == null) { - mHandler = createEventHandler(); - } - } - - /** - * Sets the LNB's power voltage. - * - * @param voltage the power voltage the Lnb to use. - * @return result status of the operation. - */ - @Result - public int setVoltage(@LnbVoltage int voltage) { - return nativeSetVoltage(voltage); - } - - /** - * Sets the LNB's tone mode. - * - * @param tone the tone mode the Lnb to use. - * @return result status of the operation. - */ - @Result - public int setTone(@LnbTone int tone) { - return nativeSetTone(tone); - } - - /** - * Selects the LNB's position. - * - * @param position the position the Lnb to use. - * @return result status of the operation. - */ - @Result - public int setSatellitePosition(@LnbPosition int position) { - return nativeSetSatellitePosition(position); - } - - /** - * Sends DiSEqC (Digital Satellite Equipment Control) message. - * - * The response message from the device comes back through callback onDiseqcMessage. - * - * @param message a byte array of data for DiSEqC message which is specified by EUTELSAT Bus - * Functional Specification Version 4.2. - * - * @return result status of the operation. - */ - @Result - public int sendDiseqcMessage(byte[] message) { - return nativeSendDiseqcMessage(message); - } - - /** - * Releases the LNB instance - * - * @return result status of the operation. - */ - @Result - public int close() { - return nativeClose(); - } - } - private List<Integer> getLnbIds() { mLnbIds = nativeGetLnbIds(); return mLnbIds; @@ -690,81 +494,11 @@ public final class Tuner implements AutoCloseable { * <p> Descrambler is a hardware component used to descramble data. * * <p> This class controls the TIS interaction with Tuner HAL. - * TODO: make it static and extends Closable. + * TODO: Remove */ public class Descrambler { - private long mNativeContext; - - private native int nativeAddPid(int pidType, int pid, Filter filter); - private native int nativeRemovePid(int pidType, int pid, Filter filter); - private native int nativeSetKeyToken(byte[] keyToken); - private native int nativeClose(); - - private Descrambler() {} - - /** - * Add packets' PID to the descrambler for descrambling. - * - * The descrambler will start descrambling packets with this PID. Multiple PIDs can be added - * into one descrambler instance because descambling can happen simultaneously on packets - * from different PIDs. - * - * If the Descrambler previously contained a filter for the PID, the old filter is replaced - * by the specified filter. - * - * @param pidType the type of the PID. - * @param pid the PID of packets to start to be descrambled. - * @param filter an optional filter instance to identify upper stream. - * @return result status of the operation. - * - * @hide - */ - public int addPid(@DemuxPidType int pidType, int pid, @Nullable Filter filter) { - return nativeAddPid(pidType, pid, filter); + private Descrambler() { } - - /** - * Remove packets' PID from the descrambler - * - * The descrambler will stop descrambling packets with this PID. - * - * @param pidType the type of the PID. - * @param pid the PID of packets to stop to be descrambled. - * @param filter an optional filter instance to identify upper stream. - * @return result status of the operation. - * - * @hide - */ - public int removePid(@DemuxPidType int pidType, int pid, @Nullable Filter filter) { - return nativeRemovePid(pidType, pid, filter); - } - - /** - * Set a key token to link descrambler to a key slot - * - * A descrambler instance can have only one key slot to link, but a key slot can hold a few - * keys for different purposes. - * - * @param keyToken the token to be used to link the key slot. - * @return result status of the operation. - * - * @hide - */ - public int setKeyToken(byte[] keyToken) { - return nativeSetKeyToken(keyToken); - } - - /** - * Release the descrambler instance. - * - * @return result status of the operation. - * - * @hide - */ - public int close() { - return nativeClose(); - } - } /** @@ -779,137 +513,6 @@ public final class Tuner implements AutoCloseable { return nativeOpenDescrambler(); } - // TODO: consider splitting Dvr to Playback and Recording - /** @hide */ - public class Dvr { - private long mNativeContext; - private DvrCallback mCallback; - - private native int nativeAttachFilter(Filter filter); - private native int nativeDetachFilter(Filter filter); - private native int nativeConfigureDvr(DvrSettings settings); - private native int nativeStartDvr(); - private native int nativeStopDvr(); - private native int nativeFlushDvr(); - private native int nativeClose(); - private native void nativeSetFileDescriptor(FileDescriptor fd); - private native int nativeRead(int size); - private native int nativeRead(byte[] bytes, int offset, int size); - private native int nativeWrite(int size); - private native int nativeWrite(byte[] bytes, int offset, int size); - - private Dvr() {} - - /** - * Attaches a filter to DVR interface for recording. - * - * @param filter the filter to be attached. - * @return result status of the operation. - */ - public int attachFilter(Filter filter) { - return nativeAttachFilter(filter); - } - - /** - * Detaches a filter from DVR interface. - * - * @param filter the filter to be detached. - * @return result status of the operation. - */ - public int detachFilter(Filter filter) { - return nativeDetachFilter(filter); - } - - /** - * Configures the DVR. - * - * @param settings the settings of the DVR interface. - * @return result status of the operation. - */ - public int configure(DvrSettings settings) { - return nativeConfigureDvr(settings); - } - - /** - * Starts DVR. - * - * Starts consuming playback data or producing data for recording. - * - * @return result status of the operation. - */ - public int start() { - return nativeStartDvr(); - } - - /** - * Stops DVR. - * - * Stops consuming playback data or producing data for recording. - * - * @return result status of the operation. - */ - public int stop() { - return nativeStopDvr(); - } - - /** - * Flushed DVR data. - * - * @return result status of the operation. - */ - public int flush() { - return nativeFlushDvr(); - } - - /** - * closes the DVR instance to release resources. - * - * @return result status of the operation. - */ - public int close() { - return nativeClose(); - } - - /** - * Sets file descriptor to read/write data. - */ - public void setFileDescriptor(FileDescriptor fd) { - nativeSetFileDescriptor(fd); - } - - /** - * Reads data from the file for DVR playback. - */ - public int read(int size) { - return nativeRead(size); - } - - /** - * Reads data from the buffer for DVR playback. - */ - public int read(@NonNull byte[] bytes, int offset, int size) { - if (size + offset > bytes.length) { - throw new ArrayIndexOutOfBoundsException( - "Array length=" + bytes.length + ", offset=" + offset + ", size=" + size); - } - return nativeRead(bytes, offset, size); - } - - /** - * Writes recording data to file. - */ - public int write(int size) { - return nativeWrite(size); - } - - /** - * Writes recording data to buffer. - */ - public int write(@NonNull byte[] bytes, int offset, int size) { - return nativeWrite(bytes, offset, size); - } - } - private Dvr openDvr(int type, int bufferSize) { Dvr dvr = nativeOpenDvr(type, bufferSize); return dvr; diff --git a/media/java/android/media/tv/tuner/TunerConstants.java b/media/java/android/media/tv/tuner/TunerConstants.java index e8e4b22c7963..203eff8187b2 100644 --- a/media/java/android/media/tv/tuner/TunerConstants.java +++ b/media/java/android/media/tv/tuner/TunerConstants.java @@ -18,16 +18,22 @@ package android.media.tv.tuner; import android.annotation.IntDef; import android.annotation.LongDef; +import android.annotation.SystemApi; import android.hardware.tv.tuner.V1_0.Constants; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** + * Constants for tuner framework. + * * @hide */ +@SystemApi public final class TunerConstants { + /** @hide */ public static final int INVALID_TS_PID = Constants.Constant.INVALID_TS_PID; + /** @hide */ public static final int INVALID_STREAM_ID = Constants.Constant.INVALID_STREAM_ID; @@ -122,21 +128,6 @@ public final class TunerConstants { public static final int FRONTEND_SETTINGS_ISDBT = 9; /** @hide */ - @IntDef({FILTER_TYPE_TS, FILTER_TYPE_MMTP, FILTER_TYPE_IP, FILTER_TYPE_TLV, FILTER_TYPE_ALP}) - @Retention(RetentionPolicy.SOURCE) - public @interface FilterType {} - /** @hide */ - public static final int FILTER_TYPE_TS = Constants.DemuxFilterMainType.TS; - /** @hide */ - public static final int FILTER_TYPE_MMTP = Constants.DemuxFilterMainType.MMTP; - /** @hide */ - public static final int FILTER_TYPE_IP = Constants.DemuxFilterMainType.IP; - /** @hide */ - public static final int FILTER_TYPE_TLV = Constants.DemuxFilterMainType.TLV; - /** @hide */ - public static final int FILTER_TYPE_ALP = Constants.DemuxFilterMainType.ALP; - - /** @hide */ @IntDef({FILTER_SUBTYPE_UNDEFINED, FILTER_SUBTYPE_SECTION, FILTER_SUBTYPE_PES, FILTER_SUBTYPE_AUDIO, FILTER_SUBTYPE_VIDEO, FILTER_SUBTYPE_DOWNLOAD, FILTER_SUBTYPE_RECORD, FILTER_SUBTYPE_TS, FILTER_SUBTYPE_PCR, FILTER_SUBTYPE_TEMI, @@ -180,6 +171,37 @@ public final class TunerConstants { public static final int FILTER_SUBTYPE_PTP = 16; /** @hide */ + @IntDef({FILTER_STATUS_DATA_READY, FILTER_STATUS_LOW_WATER, FILTER_STATUS_HIGH_WATER, + FILTER_STATUS_OVERFLOW}) + @Retention(RetentionPolicy.SOURCE) + public @interface FilterStatus {} + + /** + * The status of a filter that the data in the filter buffer is ready to be read. + */ + public static final int FILTER_STATUS_DATA_READY = Constants.DemuxFilterStatus.DATA_READY; + /** + * The status of a filter that the amount of available data in the filter buffer is at low + * level. + * + * The value is set to 25 percent of the buffer size by default. It can be changed when + * configuring the filter. + */ + public static final int FILTER_STATUS_LOW_WATER = Constants.DemuxFilterStatus.LOW_WATER; + /** + * The status of a filter that the amount of available data in the filter buffer is at high + * level. + * The value is set to 75 percent of the buffer size by default. It can be changed when + * configuring the filter. + */ + public static final int FILTER_STATUS_HIGH_WATER = Constants.DemuxFilterStatus.HIGH_WATER; + /** + * The status of a filter that the filter buffer is full and newly filtered data is being + * discarded. + */ + public static final int FILTER_STATUS_OVERFLOW = Constants.DemuxFilterStatus.OVERFLOW; + + /** @hide */ @IntDef({FRONTEND_SCAN_UNDEFINED, FRONTEND_SCAN_AUTO, FRONTEND_SCAN_BLIND}) @Retention(RetentionPolicy.SOURCE) public @interface FrontendScanType {} @@ -190,41 +212,6 @@ public final class TunerConstants { /** @hide */ public static final int FRONTEND_SCAN_BLIND = Constants.FrontendScanType.SCAN_BLIND; - /** @hide */ - @IntDef({SCAN_MESSAGE_TYPE_LOCKED, SCAN_MESSAGE_TYPE_END, SCAN_MESSAGE_TYPE_PROGRESS_PERCENT, - SCAN_MESSAGE_TYPE_FREQUENCY, SCAN_MESSAGE_TYPE_SYMBOL_RATE, SCAN_MESSAGE_TYPE_PLP_IDS, - SCAN_MESSAGE_TYPE_GROUP_IDS, SCAN_MESSAGE_TYPE_INPUT_STREAM_IDS, - SCAN_MESSAGE_TYPE_STANDARD, SCAN_MESSAGE_TYPE_ATSC3_PLP_INFO}) - @Retention(RetentionPolicy.SOURCE) - public @interface ScanMessageType {} - /** @hide */ - public static final int SCAN_MESSAGE_TYPE_LOCKED = Constants.FrontendScanMessageType.LOCKED; - /** @hide */ - public static final int SCAN_MESSAGE_TYPE_END = Constants.FrontendScanMessageType.END; - /** @hide */ - public static final int SCAN_MESSAGE_TYPE_PROGRESS_PERCENT = - Constants.FrontendScanMessageType.PROGRESS_PERCENT; - /** @hide */ - public static final int SCAN_MESSAGE_TYPE_FREQUENCY = - Constants.FrontendScanMessageType.FREQUENCY; - /** @hide */ - public static final int SCAN_MESSAGE_TYPE_SYMBOL_RATE = - Constants.FrontendScanMessageType.SYMBOL_RATE; - /** @hide */ - public static final int SCAN_MESSAGE_TYPE_PLP_IDS = Constants.FrontendScanMessageType.PLP_IDS; - /** @hide */ - public static final int SCAN_MESSAGE_TYPE_GROUP_IDS = - Constants.FrontendScanMessageType.GROUP_IDS; - /** @hide */ - public static final int SCAN_MESSAGE_TYPE_INPUT_STREAM_IDS = - Constants.FrontendScanMessageType.INPUT_STREAM_IDS; - /** @hide */ - public static final int SCAN_MESSAGE_TYPE_STANDARD = - Constants.FrontendScanMessageType.STANDARD; - /** @hide */ - public static final int SCAN_MESSAGE_TYPE_ATSC3_PLP_INFO = - Constants.FrontendScanMessageType.ATSC3_PLP_INFO; - /** @hide */ @IntDef({FRONTEND_STATUS_TYPE_DEMOD_LOCK, FRONTEND_STATUS_TYPE_SNR, FRONTEND_STATUS_TYPE_BER, @@ -739,119 +726,162 @@ public final class TunerConstants { public static final int HIERARCHY_4_INDEPTH = Constants.FrontendDvbtHierarchy.HIERARCHY_4_INDEPTH; - @Retention(RetentionPolicy.SOURCE) + /** @hide */ @IntDef({FRONTEND_ANALOG_TYPE_UNDEFINED, FRONTEND_ANALOG_TYPE_PAL, FRONTEND_ANALOG_TYPE_SECAM, FRONTEND_ANALOG_TYPE_NTSC}) + @Retention(RetentionPolicy.SOURCE) public @interface FrontendAnalogType {} - + /** @hide */ public static final int FRONTEND_ANALOG_TYPE_UNDEFINED = Constants.FrontendAnalogType.UNDEFINED; + /** @hide */ public static final int FRONTEND_ANALOG_TYPE_PAL = Constants.FrontendAnalogType.PAL; + /** @hide */ public static final int FRONTEND_ANALOG_TYPE_SECAM = Constants.FrontendAnalogType.SECAM; + /** @hide */ public static final int FRONTEND_ANALOG_TYPE_NTSC = Constants.FrontendAnalogType.NTSC; - @Retention(RetentionPolicy.SOURCE) + /** @hide */ @IntDef({FRONTEND_ANALOG_SIF_UNDEFINED, FRONTEND_ANALOG_SIF_BG, FRONTEND_ANALOG_SIF_BG_A2, FRONTEND_ANALOG_SIF_BG_NICAM, FRONTEND_ANALOG_SIF_I, FRONTEND_ANALOG_SIF_DK, FRONTEND_ANALOG_SIF_DK1, FRONTEND_ANALOG_SIF_DK2, FRONTEND_ANALOG_SIF_DK3, FRONTEND_ANALOG_SIF_DK_NICAM, FRONTEND_ANALOG_SIF_L, FRONTEND_ANALOG_SIF_M, FRONTEND_ANALOG_SIF_M_BTSC, FRONTEND_ANALOG_SIF_M_A2, FRONTEND_ANALOG_SIF_M_EIA_J, FRONTEND_ANALOG_SIF_I_NICAM, FRONTEND_ANALOG_SIF_L_NICAM, FRONTEND_ANALOG_SIF_L_PRIME}) + @Retention(RetentionPolicy.SOURCE) public @interface FrontendAnalogSifStandard {} - + /** @hide */ public static final int FRONTEND_ANALOG_SIF_UNDEFINED = Constants.FrontendAnalogSifStandard.UNDEFINED; + /** @hide */ public static final int FRONTEND_ANALOG_SIF_BG = Constants.FrontendAnalogSifStandard.BG; + /** @hide */ public static final int FRONTEND_ANALOG_SIF_BG_A2 = Constants.FrontendAnalogSifStandard.BG_A2; + /** @hide */ public static final int FRONTEND_ANALOG_SIF_BG_NICAM = Constants.FrontendAnalogSifStandard.BG_NICAM; + /** @hide */ public static final int FRONTEND_ANALOG_SIF_I = Constants.FrontendAnalogSifStandard.I; + /** @hide */ public static final int FRONTEND_ANALOG_SIF_DK = Constants.FrontendAnalogSifStandard.DK; + /** @hide */ public static final int FRONTEND_ANALOG_SIF_DK1 = Constants.FrontendAnalogSifStandard.DK1; + /** @hide */ public static final int FRONTEND_ANALOG_SIF_DK2 = Constants.FrontendAnalogSifStandard.DK2; + /** @hide */ public static final int FRONTEND_ANALOG_SIF_DK3 = Constants.FrontendAnalogSifStandard.DK3; + /** @hide */ public static final int FRONTEND_ANALOG_SIF_DK_NICAM = Constants.FrontendAnalogSifStandard.DK_NICAM; + /** @hide */ public static final int FRONTEND_ANALOG_SIF_L = Constants.FrontendAnalogSifStandard.L; + /** @hide */ public static final int FRONTEND_ANALOG_SIF_M = Constants.FrontendAnalogSifStandard.M; + /** @hide */ public static final int FRONTEND_ANALOG_SIF_M_BTSC = Constants.FrontendAnalogSifStandard.M_BTSC; + /** @hide */ public static final int FRONTEND_ANALOG_SIF_M_A2 = Constants.FrontendAnalogSifStandard.M_A2; + /** @hide */ public static final int FRONTEND_ANALOG_SIF_M_EIA_J = Constants.FrontendAnalogSifStandard.M_EIA_J; + /** @hide */ public static final int FRONTEND_ANALOG_SIF_I_NICAM = Constants.FrontendAnalogSifStandard.I_NICAM; + /** @hide */ public static final int FRONTEND_ANALOG_SIF_L_NICAM = Constants.FrontendAnalogSifStandard.L_NICAM; + /** @hide */ public static final int FRONTEND_ANALOG_SIF_L_PRIME = Constants.FrontendAnalogSifStandard.L_PRIME; - @Retention(RetentionPolicy.SOURCE) + /** @hide */ @IntDef({FRONTEND_ATSC_MODULATION_UNDEFINED, FRONTEND_ATSC_MODULATION_AUTO, FRONTEND_ATSC_MODULATION_MOD_8VSB, FRONTEND_ATSC_MODULATION_MOD_16VSB}) + @Retention(RetentionPolicy.SOURCE) public @interface FrontendAtscModulation {} - + /** @hide */ public static final int FRONTEND_ATSC_MODULATION_UNDEFINED = Constants.FrontendAtscModulation.UNDEFINED; + /** @hide */ public static final int FRONTEND_ATSC_MODULATION_AUTO = Constants.FrontendAtscModulation.AUTO; + /** @hide */ public static final int FRONTEND_ATSC_MODULATION_MOD_8VSB = Constants.FrontendAtscModulation.MOD_8VSB; + /** @hide */ public static final int FRONTEND_ATSC_MODULATION_MOD_16VSB = Constants.FrontendAtscModulation.MOD_16VSB; - @Retention(RetentionPolicy.SOURCE) + /** @hide */ @IntDef({FRONTEND_ATSC3_BANDWIDTH_UNDEFINED, FRONTEND_ATSC3_BANDWIDTH_AUTO, FRONTEND_ATSC3_BANDWIDTH_BANDWIDTH_6MHZ, FRONTEND_ATSC3_BANDWIDTH_BANDWIDTH_7MHZ, FRONTEND_ATSC3_BANDWIDTH_BANDWIDTH_8MHZ}) + @Retention(RetentionPolicy.SOURCE) public @interface FrontendAtsc3Bandwidth {} - + /** @hide */ public static final int FRONTEND_ATSC3_BANDWIDTH_UNDEFINED = Constants.FrontendAtsc3Bandwidth.UNDEFINED; + /** @hide */ public static final int FRONTEND_ATSC3_BANDWIDTH_AUTO = Constants.FrontendAtsc3Bandwidth.AUTO; + /** @hide */ public static final int FRONTEND_ATSC3_BANDWIDTH_BANDWIDTH_6MHZ = Constants.FrontendAtsc3Bandwidth.BANDWIDTH_6MHZ; + /** @hide */ public static final int FRONTEND_ATSC3_BANDWIDTH_BANDWIDTH_7MHZ = Constants.FrontendAtsc3Bandwidth.BANDWIDTH_7MHZ; + /** @hide */ public static final int FRONTEND_ATSC3_BANDWIDTH_BANDWIDTH_8MHZ = Constants.FrontendAtsc3Bandwidth.BANDWIDTH_8MHZ; - @Retention(RetentionPolicy.SOURCE) + /** @hide */ @IntDef({FRONTEND_ATSC3_MODULATION_UNDEFINED, FRONTEND_ATSC3_MODULATION_AUTO, FRONTEND_ATSC3_MODULATION_MOD_QPSK, FRONTEND_ATSC3_MODULATION_MOD_16QAM, FRONTEND_ATSC3_MODULATION_MOD_64QAM, FRONTEND_ATSC3_MODULATION_MOD_256QAM, FRONTEND_ATSC3_MODULATION_MOD_1024QAM, FRONTEND_ATSC3_MODULATION_MOD_4096QAM}) + @Retention(RetentionPolicy.SOURCE) public @interface FrontendAtsc3Modulation {} - + /** @hide */ public static final int FRONTEND_ATSC3_MODULATION_UNDEFINED = Constants.FrontendAtsc3Modulation.UNDEFINED; + /** @hide */ public static final int FRONTEND_ATSC3_MODULATION_AUTO = Constants.FrontendAtsc3Modulation.AUTO; + /** @hide */ public static final int FRONTEND_ATSC3_MODULATION_MOD_QPSK = Constants.FrontendAtsc3Modulation.MOD_QPSK; + /** @hide */ public static final int FRONTEND_ATSC3_MODULATION_MOD_16QAM = Constants.FrontendAtsc3Modulation.MOD_16QAM; + /** @hide */ public static final int FRONTEND_ATSC3_MODULATION_MOD_64QAM = Constants.FrontendAtsc3Modulation.MOD_64QAM; + /** @hide */ public static final int FRONTEND_ATSC3_MODULATION_MOD_256QAM = Constants.FrontendAtsc3Modulation.MOD_256QAM; + /** @hide */ public static final int FRONTEND_ATSC3_MODULATION_MOD_1024QAM = Constants.FrontendAtsc3Modulation.MOD_1024QAM; + /** @hide */ public static final int FRONTEND_ATSC3_MODULATION_MOD_4096QAM = Constants.FrontendAtsc3Modulation.MOD_4096QAM; - @Retention(RetentionPolicy.SOURCE) + /** @hide */ @IntDef({FRONTEND_ATSC3_TIME_INTERLEAVE_MODE_UNDEFINED, FRONTEND_ATSC3_TIME_INTERLEAVE_MODE_AUTO, FRONTEND_ATSC3_TIME_INTERLEAVE_MODE_CTI, FRONTEND_ATSC3_TIME_INTERLEAVE_MODE_HTI}) + @Retention(RetentionPolicy.SOURCE) public @interface FrontendAtsc3TimeInterleaveMode {} - + /** @hide */ public static final int FRONTEND_ATSC3_TIME_INTERLEAVE_MODE_UNDEFINED = Constants.FrontendAtsc3TimeInterleaveMode.UNDEFINED; + /** @hide */ public static final int FRONTEND_ATSC3_TIME_INTERLEAVE_MODE_AUTO = Constants.FrontendAtsc3TimeInterleaveMode.AUTO; + /** @hide */ public static final int FRONTEND_ATSC3_TIME_INTERLEAVE_MODE_CTI = Constants.FrontendAtsc3TimeInterleaveMode.CTI; + /** @hide */ public static final int FRONTEND_ATSC3_TIME_INTERLEAVE_MODE_HTI = Constants.FrontendAtsc3TimeInterleaveMode.HTI; - @Retention(RetentionPolicy.SOURCE) + /** @hide */ @IntDef({FRONTEND_ATSC3_CODERATE_UNDEFINED, FRONTEND_ATSC3_CODERATE_AUTO, FRONTEND_ATSC3_CODERATE_2_15, FRONTEND_ATSC3_CODERATE_3_15, FRONTEND_ATSC3_CODERATE_4_15, FRONTEND_ATSC3_CODERATE_5_15, @@ -859,258 +889,350 @@ public final class TunerConstants { FRONTEND_ATSC3_CODERATE_8_15, FRONTEND_ATSC3_CODERATE_9_15, FRONTEND_ATSC3_CODERATE_10_15, FRONTEND_ATSC3_CODERATE_11_15, FRONTEND_ATSC3_CODERATE_12_15, FRONTEND_ATSC3_CODERATE_13_15}) + @Retention(RetentionPolicy.SOURCE) public @interface FrontendAtsc3CodeRate {} - + /** @hide */ public static final int FRONTEND_ATSC3_CODERATE_UNDEFINED = Constants.FrontendAtsc3CodeRate.UNDEFINED; + /** @hide */ public static final int FRONTEND_ATSC3_CODERATE_AUTO = Constants.FrontendAtsc3CodeRate.AUTO; + /** @hide */ public static final int FRONTEND_ATSC3_CODERATE_2_15 = Constants.FrontendAtsc3CodeRate.CODERATE_2_15; + /** @hide */ public static final int FRONTEND_ATSC3_CODERATE_3_15 = Constants.FrontendAtsc3CodeRate.CODERATE_3_15; + /** @hide */ public static final int FRONTEND_ATSC3_CODERATE_4_15 = Constants.FrontendAtsc3CodeRate.CODERATE_4_15; + /** @hide */ public static final int FRONTEND_ATSC3_CODERATE_5_15 = Constants.FrontendAtsc3CodeRate.CODERATE_5_15; + /** @hide */ public static final int FRONTEND_ATSC3_CODERATE_6_15 = Constants.FrontendAtsc3CodeRate.CODERATE_6_15; + /** @hide */ public static final int FRONTEND_ATSC3_CODERATE_7_15 = Constants.FrontendAtsc3CodeRate.CODERATE_7_15; + /** @hide */ public static final int FRONTEND_ATSC3_CODERATE_8_15 = Constants.FrontendAtsc3CodeRate.CODERATE_8_15; + /** @hide */ public static final int FRONTEND_ATSC3_CODERATE_9_15 = Constants.FrontendAtsc3CodeRate.CODERATE_9_15; + /** @hide */ public static final int FRONTEND_ATSC3_CODERATE_10_15 = Constants.FrontendAtsc3CodeRate.CODERATE_10_15; + /** @hide */ public static final int FRONTEND_ATSC3_CODERATE_11_15 = Constants.FrontendAtsc3CodeRate.CODERATE_11_15; + /** @hide */ public static final int FRONTEND_ATSC3_CODERATE_12_15 = Constants.FrontendAtsc3CodeRate.CODERATE_12_15; + /** @hide */ public static final int FRONTEND_ATSC3_CODERATE_13_15 = Constants.FrontendAtsc3CodeRate.CODERATE_13_15; - @Retention(RetentionPolicy.SOURCE) + /** @hide */ @IntDef({FRONTEND_ATSC3_FEC_UNDEFINED, FRONTEND_ATSC3_FEC_AUTO, FRONTEND_ATSC3_FEC_BCH_LDPC_16K, FRONTEND_ATSC3_FEC_BCH_LDPC_64K, FRONTEND_ATSC3_FEC_CRC_LDPC_16K, FRONTEND_ATSC3_FEC_CRC_LDPC_64K, FRONTEND_ATSC3_FEC_LDPC_16K, FRONTEND_ATSC3_FEC_LDPC_64K}) + @Retention(RetentionPolicy.SOURCE) public @interface FrontendAtsc3Fec {} - + /** @hide */ public static final int FRONTEND_ATSC3_FEC_UNDEFINED = Constants.FrontendAtsc3Fec.UNDEFINED; + /** @hide */ public static final int FRONTEND_ATSC3_FEC_AUTO = Constants.FrontendAtsc3Fec.AUTO; + /** @hide */ public static final int FRONTEND_ATSC3_FEC_BCH_LDPC_16K = Constants.FrontendAtsc3Fec.BCH_LDPC_16K; + /** @hide */ public static final int FRONTEND_ATSC3_FEC_BCH_LDPC_64K = Constants.FrontendAtsc3Fec.BCH_LDPC_64K; + /** @hide */ public static final int FRONTEND_ATSC3_FEC_CRC_LDPC_16K = Constants.FrontendAtsc3Fec.CRC_LDPC_16K; + /** @hide */ public static final int FRONTEND_ATSC3_FEC_CRC_LDPC_64K = Constants.FrontendAtsc3Fec.CRC_LDPC_64K; + /** @hide */ public static final int FRONTEND_ATSC3_FEC_LDPC_16K = Constants.FrontendAtsc3Fec.LDPC_16K; + /** @hide */ public static final int FRONTEND_ATSC3_FEC_LDPC_64K = Constants.FrontendAtsc3Fec.LDPC_64K; - @Retention(RetentionPolicy.SOURCE) + /** @hide */ @IntDef({FRONTEND_ATSC3_DEMOD_OUTPUT_FORMAT_UNDEFINED, FRONTEND_ATSC3_DEMOD_OUTPUT_FORMAT_ATSC3_LINKLAYER_PACKET, FRONTEND_ATSC3_DEMOD_OUTPUT_FORMAT_BASEBAND_PACKET}) + @Retention(RetentionPolicy.SOURCE) public @interface FrontendAtsc3DemodOutputFormat {} - + /** @hide */ public static final int FRONTEND_ATSC3_DEMOD_OUTPUT_FORMAT_UNDEFINED = Constants.FrontendAtsc3DemodOutputFormat.UNDEFINED; + /** @hide */ public static final int FRONTEND_ATSC3_DEMOD_OUTPUT_FORMAT_ATSC3_LINKLAYER_PACKET = Constants.FrontendAtsc3DemodOutputFormat.ATSC3_LINKLAYER_PACKET; + /** @hide */ public static final int FRONTEND_ATSC3_DEMOD_OUTPUT_FORMAT_BASEBAND_PACKET = Constants.FrontendAtsc3DemodOutputFormat.BASEBAND_PACKET; - @Retention(RetentionPolicy.SOURCE) + /** @hide */ @IntDef({FRONTEND_DVBS_STANDARD_AUTO, FRONTEND_DVBS_STANDARD_S, FRONTEND_DVBS_STANDARD_S2, FRONTEND_DVBS_STANDARD_S2X}) + @Retention(RetentionPolicy.SOURCE) public @interface FrontendDvbsStandard {} - + /** @hide */ public static final int FRONTEND_DVBS_STANDARD_AUTO = Constants.FrontendDvbsStandard.AUTO; + /** @hide */ public static final int FRONTEND_DVBS_STANDARD_S = Constants.FrontendDvbsStandard.S; + /** @hide */ public static final int FRONTEND_DVBS_STANDARD_S2 = Constants.FrontendDvbsStandard.S2; + /** @hide */ public static final int FRONTEND_DVBS_STANDARD_S2X = Constants.FrontendDvbsStandard.S2X; - @Retention(RetentionPolicy.SOURCE) + /** @hide */ @IntDef({FRONTEND_DVBC_ANNEX_UNDEFINED, FRONTEND_DVBC_ANNEX_A, FRONTEND_DVBC_ANNEX_B, FRONTEND_DVBC_ANNEX_C}) + @Retention(RetentionPolicy.SOURCE) public @interface FrontendDvbcAnnex {} - + /** @hide */ public static final int FRONTEND_DVBC_ANNEX_UNDEFINED = Constants.FrontendDvbcAnnex.UNDEFINED; + /** @hide */ public static final int FRONTEND_DVBC_ANNEX_A = Constants.FrontendDvbcAnnex.A; + /** @hide */ public static final int FRONTEND_DVBC_ANNEX_B = Constants.FrontendDvbcAnnex.B; + /** @hide */ public static final int FRONTEND_DVBC_ANNEX_C = Constants.FrontendDvbcAnnex.C; - @Retention(RetentionPolicy.SOURCE) + /** @hide */ @IntDef({FRONTEND_DVBT_TRANSMISSION_MODE_UNDEFINED, FRONTEND_DVBT_TRANSMISSION_MODE_AUTO, FRONTEND_DVBT_TRANSMISSION_MODE_2K, FRONTEND_DVBT_TRANSMISSION_MODE_8K, FRONTEND_DVBT_TRANSMISSION_MODE_4K, FRONTEND_DVBT_TRANSMISSION_MODE_1K, FRONTEND_DVBT_TRANSMISSION_MODE_16K, FRONTEND_DVBT_TRANSMISSION_MODE_32K}) + @Retention(RetentionPolicy.SOURCE) public @interface FrontendDvbtTransmissionMode {} - + /** @hide */ public static final int FRONTEND_DVBT_TRANSMISSION_MODE_UNDEFINED = Constants.FrontendDvbtTransmissionMode.UNDEFINED; + /** @hide */ public static final int FRONTEND_DVBT_TRANSMISSION_MODE_AUTO = Constants.FrontendDvbtTransmissionMode.AUTO; + /** @hide */ public static final int FRONTEND_DVBT_TRANSMISSION_MODE_2K = Constants.FrontendDvbtTransmissionMode.MODE_2K; + /** @hide */ public static final int FRONTEND_DVBT_TRANSMISSION_MODE_8K = Constants.FrontendDvbtTransmissionMode.MODE_8K; + /** @hide */ public static final int FRONTEND_DVBT_TRANSMISSION_MODE_4K = Constants.FrontendDvbtTransmissionMode.MODE_4K; + /** @hide */ public static final int FRONTEND_DVBT_TRANSMISSION_MODE_1K = Constants.FrontendDvbtTransmissionMode.MODE_1K; + /** @hide */ public static final int FRONTEND_DVBT_TRANSMISSION_MODE_16K = Constants.FrontendDvbtTransmissionMode.MODE_16K; + /** @hide */ public static final int FRONTEND_DVBT_TRANSMISSION_MODE_32K = Constants.FrontendDvbtTransmissionMode.MODE_32K; - @Retention(RetentionPolicy.SOURCE) + /** @hide */ @IntDef({FRONTEND_DVBT_BANDWIDTH_UNDEFINED, FRONTEND_DVBT_BANDWIDTH_AUTO, FRONTEND_DVBT_BANDWIDTH_8MHZ, FRONTEND_DVBT_BANDWIDTH_7MHZ, FRONTEND_DVBT_BANDWIDTH_6MHZ, FRONTEND_DVBT_BANDWIDTH_5MHZ, FRONTEND_DVBT_BANDWIDTH_1_7MHZ, FRONTEND_DVBT_BANDWIDTH_10MHZ}) + @Retention(RetentionPolicy.SOURCE) public @interface FrontendDvbtBandwidth {} - + /** @hide */ public static final int FRONTEND_DVBT_BANDWIDTH_UNDEFINED = Constants.FrontendDvbtBandwidth.UNDEFINED; + /** @hide */ public static final int FRONTEND_DVBT_BANDWIDTH_AUTO = Constants.FrontendDvbtBandwidth.AUTO; + /** @hide */ public static final int FRONTEND_DVBT_BANDWIDTH_8MHZ = Constants.FrontendDvbtBandwidth.BANDWIDTH_8MHZ; + /** @hide */ public static final int FRONTEND_DVBT_BANDWIDTH_7MHZ = Constants.FrontendDvbtBandwidth.BANDWIDTH_7MHZ; + /** @hide */ public static final int FRONTEND_DVBT_BANDWIDTH_6MHZ = Constants.FrontendDvbtBandwidth.BANDWIDTH_6MHZ; + /** @hide */ public static final int FRONTEND_DVBT_BANDWIDTH_5MHZ = Constants.FrontendDvbtBandwidth.BANDWIDTH_5MHZ; + /** @hide */ public static final int FRONTEND_DVBT_BANDWIDTH_1_7MHZ = Constants.FrontendDvbtBandwidth.BANDWIDTH_1_7MHZ; + /** @hide */ public static final int FRONTEND_DVBT_BANDWIDTH_10MHZ = Constants.FrontendDvbtBandwidth.BANDWIDTH_10MHZ; - @Retention(RetentionPolicy.SOURCE) + /** @hide */ @IntDef({FRONTEND_DVBT_CONSTELLATION_UNDEFINED, FRONTEND_DVBT_CONSTELLATION_AUTO, FRONTEND_DVBT_CONSTELLATION_CONSTELLATION_QPSK, FRONTEND_DVBT_CONSTELLATION_CONSTELLATION_16QAM, FRONTEND_DVBT_CONSTELLATION_CONSTELLATION_64QAM, FRONTEND_DVBT_CONSTELLATION_CONSTELLATION_256QAM}) + @Retention(RetentionPolicy.SOURCE) public @interface FrontendDvbtConstellation {} - + /** @hide */ public static final int FRONTEND_DVBT_CONSTELLATION_UNDEFINED = Constants.FrontendDvbtConstellation.UNDEFINED; + /** @hide */ public static final int FRONTEND_DVBT_CONSTELLATION_AUTO = Constants.FrontendDvbtConstellation.AUTO; + /** @hide */ public static final int FRONTEND_DVBT_CONSTELLATION_CONSTELLATION_QPSK = Constants.FrontendDvbtConstellation.CONSTELLATION_QPSK; + /** @hide */ public static final int FRONTEND_DVBT_CONSTELLATION_CONSTELLATION_16QAM = Constants.FrontendDvbtConstellation.CONSTELLATION_16QAM; + /** @hide */ public static final int FRONTEND_DVBT_CONSTELLATION_CONSTELLATION_64QAM = Constants.FrontendDvbtConstellation.CONSTELLATION_64QAM; + /** @hide */ public static final int FRONTEND_DVBT_CONSTELLATION_CONSTELLATION_256QAM = Constants.FrontendDvbtConstellation.CONSTELLATION_256QAM; - @Retention(RetentionPolicy.SOURCE) + /** @hide */ @IntDef({FRONTEND_DVBT_CODERATE_UNDEFINED, FRONTEND_DVBT_CODERATE_AUTO, FRONTEND_DVBT_CODERATE_1_2, FRONTEND_DVBT_CODERATE_2_3, FRONTEND_DVBT_CODERATE_3_4, FRONTEND_DVBT_CODERATE_5_6, FRONTEND_DVBT_CODERATE_7_8, FRONTEND_DVBT_CODERATE_3_5, FRONTEND_DVBT_CODERATE_4_5, FRONTEND_DVBT_CODERATE_6_7, FRONTEND_DVBT_CODERATE_8_9}) + @Retention(RetentionPolicy.SOURCE) public @interface FrontendDvbtCoderate {} - + /** @hide */ public static final int FRONTEND_DVBT_CODERATE_UNDEFINED = Constants.FrontendDvbtCoderate.UNDEFINED; + /** @hide */ public static final int FRONTEND_DVBT_CODERATE_AUTO = Constants.FrontendDvbtCoderate.AUTO; + /** @hide */ public static final int FRONTEND_DVBT_CODERATE_1_2 = Constants.FrontendDvbtCoderate.CODERATE_1_2; + /** @hide */ public static final int FRONTEND_DVBT_CODERATE_2_3 = Constants.FrontendDvbtCoderate.CODERATE_2_3; + /** @hide */ public static final int FRONTEND_DVBT_CODERATE_3_4 = Constants.FrontendDvbtCoderate.CODERATE_3_4; + /** @hide */ public static final int FRONTEND_DVBT_CODERATE_5_6 = Constants.FrontendDvbtCoderate.CODERATE_5_6; + /** @hide */ public static final int FRONTEND_DVBT_CODERATE_7_8 = Constants.FrontendDvbtCoderate.CODERATE_7_8; + /** @hide */ public static final int FRONTEND_DVBT_CODERATE_3_5 = Constants.FrontendDvbtCoderate.CODERATE_3_5; + /** @hide */ public static final int FRONTEND_DVBT_CODERATE_4_5 = Constants.FrontendDvbtCoderate.CODERATE_4_5; + /** @hide */ public static final int FRONTEND_DVBT_CODERATE_6_7 = Constants.FrontendDvbtCoderate.CODERATE_6_7; + /** @hide */ public static final int FRONTEND_DVBT_CODERATE_8_9 = Constants.FrontendDvbtCoderate.CODERATE_8_9; - @Retention(RetentionPolicy.SOURCE) + /** @hide */ @IntDef({FRONTEND_DVBT_GUARD_INTERVAL_UNDEFINED, FRONTEND_DVBT_GUARD_INTERVAL_AUTO, FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_32, FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_16, FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_8, FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_4, FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_128, FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_19_128, FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_19_256}) + @Retention(RetentionPolicy.SOURCE) public @interface FrontendDvbtGuardInterval {} - + /** @hide */ public static final int FRONTEND_DVBT_GUARD_INTERVAL_UNDEFINED = Constants.FrontendDvbtGuardInterval.UNDEFINED; + /** @hide */ public static final int FRONTEND_DVBT_GUARD_INTERVAL_AUTO = Constants.FrontendDvbtGuardInterval.AUTO; + /** @hide */ public static final int FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_32 = Constants.FrontendDvbtGuardInterval.INTERVAL_1_32; + /** @hide */ public static final int FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_16 = Constants.FrontendDvbtGuardInterval.INTERVAL_1_16; + /** @hide */ public static final int FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_8 = Constants.FrontendDvbtGuardInterval.INTERVAL_1_8; + /** @hide */ public static final int FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_4 = Constants.FrontendDvbtGuardInterval.INTERVAL_1_4; + /** @hide */ public static final int FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_128 = Constants.FrontendDvbtGuardInterval.INTERVAL_1_128; + /** @hide */ public static final int FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_19_128 = Constants.FrontendDvbtGuardInterval.INTERVAL_19_128; + /** @hide */ public static final int FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_19_256 = Constants.FrontendDvbtGuardInterval.INTERVAL_19_256; - @Retention(RetentionPolicy.SOURCE) + /** @hide */ @IntDef({FRONTEND_ISDBS_CODERATE_UNDEFINED, FRONTEND_ISDBS_CODERATE_AUTO, FRONTEND_ISDBS_CODERATE_1_2, FRONTEND_ISDBS_CODERATE_2_3, FRONTEND_ISDBS_CODERATE_3_4, FRONTEND_ISDBS_CODERATE_5_6, FRONTEND_ISDBS_CODERATE_7_8}) + @Retention(RetentionPolicy.SOURCE) public @interface FrontendIsdbsCoderate {} - + /** @hide */ public static final int FRONTEND_ISDBS_CODERATE_UNDEFINED = Constants.FrontendIsdbsCoderate.UNDEFINED; + /** @hide */ public static final int FRONTEND_ISDBS_CODERATE_AUTO = Constants.FrontendIsdbsCoderate.AUTO; + /** @hide */ public static final int FRONTEND_ISDBS_CODERATE_1_2 = Constants.FrontendIsdbsCoderate.CODERATE_1_2; + /** @hide */ public static final int FRONTEND_ISDBS_CODERATE_2_3 = Constants.FrontendIsdbsCoderate.CODERATE_2_3; + /** @hide */ public static final int FRONTEND_ISDBS_CODERATE_3_4 = Constants.FrontendIsdbsCoderate.CODERATE_3_4; + /** @hide */ public static final int FRONTEND_ISDBS_CODERATE_5_6 = Constants.FrontendIsdbsCoderate.CODERATE_5_6; + /** @hide */ public static final int FRONTEND_ISDBS_CODERATE_7_8 = Constants.FrontendIsdbsCoderate.CODERATE_7_8; - @Retention(RetentionPolicy.SOURCE) + /** @hide */ @IntDef({FRONTEND_ISDBT_MODE_UNDEFINED, FRONTEND_ISDBT_MODE_AUTO, FRONTEND_ISDBT_MODE_1, FRONTEND_ISDBT_MODE_2, FRONTEND_ISDBT_MODE_3}) + @Retention(RetentionPolicy.SOURCE) public @interface FrontendIsdbtMode {} - + /** @hide */ public static final int FRONTEND_ISDBT_MODE_UNDEFINED = Constants.FrontendIsdbtMode.UNDEFINED; + /** @hide */ public static final int FRONTEND_ISDBT_MODE_AUTO = Constants.FrontendIsdbtMode.AUTO; + /** @hide */ public static final int FRONTEND_ISDBT_MODE_1 = Constants.FrontendIsdbtMode.MODE_1; + /** @hide */ public static final int FRONTEND_ISDBT_MODE_2 = Constants.FrontendIsdbtMode.MODE_2; + /** @hide */ public static final int FRONTEND_ISDBT_MODE_3 = Constants.FrontendIsdbtMode.MODE_3; - @Retention(RetentionPolicy.SOURCE) + /** @hide */ @IntDef({FRONTEND_ISDBT_BANDWIDTH_UNDEFINED, FRONTEND_ISDBT_BANDWIDTH_AUTO, FRONTEND_ISDBT_BANDWIDTH_8MHZ, FRONTEND_ISDBT_BANDWIDTH_7MHZ, FRONTEND_ISDBT_BANDWIDTH_6MHZ}) + @Retention(RetentionPolicy.SOURCE) public @interface FrontendIsdbtBandwidth {} - + /** @hide */ public static final int FRONTEND_ISDBT_BANDWIDTH_UNDEFINED = Constants.FrontendIsdbtBandwidth.UNDEFINED; + /** @hide */ public static final int FRONTEND_ISDBT_BANDWIDTH_AUTO = Constants.FrontendIsdbtBandwidth.AUTO; + /** @hide */ public static final int FRONTEND_ISDBT_BANDWIDTH_8MHZ = Constants.FrontendIsdbtBandwidth.BANDWIDTH_8MHZ; + /** @hide */ public static final int FRONTEND_ISDBT_BANDWIDTH_7MHZ = Constants.FrontendIsdbtBandwidth.BANDWIDTH_7MHZ; + /** @hide */ public static final int FRONTEND_ISDBT_BANDWIDTH_6MHZ = Constants.FrontendIsdbtBandwidth.BANDWIDTH_6MHZ; @@ -1141,68 +1263,45 @@ public final class TunerConstants { /** @hide */ - @IntDef({LNB_VOLTAGE_NONE, LNB_VOLTAGE_5V, LNB_VOLTAGE_11V, LNB_VOLTAGE_12V, LNB_VOLTAGE_13V, - LNB_VOLTAGE_14V, LNB_VOLTAGE_15V, LNB_VOLTAGE_18V, LNB_VOLTAGE_19V}) - @Retention(RetentionPolicy.SOURCE) - public @interface LnbVoltage {} - /** @hide */ - public static final int LNB_VOLTAGE_NONE = Constants.LnbVoltage.NONE; - /** @hide */ - public static final int LNB_VOLTAGE_5V = Constants.LnbVoltage.VOLTAGE_5V; - /** @hide */ - public static final int LNB_VOLTAGE_11V = Constants.LnbVoltage.VOLTAGE_11V; - /** @hide */ - public static final int LNB_VOLTAGE_12V = Constants.LnbVoltage.VOLTAGE_12V; - /** @hide */ - public static final int LNB_VOLTAGE_13V = Constants.LnbVoltage.VOLTAGE_13V; - /** @hide */ - public static final int LNB_VOLTAGE_14V = Constants.LnbVoltage.VOLTAGE_14V; - /** @hide */ - public static final int LNB_VOLTAGE_15V = Constants.LnbVoltage.VOLTAGE_15V; - /** @hide */ - public static final int LNB_VOLTAGE_18V = Constants.LnbVoltage.VOLTAGE_18V; - /** @hide */ - public static final int LNB_VOLTAGE_19V = Constants.LnbVoltage.VOLTAGE_19V; - - /** @hide */ - @IntDef({LNB_TONE_NONE, LNB_TONE_CONTINUOUS}) - @Retention(RetentionPolicy.SOURCE) - public @interface LnbTone {} - /** @hide */ - public static final int LNB_TONE_NONE = Constants.LnbTone.NONE; - /** @hide */ - public static final int LNB_TONE_CONTINUOUS = Constants.LnbTone.CONTINUOUS; - - /** @hide */ - @IntDef({LNB_POSITION_UNDEFINED, LNB_POSITION_A, LNB_POSITION_B}) - @Retention(RetentionPolicy.SOURCE) - public @interface LnbPosition {} - /** @hide */ - public static final int LNB_POSITION_UNDEFINED = Constants.LnbPosition.UNDEFINED; - /** @hide */ - public static final int LNB_POSITION_A = Constants.LnbPosition.POSITION_A; - /** @hide */ - public static final int LNB_POSITION_B = Constants.LnbPosition.POSITION_B; - - - /** @hide */ @IntDef({RESULT_SUCCESS, RESULT_UNAVAILABLE, RESULT_NOT_INITIALIZED, RESULT_INVALID_STATE, RESULT_INVALID_ARGUMENT, RESULT_OUT_OF_MEMORY, RESULT_UNKNOWN_ERROR}) @Retention(RetentionPolicy.SOURCE) public @interface Result {} - /** @hide */ + + /** + * Operation succeeded. + * @hide + */ public static final int RESULT_SUCCESS = Constants.Result.SUCCESS; - /** @hide */ + /** + * Operation failed because the corresponding resources are not available. + * @hide + */ public static final int RESULT_UNAVAILABLE = Constants.Result.UNAVAILABLE; - /** @hide */ + /** + * Operation failed because the corresponding resources are not initialized. + * @hide + */ public static final int RESULT_NOT_INITIALIZED = Constants.Result.NOT_INITIALIZED; - /** @hide */ + /** + * Operation failed because it's not in a valid state. + * @hide + */ public static final int RESULT_INVALID_STATE = Constants.Result.INVALID_STATE; - /** @hide */ + /** + * Operation failed because there are invalid arguments. + * @hide + */ public static final int RESULT_INVALID_ARGUMENT = Constants.Result.INVALID_ARGUMENT; - /** @hide */ + /** + * Memory allocation failed. + * @hide + */ public static final int RESULT_OUT_OF_MEMORY = Constants.Result.OUT_OF_MEMORY; - /** @hide */ + /** + * Operation failed due to unknown errors. + * @hide + */ public static final int RESULT_UNKNOWN_ERROR = Constants.Result.UNKNOWN_ERROR; private TunerConstants() { diff --git a/media/java/android/media/tv/tuner/TunerUtils.java b/media/java/android/media/tv/tuner/TunerUtils.java index a7ccb0288e03..8780b726e3b2 100644 --- a/media/java/android/media/tv/tuner/TunerUtils.java +++ b/media/java/android/media/tv/tuner/TunerUtils.java @@ -20,7 +20,8 @@ import android.content.Context; import android.content.pm.PackageManager; import android.hardware.tv.tuner.V1_0.Constants; import android.media.tv.tuner.TunerConstants.FilterSubtype; -import android.media.tv.tuner.TunerConstants.FilterType; +import android.media.tv.tuner.filter.FilterConfiguration; +import android.media.tv.tuner.filter.FilterConfiguration.FilterType; /** * Utility class for tuner framework. @@ -50,7 +51,7 @@ public final class TunerUtils { * @param subtype filter subtype. */ public static int getFilterSubtype(@FilterType int mainType, @FilterSubtype int subtype) { - if (mainType == TunerConstants.FILTER_TYPE_TS) { + if (mainType == FilterConfiguration.FILTER_TYPE_TS) { switch (subtype) { case TunerConstants.FILTER_SUBTYPE_UNDEFINED: return Constants.DemuxTsFilterType.UNDEFINED; @@ -73,7 +74,7 @@ public final class TunerUtils { default: break; } - } else if (mainType == TunerConstants.FILTER_TYPE_MMTP) { + } else if (mainType == FilterConfiguration.FILTER_TYPE_MMTP) { switch (subtype) { case TunerConstants.FILTER_SUBTYPE_UNDEFINED: return Constants.DemuxMmtpFilterType.UNDEFINED; @@ -95,7 +96,7 @@ public final class TunerUtils { break; } - } else if (mainType == TunerConstants.FILTER_TYPE_IP) { + } else if (mainType == FilterConfiguration.FILTER_TYPE_IP) { switch (subtype) { case TunerConstants.FILTER_SUBTYPE_UNDEFINED: return Constants.DemuxIpFilterType.UNDEFINED; @@ -112,7 +113,7 @@ public final class TunerUtils { default: break; } - } else if (mainType == TunerConstants.FILTER_TYPE_TLV) { + } else if (mainType == FilterConfiguration.FILTER_TYPE_TLV) { switch (subtype) { case TunerConstants.FILTER_SUBTYPE_UNDEFINED: return Constants.DemuxTlvFilterType.UNDEFINED; @@ -125,7 +126,7 @@ public final class TunerUtils { default: break; } - } else if (mainType == TunerConstants.FILTER_TYPE_ALP) { + } else if (mainType == FilterConfiguration.FILTER_TYPE_ALP) { switch (subtype) { case TunerConstants.FILTER_SUBTYPE_UNDEFINED: return Constants.DemuxAlpFilterType.UNDEFINED; diff --git a/media/java/android/media/tv/tuner/filter/AlpFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/AlpFilterConfiguration.java new file mode 100644 index 000000000000..f0fe533093ba --- /dev/null +++ b/media/java/android/media/tv/tuner/filter/AlpFilterConfiguration.java @@ -0,0 +1,35 @@ +/* + * 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.media.tv.tuner.filter; + +/** + * Filter configuration for a ALP filter. + * @hide + */ +public class AlpFilterConfiguration extends FilterConfiguration { + private int mPacketType; + private int mLengthType; + + public AlpFilterConfiguration(Settings settings) { + super(settings); + } + + @Override + public int getType() { + return FilterConfiguration.FILTER_TYPE_ALP; + } +} diff --git a/media/java/android/media/tv/tuner/filter/AvSettings.java b/media/java/android/media/tv/tuner/filter/AvSettings.java new file mode 100644 index 000000000000..a7c49d5585f8 --- /dev/null +++ b/media/java/android/media/tv/tuner/filter/AvSettings.java @@ -0,0 +1,36 @@ +/* + * 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.media.tv.tuner.filter; + +import android.media.tv.tuner.TunerConstants; +import android.media.tv.tuner.TunerUtils; + +/** + * Filter Settings for a Video and Audio. + * @hide + */ +public class AvSettings extends Settings { + private boolean mIsPassthrough; + + private AvSettings(int mainType, boolean isAudio) { + super(TunerUtils.getFilterSubtype( + mainType, + isAudio + ? TunerConstants.FILTER_SUBTYPE_AUDIO + : TunerConstants.FILTER_SUBTYPE_VIDEO)); + } +} diff --git a/media/java/android/media/tv/tuner/filter/DownloadSettings.java b/media/java/android/media/tv/tuner/filter/DownloadSettings.java new file mode 100644 index 000000000000..0742b1166ede --- /dev/null +++ b/media/java/android/media/tv/tuner/filter/DownloadSettings.java @@ -0,0 +1,32 @@ +/* + * 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.media.tv.tuner.filter; + +import android.media.tv.tuner.TunerConstants; +import android.media.tv.tuner.TunerUtils; + +/** + * Filter Settings for a Download. + * @hide + */ +public class DownloadSettings extends Settings { + private int mDownloadId; + + public DownloadSettings(int mainType) { + super(TunerUtils.getFilterSubtype(mainType, TunerConstants.FILTER_SUBTYPE_DOWNLOAD)); + } +} diff --git a/media/java/android/media/tv/tuner/filter/FilterConfiguration.java b/media/java/android/media/tv/tuner/filter/FilterConfiguration.java new file mode 100644 index 000000000000..99b10cde34f9 --- /dev/null +++ b/media/java/android/media/tv/tuner/filter/FilterConfiguration.java @@ -0,0 +1,78 @@ +/* + * 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.media.tv.tuner.filter; + +import android.annotation.IntDef; +import android.annotation.Nullable; +import android.hardware.tv.tuner.V1_0.Constants; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Filter configuration used to configure filters. + * + * @hide + */ +public abstract class FilterConfiguration { + + /** @hide */ + @IntDef({FILTER_TYPE_TS, FILTER_TYPE_MMTP, FILTER_TYPE_IP, FILTER_TYPE_TLV, FILTER_TYPE_ALP}) + @Retention(RetentionPolicy.SOURCE) + public @interface FilterType {} + + /** + * TS filter type. + */ + public static final int FILTER_TYPE_TS = Constants.DemuxFilterMainType.TS; + /** + * MMTP filter type. + */ + public static final int FILTER_TYPE_MMTP = Constants.DemuxFilterMainType.MMTP; + /** + * IP filter type. + */ + public static final int FILTER_TYPE_IP = Constants.DemuxFilterMainType.IP; + /** + * TLV filter type. + */ + public static final int FILTER_TYPE_TLV = Constants.DemuxFilterMainType.TLV; + /** + * ALP filter type. + */ + public static final int FILTER_TYPE_ALP = Constants.DemuxFilterMainType.ALP; + + @Nullable + private final Settings mSettings; + + /* package */ FilterConfiguration(Settings settings) { + mSettings = settings; + } + + /** + * Gets filter configuration type. + * @hide + */ + @FilterType + public abstract int getType(); + + /** @hide */ + @Nullable + public Settings getSettings() { + return mSettings; + } +} diff --git a/media/java/android/media/tv/tuner/filter/FilterEvent.java b/media/java/android/media/tv/tuner/filter/FilterEvent.java index 0a4fa8652e52..56a77d4cd229 100644 --- a/media/java/android/media/tv/tuner/filter/FilterEvent.java +++ b/media/java/android/media/tv/tuner/filter/FilterEvent.java @@ -16,10 +16,13 @@ package android.media.tv.tuner.filter; +import android.annotation.SystemApi; + /** - * Demux filter event. + * An entity class that is passed to the filter callbacks. * * @hide */ +@SystemApi public abstract class FilterEvent { } diff --git a/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java new file mode 100644 index 000000000000..c89636887628 --- /dev/null +++ b/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java @@ -0,0 +1,38 @@ +/* + * 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.media.tv.tuner.filter; + +/** + * Filter configuration for a IP filter. + * @hide + */ +public class IpFilterConfiguration extends FilterConfiguration { + private byte[] mSrcIpAddress; + private byte[] mDstIpAddress; + private int mSrcPort; + private int mDstPort; + private boolean mPassthrough; + + public IpFilterConfiguration(Settings settings) { + super(settings); + } + + @Override + public int getType() { + return FilterConfiguration.FILTER_TYPE_IP; + } +} diff --git a/media/java/android/media/tv/tuner/filter/MediaEvent.java b/media/java/android/media/tv/tuner/filter/MediaEvent.java index 5e45350950b6..7703248535e5 100644 --- a/media/java/android/media/tv/tuner/filter/MediaEvent.java +++ b/media/java/android/media/tv/tuner/filter/MediaEvent.java @@ -22,7 +22,7 @@ import android.os.NativeHandle; * Media event. * @hide */ -public class MediaEvent { +public class MediaEvent extends FilterEvent { private int mStreamId; private boolean mIsPtsPresent; private long mPts; diff --git a/media/java/android/media/tv/tuner/filter/MmtpFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/MmtpFilterConfiguration.java new file mode 100644 index 000000000000..9045ce67a61f --- /dev/null +++ b/media/java/android/media/tv/tuner/filter/MmtpFilterConfiguration.java @@ -0,0 +1,34 @@ +/* + * 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.media.tv.tuner.filter; + +/** + * Filter configuration for a MMTP filter. + * @hide + */ +public class MmtpFilterConfiguration extends FilterConfiguration { + private int mMmtpPid; + + public MmtpFilterConfiguration(Settings settings) { + super(settings); + } + + @Override + public int getType() { + return FilterConfiguration.FILTER_TYPE_MMTP; + } +} diff --git a/media/java/android/media/tv/tuner/filter/PesSettings.java b/media/java/android/media/tv/tuner/filter/PesSettings.java new file mode 100644 index 000000000000..f38abf12e120 --- /dev/null +++ b/media/java/android/media/tv/tuner/filter/PesSettings.java @@ -0,0 +1,92 @@ +/* + * 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.media.tv.tuner.filter; + +import android.annotation.NonNull; +import android.media.tv.tuner.TunerConstants; +import android.media.tv.tuner.TunerUtils; +import android.media.tv.tuner.filter.FilterConfiguration.FilterType; + +/** + * Filter Settings for a PES Data. + * + * @hide + */ +public class PesSettings extends Settings { + private final int mStreamId; + private final boolean mIsRaw; + + private PesSettings(@FilterType int mainType, int streamId, boolean isRaw) { + super(TunerUtils.getFilterSubtype(mainType, TunerConstants.FILTER_SUBTYPE_PES)); + mStreamId = streamId; + mIsRaw = isRaw; + } + + /** + * Creates a builder for {@link PesSettings}. + * + * @param mainType the filter main type of the settings. + */ + @NonNull + public static Builder newBuilder(@FilterType int mainType) { + return new Builder(mainType); + } + + /** + * Builder for {@link PesSettings}. + */ + public static class Builder { + private final int mMainType; + private int mStreamId; + private boolean mIsRaw; + + private Builder(int mainType) { + mMainType = mainType; + } + + /** + * Sets stream ID. + * + * @param streamId the stream ID. + */ + @NonNull + public Builder setStreamId(int streamId) { + mStreamId = streamId; + return this; + } + + /** + * Sets whether it's raw. + * + * @param isRaw {@code true} if the data is raw. Filter sends onFilterStatus callback + * instead of onFilterEvent for raw data. {@code false} otherwise. + */ + @NonNull + public Builder setIsRaw(boolean isRaw) { + mIsRaw = isRaw; + return this; + } + + /** + * Builds a {@link PesSettings} object. + */ + @NonNull + public PesSettings build() { + return new PesSettings(mMainType, mStreamId, mIsRaw); + } + } +} diff --git a/media/java/android/media/tv/tuner/filter/RecordSettings.java b/media/java/android/media/tv/tuner/filter/RecordSettings.java new file mode 100644 index 000000000000..701868afc789 --- /dev/null +++ b/media/java/android/media/tv/tuner/filter/RecordSettings.java @@ -0,0 +1,33 @@ +/* + * 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.media.tv.tuner.filter; + +import android.media.tv.tuner.TunerConstants; +import android.media.tv.tuner.TunerUtils; + +/** + * The Settings for the record in DVR. + * @hide + */ +public class RecordSettings extends Settings { + private int mIndexType; + private int mIndexMask; + + public RecordSettings(int mainType) { + super(TunerUtils.getFilterSubtype(mainType, TunerConstants.FILTER_SUBTYPE_RECORD)); + } +} diff --git a/media/java/android/media/tv/tuner/filter/SectionEvent.java b/media/java/android/media/tv/tuner/filter/SectionEvent.java index 5b5f8c91ce55..e211ddab0530 100644 --- a/media/java/android/media/tv/tuner/filter/SectionEvent.java +++ b/media/java/android/media/tv/tuner/filter/SectionEvent.java @@ -16,14 +16,54 @@ package android.media.tv.tuner.filter; +import android.annotation.SystemApi; +import android.media.tv.tuner.Tuner.Filter; + /** - * Section event. + * Filter event sent from {@link Filter} objects with section type. + * * @hide */ -public class SectionEvent { - // TODO: add constructor and getters - private int mTableId; - private int mVersion; - private int mSectionNum; - private int mDataLength; +@SystemApi +public class SectionEvent extends FilterEvent { + private final int mTableId; + private final int mVersion; + private final int mSectionNum; + private final int mDataLength; + + // This constructor is used by JNI code only + private SectionEvent(int tableId, int version, int sectionNum, int dataLength) { + mTableId = tableId; + mVersion = version; + mSectionNum = sectionNum; + mDataLength = dataLength; + } + + /** + * Gets table ID of filtered data. + */ + public int getTableId() { + return mTableId; + } + + /** + * Gets version number of filtered data. + */ + public int getVersion() { + return mVersion; + } + + /** + * Gets section number of filtered data. + */ + public int getSectionNumber() { + return mSectionNum; + } + + /** + * Gets data size in bytes of filtered data. + */ + public int getDataLength() { + return mDataLength; + } } diff --git a/media/java/android/media/tv/tuner/filter/SectionSettings.java b/media/java/android/media/tv/tuner/filter/SectionSettings.java new file mode 100644 index 000000000000..36e3d7cfee43 --- /dev/null +++ b/media/java/android/media/tv/tuner/filter/SectionSettings.java @@ -0,0 +1,31 @@ +/* + * 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.media.tv.tuner.filter; + +import android.media.tv.tuner.TunerConstants; +import android.media.tv.tuner.TunerUtils; + +/** + * Filter Settings for Section data according to ISO/IEC 13818-1. + * @hide + */ +public class SectionSettings extends Settings { + + SectionSettings(int mainType) { + super(TunerUtils.getFilterSubtype(mainType, TunerConstants.FILTER_SUBTYPE_SECTION)); + } +} diff --git a/media/java/android/media/tv/tuner/filter/SectionSettingsWithSectionBits.java b/media/java/android/media/tv/tuner/filter/SectionSettingsWithSectionBits.java new file mode 100644 index 000000000000..414ea6790bf5 --- /dev/null +++ b/media/java/android/media/tv/tuner/filter/SectionSettingsWithSectionBits.java @@ -0,0 +1,33 @@ +/* + * 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.media.tv.tuner.filter; + +import java.util.List; + +/** + * Bits Settings for Section Filter. + * @hide + */ +public class SectionSettingsWithSectionBits extends SectionSettings { + private List<Byte> mFilter; + private List<Byte> mMask; + private List<Byte> mMode; + + private SectionSettingsWithSectionBits(int mainType) { + super(mainType); + } +} diff --git a/media/java/android/media/tv/tuner/filter/SectionSettingsWithTableInfo.java b/media/java/android/media/tv/tuner/filter/SectionSettingsWithTableInfo.java new file mode 100644 index 000000000000..0df1d7308e60 --- /dev/null +++ b/media/java/android/media/tv/tuner/filter/SectionSettingsWithTableInfo.java @@ -0,0 +1,30 @@ +/* + * 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.media.tv.tuner.filter; + +/** + * Table information for Section Filter. + * @hide + */ +public class SectionSettingsWithTableInfo extends SectionSettings { + private int mTableId; + private int mVersion; + + private SectionSettingsWithTableInfo(int mainType) { + super(mainType); + } +} diff --git a/media/java/android/media/tv/tuner/filter/Settings.java b/media/java/android/media/tv/tuner/filter/Settings.java new file mode 100644 index 000000000000..146aca74ce0e --- /dev/null +++ b/media/java/android/media/tv/tuner/filter/Settings.java @@ -0,0 +1,39 @@ +/* + * 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.media.tv.tuner.filter; + +/** + * Settings for filters of different subtypes. + * + * @hide + */ +public abstract class Settings { + private final int mType; + + /* package */ Settings(int type) { + mType = type; + } + + /** + * Gets filter settings type. + * + * @hide + */ + public int getType() { + return mType; + } +} diff --git a/media/java/android/media/tv/tuner/filter/TlvFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/TlvFilterConfiguration.java new file mode 100644 index 000000000000..de8ee754a28c --- /dev/null +++ b/media/java/android/media/tv/tuner/filter/TlvFilterConfiguration.java @@ -0,0 +1,36 @@ +/* + * 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.media.tv.tuner.filter; + +/** + * Filter configuration for a TLV filter. + * @hide + */ +public class TlvFilterConfiguration extends FilterConfiguration { + private int mPacketType; + private boolean mIsCompressedIpPacket; + private boolean mPassthrough; + + public TlvFilterConfiguration(Settings settings) { + super(settings); + } + + @Override + public int getType() { + return FilterConfiguration.FILTER_TYPE_TLV; + } +} diff --git a/media/java/android/media/tv/tuner/filter/TsFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/TsFilterConfiguration.java new file mode 100644 index 000000000000..d0241b6aba09 --- /dev/null +++ b/media/java/android/media/tv/tuner/filter/TsFilterConfiguration.java @@ -0,0 +1,84 @@ +/* + * 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.media.tv.tuner.filter; + +import android.annotation.NonNull; + +/** + * Filter configuration for a TS filter. + * + * @hide + */ +public class TsFilterConfiguration extends FilterConfiguration { + private final int mTpid; + + private TsFilterConfiguration(Settings settings, int tpid) { + super(settings); + mTpid = tpid; + } + + @Override + public int getType() { + return FilterConfiguration.FILTER_TYPE_TS; + } + + /** + * Creates a builder for {@link TsFilterConfiguration}. + */ + @NonNull + public static Builder newBuilder() { + return new Builder(); + } + + /** + * Builder for {@link TsFilterConfiguration}. + */ + public static class Builder { + private Settings mSettings; + private int mTpid; + + /** + * Sets filter settings. + * + * @param settings the filter settings. + */ + @NonNull + public Builder setSettings(@NonNull Settings settings) { + mSettings = settings; + return this; + } + + /** + * Sets Tag Protocol ID. + * + * @param tpid the Tag Protocol ID. + */ + @NonNull + public Builder setTpid(int tpid) { + mTpid = tpid; + return this; + } + + /** + * Builds a {@link TsFilterConfiguration} object. + */ + @NonNull + public TsFilterConfiguration build() { + return new TsFilterConfiguration(mSettings, mTpid); + } + } +} diff --git a/media/java/android/media/tv/tuner/frontend/AnalogFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/AnalogFrontendCapabilities.java new file mode 100644 index 000000000000..2962e98790e5 --- /dev/null +++ b/media/java/android/media/tv/tuner/frontend/AnalogFrontendCapabilities.java @@ -0,0 +1,41 @@ +/* + * 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.media.tv.tuner.frontend; + +/** + * Analog Capabilities. + * @hide + */ +public class AnalogFrontendCapabilities extends FrontendCapabilities { + private final int mTypeCap; + private final int mSifStandardCap; + + AnalogFrontendCapabilities(int typeCap, int sifStandardCap) { + mTypeCap = typeCap; + mSifStandardCap = sifStandardCap; + } + /** + * Gets type capability. + */ + public int getTypeCapability() { + return mTypeCap; + } + /** Gets SIF standard capability. */ + public int getSifStandardCapability() { + return mSifStandardCap; + } +} diff --git a/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java new file mode 100644 index 000000000000..16308ced2300 --- /dev/null +++ b/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java @@ -0,0 +1,97 @@ +/* + * 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.media.tv.tuner.frontend; + +import android.media.tv.tuner.FrontendSettings; +import android.media.tv.tuner.TunerConstants; + +/** + * Frontend settings for analog. + * @hide + */ +public class AnalogFrontendSettings extends FrontendSettings { + private int mAnalogType; + private int mSifStandard; + + @Override + public int getType() { + return TunerConstants.FRONTEND_TYPE_ANALOG; + } + + public int getAnalogType() { + return mAnalogType; + } + + public int getSifStandard() { + return mSifStandard; + } + + /** + * Creates a new builder object. + */ + public static Builder newBuilder() { + return new Builder(); + } + + private AnalogFrontendSettings(int frequency, int analogType, int sifStandard) { + super(frequency); + mAnalogType = analogType; + mSifStandard = sifStandard; + } + + /** + * Builder for FrontendAnalogSettings. + */ + public static class Builder { + private int mFrequency; + private int mAnalogType; + private int mSifStandard; + + private Builder() {} + + /** + * Sets frequency. + */ + public Builder setFrequency(int frequency) { + mFrequency = frequency; + return this; + } + + /** + * Sets analog type. + */ + public Builder setAnalogType(int analogType) { + mAnalogType = analogType; + return this; + } + + /** + * Sets sif standard. + */ + public Builder setSifStandard(int sifStandard) { + mSifStandard = sifStandard; + return this; + } + + /** + * Builds a FrontendAnalogSettings instance. + */ + public AnalogFrontendSettings build() { + return new AnalogFrontendSettings(mFrequency, mAnalogType, mSifStandard); + } + } +} diff --git a/media/java/android/media/tv/tuner/frontend/Atsc3FrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/Atsc3FrontendCapabilities.java new file mode 100644 index 000000000000..677f9387c6d2 --- /dev/null +++ b/media/java/android/media/tv/tuner/frontend/Atsc3FrontendCapabilities.java @@ -0,0 +1,65 @@ +/* + * 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.media.tv.tuner.frontend; + +/** + * ATSC-3 Capabilities. + * @hide + */ +public class Atsc3FrontendCapabilities extends FrontendCapabilities { + private final int mBandwidthCap; + private final int mModulationCap; + private final int mTimeInterleaveModeCap; + private final int mCodeRateCap; + private final int mFecCap; + private final int mDemodOutputFormatCap; + + Atsc3FrontendCapabilities(int bandwidthCap, int modulationCap, int timeInterleaveModeCap, + int codeRateCap, int fecCap, int demodOutputFormatCap) { + mBandwidthCap = bandwidthCap; + mModulationCap = modulationCap; + mTimeInterleaveModeCap = timeInterleaveModeCap; + mCodeRateCap = codeRateCap; + mFecCap = fecCap; + mDemodOutputFormatCap = demodOutputFormatCap; + } + + /** Gets bandwidth capability. */ + public int getBandwidthCapability() { + return mBandwidthCap; + } + /** Gets modulation capability. */ + public int getModulationCapability() { + return mModulationCap; + } + /** Gets time interleave mod capability. */ + public int getTimeInterleaveModeCapability() { + return mTimeInterleaveModeCap; + } + /** Gets code rate capability. */ + public int getCodeRateCapability() { + return mCodeRateCap; + } + /** Gets FEC capability. */ + public int getFecCapability() { + return mFecCap; + } + /** Gets demodulator output format capability. */ + public int getDemodOutputFormatCapability() { + return mDemodOutputFormatCap; + } +} diff --git a/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java b/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java new file mode 100644 index 000000000000..bce8a640c7f2 --- /dev/null +++ b/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java @@ -0,0 +1,42 @@ +/* + * 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.media.tv.tuner.frontend; + + +import android.media.tv.tuner.FrontendSettings; +import android.media.tv.tuner.TunerConstants; + +import java.util.List; + +/** + * Frontend settings for ATSC-3. + * @hide + */ +public class Atsc3FrontendSettings extends FrontendSettings { + public int bandwidth; + public byte demodOutputFormat; + public List<Atsc3PlpSettings> plpSettings; + + Atsc3FrontendSettings(int frequency) { + super(frequency); + } + + @Override + public int getType() { + return TunerConstants.FRONTEND_TYPE_ATSC3; + } +} diff --git a/media/java/android/media/tv/tuner/frontend/Atsc3PlpSettings.java b/media/java/android/media/tv/tuner/frontend/Atsc3PlpSettings.java new file mode 100644 index 000000000000..61c6fec154a8 --- /dev/null +++ b/media/java/android/media/tv/tuner/frontend/Atsc3PlpSettings.java @@ -0,0 +1,29 @@ +/* + * 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.media.tv.tuner.frontend; + +/** + * PLP settings for ATSC-3. + * @hide + */ +public class Atsc3PlpSettings { + public byte plpId; + public int modulation; + public int interleaveMode; + public int codeRate; + public int fec; +} diff --git a/media/java/android/media/tv/tuner/frontend/AtscFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/AtscFrontendCapabilities.java new file mode 100644 index 000000000000..6ae3c632f5db --- /dev/null +++ b/media/java/android/media/tv/tuner/frontend/AtscFrontendCapabilities.java @@ -0,0 +1,33 @@ +/* + * 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.media.tv.tuner.frontend; + +/** + * ATSC Capabilities. + * @hide + */ +public class AtscFrontendCapabilities extends FrontendCapabilities { + private final int mModulationCap; + + AtscFrontendCapabilities(int modulationCap) { + mModulationCap = modulationCap; + } + /** Gets modulation capability. */ + public int getModulationCapability() { + return mModulationCap; + } +} diff --git a/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java new file mode 100644 index 000000000000..14c5cdd112e1 --- /dev/null +++ b/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java @@ -0,0 +1,37 @@ +/* + * 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.media.tv.tuner.frontend; + +import android.media.tv.tuner.FrontendSettings; +import android.media.tv.tuner.TunerConstants; + +/** + * Frontend settings for ATSC. + * @hide + */ +public class AtscFrontendSettings extends FrontendSettings { + public int modulation; + + AtscFrontendSettings(int frequency) { + super(frequency); + } + + @Override + public int getType() { + return TunerConstants.FRONTEND_TYPE_ATSC; + } +} diff --git a/media/java/android/media/tv/tuner/frontend/DvbcFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/DvbcFrontendCapabilities.java new file mode 100644 index 000000000000..edea7af06774 --- /dev/null +++ b/media/java/android/media/tv/tuner/frontend/DvbcFrontendCapabilities.java @@ -0,0 +1,46 @@ +/* + * 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.media.tv.tuner.frontend; + +/** + * DVBC Capabilities. + * @hide + */ +public class DvbcFrontendCapabilities extends FrontendCapabilities { + private final int mModulationCap; + private final int mFecCap; + private final int mAnnexCap; + + DvbcFrontendCapabilities(int modulationCap, int fecCap, int annexCap) { + mModulationCap = modulationCap; + mFecCap = fecCap; + mAnnexCap = annexCap; + } + + /** Gets modulation capability. */ + public int getModulationCapability() { + return mModulationCap; + } + /** Gets FEC capability. */ + public int getFecCapability() { + return mFecCap; + } + /** Gets annex capability. */ + public int getAnnexCapability() { + return mAnnexCap; + } +} diff --git a/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java new file mode 100644 index 000000000000..07e49ff24de1 --- /dev/null +++ b/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java @@ -0,0 +1,42 @@ +/* + * 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.media.tv.tuner.frontend; + +import android.media.tv.tuner.FrontendSettings; +import android.media.tv.tuner.TunerConstants; + +/** + * Frontend settings for DVBC. + * @hide + */ +public class DvbcFrontendSettings extends FrontendSettings { + public int modulation; + public long fec; + public int symbolRate; + public int outerFec; + public byte annex; + public int spectralInversion; + + DvbcFrontendSettings(int frequency) { + super(frequency); + } + + @Override + public int getType() { + return TunerConstants.FRONTEND_TYPE_DVBC; + } +} diff --git a/media/java/android/media/tv/tuner/frontend/DvbsCodeRate.java b/media/java/android/media/tv/tuner/frontend/DvbsCodeRate.java new file mode 100644 index 000000000000..bfa439154172 --- /dev/null +++ b/media/java/android/media/tv/tuner/frontend/DvbsCodeRate.java @@ -0,0 +1,28 @@ +/* + * 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.media.tv.tuner.frontend; + +/** + * Code rate for DVBS. + * @hide + */ +public class DvbsCodeRate { + public long fec; + public boolean isLinear; + public boolean isShortFrames; + public int bitsPer1000Symbol; +} diff --git a/media/java/android/media/tv/tuner/frontend/DvbsFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/DvbsFrontendCapabilities.java new file mode 100644 index 000000000000..f5a41574cd04 --- /dev/null +++ b/media/java/android/media/tv/tuner/frontend/DvbsFrontendCapabilities.java @@ -0,0 +1,46 @@ +/* + * 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.media.tv.tuner.frontend; + +/** + * DVBS Capabilities. + * @hide + */ +public class DvbsFrontendCapabilities extends FrontendCapabilities { + private final int mModulationCap; + private final long mInnerFecCap; + private final int mStandard; + + DvbsFrontendCapabilities(int modulationCap, long innerFecCap, int standard) { + mModulationCap = modulationCap; + mInnerFecCap = innerFecCap; + mStandard = standard; + } + + /** Gets modulation capability. */ + public int getModulationCapability() { + return mModulationCap; + } + /** Gets inner FEC capability. */ + public long getInnerFecCapability() { + return mInnerFecCap; + } + /** Gets DVBS standard capability. */ + public int getStandardCapability() { + return mStandard; + } +} diff --git a/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java new file mode 100644 index 000000000000..23c0a7b15e52 --- /dev/null +++ b/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java @@ -0,0 +1,43 @@ +/* + * 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.media.tv.tuner.frontend; + +import android.media.tv.tuner.FrontendSettings; +import android.media.tv.tuner.TunerConstants; + +/** + * Frontend settings for DVBS. + * @hide + */ +public class DvbsFrontendSettings extends FrontendSettings { + public int modulation; + public DvbsCodeRate coderate; + public int symbolRate; + public int rolloff; + public int pilot; + public int inputStreamId; + public byte standard; + + DvbsFrontendSettings(int frequency) { + super(frequency); + } + + @Override + public int getType() { + return TunerConstants.FRONTEND_TYPE_DVBS; + } +} diff --git a/media/java/android/media/tv/tuner/frontend/DvbtFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/DvbtFrontendCapabilities.java new file mode 100644 index 000000000000..e9c16ddd4dc8 --- /dev/null +++ b/media/java/android/media/tv/tuner/frontend/DvbtFrontendCapabilities.java @@ -0,0 +1,78 @@ +/* + * 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.media.tv.tuner.frontend; + +/** + * DVBT Capabilities. + * @hide + */ +public class DvbtFrontendCapabilities extends FrontendCapabilities { + private final int mTransmissionModeCap; + private final int mBandwidthCap; + private final int mConstellationCap; + private final int mCoderateCap; + private final int mHierarchyCap; + private final int mGuardIntervalCap; + private final boolean mIsT2Supported; + private final boolean mIsMisoSupported; + + DvbtFrontendCapabilities(int transmissionModeCap, int bandwidthCap, int constellationCap, + int coderateCap, int hierarchyCap, int guardIntervalCap, boolean isT2Supported, + boolean isMisoSupported) { + mTransmissionModeCap = transmissionModeCap; + mBandwidthCap = bandwidthCap; + mConstellationCap = constellationCap; + mCoderateCap = coderateCap; + mHierarchyCap = hierarchyCap; + mGuardIntervalCap = guardIntervalCap; + mIsT2Supported = isT2Supported; + mIsMisoSupported = isMisoSupported; + } + + /** Gets transmission mode capability. */ + public int getTransmissionModeCapability() { + return mTransmissionModeCap; + } + /** Gets bandwidth capability. */ + public int getBandwidthCapability() { + return mBandwidthCap; + } + /** Gets constellation capability. */ + public int getConstellationCapability() { + return mConstellationCap; + } + /** Gets code rate capability. */ + public int getCodeRateCapability() { + return mCoderateCap; + } + /** Gets hierarchy capability. */ + public int getHierarchyCapability() { + return mHierarchyCap; + } + /** Gets guard interval capability. */ + public int getGuardIntervalCapability() { + return mGuardIntervalCap; + } + /** Returns whether T2 is supported. */ + public boolean getIsT2Supported() { + return mIsT2Supported; + } + /** Returns whether MISO is supported. */ + public boolean getIsMisoSupported() { + return mIsMisoSupported; + } +} diff --git a/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java new file mode 100644 index 000000000000..eec00f3fab80 --- /dev/null +++ b/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java @@ -0,0 +1,50 @@ +/* + * 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.media.tv.tuner.frontend; + + +import android.media.tv.tuner.FrontendSettings; +import android.media.tv.tuner.TunerConstants; + +/** + * Frontend settings for DVBT. + * @hide + */ +public class DvbtFrontendSettings extends FrontendSettings { + public int transmissionMode; + public int bandwidth; + public int constellation; + public int hierarchy; + public int hpCoderate; + public int lpCoderate; + public int guardInterval; + public boolean isHighPriority; + public byte standard; + public boolean isMiso; + public int plpMode; + public byte plpId; + public byte plpGroupId; + + DvbtFrontendSettings(int frequency) { + super(frequency); + } + + @Override + public int getType() { + return TunerConstants.FRONTEND_TYPE_DVBT; + } +} diff --git a/media/java/android/media/tv/tuner/frontend/FrontendCallback.java b/media/java/android/media/tv/tuner/frontend/FrontendCallback.java new file mode 100644 index 000000000000..0992eb665330 --- /dev/null +++ b/media/java/android/media/tv/tuner/frontend/FrontendCallback.java @@ -0,0 +1,36 @@ +/* + * 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.media.tv.tuner.frontend; + +/** + * Frontend Callback. + * + * @hide + */ +public interface FrontendCallback { + + /** + * Invoked when there is a frontend event. + */ + void onEvent(int frontendEventType); + + /** + * Invoked when there is a scan message. + * @param msg + */ + void onScanMessage(ScanMessage msg); +} diff --git a/media/java/android/media/tv/tuner/frontend/FrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/FrontendCapabilities.java new file mode 100644 index 000000000000..7350bc0c3914 --- /dev/null +++ b/media/java/android/media/tv/tuner/frontend/FrontendCapabilities.java @@ -0,0 +1,24 @@ +/* + * 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.media.tv.tuner.frontend; + +/** + * Frontend Capabilities. + * @hide + */ +public abstract class FrontendCapabilities { +} diff --git a/media/java/android/media/tv/tuner/FrontendInfo.java b/media/java/android/media/tv/tuner/frontend/FrontendInfo.java index 2ab100dc3b08..5d03570eea80 100644 --- a/media/java/android/media/tv/tuner/FrontendInfo.java +++ b/media/java/android/media/tv/tuner/frontend/FrontendInfo.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.media.tv.tuner; +package android.media.tv.tuner.frontend; import android.media.tv.tuner.TunerConstants.FrontendType; diff --git a/media/java/android/media/tv/tuner/FrontendStatus.java b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java index f8b2d128d657..fb5d62afd2f2 100644 --- a/media/java/android/media/tv/tuner/FrontendStatus.java +++ b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java @@ -14,14 +14,15 @@ * limitations under the License. */ -package android.media.tv.tuner; +package android.media.tv.tuner.frontend; +import android.media.tv.tuner.Lnb; +import android.media.tv.tuner.TunerConstants; import android.media.tv.tuner.TunerConstants.FrontendDvbcSpectralInversion; import android.media.tv.tuner.TunerConstants.FrontendDvbtHierarchy; import android.media.tv.tuner.TunerConstants.FrontendInnerFec; import android.media.tv.tuner.TunerConstants.FrontendModulation; import android.media.tv.tuner.TunerConstants.FrontendStatusType; -import android.media.tv.tuner.TunerConstants.LnbVoltage; /** * Frontend status @@ -127,7 +128,7 @@ public class FrontendStatus { return (int) mValue; } /** Power Voltage Type for LNB. */ - @LnbVoltage + @Lnb.Voltage public int getLnbVoltage() { if (mType != TunerConstants.FRONTEND_STATUS_TYPE_LNB_VOLTAGE) { throw new IllegalStateException(); diff --git a/media/java/android/media/tv/tuner/frontend/IsdbcFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/IsdbcFrontendCapabilities.java new file mode 100644 index 000000000000..6544b17609c2 --- /dev/null +++ b/media/java/android/media/tv/tuner/frontend/IsdbcFrontendCapabilities.java @@ -0,0 +1,59 @@ +/* + * 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.media.tv.tuner.frontend; + +/** + * ISDBC Capabilities. + * @hide + */ +public class IsdbcFrontendCapabilities extends FrontendCapabilities { + private final int mModeCap; + private final int mBandwidthCap; + private final int mModulationCap; + private final int mCoderateCap; + private final int mGuardIntervalCap; + + IsdbcFrontendCapabilities(int modeCap, int bandwidthCap, int modulationCap, int coderateCap, + int guardIntervalCap) { + mModeCap = modeCap; + mBandwidthCap = bandwidthCap; + mModulationCap = modulationCap; + mCoderateCap = coderateCap; + mGuardIntervalCap = guardIntervalCap; + } + + /** Gets mode capability. */ + public int getModeCapability() { + return mModeCap; + } + /** Gets bandwidth capability. */ + public int getBandwidthCapability() { + return mBandwidthCap; + } + /** Gets modulation capability. */ + public int getModulationCapability() { + return mModulationCap; + } + /** Gets code rate capability. */ + public int getCodeRateCapability() { + return mCoderateCap; + } + /** Gets guard interval capability. */ + public int getGuardIntervalCapability() { + return mGuardIntervalCap; + } +} diff --git a/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendCapabilities.java new file mode 100644 index 000000000000..92832b7fcbdd --- /dev/null +++ b/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendCapabilities.java @@ -0,0 +1,40 @@ +/* + * 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.media.tv.tuner.frontend; + +/** + * ISDBS-3 Capabilities. + * @hide + */ +public class Isdbs3FrontendCapabilities extends FrontendCapabilities { + private final int mModulationCap; + private final int mCoderateCap; + + Isdbs3FrontendCapabilities(int modulationCap, int coderateCap) { + mModulationCap = modulationCap; + mCoderateCap = coderateCap; + } + + /** Gets modulation capability. */ + public int getModulationCapability() { + return mModulationCap; + } + /** Gets code rate capability. */ + public int getCodeRateCapability() { + return mCoderateCap; + } +} diff --git a/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java b/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java new file mode 100644 index 000000000000..736d0b199d29 --- /dev/null +++ b/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java @@ -0,0 +1,42 @@ +/* + * 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.media.tv.tuner.frontend; + +import android.media.tv.tuner.FrontendSettings; +import android.media.tv.tuner.TunerConstants; + +/** + * Frontend settings for ISDBS-3. + * @hide + */ +public class Isdbs3FrontendSettings extends FrontendSettings { + public int streamId; + public int streamIdType; + public int modulation; + public int coderate; + public int symbolRate; + public int rolloff; + + Isdbs3FrontendSettings(int frequency) { + super(frequency); + } + + @Override + public int getType() { + return TunerConstants.FRONTEND_TYPE_ISDBS3; + } +} diff --git a/media/java/android/media/tv/tuner/frontend/IsdbsFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/IsdbsFrontendCapabilities.java new file mode 100644 index 000000000000..b930b2578092 --- /dev/null +++ b/media/java/android/media/tv/tuner/frontend/IsdbsFrontendCapabilities.java @@ -0,0 +1,40 @@ +/* + * 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.media.tv.tuner.frontend; + +/** + * ISDBS Capabilities. + * @hide + */ +public class IsdbsFrontendCapabilities extends FrontendCapabilities { + private final int mModulationCap; + private final int mCoderateCap; + + IsdbsFrontendCapabilities(int modulationCap, int coderateCap) { + mModulationCap = modulationCap; + mCoderateCap = coderateCap; + } + + /** Gets modulation capability. */ + public int getModulationCapability() { + return mModulationCap; + } + /** Gets code rate capability. */ + public int getCodeRateCapability() { + return mCoderateCap; + } +} diff --git a/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java new file mode 100644 index 000000000000..7fd5da78c600 --- /dev/null +++ b/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java @@ -0,0 +1,42 @@ +/* + * 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.media.tv.tuner.frontend; + +import android.media.tv.tuner.FrontendSettings; +import android.media.tv.tuner.TunerConstants; + +/** + * Frontend settings for ISDBS. + * @hide + */ +public class IsdbsFrontendSettings extends FrontendSettings { + public int streamId; + public int streamIdType; + public int modulation; + public int coderate; + public int symbolRate; + public int rolloff; + + IsdbsFrontendSettings(int frequency) { + super(frequency); + } + + @Override + public int getType() { + return TunerConstants.FRONTEND_TYPE_ISDBS; + } +} diff --git a/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java new file mode 100644 index 000000000000..3f83267fe5b4 --- /dev/null +++ b/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java @@ -0,0 +1,42 @@ +/* + * 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.media.tv.tuner.frontend; + + +import android.media.tv.tuner.FrontendSettings; +import android.media.tv.tuner.TunerConstants; + +/** + * Frontend settings for ISDBT. + * @hide + */ +public class IsdbtFrontendSettings extends FrontendSettings { + public int modulation; + public int bandwidth; + public int coderate; + public int guardInterval; + public int serviceAreaId; + + IsdbtFrontendSettings(int frequency) { + super(frequency); + } + + @Override + public int getType() { + return TunerConstants.FRONTEND_TYPE_ISDBT; + } +} diff --git a/media/java/android/media/tv/tuner/ScanMessage.java b/media/java/android/media/tv/tuner/frontend/ScanMessage.java index 35f54f8447c0..dd687dd2959c 100644 --- a/media/java/android/media/tv/tuner/ScanMessage.java +++ b/media/java/android/media/tv/tuner/frontend/ScanMessage.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 The Android Open Source Project + * 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. @@ -14,9 +14,13 @@ * limitations under the License. */ -package android.media.tv.tuner; +package android.media.tv.tuner.frontend; -import android.media.tv.tuner.TunerConstants.ScanMessageType; +import android.annotation.IntDef; +import android.hardware.tv.tuner.V1_0.Constants; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** * Message from frontend during scan operations. @@ -24,6 +28,43 @@ import android.media.tv.tuner.TunerConstants.ScanMessageType; * @hide */ public class ScanMessage { + + /** @hide */ + @IntDef({ + LOCKED, + END, + PROGRESS_PERCENT, + FREQUENCY, + SYMBOL_RATE, + PLP_IDS, + GROUP_IDS, + INPUT_STREAM_IDS, + STANDARD, + ATSC3_PLP_INFO + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Type {} + /** @hide */ + public static final int LOCKED = Constants.FrontendScanMessageType.LOCKED; + /** @hide */ + public static final int END = Constants.FrontendScanMessageType.END; + /** @hide */ + public static final int PROGRESS_PERCENT = Constants.FrontendScanMessageType.PROGRESS_PERCENT; + /** @hide */ + public static final int FREQUENCY = Constants.FrontendScanMessageType.FREQUENCY; + /** @hide */ + public static final int SYMBOL_RATE = Constants.FrontendScanMessageType.SYMBOL_RATE; + /** @hide */ + public static final int PLP_IDS = Constants.FrontendScanMessageType.PLP_IDS; + /** @hide */ + public static final int GROUP_IDS = Constants.FrontendScanMessageType.GROUP_IDS; + /** @hide */ + public static final int INPUT_STREAM_IDS = Constants.FrontendScanMessageType.INPUT_STREAM_IDS; + /** @hide */ + public static final int STANDARD = Constants.FrontendScanMessageType.STANDARD; + /** @hide */ + public static final int ATSC3_PLP_INFO = Constants.FrontendScanMessageType.ATSC3_PLP_INFO; + private final int mType; private final Object mValue; @@ -33,69 +74,69 @@ public class ScanMessage { } /** Gets scan message type. */ - @ScanMessageType + @Type public int getMessageType() { return mType; } /** Message indicates whether frontend is locked or not. */ public boolean getIsLocked() { - if (mType != TunerConstants.SCAN_MESSAGE_TYPE_LOCKED) { + if (mType != LOCKED) { throw new IllegalStateException(); } return (Boolean) mValue; } /** Message indicates whether the scan has reached the end or not. */ public boolean getIsEnd() { - if (mType != TunerConstants.SCAN_MESSAGE_TYPE_END) { + if (mType != END) { throw new IllegalStateException(); } return (Boolean) mValue; } /** Progress message in percent. */ public int getProgressPercent() { - if (mType != TunerConstants.SCAN_MESSAGE_TYPE_PROGRESS_PERCENT) { + if (mType != PROGRESS_PERCENT) { throw new IllegalStateException(); } return (Integer) mValue; } /** Gets frequency. */ public int getFrequency() { - if (mType != TunerConstants.SCAN_MESSAGE_TYPE_FREQUENCY) { + if (mType != FREQUENCY) { throw new IllegalStateException(); } return (Integer) mValue; } /** Gets symbol rate. */ public int getSymbolRate() { - if (mType != TunerConstants.SCAN_MESSAGE_TYPE_SYMBOL_RATE) { + if (mType != SYMBOL_RATE) { throw new IllegalStateException(); } return (Integer) mValue; } /** Gets PLP IDs. */ public int[] getPlpIds() { - if (mType != TunerConstants.SCAN_MESSAGE_TYPE_PLP_IDS) { + if (mType != PLP_IDS) { throw new IllegalStateException(); } return (int[]) mValue; } /** Gets group IDs. */ public int[] getGroupIds() { - if (mType != TunerConstants.SCAN_MESSAGE_TYPE_GROUP_IDS) { + if (mType != GROUP_IDS) { throw new IllegalStateException(); } return (int[]) mValue; } /** Gets Input stream IDs. */ public int[] getInputStreamIds() { - if (mType != TunerConstants.SCAN_MESSAGE_TYPE_INPUT_STREAM_IDS) { + if (mType != INPUT_STREAM_IDS) { throw new IllegalStateException(); } return (int[]) mValue; } /** Gets the DVB-T or DVB-S standard. */ public int getStandard() { - if (mType != TunerConstants.SCAN_MESSAGE_TYPE_STANDARD) { + if (mType != STANDARD) { throw new IllegalStateException(); } return (int) mValue; @@ -103,7 +144,7 @@ public class ScanMessage { /** Gets PLP information for ATSC3. */ public Atsc3PlpInfo[] getAtsc3PlpInfos() { - if (mType != TunerConstants.SCAN_MESSAGE_TYPE_ATSC3_PLP_INFO) { + if (mType != ATSC3_PLP_INFO) { throw new IllegalStateException(); } return (Atsc3PlpInfo[]) mValue; diff --git a/media/java/android/mtp/MtpPropertyList.java b/media/java/android/mtp/MtpPropertyList.java index 557f099c25c1..53d838d84518 100644 --- a/media/java/android/mtp/MtpPropertyList.java +++ b/media/java/android/mtp/MtpPropertyList.java @@ -16,7 +16,8 @@ package android.mtp; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; + import java.util.ArrayList; import java.util.List; diff --git a/media/java/android/mtp/MtpStorage.java b/media/java/android/mtp/MtpStorage.java index c7dbca61f90a..ba752633718c 100644 --- a/media/java/android/mtp/MtpStorage.java +++ b/media/java/android/mtp/MtpStorage.java @@ -16,9 +16,8 @@ package android.mtp; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.os.storage.StorageVolume; -import android.provider.MediaStore; /** * This class represents a storage unit on an MTP device. diff --git a/media/java/android/service/media/MediaBrowserService.java b/media/java/android/service/media/MediaBrowserService.java index 86a1076af122..06adf30a8303 100644 --- a/media/java/android/service/media/MediaBrowserService.java +++ b/media/java/android/service/media/MediaBrowserService.java @@ -21,8 +21,8 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; -import android.annotation.UnsupportedAppUsage; import android.app.Service; +import android.compat.annotation.UnsupportedAppUsage; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; diff --git a/media/jni/Android.bp b/media/jni/Android.bp index ee6761344613..536a061190d7 100644 --- a/media/jni/Android.bp +++ b/media/jni/Android.bp @@ -143,6 +143,10 @@ cc_library_shared { "libutils", ], + header_libs: [ + "libstagefright_foundation_headers", + ], + export_include_dirs: ["."], cflags: [ diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp index 3562782eb275..4f1125f5e482 100644 --- a/media/jni/android_media_tv_Tuner.cpp +++ b/media/jni/android_media_tv_Tuner.cpp @@ -100,7 +100,7 @@ void DvrCallback::setDvr(const jobject dvr) { /////////////// Dvr /////////////////////// -Dvr::Dvr(sp<IDvr> sp, jweak obj) : mDvrSp(sp), mDvrObj(obj) {} +Dvr::Dvr(sp<IDvr> sp, jweak obj) : mDvrSp(sp), mDvrObj(obj), mDvrMQEventFlag(nullptr) {} Dvr::~Dvr() { EventFlag::deleteEventFlag(&mDvrMQEventFlag); @@ -696,6 +696,10 @@ static jobject android_media_tv_Tuner_open_filter( return tuner->openFilter(filterType, bufferSize); } +static jobject android_media_tv_Tuner_open_time_filter(JNIEnv, jobject) { + return NULL; +} + static DemuxFilterSettings getFilterSettings( JNIEnv *env, int type, int subtype, jobject filterSettingsObj) { DemuxFilterSettings filterSettings; @@ -840,6 +844,28 @@ static int android_media_tv_Tuner_close_filter(JNIEnv*, jobject) { return 0; } +// TODO: implement TimeFilter functions +static int android_media_tv_Tuner_time_filter_set_timestamp( + JNIEnv, jobject, jlong) { + return 0; +} + +static int android_media_tv_Tuner_time_filter_clear_timestamp(JNIEnv, jobject) { + return 0; +} + +static jobject android_media_tv_Tuner_time_filter_get_timestamp(JNIEnv, jobject) { + return NULL; +} + +static jobject android_media_tv_Tuner_time_filter_get_source_time(JNIEnv, jobject) { + return NULL; +} + +static int android_media_tv_Tuner_time_filter_close(JNIEnv, jobject) { + return 0; +} + static jobject android_media_tv_Tuner_open_descrambler(JNIEnv *env, jobject thiz) { sp<JTuner> tuner = getTuner(env, thiz); return tuner->openDescrambler(); @@ -880,6 +906,10 @@ static jobject android_media_tv_Tuner_open_dvr(JNIEnv *env, jobject thiz, jint t return tuner->openDvr(static_cast<DvrType>(type), bufferSize); } +static jobject android_media_tv_Tuner_get_demux_caps(JNIEnv*, jobject) { + return NULL; +} + static int android_media_tv_Tuner_attach_filter(JNIEnv *env, jobject dvr, jobject filter) { sp<IDvr> dvrSp = getDvr(env, dvr)->getIDvr(); sp<IFilter> filterSp = getFilter(env, filter)->getIFilter(); @@ -1115,6 +1145,8 @@ static const JNINativeMethod gTunerMethods[] = { (void *)android_media_tv_Tuner_get_frontend_info }, { "nativeOpenFilter", "(III)Landroid/media/tv/tuner/Tuner$Filter;", (void *)android_media_tv_Tuner_open_filter }, + { "nativeOpenTimeFilter", "()Landroid/media/tv/tuner/Tuner$TimeFilter;", + (void *)android_media_tv_Tuner_open_time_filter }, { "nativeGetLnbIds", "()Ljava/util/List;", (void *)android_media_tv_Tuner_get_lnb_ids }, { "nativeOpenLnbById", "(I)Landroid/media/tv/tuner/Tuner$Lnb;", @@ -1123,6 +1155,8 @@ static const JNINativeMethod gTunerMethods[] = { (void *)android_media_tv_Tuner_open_descrambler }, { "nativeOpenDvr", "(II)Landroid/media/tv/tuner/Tuner$Dvr;", (void *)android_media_tv_Tuner_open_dvr }, + { "nativeGetDemuxCapabilities", "()Landroid/media/tv/tuner/DemuxCapabilities;", + (void *)android_media_tv_Tuner_get_demux_caps }, }; static const JNINativeMethod gFilterMethods[] = { @@ -1138,6 +1172,16 @@ static const JNINativeMethod gFilterMethods[] = { { "nativeClose", "()I", (void *)android_media_tv_Tuner_close_filter }, }; +static const JNINativeMethod gTimeFilterMethods[] = { + { "nativeSetTimeStamp", "(J)I", (void *)android_media_tv_Tuner_time_filter_set_timestamp }, + { "nativeClearTimeStamp", "()I", (void *)android_media_tv_Tuner_time_filter_clear_timestamp }, + { "nativeGetTimeStamp", "()Ljava/lang/Long;", + (void *)android_media_tv_Tuner_time_filter_get_timestamp }, + { "nativeGetSourceTime", "()Ljava/lang/Long;", + (void *)android_media_tv_Tuner_time_filter_get_source_time }, + { "nativeClose", "()I", (void *)android_media_tv_Tuner_time_filter_close }, +}; + static const JNINativeMethod gDescramblerMethods[] = { { "nativeAddPid", "(IILandroid/media/tv/tuner/Tuner$Filter;)I", (void *)android_media_tv_Tuner_add_pid }, @@ -1188,6 +1232,13 @@ static bool register_android_media_tv_Tuner(JNIEnv *env) { return false; } if (AndroidRuntime::registerNativeMethods( + env, "android/media/tv/tuner/Tuner$TimeFilter", + gTimeFilterMethods, + NELEM(gTimeFilterMethods)) != JNI_OK) { + ALOGE("Failed to register time filter native methods"); + return false; + } + if (AndroidRuntime::registerNativeMethods( env, "android/media/tv/tuner/Tuner$Descrambler", gDescramblerMethods, NELEM(gDescramblerMethods)) != JNI_OK) { diff --git a/media/jni/audioeffect/android_media_AudioEffect.cpp b/media/jni/audioeffect/android_media_AudioEffect.cpp index 747d4c01867e..007dd10aba1f 100644 --- a/media/jni/audioeffect/android_media_AudioEffect.cpp +++ b/media/jni/audioeffect/android_media_AudioEffect.cpp @@ -268,8 +268,9 @@ android_media_AudioEffect_native_init(JNIEnv *env) static jint android_media_AudioEffect_native_setup(JNIEnv *env, jobject thiz, jobject weak_this, - jstring type, jstring uuid, jint priority, jint sessionId, jintArray jId, - jobjectArray javadesc, jstring opPackageName) + jstring type, jstring uuid, jint priority, jint sessionId, + jint deviceType, jstring deviceAddress, + jintArray jId, jobjectArray javadesc, jstring opPackageName) { ALOGV("android_media_AudioEffect_native_setup"); AudioEffectJniStorage* lpJniStorage = NULL; @@ -280,6 +281,7 @@ android_media_AudioEffect_native_setup(JNIEnv *env, jobject thiz, jobject weak_t const char *uuidStr = NULL; effect_descriptor_t desc; jobject jdesc; + AudioDeviceTypeAddr device; ScopedUtfChars opPackageNameStr(env, opPackageName); @@ -328,6 +330,12 @@ android_media_AudioEffect_native_setup(JNIEnv *env, jobject thiz, jobject weak_t goto setup_failure; } + if (deviceType != AUDIO_DEVICE_NONE) { + device.mType = deviceType; + ScopedUtfChars address(env, deviceAddress); + device.mAddress = address.c_str(); + } + // create the native AudioEffect object lpAudioEffect = new AudioEffect(typeStr, String16(opPackageNameStr.c_str()), @@ -336,7 +344,8 @@ android_media_AudioEffect_native_setup(JNIEnv *env, jobject thiz, jobject weak_t effectCallback, &lpJniStorage->mCallbackData, (audio_session_t) sessionId, - AUDIO_IO_HANDLE_NONE); + AUDIO_IO_HANDLE_NONE, + device); if (lpAudioEffect == 0) { ALOGE("Error creating AudioEffect"); goto setup_failure; @@ -757,7 +766,7 @@ android_media_AudioEffect_native_queryPreProcessings(JNIEnv *env, jclass clazz _ // Dalvik VM type signatures static const JNINativeMethod gMethods[] = { {"native_init", "()V", (void *)android_media_AudioEffect_native_init}, - {"native_setup", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;II[I[Ljava/lang/Object;Ljava/lang/String;)I", + {"native_setup", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;IIILjava/lang/String;[I[Ljava/lang/Object;Ljava/lang/String;)I", (void *)android_media_AudioEffect_native_setup}, {"native_finalize", "()V", (void *)android_media_AudioEffect_native_finalize}, {"native_release", "()V", (void *)android_media_AudioEffect_native_release}, diff --git a/media/mca/effect/java/android/media/effect/SingleFilterEffect.java b/media/mca/effect/java/android/media/effect/SingleFilterEffect.java index dfbf5d20e074..121443f56285 100644 --- a/media/mca/effect/java/android/media/effect/SingleFilterEffect.java +++ b/media/mca/effect/java/android/media/effect/SingleFilterEffect.java @@ -17,12 +17,11 @@ package android.media.effect; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.filterfw.core.Filter; import android.filterfw.core.FilterFactory; import android.filterfw.core.FilterFunction; import android.filterfw.core.Frame; -import android.media.effect.EffectContext; /** * Effect subclass for effects based on a single Filter. Subclasses need only invoke the diff --git a/media/mca/filterfw/java/android/filterfw/GraphEnvironment.java b/media/mca/filterfw/java/android/filterfw/GraphEnvironment.java index 52615bf09faa..3a7f1ed4f7ec 100644 --- a/media/mca/filterfw/java/android/filterfw/GraphEnvironment.java +++ b/media/mca/filterfw/java/android/filterfw/GraphEnvironment.java @@ -17,11 +17,11 @@ package android.filterfw; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.filterfw.core.AsyncRunner; -import android.filterfw.core.FilterGraph; import android.filterfw.core.FilterContext; +import android.filterfw.core.FilterGraph; import android.filterfw.core.FrameManager; import android.filterfw.core.GraphRunner; import android.filterfw.core.RoundRobinScheduler; diff --git a/media/mca/filterfw/java/android/filterfw/core/Filter.java b/media/mca/filterfw/java/android/filterfw/core/Filter.java index 4f56b923f6ed..a608ef5be3f4 100644 --- a/media/mca/filterfw/java/android/filterfw/core/Filter.java +++ b/media/mca/filterfw/java/android/filterfw/core/Filter.java @@ -17,19 +17,15 @@ package android.filterfw.core; -import android.annotation.UnsupportedAppUsage; -import android.filterfw.core.FilterContext; -import android.filterfw.core.FilterPort; -import android.filterfw.core.KeyValueMap; -import android.filterfw.io.TextGraphReader; -import android.filterfw.io.GraphIOException; +import android.compat.annotation.UnsupportedAppUsage; import android.filterfw.format.ObjectFormat; +import android.filterfw.io.GraphIOException; +import android.filterfw.io.TextGraphReader; import android.util.Log; import java.io.Serializable; import java.lang.annotation.Annotation; import java.lang.reflect.Field; -import java.lang.Thread; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; diff --git a/media/mca/filterfw/java/android/filterfw/core/FilterContext.java b/media/mca/filterfw/java/android/filterfw/core/FilterContext.java index a19220ef85f8..6b0a2193dceb 100644 --- a/media/mca/filterfw/java/android/filterfw/core/FilterContext.java +++ b/media/mca/filterfw/java/android/filterfw/core/FilterContext.java @@ -17,11 +17,7 @@ package android.filterfw.core; -import android.annotation.UnsupportedAppUsage; -import android.filterfw.core.Filter; -import android.filterfw.core.Frame; -import android.filterfw.core.FrameManager; -import android.filterfw.core.GLEnvironment; +import android.compat.annotation.UnsupportedAppUsage; import java.util.HashMap; import java.util.HashSet; diff --git a/media/mca/filterfw/java/android/filterfw/core/FilterGraph.java b/media/mca/filterfw/java/android/filterfw/core/FilterGraph.java index e6ca11ffca3c..35a298fd6dfb 100644 --- a/media/mca/filterfw/java/android/filterfw/core/FilterGraph.java +++ b/media/mca/filterfw/java/android/filterfw/core/FilterGraph.java @@ -17,6 +17,11 @@ package android.filterfw.core; +import android.compat.annotation.UnsupportedAppUsage; +import android.filterpacks.base.FrameBranch; +import android.filterpacks.base.NullFilter; +import android.util.Log; + import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -25,14 +30,6 @@ import java.util.Map.Entry; import java.util.Set; import java.util.Stack; -import android.filterfw.core.FilterContext; -import android.filterfw.core.KeyValueMap; -import android.filterpacks.base.FrameBranch; -import android.filterpacks.base.NullFilter; - -import android.annotation.UnsupportedAppUsage; -import android.util.Log; - /** * @hide */ diff --git a/media/mca/filterfw/java/android/filterfw/core/Frame.java b/media/mca/filterfw/java/android/filterfw/core/Frame.java index e880783247a3..c4d935ae4873 100644 --- a/media/mca/filterfw/java/android/filterfw/core/Frame.java +++ b/media/mca/filterfw/java/android/filterfw/core/Frame.java @@ -17,9 +17,7 @@ package android.filterfw.core; -import android.annotation.UnsupportedAppUsage; -import android.filterfw.core.FrameFormat; -import android.filterfw.core.FrameManager; +import android.compat.annotation.UnsupportedAppUsage; import android.graphics.Bitmap; import java.nio.ByteBuffer; diff --git a/media/mca/filterfw/java/android/filterfw/core/FrameFormat.java b/media/mca/filterfw/java/android/filterfw/core/FrameFormat.java index eb0ff0a32c3f..a87e9b9ffbcf 100644 --- a/media/mca/filterfw/java/android/filterfw/core/FrameFormat.java +++ b/media/mca/filterfw/java/android/filterfw/core/FrameFormat.java @@ -17,9 +17,7 @@ package android.filterfw.core; -import android.annotation.UnsupportedAppUsage; -import android.filterfw.core.KeyValueMap; -import android.filterfw.core.MutableFrameFormat; +import android.compat.annotation.UnsupportedAppUsage; import java.util.Arrays; import java.util.Map.Entry; diff --git a/media/mca/filterfw/java/android/filterfw/core/FrameManager.java b/media/mca/filterfw/java/android/filterfw/core/FrameManager.java index 85c8fcd9787d..e49aaf1d6fad 100644 --- a/media/mca/filterfw/java/android/filterfw/core/FrameManager.java +++ b/media/mca/filterfw/java/android/filterfw/core/FrameManager.java @@ -17,10 +17,7 @@ package android.filterfw.core; -import android.annotation.UnsupportedAppUsage; -import android.filterfw.core.Frame; -import android.filterfw.core.FrameFormat; -import android.filterfw.core.MutableFrameFormat; +import android.compat.annotation.UnsupportedAppUsage; /** * @hide diff --git a/media/mca/filterfw/java/android/filterfw/core/GLEnvironment.java b/media/mca/filterfw/java/android/filterfw/core/GLEnvironment.java index e25d6a7d70ab..7e4e8a64a81f 100644 --- a/media/mca/filterfw/java/android/filterfw/core/GLEnvironment.java +++ b/media/mca/filterfw/java/android/filterfw/core/GLEnvironment.java @@ -17,13 +17,12 @@ package android.filterfw.core; -import android.annotation.UnsupportedAppUsage; -import android.filterfw.core.NativeAllocatorTag; +import android.compat.annotation.UnsupportedAppUsage; import android.graphics.SurfaceTexture; +import android.media.MediaRecorder; import android.os.Looper; import android.util.Log; import android.view.Surface; -import android.media.MediaRecorder; /** * @hide diff --git a/media/mca/filterfw/java/android/filterfw/core/GLFrame.java b/media/mca/filterfw/java/android/filterfw/core/GLFrame.java index 9e3025fafb6e..1ccd7feaa7c3 100644 --- a/media/mca/filterfw/java/android/filterfw/core/GLFrame.java +++ b/media/mca/filterfw/java/android/filterfw/core/GLFrame.java @@ -17,15 +17,10 @@ package android.filterfw.core; -import android.annotation.UnsupportedAppUsage; -import android.filterfw.core.Frame; -import android.filterfw.core.FrameFormat; -import android.filterfw.core.FrameManager; -import android.filterfw.core.NativeFrame; -import android.filterfw.core.StopWatchMap; +import android.compat.annotation.UnsupportedAppUsage; import android.graphics.Bitmap; -import android.opengl.GLES20; import android.graphics.Rect; +import android.opengl.GLES20; import java.nio.ByteBuffer; diff --git a/media/mca/filterfw/java/android/filterfw/core/GraphRunner.java b/media/mca/filterfw/java/android/filterfw/core/GraphRunner.java index 250cfaaba9d4..b57e8bb7262e 100644 --- a/media/mca/filterfw/java/android/filterfw/core/GraphRunner.java +++ b/media/mca/filterfw/java/android/filterfw/core/GraphRunner.java @@ -17,7 +17,7 @@ package android.filterfw.core; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; /** * @hide diff --git a/media/mca/filterfw/java/android/filterfw/core/MutableFrameFormat.java b/media/mca/filterfw/java/android/filterfw/core/MutableFrameFormat.java index ae2ad99899f0..da00b1ffb180 100644 --- a/media/mca/filterfw/java/android/filterfw/core/MutableFrameFormat.java +++ b/media/mca/filterfw/java/android/filterfw/core/MutableFrameFormat.java @@ -17,9 +17,7 @@ package android.filterfw.core; -import android.annotation.UnsupportedAppUsage; -import android.filterfw.core.FrameFormat; -import android.filterfw.core.KeyValueMap; +import android.compat.annotation.UnsupportedAppUsage; import java.util.Arrays; diff --git a/media/mca/filterfw/java/android/filterfw/core/Program.java b/media/mca/filterfw/java/android/filterfw/core/Program.java index 376c08554eb2..145388e4437e 100644 --- a/media/mca/filterfw/java/android/filterfw/core/Program.java +++ b/media/mca/filterfw/java/android/filterfw/core/Program.java @@ -17,8 +17,7 @@ package android.filterfw.core; -import android.annotation.UnsupportedAppUsage; -import android.filterfw.core.Frame; +import android.compat.annotation.UnsupportedAppUsage; /** * @hide diff --git a/media/mca/filterfw/java/android/filterfw/core/ShaderProgram.java b/media/mca/filterfw/java/android/filterfw/core/ShaderProgram.java index f41636e7cf76..e043be0e27bd 100644 --- a/media/mca/filterfw/java/android/filterfw/core/ShaderProgram.java +++ b/media/mca/filterfw/java/android/filterfw/core/ShaderProgram.java @@ -17,12 +17,7 @@ package android.filterfw.core; -import android.annotation.UnsupportedAppUsage; -import android.filterfw.core.Frame; -import android.filterfw.core.NativeAllocatorTag; -import android.filterfw.core.Program; -import android.filterfw.core.StopWatchMap; -import android.filterfw.core.VertexFrame; +import android.compat.annotation.UnsupportedAppUsage; import android.filterfw.geometry.Quad; import android.opengl.GLES20; diff --git a/media/mca/filterfw/java/android/filterfw/format/ImageFormat.java b/media/mca/filterfw/java/android/filterfw/format/ImageFormat.java index ac087305287f..0e05092d0cdd 100644 --- a/media/mca/filterfw/java/android/filterfw/format/ImageFormat.java +++ b/media/mca/filterfw/java/android/filterfw/format/ImageFormat.java @@ -17,7 +17,7 @@ package android.filterfw.format; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.filterfw.core.FrameFormat; import android.filterfw.core.MutableFrameFormat; import android.graphics.Bitmap; diff --git a/media/mca/filterfw/java/android/filterfw/geometry/Point.java b/media/mca/filterfw/java/android/filterfw/geometry/Point.java index d7acf12dd1de..96d2d7b08b74 100644 --- a/media/mca/filterfw/java/android/filterfw/geometry/Point.java +++ b/media/mca/filterfw/java/android/filterfw/geometry/Point.java @@ -17,8 +17,7 @@ package android.filterfw.geometry; -import android.annotation.UnsupportedAppUsage; -import java.lang.Math; +import android.compat.annotation.UnsupportedAppUsage; /** * @hide diff --git a/media/mca/filterfw/java/android/filterfw/geometry/Quad.java b/media/mca/filterfw/java/android/filterfw/geometry/Quad.java index 610e5b80399d..2b308a91576f 100644 --- a/media/mca/filterfw/java/android/filterfw/geometry/Quad.java +++ b/media/mca/filterfw/java/android/filterfw/geometry/Quad.java @@ -17,10 +17,8 @@ package android.filterfw.geometry; -import android.annotation.UnsupportedAppUsage; -import android.filterfw.geometry.Point; +import android.compat.annotation.UnsupportedAppUsage; -import java.lang.Float; import java.util.Arrays; import java.util.Collections; import java.util.List; diff --git a/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java b/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java index 04fccc7e0f94..8c0273b06e8c 100644 --- a/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java +++ b/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java @@ -46,8 +46,8 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService public static final String ROUTE_ID5_TO_TRANSFER_TO = "route_id5_to_transfer_to"; public static final String ROUTE_NAME5 = "Sample Route 5 - Route to transfer to"; - public static final String ROUTE_ID_SPECIAL_CATEGORY = "route_special_category"; - public static final String ROUTE_NAME_SPECIAL_CATEGORY = "Special Category Route"; + public static final String ROUTE_ID_SPECIAL_TYPE = "route_special_type"; + public static final String ROUTE_NAME_SPECIAL_TYPE = "Special Type Route"; public static final int VOLUME_MAX = 100; public static final String ROUTE_ID_FIXED_VOLUME = "route_fixed_volume"; @@ -58,49 +58,49 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService public static final String ACTION_REMOVE_ROUTE = "com.android.mediarouteprovider.action_remove_route"; - public static final String CATEGORY_SAMPLE = - "com.android.mediarouteprovider.CATEGORY_SAMPLE"; - public static final String CATEGORY_SPECIAL = - "com.android.mediarouteprovider.CATEGORY_SPECIAL"; + public static final String TYPE_SAMPLE = + "com.android.mediarouteprovider.TYPE_SAMPLE"; + public static final String TYPE_SPECIAL = + "com.android.mediarouteprovider.TYPE_SPECIAL"; Map<String, MediaRoute2Info> mRoutes = new HashMap<>(); - Map<String, Integer> mRouteSessionMap = new HashMap<>(); + Map<String, String> mRouteSessionMap = new HashMap<>(); private int mNextSessionId = 1000; private void initializeRoutes() { MediaRoute2Info route1 = new MediaRoute2Info.Builder(ROUTE_ID1, ROUTE_NAME1) - .addSupportedCategory(CATEGORY_SAMPLE) + .addRouteType(TYPE_SAMPLE) .setDeviceType(DEVICE_TYPE_TV) .build(); MediaRoute2Info route2 = new MediaRoute2Info.Builder(ROUTE_ID2, ROUTE_NAME2) - .addSupportedCategory(CATEGORY_SAMPLE) + .addRouteType(TYPE_SAMPLE) .setDeviceType(DEVICE_TYPE_SPEAKER) .build(); MediaRoute2Info route3 = new MediaRoute2Info.Builder( ROUTE_ID3_SESSION_CREATION_FAILED, ROUTE_NAME3) - .addSupportedCategory(CATEGORY_SAMPLE) + .addRouteType(TYPE_SAMPLE) .build(); MediaRoute2Info route4 = new MediaRoute2Info.Builder( ROUTE_ID4_TO_SELECT_AND_DESELECT, ROUTE_NAME4) - .addSupportedCategory(CATEGORY_SAMPLE) + .addRouteType(TYPE_SAMPLE) .build(); MediaRoute2Info route5 = new MediaRoute2Info.Builder( ROUTE_ID5_TO_TRANSFER_TO, ROUTE_NAME5) - .addSupportedCategory(CATEGORY_SAMPLE) + .addRouteType(TYPE_SAMPLE) .build(); MediaRoute2Info routeSpecial = - new MediaRoute2Info.Builder(ROUTE_ID_SPECIAL_CATEGORY, ROUTE_NAME_SPECIAL_CATEGORY) - .addSupportedCategory(CATEGORY_SAMPLE) - .addSupportedCategory(CATEGORY_SPECIAL) + new MediaRoute2Info.Builder(ROUTE_ID_SPECIAL_TYPE, ROUTE_NAME_SPECIAL_TYPE) + .addRouteType(TYPE_SAMPLE) + .addRouteType(TYPE_SPECIAL) .build(); MediaRoute2Info fixedVolumeRoute = new MediaRoute2Info.Builder(ROUTE_ID_FIXED_VOLUME, ROUTE_NAME_FIXED_VOLUME) - .addSupportedCategory(CATEGORY_SAMPLE) + .addRouteType(TYPE_SAMPLE) .setVolumeHandling(MediaRoute2Info.PLAYBACK_VOLUME_FIXED) .build(); MediaRoute2Info variableVolumeRoute = new MediaRoute2Info.Builder(ROUTE_ID_VARIABLE_VOLUME, ROUTE_NAME_VARIABLE_VOLUME) - .addSupportedCategory(CATEGORY_SAMPLE) + .addRouteType(TYPE_SAMPLE) .setVolumeHandling(MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE) .setVolumeMax(VOLUME_MAX) .build(); @@ -167,7 +167,7 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService } @Override - public void onCreateSession(String packageName, String routeId, String controlCategory, + public void onCreateSession(String packageName, String routeId, String routeType, long requestId) { MediaRoute2Info route = mRoutes.get(routeId); if (route == null || TextUtils.equals(ROUTE_ID3_SESSION_CREATION_FAILED, routeId)) { @@ -177,7 +177,7 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService } maybeDeselectRoute(routeId); - final int sessionId = mNextSessionId; + final String sessionId = String.valueOf(mNextSessionId); mNextSessionId++; mRoutes.put(routeId, new MediaRoute2Info.Builder(route) @@ -186,7 +186,7 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService mRouteSessionMap.put(routeId, sessionId); RouteSessionInfo sessionInfo = new RouteSessionInfo.Builder( - sessionId, packageName, controlCategory) + sessionId, packageName, routeType) .addSelectedRoute(routeId) .addSelectableRoute(ROUTE_ID4_TO_SELECT_AND_DESELECT) .addTransferrableRoute(ROUTE_ID5_TO_TRANSFER_TO) @@ -196,7 +196,7 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService } @Override - public void onDestroySession(int sessionId, RouteSessionInfo lastSessionInfo) { + public void onDestroySession(String sessionId, RouteSessionInfo lastSessionInfo) { for (String routeId : lastSessionInfo.getSelectedRoutes()) { mRouteSessionMap.remove(routeId); MediaRoute2Info route = mRoutes.get(routeId); @@ -209,7 +209,7 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService } @Override - public void onSelectRoute(int sessionId, String routeId) { + public void onSelectRoute(String sessionId, String routeId) { RouteSessionInfo sessionInfo = getSessionInfo(sessionId); MediaRoute2Info route = mRoutes.get(routeId); if (route == null || sessionInfo == null) { @@ -218,7 +218,7 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService maybeDeselectRoute(routeId); mRoutes.put(routeId, new MediaRoute2Info.Builder(route) - .setClientPackageName(sessionInfo.getPackageName()) + .setClientPackageName(sessionInfo.getClientPackageName()) .build()); mRouteSessionMap.put(routeId, sessionId); @@ -232,7 +232,7 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService } @Override - public void onDeselectRoute(int sessionId, String routeId) { + public void onDeselectRoute(String sessionId, String routeId) { RouteSessionInfo sessionInfo = getSessionInfo(sessionId); MediaRoute2Info route = mRoutes.get(routeId); @@ -254,7 +254,7 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService } @Override - public void onTransferToRoute(int sessionId, String routeId) { + public void onTransferToRoute(String sessionId, String routeId) { RouteSessionInfo sessionInfo = getSessionInfo(sessionId); RouteSessionInfo newSessionInfo = new RouteSessionInfo.Builder(sessionInfo) .clearSelectedRoutes() @@ -271,7 +271,7 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService return; } - int sessionId = mRouteSessionMap.get(routeId); + String sessionId = mRouteSessionMap.get(routeId); onDeselectRoute(sessionId, routeId); } diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java index 6fe847bf5f3a..ce4bb8ef2688 100644 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java @@ -23,18 +23,18 @@ import static android.media.MediaRoute2Info.DEVICE_TYPE_TV; import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_FIXED; import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE; -import static com.android.mediaroutertest.MediaRouterManagerTest.CATEGORIES_ALL; -import static com.android.mediaroutertest.MediaRouterManagerTest.CATEGORIES_SPECIAL; -import static com.android.mediaroutertest.MediaRouterManagerTest.CATEGORY_SAMPLE; -import static com.android.mediaroutertest.MediaRouterManagerTest.CATEGORY_SPECIAL; import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID1; import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID2; import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID3_SESSION_CREATION_FAILED; import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID4_TO_SELECT_AND_DESELECT; import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID5_TO_TRANSFER_TO; -import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID_SPECIAL_CATEGORY; +import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID_SPECIAL_TYPE; import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID_VARIABLE_VOLUME; import static com.android.mediaroutertest.MediaRouterManagerTest.SYSTEM_PROVIDER_ID; +import static com.android.mediaroutertest.MediaRouterManagerTest.TYPES_ALL; +import static com.android.mediaroutertest.MediaRouterManagerTest.TYPES_SPECIAL; +import static com.android.mediaroutertest.MediaRouterManagerTest.TYPE_SAMPLE; +import static com.android.mediaroutertest.MediaRouterManagerTest.TYPE_SPECIAL; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -50,6 +50,7 @@ import android.media.MediaRouter2; import android.media.MediaRouter2.RouteCallback; import android.media.MediaRouter2.RouteSessionController; import android.media.MediaRouter2.SessionCallback; +import android.media.RouteDiscoveryRequest; import android.media.RouteSessionInfo; import android.net.Uri; import android.os.Parcel; @@ -95,14 +96,14 @@ public class MediaRouter2Test { } /** - * Tests if we get proper routes for application that has special control category. + * Tests if we get proper routes for application that has special route type. */ @Test public void testGetRoutes() throws Exception { - Map<String, MediaRoute2Info> routes = waitAndGetRoutes(CATEGORIES_SPECIAL); + Map<String, MediaRoute2Info> routes = waitAndGetRoutes(TYPES_SPECIAL); assertEquals(1, routes.size()); - assertNotNull(routes.get(ROUTE_ID_SPECIAL_CATEGORY)); + assertNotNull(routes.get(ROUTE_ID_SPECIAL_TYPE)); } @Test @@ -114,7 +115,7 @@ public class MediaRouter2Test { .setIconUri(new Uri.Builder().path("icon").build()) .setVolume(5) .setVolumeMax(20) - .addSupportedCategory(CATEGORY_SAMPLE) + .addRouteType(TYPE_SAMPLE) .setVolumeHandling(PLAYBACK_VOLUME_VARIABLE) .setDeviceType(DEVICE_TYPE_SPEAKER) .build(); @@ -137,7 +138,7 @@ public class MediaRouter2Test { .setClientPackageName("com.android.mediaroutertest") .setConnectionState(CONNECTION_STATE_CONNECTING) .setIconUri(new Uri.Builder().path("icon").build()) - .addSupportedCategory(CATEGORY_SAMPLE) + .addRouteType(TYPE_SAMPLE) .setVolume(5) .setVolumeMax(20) .setVolumeHandling(PLAYBACK_VOLUME_VARIABLE) @@ -168,9 +169,9 @@ public class MediaRouter2Test { .setClientPackageName("another.client.package").build(); assertNotEquals(route, routeClient); - MediaRoute2Info routeCategory = new MediaRoute2Info.Builder(route) - .addSupportedCategory(CATEGORY_SPECIAL).build(); - assertNotEquals(route, routeCategory); + MediaRoute2Info routeType = new MediaRoute2Info.Builder(route) + .addRouteType(TYPE_SPECIAL).build(); + assertNotEquals(route, routeType); MediaRoute2Info routeVolume = new MediaRoute2Info.Builder(route) .setVolume(10).build(); @@ -191,7 +192,7 @@ public class MediaRouter2Test { @Test public void testControlVolumeWithRouter() throws Exception { - Map<String, MediaRoute2Info> routes = waitAndGetRoutes(CATEGORIES_ALL); + Map<String, MediaRoute2Info> routes = waitAndGetRoutes(TYPES_ALL); MediaRoute2Info volRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME); assertNotNull(volRoute); @@ -202,12 +203,14 @@ public class MediaRouter2Test { awaitOnRouteChanged( () -> mRouter2.requestUpdateVolume(volRoute, deltaVolume), ROUTE_ID_VARIABLE_VOLUME, - (route -> route.getVolume() == originalVolume + deltaVolume)); + (route -> route.getVolume() == originalVolume + deltaVolume), + TYPES_ALL); awaitOnRouteChanged( () -> mRouter2.requestSetVolume(volRoute, originalVolume), ROUTE_ID_VARIABLE_VOLUME, - (route -> route.getVolume() == originalVolume)); + (route -> route.getVolume() == originalVolume), + TYPES_ALL); } @Test @@ -234,13 +237,13 @@ public class MediaRouter2Test { @Test public void testRequestCreateSessionWithInvalidArguments() { MediaRoute2Info route = new MediaRoute2Info.Builder("id", "name").build(); - String controlCategory = "controlCategory"; + String routeType = "routeType"; // Tests null route assertThrows(NullPointerException.class, - () -> mRouter2.requestCreateSession(null, controlCategory)); + () -> mRouter2.requestCreateSession(null, routeType)); - // Tests null or empty control category + // Tests null or empty route type assertThrows(IllegalArgumentException.class, () -> mRouter2.requestCreateSession(route, null)); assertThrows(IllegalArgumentException.class, @@ -249,10 +252,10 @@ public class MediaRouter2Test { @Test public void testRequestCreateSessionSuccess() throws Exception { - final List<String> sampleControlCategory = new ArrayList<>(); - sampleControlCategory.add(CATEGORY_SAMPLE); + final List<String> sampleRouteType = new ArrayList<>(); + sampleRouteType.add(TYPE_SAMPLE); - Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleControlCategory); + Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType); MediaRoute2Info route = routes.get(ROUTE_ID1); assertNotNull(route); @@ -266,25 +269,25 @@ public class MediaRouter2Test { public void onSessionCreated(RouteSessionController controller) { assertNotNull(controller); assertTrue(createRouteMap(controller.getSelectedRoutes()).containsKey(ROUTE_ID1)); - assertTrue(TextUtils.equals(CATEGORY_SAMPLE, controller.getControlCategory())); + assertTrue(TextUtils.equals(TYPE_SAMPLE, controller.getRouteType())); controllers.add(controller); successLatch.countDown(); } @Override public void onSessionCreationFailed(MediaRoute2Info requestedRoute, - String requestedControlCategory) { + String requestedRouteType) { failureLatch.countDown(); } }; // TODO: Remove this once the MediaRouter2 becomes always connected to the service. RouteCallback routeCallback = new RouteCallback(); - mRouter2.registerRouteCallback(mExecutor, routeCallback); + mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryRequest.EMPTY); try { mRouter2.registerSessionCallback(mExecutor, sessionCallback); - mRouter2.requestCreateSession(route, CATEGORY_SAMPLE); + mRouter2.requestCreateSession(route, TYPE_SAMPLE); assertTrue(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); // onSessionCreationFailed should not be called. @@ -298,10 +301,10 @@ public class MediaRouter2Test { @Test public void testRequestCreateSessionFailure() throws Exception { - final List<String> sampleControlCategory = new ArrayList<>(); - sampleControlCategory.add(CATEGORY_SAMPLE); + final List<String> sampleRouteType = new ArrayList<>(); + sampleRouteType.add(TYPE_SAMPLE); - Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleControlCategory); + Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType); MediaRoute2Info route = routes.get(ROUTE_ID3_SESSION_CREATION_FAILED); assertNotNull(route); @@ -319,20 +322,20 @@ public class MediaRouter2Test { @Override public void onSessionCreationFailed(MediaRoute2Info requestedRoute, - String requestedControlCategory) { + String requestedRouteType) { assertEquals(route, requestedRoute); - assertTrue(TextUtils.equals(CATEGORY_SAMPLE, requestedControlCategory)); + assertTrue(TextUtils.equals(TYPE_SAMPLE, requestedRouteType)); failureLatch.countDown(); } }; // TODO: Remove this once the MediaRouter2 becomes always connected to the service. RouteCallback routeCallback = new RouteCallback(); - mRouter2.registerRouteCallback(mExecutor, routeCallback); + mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryRequest.EMPTY); try { mRouter2.registerSessionCallback(mExecutor, sessionCallback); - mRouter2.requestCreateSession(route, CATEGORY_SAMPLE); + mRouter2.requestCreateSession(route, TYPE_SAMPLE); assertTrue(failureLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); // onSessionCreated should not be called. @@ -346,8 +349,8 @@ public class MediaRouter2Test { @Test public void testRequestCreateSessionMultipleSessions() throws Exception { - final List<String> sampleControlCategory = new ArrayList<>(); - sampleControlCategory.add(CATEGORY_SAMPLE); + final List<String> sampleRouteType = new ArrayList<>(); + sampleRouteType.add(TYPE_SAMPLE); final CountDownLatch successLatch = new CountDownLatch(2); final CountDownLatch failureLatch = new CountDownLatch(1); @@ -363,12 +366,12 @@ public class MediaRouter2Test { @Override public void onSessionCreationFailed(MediaRoute2Info requestedRoute, - String requestedControlCategory) { + String requestedRouteType) { failureLatch.countDown(); } }; - Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleControlCategory); + Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType); MediaRoute2Info route1 = routes.get(ROUTE_ID1); MediaRoute2Info route2 = routes.get(ROUTE_ID2); assertNotNull(route1); @@ -376,12 +379,12 @@ public class MediaRouter2Test { // TODO: Remove this once the MediaRouter2 becomes always connected to the service. RouteCallback routeCallback = new RouteCallback(); - mRouter2.registerRouteCallback(mExecutor, routeCallback); + mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryRequest.EMPTY); try { mRouter2.registerSessionCallback(mExecutor, sessionCallback); - mRouter2.requestCreateSession(route1, CATEGORY_SAMPLE); - mRouter2.requestCreateSession(route2, CATEGORY_SAMPLE); + mRouter2.requestCreateSession(route1, TYPE_SAMPLE); + mRouter2.requestCreateSession(route2, TYPE_SAMPLE); assertTrue(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); // onSessionCreationFailed should not be called. @@ -395,8 +398,9 @@ public class MediaRouter2Test { assertNotEquals(controller1.getSessionId(), controller2.getSessionId()); assertTrue(createRouteMap(controller1.getSelectedRoutes()).containsKey(ROUTE_ID1)); assertTrue(createRouteMap(controller2.getSelectedRoutes()).containsKey(ROUTE_ID2)); - assertTrue(TextUtils.equals(CATEGORY_SAMPLE, controller1.getControlCategory())); - assertTrue(TextUtils.equals(CATEGORY_SAMPLE, controller2.getControlCategory())); + assertTrue(TextUtils.equals(TYPE_SAMPLE, controller1.getRouteType())); + assertTrue(TextUtils.equals(TYPE_SAMPLE, controller2.getRouteType())); + } finally { releaseControllers(createdControllers); mRouter2.unregisterRouteCallback(routeCallback); @@ -406,10 +410,10 @@ public class MediaRouter2Test { @Test public void testSessionCallbackIsNotCalledAfterUnregistered() throws Exception { - final List<String> sampleControlCategory = new ArrayList<>(); - sampleControlCategory.add(CATEGORY_SAMPLE); + final List<String> sampleRouteType = new ArrayList<>(); + sampleRouteType.add(TYPE_SAMPLE); - Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleControlCategory); + Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType); MediaRoute2Info route = routes.get(ROUTE_ID1); assertNotNull(route); @@ -427,18 +431,18 @@ public class MediaRouter2Test { @Override public void onSessionCreationFailed(MediaRoute2Info requestedRoute, - String requestedControlCategory) { + String requestedRouteType) { failureLatch.countDown(); } }; // TODO: Remove this once the MediaRouter2 becomes always connected to the service. RouteCallback routeCallback = new RouteCallback(); - mRouter2.registerRouteCallback(mExecutor, routeCallback); + mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryRequest.EMPTY); try { mRouter2.registerSessionCallback(mExecutor, sessionCallback); - mRouter2.requestCreateSession(route, CATEGORY_SAMPLE); + mRouter2.requestCreateSession(route, TYPE_SAMPLE); // Unregisters session callback mRouter2.unregisterSessionCallback(sessionCallback); @@ -456,10 +460,10 @@ public class MediaRouter2Test { // TODO: Add tests for illegal inputs if needed (e.g. selecting already selected route) @Test public void testRouteSessionControllerSelectAndDeselectRoute() throws Exception { - final List<String> sampleControlCategory = new ArrayList<>(); - sampleControlCategory.add(CATEGORY_SAMPLE); + final List<String> sampleRouteType = new ArrayList<>(); + sampleRouteType.add(TYPE_SAMPLE); - Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleControlCategory); + Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType); MediaRoute2Info routeToCreateSessionWith = routes.get(ROUTE_ID1); assertNotNull(routeToCreateSessionWith); @@ -474,7 +478,7 @@ public class MediaRouter2Test { public void onSessionCreated(RouteSessionController controller) { assertNotNull(controller); assertTrue(getRouteIds(controller.getSelectedRoutes()).contains(ROUTE_ID1)); - assertTrue(TextUtils.equals(CATEGORY_SAMPLE, controller.getControlCategory())); + assertTrue(TextUtils.equals(TYPE_SAMPLE, controller.getRouteType())); controllers.add(controller); onSessionCreatedLatch.countDown(); } @@ -483,20 +487,21 @@ public class MediaRouter2Test { public void onSessionInfoChanged(RouteSessionController controller, RouteSessionInfo oldInfo, RouteSessionInfo newInfo) { if (onSessionCreatedLatch.getCount() != 0 - || controllers.get(0).getSessionId() != controller.getSessionId()) { + || !TextUtils.equals( + controllers.get(0).getSessionId(), controller.getSessionId())) { return; } if (onSessionInfoChangedLatchForSelect.getCount() != 0) { // Check oldInfo - assertEquals(controller.getSessionId(), oldInfo.getSessionId()); + assertEquals(controller.getSessionId(), oldInfo.getId()); assertEquals(1, oldInfo.getSelectedRoutes().size()); assertTrue(oldInfo.getSelectedRoutes().contains(ROUTE_ID1)); assertTrue(oldInfo.getSelectableRoutes().contains( ROUTE_ID4_TO_SELECT_AND_DESELECT)); // Check newInfo - assertEquals(controller.getSessionId(), newInfo.getSessionId()); + assertEquals(controller.getSessionId(), newInfo.getId()); assertEquals(2, newInfo.getSelectedRoutes().size()); assertTrue(newInfo.getSelectedRoutes().contains(ROUTE_ID1)); assertTrue(newInfo.getSelectedRoutes().contains( @@ -507,7 +512,7 @@ public class MediaRouter2Test { onSessionInfoChangedLatchForSelect.countDown(); } else { // Check newInfo - assertEquals(controller.getSessionId(), newInfo.getSessionId()); + assertEquals(controller.getSessionId(), newInfo.getId()); assertEquals(1, newInfo.getSelectedRoutes().size()); assertTrue(newInfo.getSelectedRoutes().contains(ROUTE_ID1)); assertFalse(newInfo.getSelectedRoutes().contains( @@ -522,11 +527,11 @@ public class MediaRouter2Test { // TODO: Remove this once the MediaRouter2 becomes always connected to the service. RouteCallback routeCallback = new RouteCallback(); - mRouter2.registerRouteCallback(mExecutor, routeCallback); + mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryRequest.EMPTY); try { mRouter2.registerSessionCallback(mExecutor, sessionCallback); - mRouter2.requestCreateSession(routeToCreateSessionWith, CATEGORY_SAMPLE); + mRouter2.requestCreateSession(routeToCreateSessionWith, TYPE_SAMPLE); assertTrue(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); assertEquals(1, controllers.size()); @@ -554,10 +559,10 @@ public class MediaRouter2Test { @Test public void testRouteSessionControllerTransferToRoute() throws Exception { - final List<String> sampleControlCategory = new ArrayList<>(); - sampleControlCategory.add(CATEGORY_SAMPLE); + final List<String> sampleRouteType = new ArrayList<>(); + sampleRouteType.add(TYPE_SAMPLE); - Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleControlCategory); + Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType); MediaRoute2Info routeToCreateSessionWith = routes.get(ROUTE_ID1); assertNotNull(routeToCreateSessionWith); @@ -570,8 +575,12 @@ public class MediaRouter2Test { @Override public void onSessionCreated(RouteSessionController controller) { assertNotNull(controller); + android.util.Log.d(TAG, "selected route ids "); + for (String routeId : getRouteIds(controller.getSelectedRoutes())) { + android.util.Log.d(TAG, "route id : " + routeId); + } assertTrue(getRouteIds(controller.getSelectedRoutes()).contains(ROUTE_ID1)); - assertTrue(TextUtils.equals(CATEGORY_SAMPLE, controller.getControlCategory())); + assertTrue(TextUtils.equals(TYPE_SAMPLE, controller.getRouteType())); controllers.add(controller); onSessionCreatedLatch.countDown(); } @@ -580,18 +589,19 @@ public class MediaRouter2Test { public void onSessionInfoChanged(RouteSessionController controller, RouteSessionInfo oldInfo, RouteSessionInfo newInfo) { if (onSessionCreatedLatch.getCount() != 0 - || controllers.get(0).getSessionId() != controller.getSessionId()) { + || !TextUtils.equals( + controllers.get(0).getSessionId(), controller.getSessionId())) { return; } // Check oldInfo - assertEquals(controller.getSessionId(), oldInfo.getSessionId()); + assertEquals(controller.getSessionId(), oldInfo.getId()); assertEquals(1, oldInfo.getSelectedRoutes().size()); assertTrue(oldInfo.getSelectedRoutes().contains(ROUTE_ID1)); assertTrue(oldInfo.getTransferrableRoutes().contains(ROUTE_ID5_TO_TRANSFER_TO)); // Check newInfo - assertEquals(controller.getSessionId(), newInfo.getSessionId()); + assertEquals(controller.getSessionId(), newInfo.getId()); assertEquals(1, newInfo.getSelectedRoutes().size()); assertFalse(newInfo.getSelectedRoutes().contains(ROUTE_ID1)); assertTrue(newInfo.getSelectedRoutes().contains(ROUTE_ID5_TO_TRANSFER_TO)); @@ -603,11 +613,11 @@ public class MediaRouter2Test { // TODO: Remove this once the MediaRouter2 becomes always connected to the service. RouteCallback routeCallback = new RouteCallback(); - mRouter2.registerRouteCallback(mExecutor, routeCallback); + mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryRequest.EMPTY); try { mRouter2.registerSessionCallback(mExecutor, sessionCallback); - mRouter2.requestCreateSession(routeToCreateSessionWith, CATEGORY_SAMPLE); + mRouter2.requestCreateSession(routeToCreateSessionWith, TYPE_SAMPLE); assertTrue(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); assertEquals(1, controllers.size()); @@ -632,10 +642,10 @@ public class MediaRouter2Test { @Test public void testRouteSessionControllerReleaseShouldIgnoreTransferTo() throws Exception { - final List<String> sampleControlCategory = new ArrayList<>(); - sampleControlCategory.add(CATEGORY_SAMPLE); + final List<String> sampleRouteType = new ArrayList<>(); + sampleRouteType.add(TYPE_SAMPLE); - Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleControlCategory); + Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType); MediaRoute2Info routeToCreateSessionWith = routes.get(ROUTE_ID1); assertNotNull(routeToCreateSessionWith); @@ -649,7 +659,7 @@ public class MediaRouter2Test { public void onSessionCreated(RouteSessionController controller) { assertNotNull(controller); assertTrue(getRouteIds(controller.getSelectedRoutes()).contains(ROUTE_ID1)); - assertTrue(TextUtils.equals(CATEGORY_SAMPLE, controller.getControlCategory())); + assertTrue(TextUtils.equals(TYPE_SAMPLE, controller.getRouteType())); controllers.add(controller); onSessionCreatedLatch.countDown(); } @@ -658,7 +668,8 @@ public class MediaRouter2Test { public void onSessionInfoChanged(RouteSessionController controller, RouteSessionInfo oldInfo, RouteSessionInfo newInfo) { if (onSessionCreatedLatch.getCount() != 0 - || controllers.get(0).getSessionId() != controller.getSessionId()) { + || !TextUtils.equals( + controllers.get(0).getSessionId(), controller.getSessionId())) { return; } onSessionInfoChangedLatch.countDown(); @@ -667,11 +678,11 @@ public class MediaRouter2Test { // TODO: Remove this once the MediaRouter2 becomes always connected to the service. RouteCallback routeCallback = new RouteCallback(); - mRouter2.registerRouteCallback(mExecutor, routeCallback); + mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryRequest.EMPTY); try { mRouter2.registerSessionCallback(mExecutor, sessionCallback); - mRouter2.requestCreateSession(routeToCreateSessionWith, CATEGORY_SAMPLE); + mRouter2.requestCreateSession(routeToCreateSessionWith, TYPE_SAMPLE); assertTrue(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); assertEquals(1, controllers.size()); @@ -701,17 +712,16 @@ public class MediaRouter2Test { static Map<String, MediaRoute2Info> createRouteMap(List<MediaRoute2Info> routes) { Map<String, MediaRoute2Info> routeMap = new HashMap<>(); for (MediaRoute2Info route : routes) { - // intentionally not using route.getUniqueId() for convenience. routeMap.put(route.getId(), route); } return routeMap; } - Map<String, MediaRoute2Info> waitAndGetRoutes(List<String> controlCategories) + Map<String, MediaRoute2Info> waitAndGetRoutes(List<String> routeTypes) throws Exception { CountDownLatch latch = new CountDownLatch(1); - // A dummy callback is required to send control category info. + // A dummy callback is required to send route type info. RouteCallback routeCallback = new RouteCallback() { @Override public void onRoutesAdded(List<MediaRoute2Info> routes) { @@ -724,8 +734,8 @@ public class MediaRouter2Test { } }; - mRouter2.setControlCategories(controlCategories); - mRouter2.registerRouteCallback(mExecutor, routeCallback); + mRouter2.registerRouteCallback(mExecutor, routeCallback, + new RouteDiscoveryRequest.Builder(routeTypes, true).build()); try { latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); return createRouteMap(mRouter2.getRoutes()); @@ -741,7 +751,7 @@ public class MediaRouter2Test { } /** - * Returns a list of IDs (not uniqueId) of the given route list. + * Returns a list of IDs of the given route list. */ List<String> getRouteIds(@NonNull List<MediaRoute2Info> routes) { List<String> result = new ArrayList<>(); @@ -752,7 +762,8 @@ public class MediaRouter2Test { } void awaitOnRouteChanged(Runnable task, String routeId, - Predicate<MediaRoute2Info> predicate) throws Exception { + Predicate<MediaRoute2Info> predicate, + List<String> routeTypes) throws Exception { CountDownLatch latch = new CountDownLatch(1); RouteCallback routeCallback = new RouteCallback() { @Override @@ -763,7 +774,8 @@ public class MediaRouter2Test { } } }; - mRouter2.registerRouteCallback(mExecutor, routeCallback); + mRouter2.registerRouteCallback(mExecutor, routeCallback, + new RouteDiscoveryRequest.Builder(routeTypes, true).build()); try { task.run(); assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java index 83c7c173e8e8..9ff9177c1b40 100644 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java @@ -31,6 +31,7 @@ import android.media.MediaRouter2; import android.media.MediaRouter2.RouteCallback; import android.media.MediaRouter2.SessionCallback; import android.media.MediaRouter2Manager; +import android.media.RouteDiscoveryRequest; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; @@ -57,41 +58,48 @@ import java.util.function.Predicate; public class MediaRouterManagerTest { private static final String TAG = "MediaRouterManagerTest"; - // Must be the same as SampleMediaRoute2ProviderService - public static final String ROUTE_ID1 = "route_id1"; + public static final String SAMPLE_PROVIDER_ROUTES_ID_PREFIX = + "com.android.mediarouteprovider.example/.SampleMediaRoute2ProviderService:"; + + // Must be the same as SampleMediaRoute2ProviderService except the prefix of IDs. + public static final String ROUTE_ID1 = SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_id1"; public static final String ROUTE_NAME1 = "Sample Route 1"; - public static final String ROUTE_ID2 = "route_id2"; + public static final String ROUTE_ID2 = SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_id2"; public static final String ROUTE_NAME2 = "Sample Route 2"; public static final String ROUTE_ID3_SESSION_CREATION_FAILED = - "route_id3_session_creation_failed"; + SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_id3_session_creation_failed"; public static final String ROUTE_NAME3 = "Sample Route 3 - Session creation failed"; public static final String ROUTE_ID4_TO_SELECT_AND_DESELECT = - "route_id4_to_select_and_deselect"; + SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_id4_to_select_and_deselect"; public static final String ROUTE_NAME4 = "Sample Route 4 - Route to select and deselect"; - public static final String ROUTE_ID5_TO_TRANSFER_TO = "route_id5_to_transfer_to"; + public static final String ROUTE_ID5_TO_TRANSFER_TO = + SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_id5_to_transfer_to"; public static final String ROUTE_NAME5 = "Sample Route 5 - Route to transfer to"; - public static final String ROUTE_ID_SPECIAL_CATEGORY = "route_special_category"; - public static final String ROUTE_NAME_SPECIAL_CATEGORY = "Special Category Route"; + public static final String ROUTE_ID_SPECIAL_TYPE = + SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_special_type"; + public static final String ROUTE_NAME_SPECIAL_TYPE = "Special Type Route"; public static final String SYSTEM_PROVIDER_ID = "com.android.server.media/.SystemMediaRoute2Provider"; public static final int VOLUME_MAX = 100; - public static final String ROUTE_ID_FIXED_VOLUME = "route_fixed_volume"; + public static final String ROUTE_ID_FIXED_VOLUME = + SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_fixed_volume"; public static final String ROUTE_NAME_FIXED_VOLUME = "Fixed Volume Route"; - public static final String ROUTE_ID_VARIABLE_VOLUME = "route_variable_volume"; + public static final String ROUTE_ID_VARIABLE_VOLUME = + SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_variable_volume"; public static final String ROUTE_NAME_VARIABLE_VOLUME = "Variable Volume Route"; public static final String ACTION_REMOVE_ROUTE = "com.android.mediarouteprovider.action_remove_route"; - public static final String CATEGORY_SAMPLE = - "com.android.mediarouteprovider.CATEGORY_SAMPLE"; - public static final String CATEGORY_SPECIAL = - "com.android.mediarouteprovider.CATEGORY_SPECIAL"; + public static final String TYPE_SAMPLE = + "com.android.mediarouteprovider.TYPE_SAMPLE"; + public static final String TYPE_SPECIAL = + "com.android.mediarouteprovider.TYPE_SPECIAL"; - private static final String CATEGORY_LIVE_AUDIO = "android.media.intent.category.LIVE_AUDIO"; + private static final String TYPE_LIVE_AUDIO = "android.media.intent.route.TYPE_LIVE_AUDIO"; private static final int TIMEOUT_MS = 5000; @@ -105,18 +113,18 @@ public class MediaRouterManagerTest { private final List<RouteCallback> mRouteCallbacks = new ArrayList<>(); private final List<SessionCallback> mSessionCallbacks = new ArrayList<>(); - public static final List<String> CATEGORIES_ALL = new ArrayList(); - public static final List<String> CATEGORIES_SPECIAL = new ArrayList(); - private static final List<String> CATEGORIES_LIVE_AUDIO = new ArrayList<>(); + public static final List<String> TYPES_ALL = new ArrayList(); + public static final List<String> TYPES_SPECIAL = new ArrayList(); + private static final List<String> TYPES_LIVE_AUDIO = new ArrayList<>(); static { - CATEGORIES_ALL.add(CATEGORY_SAMPLE); - CATEGORIES_ALL.add(CATEGORY_SPECIAL); - CATEGORIES_ALL.add(CATEGORY_LIVE_AUDIO); + TYPES_ALL.add(TYPE_SAMPLE); + TYPES_ALL.add(TYPE_SPECIAL); + TYPES_ALL.add(TYPE_LIVE_AUDIO); - CATEGORIES_SPECIAL.add(CATEGORY_SPECIAL); + TYPES_SPECIAL.add(TYPE_SPECIAL); - CATEGORIES_LIVE_AUDIO.add(CATEGORY_LIVE_AUDIO); + TYPES_LIVE_AUDIO.add(TYPE_LIVE_AUDIO); } @Before @@ -173,7 +181,7 @@ public class MediaRouterManagerTest { @Test public void testOnRoutesRemoved() throws Exception { CountDownLatch latch = new CountDownLatch(1); - Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL); + Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(TYPES_ALL); addRouterCallback(new RouteCallback()); addManagerCallback(new MediaRouter2Manager.Callback() { @@ -195,14 +203,14 @@ public class MediaRouterManagerTest { } /** - * Tests if we get proper routes for application that has special control category. + * Tests if we get proper routes for application that has special route type. */ @Test - public void testControlCategory() throws Exception { - Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_SPECIAL); + public void testRouteType() throws Exception { + Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(TYPES_SPECIAL); assertEquals(1, routes.size()); - assertNotNull(routes.get(ROUTE_ID_SPECIAL_CATEGORY)); + assertNotNull(routes.get(ROUTE_ID_SPECIAL_TYPE)); } /** @@ -211,7 +219,7 @@ public class MediaRouterManagerTest { */ @Test public void testRouterOnSessionCreated() throws Exception { - Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL); + Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(TYPES_ALL); CountDownLatch latch = new CountDownLatch(1); @@ -247,7 +255,7 @@ public class MediaRouterManagerTest { @Ignore("TODO: test session created callback instead of onRouteSelected") public void testManagerOnRouteSelected() throws Exception { CountDownLatch latch = new CountDownLatch(1); - Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL); + Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(TYPES_ALL); addRouterCallback(new RouteCallback()); addManagerCallback(new MediaRouter2Manager.Callback() { @@ -277,7 +285,7 @@ public class MediaRouterManagerTest { public void testGetActiveRoutes() throws Exception { CountDownLatch latch = new CountDownLatch(1); - Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL); + Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(TYPES_ALL); addRouterCallback(new RouteCallback()); addManagerCallback(new MediaRouter2Manager.Callback() { @Override @@ -313,7 +321,7 @@ public class MediaRouterManagerTest { @Test @Ignore("TODO: enable when session is released") public void testSingleProviderSelect() throws Exception { - Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL); + Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(TYPES_ALL); addRouterCallback(new RouteCallback()); awaitOnRouteChangedManager( @@ -338,7 +346,7 @@ public class MediaRouterManagerTest { @Test public void testControlVolumeWithManager() throws Exception { - Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL); + Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(TYPES_ALL); MediaRoute2Info volRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME); int originalVolume = volRoute.getVolume(); @@ -357,7 +365,7 @@ public class MediaRouterManagerTest { @Test public void testVolumeHandling() throws Exception { - Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL); + Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(TYPES_ALL); MediaRoute2Info fixedVolumeRoute = routes.get(ROUTE_ID_FIXED_VOLUME); MediaRoute2Info variableVolumeRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME); @@ -367,11 +375,11 @@ public class MediaRouterManagerTest { assertEquals(VOLUME_MAX, variableVolumeRoute.getVolumeMax()); } - Map<String, MediaRoute2Info> waitAndGetRoutesWithManager(List<String> controlCategories) + Map<String, MediaRoute2Info> waitAndGetRoutesWithManager(List<String> routeTypes) throws Exception { CountDownLatch latch = new CountDownLatch(2); - // A dummy callback is required to send control category info. + // A dummy callback is required to send route type info. RouteCallback routeCallback = new RouteCallback(); MediaRouter2Manager.Callback managerCallback = new MediaRouter2Manager.Callback() { @Override @@ -386,16 +394,16 @@ public class MediaRouterManagerTest { } @Override - public void onControlCategoriesChanged(String packageName, List<String> categories) { + public void onControlCategoriesChanged(String packageName, List<String> routeTypes) { if (TextUtils.equals(mPackageName, packageName) - && controlCategories.equals(categories)) { + && routeTypes.equals(routeTypes)) { latch.countDown(); } } }; mManager.registerCallback(mExecutor, managerCallback); - mRouter2.setControlCategories(controlCategories); - mRouter2.registerRouteCallback(mExecutor, routeCallback); + mRouter2.registerRouteCallback(mExecutor, routeCallback, + new RouteDiscoveryRequest.Builder(routeTypes, true).build()); try { latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); return createRouteMap(mManager.getAvailableRoutes(mPackageName)); @@ -430,7 +438,6 @@ public class MediaRouterManagerTest { static Map<String, MediaRoute2Info> createRouteMap(List<MediaRoute2Info> routes) { Map<String, MediaRoute2Info> routeMap = new HashMap<>(); for (MediaRoute2Info route : routes) { - // intentionally not using route.getUniqueId() for convenience. routeMap.put(route.getId(), route); } return routeMap; @@ -443,7 +450,7 @@ public class MediaRouterManagerTest { private void addRouterCallback(RouteCallback routeCallback) { mRouteCallbacks.add(routeCallback); - mRouter2.registerRouteCallback(mExecutor, routeCallback); + mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryRequest.EMPTY); } private void addSessionCallback(SessionCallback sessionCallback) { diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteDiscoveryRequestTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteDiscoveryRequestTest.java new file mode 100644 index 000000000000..60d131a8fb7e --- /dev/null +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteDiscoveryRequestTest.java @@ -0,0 +1,87 @@ +/* + * 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.mediaroutertest; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import android.media.RouteDiscoveryRequest; +import android.os.Parcel; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.List; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class RouteDiscoveryRequestTest { + @Before + public void setUp() throws Exception { } + + @After + public void tearDown() throws Exception { } + + @Test + public void testEquality() { + List<String> testTypes = new ArrayList<>(); + testTypes.add("TEST_TYPE_1"); + testTypes.add("TEST_TYPE_2"); + RouteDiscoveryRequest request = new RouteDiscoveryRequest.Builder(testTypes, true) + .build(); + + RouteDiscoveryRequest requestRebuilt = new RouteDiscoveryRequest.Builder(request) + .build(); + + assertEquals(request, requestRebuilt); + + Parcel parcel = Parcel.obtain(); + parcel.writeParcelable(request, 0); + parcel.setDataPosition(0); + RouteDiscoveryRequest requestFromParcel = parcel.readParcelable(null); + + assertEquals(request, requestFromParcel); + } + + @Test + public void testInequality() { + List<String> testTypes = new ArrayList<>(); + testTypes.add("TEST_TYPE_1"); + testTypes.add("TEST_TYPE_2"); + + List<String> testTypes2 = new ArrayList<>(); + testTypes.add("TEST_TYPE_3"); + + RouteDiscoveryRequest request = new RouteDiscoveryRequest.Builder(testTypes, true) + .build(); + + RouteDiscoveryRequest requestTypes = new RouteDiscoveryRequest.Builder(request) + .setRouteTypes(testTypes2) + .build(); + assertNotEquals(request, requestTypes); + + RouteDiscoveryRequest requestActiveScan = new RouteDiscoveryRequest.Builder(request) + .setActiveScan(false) + .build(); + assertNotEquals(request, requestActiveScan); + } +} diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteSessionTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteSessionTest.java index 2e81a646b0db..9971fc3bbe9f 100644 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteSessionTest.java +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteSessionTest.java @@ -29,6 +29,7 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) @SmallTest public class RouteSessionTest { + private static final String TEST_SESSION_ID = "test_session_id"; private static final String TEST_PACKAGE_NAME = "com.android.mediaroutertest"; private static final String TEST_CONTROL_CATEGORY = "com.android.mediaroutertest.category"; @@ -36,17 +37,17 @@ public class RouteSessionTest { @Test public void testValidity() { - RouteSessionInfo emptyPackageSession = new RouteSessionInfo.Builder(1, + RouteSessionInfo emptyPackageSession = new RouteSessionInfo.Builder(TEST_SESSION_ID, "", TEST_CONTROL_CATEGORY) .addSelectedRoute(TEST_ROUTE_ID1) .build(); - RouteSessionInfo emptyCategorySession = new RouteSessionInfo.Builder(1, + RouteSessionInfo emptyCategorySession = new RouteSessionInfo.Builder(TEST_SESSION_ID, TEST_PACKAGE_NAME, "") .addSelectedRoute(TEST_ROUTE_ID1) .build(); - RouteSessionInfo emptySelectedRouteSession = new RouteSessionInfo.Builder(1, + RouteSessionInfo emptySelectedRouteSession = new RouteSessionInfo.Builder(TEST_SESSION_ID, TEST_PACKAGE_NAME, TEST_CONTROL_CATEGORY) .build(); @@ -54,9 +55,9 @@ public class RouteSessionTest { .addSelectedRoute(TEST_ROUTE_ID1) .build(); - assertFalse(emptySelectedRouteSession.isValid()); assertFalse(emptyPackageSession.isValid()); assertFalse(emptyCategorySession.isValid()); + assertFalse(emptySelectedRouteSession.isValid()); assertTrue(validSession.isValid()); } } diff --git a/mms/OWNERS b/mms/OWNERS index ba00d5d75010..befc320b949c 100644 --- a/mms/OWNERS +++ b/mms/OWNERS @@ -12,3 +12,5 @@ satk@google.com shuoq@google.com refuhoo@google.com nazaninb@google.com +sarahchin@google.com +dbright@google.com
\ No newline at end of file diff --git a/opengl/java/android/opengl/EGL14.java b/opengl/java/android/opengl/EGL14.java index 728e6e18cc31..90b46fd5901a 100644 --- a/opengl/java/android/opengl/EGL14.java +++ b/opengl/java/android/opengl/EGL14.java @@ -18,11 +18,11 @@ package android.opengl; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.graphics.SurfaceTexture; import android.view.Surface; -import android.view.SurfaceView; import android.view.SurfaceHolder; +import android.view.SurfaceView; /** * EGL 1.4 diff --git a/opengl/java/android/opengl/GLES20.java b/opengl/java/android/opengl/GLES20.java index d66e7ac84a3b..e853e4447daa 100644 --- a/opengl/java/android/opengl/GLES20.java +++ b/opengl/java/android/opengl/GLES20.java @@ -19,7 +19,7 @@ package android.opengl; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; /** OpenGL ES 2.0 */ diff --git a/opengl/java/android/opengl/GLSurfaceView.java b/opengl/java/android/opengl/GLSurfaceView.java index 8a3e6a0b0fd5..75131b0f6b9c 100644 --- a/opengl/java/android/opengl/GLSurfaceView.java +++ b/opengl/java/android/opengl/GLSurfaceView.java @@ -16,7 +16,7 @@ package android.opengl; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.os.Trace; import android.util.AttributeSet; diff --git a/opengl/java/javax/microedition/khronos/egl/EGL10.java b/opengl/java/javax/microedition/khronos/egl/EGL10.java index 8a2517062d4d..ea571c7311a1 100644 --- a/opengl/java/javax/microedition/khronos/egl/EGL10.java +++ b/opengl/java/javax/microedition/khronos/egl/EGL10.java @@ -16,8 +16,7 @@ package javax.microedition.khronos.egl; -import android.annotation.UnsupportedAppUsage; -import java.lang.String; +import android.compat.annotation.UnsupportedAppUsage; public interface EGL10 extends EGL { int EGL_SUCCESS = 0x3000; diff --git a/packages/CarSystemUI/res/drawable/car_ic_notification_selected.xml b/packages/CarSystemUI/res/drawable/car_ic_notification_selected.xml deleted file mode 100644 index dd2254509d83..000000000000 --- a/packages/CarSystemUI/res/drawable/car_ic_notification_selected.xml +++ /dev/null @@ -1,25 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ 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 - --> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:viewportWidth="44" - android:viewportHeight="44" - android:width="44dp" - android:height="44dp"> - <path - android:pathData="M22 39.125C23.925 39.125 25.5 37.55 25.5 35.625L18.5 35.625C18.5 37.55 20.0575 39.125 22 39.125ZM32.5 28.625L32.5 19.875C32.5 14.5025 29.63 10.005 24.625 8.815L24.625 7.625C24.625 6.1725 23.4525 5 22 5C20.5475 5 19.375 6.1725 19.375 7.625L19.375 8.815C14.3525 10.005 11.5 14.485 11.5 19.875L11.5 28.625L8 32.125L8 33.875L36 33.875L36 32.125L32.5 28.625Z" - android:fillColor="@color/car_nav_icon_fill_color_selected" /> -</vector>
\ No newline at end of file diff --git a/packages/CarSystemUI/res/drawable/car_ic_notification_selected_unseen.xml b/packages/CarSystemUI/res/drawable/car_ic_notification_selected_unseen.xml deleted file mode 100644 index c5d7728515ff..000000000000 --- a/packages/CarSystemUI/res/drawable/car_ic_notification_selected_unseen.xml +++ /dev/null @@ -1,34 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ 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 - --> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:viewportWidth="44" - android:viewportHeight="44" - android:width="44dp" - android:height="44dp"> - <path - android:pathData="M22 39.125C23.925 39.125 25.5 37.55 25.5 35.625L18.5 35.625C18.5 37.55 20.0575 39.125 22 39.125ZM32.5 28.625L32.5 19.875C32.5 14.5025 29.63 10.005 24.625 8.815L24.625 7.625C24.625 6.1725 23.4525 5 22 5C20.5475 5 19.375 6.1725 19.375 7.625L19.375 8.815C14.3525 10.005 11.5 14.485 11.5 19.875L11.5 28.625L8 32.125L8 33.875L36 33.875L36 32.125L32.5 28.625Z" - android:fillColor="@color/car_nav_icon_fill_color_selected" /> - <group - android:translateX="30" - android:translateY="2"> - <path - android:fillColor="@color/car_nav_notification_unseen_indicator_color" - android:strokeWidth="1" - android:pathData="M 6 0 C 9.31370849898 0 12 2.68629150102 12 6 C 12 9.31370849898 9.31370849898 12 6 12 C 2.68629150102 12 0 9.31370849898 0 6 C 0 2.68629150102 2.68629150102 0 6 0 Z" /> - </group> -</vector> - diff --git a/packages/CarSystemUI/res/drawable/car_ic_notification_unseen.xml b/packages/CarSystemUI/res/drawable/car_ic_unseen_indicator.xml index 25d1af3fcd83..025fc9c4ea3b 100644 --- a/packages/CarSystemUI/res/drawable/car_ic_notification_unseen.xml +++ b/packages/CarSystemUI/res/drawable/car_ic_unseen_indicator.xml @@ -19,14 +19,11 @@ android:viewportHeight="44" android:width="44dp" android:height="44dp"> - <path - android:pathData="M22 39.125C23.925 39.125 25.5 37.55 25.5 35.625L18.5 35.625C18.5 37.55 20.0575 39.125 22 39.125ZM32.5 28.625L32.5 19.875C32.5 14.5025 29.63 10.005 24.625 8.815L24.625 7.625C24.625 6.1725 23.4525 5 22 5C20.5475 5 19.375 6.1725 19.375 7.625L19.375 8.815C14.3525 10.005 11.5 14.485 11.5 19.875L11.5 28.625L8 32.125L8 33.875L36 33.875L36 32.125L32.5 28.625Z" - android:fillColor="@color/car_nav_icon_fill_color" /> <group android:translateX="30" android:translateY="2"> <path - android:fillColor="@color/car_nav_notification_unseen_indicator_color" + android:fillColor="@color/car_nav_unseen_indicator_color" android:strokeWidth="1" android:pathData="M 6 0 C 9.31370849898 0 12 2.68629150102 12 6 C 12 9.31370849898 9.31370849898 12 6 12 C 2.68629150102 12 0 9.31370849898 0 6 C 0 2.68629150102 2.68629150102 0 6 0 Z" /> </group> diff --git a/packages/CarSystemUI/res/layout/car_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_navigation_bar.xml index 27ad4fc16532..e2e9a336d614 100644 --- a/packages/CarSystemUI/res/layout/car_navigation_bar.xml +++ b/packages/CarSystemUI/res/layout/car_navigation_bar.xml @@ -114,7 +114,6 @@ style="@style/NavigationBarButton" systemui:icon="@drawable/car_ic_notification" systemui:longIntent="intent:#Intent;component=com.android.car.bugreport/.BugReportActivity;end" - systemui:selectedIcon="@drawable/car_ic_notification_selected" /> <Space @@ -138,8 +137,7 @@ android:paddingStart="@dimen/car_keyline_1" android:paddingEnd="@dimen/car_keyline_1" android:gravity="center" - android:visibility="gone"> - - </LinearLayout> + android:visibility="gone" + /> </com.android.systemui.navigationbar.car.CarNavigationBarView>
\ No newline at end of file diff --git a/packages/CarSystemUI/res/layout/car_navigation_button.xml b/packages/CarSystemUI/res/layout/car_navigation_button.xml index bafbb6408f77..837252b6d716 100644 --- a/packages/CarSystemUI/res/layout/car_navigation_button.xml +++ b/packages/CarSystemUI/res/layout/car_navigation_button.xml @@ -18,13 +18,12 @@ --> <merge xmlns:android="http://schemas.android.com/apk/res/android"> - <LinearLayout + <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/car_nav_button_icon" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" - android:gravity="center" android:animateLayoutChanges="true" android:orientation="vertical"> @@ -32,20 +31,35 @@ android:id="@+id/car_nav_button_icon_image" android:layout_height="wrap_content" android:layout_width="match_parent" + android:layout_gravity="center" android:animateLayoutChanges="true" android:background="@android:color/transparent" - android:scaleType="fitCenter"> - </com.android.keyguard.AlphaOptimizedImageButton> + android:scaleType="fitCenter" + android:clickable="false" + /> <com.android.keyguard.AlphaOptimizedImageButton android:id="@+id/car_nav_button_more_icon" android:layout_height="wrap_content" android:layout_width="match_parent" + android:layout_gravity="center" android:animateLayoutChanges="true" android:src="@drawable/car_ic_arrow" android:background="@android:color/transparent" - android:scaleType="fitCenter"> - </com.android.keyguard.AlphaOptimizedImageButton> + android:scaleType="fitCenter" + android:clickable="false" + /> - </LinearLayout> + <ImageView + android:id="@+id/car_nav_button_unseen_icon" + android:layout_height="wrap_content" + android:layout_width="match_parent" + android:layout_gravity="center" + android:src="@drawable/car_ic_unseen_indicator" + android:background="@android:color/transparent" + android:scaleType="fitCenter" + android:clickable="false" + /> + + </FrameLayout> </merge> diff --git a/packages/CarSystemUI/res/values/colors.xml b/packages/CarSystemUI/res/values/colors.xml index 5fcf38fce3cc..7972e09869d3 100644 --- a/packages/CarSystemUI/res/values/colors.xml +++ b/packages/CarSystemUI/res/values/colors.xml @@ -43,8 +43,8 @@ <!-- The color of the dividing line between grouped notifications. --> <color name="notification_divider_color">@*android:color/notification_action_list</color> - <!-- The color for the unseen notification indicator. --> - <color name="car_nav_notification_unseen_indicator_color">#e25142</color> + <!-- The color for the unseen indicator. --> + <color name="car_nav_unseen_indicator_color">#e25142</color> <!-- The color of the ripples on the untinted notifications --> <color name="notification_ripple_untinted_color">@color/ripple_material_light</color> diff --git a/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java b/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java index ed945e7d4e72..f7802d205a3a 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java @@ -18,6 +18,7 @@ package com.android.systemui.car; import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; +import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationRankingManager; import com.android.systemui.statusbar.notification.logging.NotifLog; @@ -40,8 +41,9 @@ public class CarNotificationEntryManager extends NotificationEntryManager { NotifLog notifLog, NotificationGroupManager groupManager, NotificationRankingManager rankingManager, - KeyguardEnvironment keyguardEnvironment) { - super(notifLog, groupManager, rankingManager, keyguardEnvironment); + KeyguardEnvironment keyguardEnvironment, + FeatureFlags featureFlags) { + super(notifLog, groupManager, rankingManager, keyguardEnvironment, featureFlags); } @Override diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationButton.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationButton.java index d08fbbc8091f..b4d478572daf 100644 --- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationButton.java +++ b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationButton.java @@ -48,13 +48,11 @@ public class CarNavigationButton extends LinearLayout { private static final String BUTTON_FILTER_DELIMITER = ";"; private static final String EXTRA_BUTTON_CATEGORIES = "categories"; private static final String EXTRA_BUTTON_PACKAGES = "packages"; - private static final int UNSEEN_ICON_RESOURCE_ID = R.drawable.car_ic_notification_unseen; - private static final int UNSEEN_SELECTED_ICON_RESOURCE_ID = - R.drawable.car_ic_notification_selected_unseen; private Context mContext; private AlphaOptimizedImageButton mIcon; private AlphaOptimizedImageButton mMoreIcon; + private ImageView mUnseenIcon; private String mIntent; private String mLongIntent; private boolean mBroadcastIntent; @@ -265,23 +263,22 @@ public class CarNavigationButton extends LinearLayout { mIcon = findViewById(R.id.car_nav_button_icon_image); mIcon.setScaleType(ImageView.ScaleType.CENTER); - mIcon.setClickable(false); // Always apply selected alpha if the button does not toggle alpha based on selection state. mIcon.setAlpha(mHighlightWhenSelected ? mUnselectedAlpha : mSelectedAlpha); mIcon.setImageResource(mIconResourceId); mMoreIcon = findViewById(R.id.car_nav_button_more_icon); - mMoreIcon.setClickable(false); mMoreIcon.setAlpha(mSelectedAlpha); mMoreIcon.setVisibility(GONE); + + mUnseenIcon = findViewById(R.id.car_nav_button_unseen_icon); + + mUnseenIcon.setVisibility(mHasUnseen ? VISIBLE : GONE); } private void updateImage() { - if (mHasUnseen) { - mIcon.setImageResource(mSelected ? UNSEEN_SELECTED_ICON_RESOURCE_ID - : UNSEEN_ICON_RESOURCE_ID); - } else { - mIcon.setImageResource(mSelected ? mSelectedIconResourceId : mIconResourceId); - } + mIcon.setImageResource(mSelected ? mSelectedIconResourceId : mIconResourceId); + mUnseenIcon.setVisibility(mHasUnseen ? VISIBLE : GONE); } + } diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationButtonTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationButtonTest.java index e1ee6162ddcc..96d567d3a8b5 100644 --- a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationButtonTest.java +++ b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationButtonTest.java @@ -31,6 +31,7 @@ import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.LayoutInflater; import android.view.View; +import android.widget.ImageView; import android.widget.LinearLayout; import androidx.test.filters.SmallTest; @@ -216,6 +217,22 @@ public class CarNavigationButtonTest extends SysuiTestCase { }), any()); } + @Test + public void onSetUnseen_hasUnseen_showsUnseenIndicator() { + mDefaultButton.setUnseen(true); + ImageView hasUnseenIndicator = mDefaultButton.findViewById(R.id.car_nav_button_unseen_icon); + + assertThat(hasUnseenIndicator.getVisibility()).isEqualTo(View.VISIBLE); + } + + @Test + public void onSetUnseen_doesNotHaveUnseen_hidesUnseenIndicator() { + mDefaultButton.setUnseen(false); + ImageView hasUnseenIndicator = mDefaultButton.findViewById(R.id.car_nav_button_unseen_icon); + + assertThat(hasUnseenIndicator.getVisibility()).isEqualTo(View.GONE); + } + private String getCurrentActivityName() { return mActivityManager.getRunningTasks(1).get(0).topActivity.flattenToShortString(); } diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java index 9e49826f70c3..c2ce84023869 100644 --- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java +++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java @@ -262,10 +262,11 @@ public class DynamicSystemInstallationService extends Service return; } + stopForeground(true); mJustCancelledByUser = true; if (mInstallTask.cancel(false)) { - // Will cleanup and post status in onResult() + // Will stopSelf() in onResult() Log.d(TAG, "Cancel request filed successfully"); } else { Log.e(TAG, "Trying to cancel installation while it's already completed."); diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java index 1e19786f5d41..ec445d4dcbee 100644 --- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java @@ -160,7 +160,7 @@ public class ExternalStorageProvider extends FileSystemProvider { final int userId = UserHandle.myUserId(); final List<VolumeInfo> volumes = mStorageManager.getVolumes(); for (VolumeInfo volume : volumes) { - if (!volume.isMountedReadable()) continue; + if (!volume.isMountedReadable() || volume.getMountUserId() != userId) continue; final String rootId; final String title; @@ -192,9 +192,8 @@ public class ExternalStorageProvider extends FileSystemProvider { title = mStorageManager.getBestVolumeDescription(privateVol); storageUuid = StorageManager.convert(privateVol.fsUuid); } - } else if ((volume.getType() == VolumeInfo.TYPE_PUBLIC - || volume.getType() == VolumeInfo.TYPE_STUB) - && volume.getMountUserId() == userId) { + } else if (volume.getType() == VolumeInfo.TYPE_PUBLIC + || volume.getType() == VolumeInfo.TYPE_STUB) { rootId = volume.getFsUuid(); title = mStorageManager.getBestVolumeDescription(volume); storageUuid = null; diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java index a2bd210b67a6..a784e04ee6a0 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java @@ -16,6 +16,9 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; @@ -148,17 +151,18 @@ public class A2dpProfile implements LocalBluetoothProfile { } public boolean connect(BluetoothDevice device) { - if (mService == null) return false; - return mService.connect(device); + if (mService == null) { + return false; + } + return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } public boolean disconnect(BluetoothDevice device) { - if (mService == null) return false; - // Downgrade priority as user is disconnecting the headset. - if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); + if (mService == null) { + return false; } - return mService.disconnect(device); + + return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } public int getConnectionStatus(BluetoothDevice device) { @@ -182,12 +186,12 @@ public class A2dpProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } public int getPreferred(BluetoothDevice device) { if (mService == null) { - return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } @@ -197,11 +201,11 @@ public class A2dpProfile implements LocalBluetoothProfile { return; } if (preferred) { - if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); + if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { + mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); + mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } } boolean isA2dpPlaying() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java index bc03c343a909..8ca5a74652dc 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java @@ -16,6 +16,9 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import android.bluetooth.BluetoothA2dpSink; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; @@ -116,18 +119,15 @@ final class A2dpSinkProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - return mService.connect(device); + return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } public boolean disconnect(BluetoothDevice device) { if (mService == null) { return false; } - // Downgrade priority as user is disconnecting the headset. - if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); - } - return mService.disconnect(device); + + return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } public int getConnectionStatus(BluetoothDevice device) { @@ -141,12 +141,12 @@ final class A2dpSinkProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } public int getPreferred(BluetoothDevice device) { if (mService == null) { - return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } @@ -156,11 +156,11 @@ final class A2dpSinkProfile implements LocalBluetoothProfile { return; } if (preferred) { - if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); + if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { + mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); + mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java index 560cb3b9b5b4..d65b5da22056 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java @@ -16,6 +16,9 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; @@ -112,18 +115,15 @@ public class HeadsetProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - return mService.connect(device); + return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } public boolean disconnect(BluetoothDevice device) { if (mService == null) { return false; } - // Downgrade priority as user is disconnecting the headset. - if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); - } - return mService.disconnect(device); + + return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } public int getConnectionStatus(BluetoothDevice device) { @@ -165,12 +165,12 @@ public class HeadsetProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } public int getPreferred(BluetoothDevice device) { if (mService == null) { - return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } @@ -180,11 +180,11 @@ public class HeadsetProfile implements LocalBluetoothProfile { return; } if (preferred) { - if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); + if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { + mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); + mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java index b4b55f363020..9f1af669c708 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java @@ -16,6 +16,9 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; @@ -146,17 +149,18 @@ public class HearingAidProfile implements LocalBluetoothProfile { } public boolean connect(BluetoothDevice device) { - if (mService == null) return false; - return mService.connect(device); + if (mService == null) { + return false; + } + return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } public boolean disconnect(BluetoothDevice device) { - if (mService == null) return false; - // Downgrade priority as user is disconnecting the hearing aid. - if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); + if (mService == null) { + return false; } - return mService.disconnect(device); + + return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } public int getConnectionStatus(BluetoothDevice device) { @@ -180,12 +184,12 @@ public class HearingAidProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } public int getPreferred(BluetoothDevice device) { if (mService == null) { - return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } @@ -195,11 +199,11 @@ public class HearingAidProfile implements LocalBluetoothProfile { return; } if (preferred) { - if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); + if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { + mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); + mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java index a372e23654e0..678f2e37c6bf 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java @@ -16,6 +16,9 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; @@ -126,7 +129,7 @@ final class HfpClientProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - return mService.connect(device); + return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } @Override @@ -134,11 +137,8 @@ final class HfpClientProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - // Downgrade priority as user is disconnecting the headset. - if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); - } - return mService.disconnect(device); + + return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } @Override @@ -154,13 +154,13 @@ final class HfpClientProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } @Override public int getPreferred(BluetoothDevice device) { if (mService == null) { - return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } @@ -171,11 +171,11 @@ final class HfpClientProfile implements LocalBluetoothProfile { return; } if (preferred) { - if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); + if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { + mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); + mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java index 975a1e67af5b..588083e73481 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java @@ -16,6 +16,9 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; @@ -99,13 +102,17 @@ public class HidProfile implements LocalBluetoothProfile { } public boolean connect(BluetoothDevice device) { - if (mService == null) return false; - return mService.connect(device); + if (mService == null) { + return false; + } + return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } public boolean disconnect(BluetoothDevice device) { - if (mService == null) return false; - return mService.disconnect(device); + if (mService == null) { + return false; + } + return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } public int getConnectionStatus(BluetoothDevice device) { @@ -119,12 +126,12 @@ public class HidProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - return mService.getConnectionPolicy(device) != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return mService.getConnectionPolicy(device) != CONNECTION_POLICY_FORBIDDEN; } public int getPreferred(BluetoothDevice device) { if (mService == null) { - return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } @@ -132,11 +139,11 @@ public class HidProfile implements LocalBluetoothProfile { public void setPreferred(BluetoothDevice device, boolean preferred) { if (mService == null) return; if (preferred) { - if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); + if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { + mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); + mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java index 95139a1bfab9..7d121aaa1ad1 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java @@ -16,6 +16,9 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; @@ -115,18 +118,15 @@ public final class MapClientProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - return mService.connect(device); + return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } public boolean disconnect(BluetoothDevice device) { if (mService == null) { return false; } - // Downgrade priority as user is disconnecting. - if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); - } - return mService.disconnect(device); + + return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } public int getConnectionStatus(BluetoothDevice device) { @@ -140,12 +140,12 @@ public final class MapClientProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } public int getPreferred(BluetoothDevice device) { if (mService == null) { - return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } @@ -155,11 +155,11 @@ public final class MapClientProfile implements LocalBluetoothProfile { return; } if (preferred) { - if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); + if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { + mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); + mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java index 31a0eea56b42..a96a4e73feea 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java @@ -16,6 +16,8 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; @@ -119,10 +121,8 @@ public class MapProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); - } - return mService.disconnect(device); + + return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } public int getConnectionStatus(BluetoothDevice device) { @@ -136,12 +136,12 @@ public class MapProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } public int getPreferred(BluetoothDevice device) { if (mService == null) { - return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } @@ -155,7 +155,7 @@ public class MapProfile implements LocalBluetoothProfile { mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); + mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java index 4ea0df621bea..56267fc596cf 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java @@ -16,6 +16,9 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; @@ -129,7 +132,7 @@ public final class PbapClientProfile implements LocalBluetoothProfile { return false; } Log.d(TAG,"PBAPClientProfile attempting to connect to " + device.getAddress()); - return mService.connect(device); + return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } public boolean disconnect(BluetoothDevice device) { @@ -137,7 +140,7 @@ public final class PbapClientProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - return mService.disconnect(device); + return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } public int getConnectionStatus(BluetoothDevice device) { @@ -151,12 +154,12 @@ public final class PbapClientProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } public int getPreferred(BluetoothDevice device) { if (mService == null) { - return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } @@ -166,11 +169,11 @@ public final class PbapClientProfile implements LocalBluetoothProfile { return; } if (preferred) { - if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); + if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { + mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); + mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java index 3f920a8cf1dd..f7c0bf5c8c9d 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java @@ -16,6 +16,8 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; @@ -96,8 +98,10 @@ public class PbapServerProfile implements LocalBluetoothProfile { } public boolean disconnect(BluetoothDevice device) { - if (mService == null) return false; - return mService.disconnect(device); + if (mService == null) { + return false; + } + return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } public int getConnectionStatus(BluetoothDevice device) { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java index 0ca4d6195a32..3022c5b566eb 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java @@ -16,6 +16,9 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; @@ -112,17 +115,15 @@ final class SapProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - return mService.connect(device); + return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } public boolean disconnect(BluetoothDevice device) { if (mService == null) { return false; } - if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); - } - return mService.disconnect(device); + + return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } public int getConnectionStatus(BluetoothDevice device) { @@ -136,12 +137,12 @@ final class SapProfile implements LocalBluetoothProfile { if (mService == null) { return false; } - return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN; } public int getPreferred(BluetoothDevice device) { if (mService == null) { - return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + return CONNECTION_POLICY_FORBIDDEN; } return mService.getConnectionPolicy(device); } @@ -151,11 +152,11 @@ final class SapProfile implements LocalBluetoothProfile { return; } if (preferred) { - if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); + if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) { + mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED); } } else { - mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); + mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java index 5b4ef3a47386..70b56ed0b391 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java @@ -194,6 +194,14 @@ public class LocalMediaManager implements BluetoothCallback { } } + void dispatchDeviceAttributesChanged() { + synchronized (mCallbacks) { + for (DeviceCallback callback : mCallbacks) { + callback.onDeviceAttributesChanged(); + } + } + } + /** * Stop scan MediaDevice */ @@ -306,14 +314,12 @@ public class LocalMediaManager implements BluetoothCallback { } mCurrentConnectedDevice = connectDevice; updatePhoneMediaDeviceSummary(); - dispatchDeviceListUpdate(); + dispatchDeviceAttributesChanged(); } @Override public void onDeviceAttributesChanged() { - addPhoneDeviceIfNecessary(); - removePhoneMediaDeviceIfNecessary(); - dispatchDeviceListUpdate(); + dispatchDeviceAttributesChanged(); } } @@ -327,7 +333,7 @@ public class LocalMediaManager implements BluetoothCallback { * * @param devices MediaDevice list */ - void onDeviceListUpdate(List<MediaDevice> devices); + default void onDeviceListUpdate(List<MediaDevice> devices) {}; /** * Callback for notifying the connected device is changed. @@ -338,6 +344,12 @@ public class LocalMediaManager implements BluetoothCallback { * {@link MediaDeviceState#STATE_CONNECTING}, * {@link MediaDeviceState#STATE_DISCONNECTED} */ - void onSelectedDeviceStateChanged(MediaDevice device, @MediaDeviceState int state); + default void onSelectedDeviceStateChanged(MediaDevice device, + @MediaDeviceState int state) {}; + + /** + * Callback for notifying the device attributes is changed. + */ + default void onDeviceAttributesChanged() {}; } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java index 976445eb8c04..ccb6646cf683 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java @@ -16,6 +16,9 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.verify; @@ -67,13 +70,13 @@ public class A2dpSinkProfileTest { @Test public void connect_shouldConnectBluetoothA2dpSink() { mProfile.connect(mBluetoothDevice); - verify(mService).connect(mBluetoothDevice); + verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED); } @Test public void disconnect_shouldDisconnectBluetoothA2dpSink() { mProfile.disconnect(mBluetoothDevice); - verify(mService).disconnect(mBluetoothDevice); + verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN); } @Test diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java index 69c020dd5c08..91807609df1a 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java @@ -16,6 +16,9 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.verify; @@ -67,13 +70,13 @@ public class HfpClientProfileTest { @Test public void connect_shouldConnectBluetoothHeadsetClient() { mProfile.connect(mBluetoothDevice); - verify(mService).connect(mBluetoothDevice); + verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED); } @Test public void disconnect_shouldDisconnectBluetoothHeadsetClient() { mProfile.disconnect(mBluetoothDevice); - verify(mService).disconnect(mBluetoothDevice); + verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN); } @Test diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java index 6f667094a5aa..1425c381256b 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java @@ -16,6 +16,9 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.verify; @@ -67,13 +70,13 @@ public class MapClientProfileTest { @Test public void connect_shouldConnectBluetoothMapClient() { mProfile.connect(mBluetoothDevice); - verify(mService).connect(mBluetoothDevice); + verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED); } @Test public void disconnect_shouldDisconnectBluetoothMapClient() { mProfile.disconnect(mBluetoothDevice); - verify(mService).disconnect(mBluetoothDevice); + verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN); } @Test diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java index b21ec9c3e52a..15f560bef73e 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java @@ -16,6 +16,9 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.verify; @@ -67,13 +70,13 @@ public class PbapClientProfileTest { @Test public void connect_shouldConnectBluetoothPbapClient() { mProfile.connect(mBluetoothDevice); - verify(mService).connect(mBluetoothDevice); + verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED); } @Test public void disconnect_shouldDisconnectBluetoothPbapClient() { mProfile.disconnect(mBluetoothDevice); - verify(mService).disconnect(mBluetoothDevice); + verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN); } @Test diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java index ec880345f6f0..4f978a822890 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java @@ -16,6 +16,9 @@ package com.android.settingslib.bluetooth; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.verify; @@ -66,13 +69,13 @@ public class SapProfileTest { @Test public void connect_shouldConnectBluetoothSap() { mProfile.connect(mBluetoothDevice); - verify(mService).connect(mBluetoothDevice); + verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED); } @Test public void disconnect_shouldDisconnectBluetoothSap() { mProfile.disconnect(mBluetoothDevice); - verify(mService).disconnect(mBluetoothDevice); + verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN); } @Test diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java index 98bb74ad0718..894aa78a978e 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java @@ -355,7 +355,7 @@ public class LocalMediaManagerTest { mLocalMediaManager.mMediaDeviceCallback.onConnectedDeviceChanged(TEST_DEVICE_ID_2); assertThat(mLocalMediaManager.getCurrentConnectedDevice()).isEqualTo(device2); - verify(mCallback).onDeviceListUpdate(any()); + verify(mCallback).onDeviceAttributesChanged(); } @Test @@ -373,7 +373,7 @@ public class LocalMediaManagerTest { mLocalMediaManager.registerCallback(mCallback); mLocalMediaManager.mMediaDeviceCallback.onConnectedDeviceChanged(TEST_DEVICE_ID_1); - verify(mCallback, never()).onDeviceListUpdate(any()); + verify(mCallback, never()).onDeviceAttributesChanged(); } @Test @@ -382,6 +382,6 @@ public class LocalMediaManagerTest { mLocalMediaManager.mMediaDeviceCallback.onDeviceAttributesChanged(); - verify(mCallback).onDeviceListUpdate(any()); + verify(mCallback).onDeviceAttributesChanged(); } } diff --git a/packages/SettingsProvider/Android.bp b/packages/SettingsProvider/Android.bp index f40d3a160513..96a98dca8a5b 100644 --- a/packages/SettingsProvider/Android.bp +++ b/packages/SettingsProvider/Android.bp @@ -8,6 +8,7 @@ android_app { libs: [ "telephony-common", "ims-common", + "unsupportedappusage", ], static_libs: [ "junit", @@ -40,6 +41,7 @@ android_test { libs: [ "android.test.base", "android.test.mock", + "unsupportedappusage", ], resource_dirs: ["res"], aaptflags: [ diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java index 049b9f0e903e..5636cc8a3ca3 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java @@ -16,7 +16,7 @@ package android.provider.settings.backup; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.provider.Settings; /** Information relating to the Secure settings which should be backed up */ diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java index 89b19de8dfcb..3f5b0daa06a7 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java @@ -16,7 +16,7 @@ package android.provider.settings.backup; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.provider.Settings; /** Information about the system settings to back up */ diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java index 94ab0f11927d..c5d4fa9f1b40 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java @@ -25,7 +25,7 @@ import static android.provider.settings.validators.SettingsValidators.URI_VALIDA import static android.provider.settings.validators.SettingsValidators.VIBRATION_INTENSITY_VALIDATOR; import android.annotation.Nullable; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.hardware.display.ColorDisplayManager; import android.os.BatteryManager; diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 347d6c2264eb..1c63efc3b5bc 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -194,6 +194,9 @@ <uses-permission android:name="android.permission.MANAGE_APPOPS" /> + <!-- Permission required for storage tests - FuseDaemonHostTest --> + <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/> + <!-- Permission needed to run network tests in CTS --> <uses-permission android:name="android.permission.MANAGE_TEST_NETWORKS" /> <!-- Permission needed to test tcp keepalive offload. --> diff --git a/packages/SystemUI/docs/broadcasts.md b/packages/SystemUI/docs/broadcasts.md index 56a637fe588c..28657f28e53b 100644 --- a/packages/SystemUI/docs/broadcasts.md +++ b/packages/SystemUI/docs/broadcasts.md @@ -42,24 +42,29 @@ Acquire the dispatcher by using `@Inject` to obtain a `BroadcastDispatcher`. The ```kotlin /** - * Register a receiver for broadcast with the dispatcher - * - * @param receiver A receiver to dispatch the [Intent] - * @param filter A filter to determine what broadcasts should be dispatched to this receiver. - * It will only take into account actions and categories for filtering. It must - * have at least one action. - * @param handler A handler to dispatch [BroadcastReceiver.onReceive]. By default, it is the - * main handler. Pass `null` to use the default. - * @param user A user handle to determine which broadcast should be dispatched to this receiver. - * By default, it is the current user. - * @throws IllegalArgumentException if the filter has other constraints that are not actions or - * categories or the filter has no actions. - */ + * Register a receiver for broadcast with the dispatcher + * + * @param receiver A receiver to dispatch the [Intent] + * @param filter A filter to determine what broadcasts should be dispatched to this receiver. + * It will only take into account actions and categories for filtering. It must + * have at least one action. + * @param executor An executor to dispatch [BroadcastReceiver.onReceive]. Pass null to use an + * executor in the main thread (default). + * @param user A user handle to determine which broadcast should be dispatched to this receiver. + * By default, it is the current user. + * @throws IllegalArgumentException if the filter has other constraints that are not actions or + * categories or the filter has no actions. + */ @JvmOverloads -fun registerReceiver(BroadcastReceiver, IntentFilter, Handler? = mainHandler, UserHandle = context.user) +fun registerReceiver( + BroadcastReceiver, + IntentFilter, + Executor? = context.mainExecutor, + UserHandle = context.user +) { ``` -All subscriptions are done with the same overloaded method. As specified in the doc, in order to pass a `UserHandle` with the default `Handler`, pass `null` for the `Handler`. +All subscriptions are done with the same overloaded method. As specified in the doc, in order to pass a `UserHandle` with the default `Executor`, pass `null` for the `Executor`. In the same way as with `Context`, subscribing the same `BroadcastReceiver` for the same user using different filters will result on two subscriptions, not in replacing the filter. diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml index f7e9fedd5f66..4d184d5758d3 100644 --- a/packages/SystemUI/res-keyguard/values/strings.xml +++ b/packages/SystemUI/res-keyguard/values/strings.xml @@ -240,6 +240,15 @@ <!-- Description of airplane mode --> <string name="airplane_mode">Airplane mode</string> + <!-- An explanation text that the PIN needs to be entered to prepare for an operating system update. [CHAR LIMIT=80] --> + <string name="kg_prompt_reason_prepare_for_update_pin">PIN required to prepare for update</string> + + <!-- An explanation text that the pattern needs to be entered to prepare for an operating system update. [CHAR LIMIT=80] --> + <string name="kg_prompt_reason_prepare_for_update_pattern">Pattern required to prepare for update</string> + + <!-- An explanation text that the password needs to be entered to prepare for an operating system update. [CHAR LIMIT=80] --> + <string name="kg_prompt_reason_prepare_for_update_password">Password required to prepare for update</string> + <!-- An explanation text that the pattern needs to be solved since the device has just been restarted. [CHAR LIMIT=80] --> <string name="kg_prompt_reason_restart_pattern">Pattern required after device restarts</string> diff --git a/packages/SystemUI/res/layout/media_carousel.xml b/packages/SystemUI/res/layout/media_carousel.xml new file mode 100644 index 000000000000..e91f840fe238 --- /dev/null +++ b/packages/SystemUI/res/layout/media_carousel.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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 + --> + +<!-- Carousel for media controls --> +<HorizontalScrollView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="@dimen/qs_media_height" + android:padding="@dimen/qs_media_padding" + android:scrollbars="none" + android:visibility="gone" + > + <LinearLayout + android:id="@+id/media_carousel" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + > + <!-- QSMediaPlayers will be added here dynamically --> + </LinearLayout> +</HorizontalScrollView> diff --git a/packages/SystemUI/res/layout/people_strip.xml b/packages/SystemUI/res/layout/people_strip.xml index f0ac08bdad37..982aa8ef6d16 100644 --- a/packages/SystemUI/res/layout/people_strip.xml +++ b/packages/SystemUI/res/layout/people_strip.xml @@ -18,7 +18,10 @@ <com.android.systemui.statusbar.notification.stack.PeopleHubView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" - android:layout_height="105dp"> + android:layout_height="@dimen/notification_section_header_height" + android:focusable="true" + android:clickable="true" +> <com.android.systemui.statusbar.notification.row.NotificationBackgroundView android:id="@+id/backgroundNormal" @@ -34,199 +37,56 @@ android:id="@+id/people_list" android:layout_width="match_parent" android:layout_height="match_parent" - android:paddingTop="12dp" - android:paddingBottom="12dp" android:gravity="center" android:orientation="horizontal"> - <View - android:layout_width="8dp" - android:layout_height="match_parent" + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" android:layout_weight="1" + android:layout_marginStart="@dimen/notification_section_header_padding_left" + android:gravity="start" + android:textAlignment="gravity" + android:text="@string/notification_section_header_conversations" + android:textSize="12sp" + android:textColor="@color/notification_section_header_label_color" + android:fontFamily="@*android:string/config_headlineFontFamilyMedium" /> - <LinearLayout - android:layout_width="70dp" - android:layout_height="match_parent" - android:gravity="center_horizontal" - android:orientation="vertical" - android:visibility="invisible"> - - <ImageView - android:id="@+id/person_icon" - android:layout_width="36dp" - android:layout_height="36dp" - android:scaleType="fitCenter" - /> - - <TextView - android:id="@+id/person_name" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:paddingTop="8dp" - android:ellipsize="end" - android:maxLines="2" - android:textAlignment="center" - /> - - </LinearLayout> - - <View - android:layout_width="8dp" - android:layout_height="match_parent" - android:layout_weight="1" + <ImageView + android:layout_width="48dp" + android:layout_height="48dp" + android:padding="8dp" + android:scaleType="fitCenter" /> - <View - android:layout_width="8dp" - android:layout_height="match_parent" - android:layout_weight="1" + <ImageView + android:layout_width="48dp" + android:layout_height="48dp" + android:padding="8dp" + android:scaleType="fitCenter" /> - <LinearLayout - android:layout_width="70dp" - android:layout_height="match_parent" - android:gravity="center_horizontal" - android:orientation="vertical" - android:visibility="invisible"> - - <ImageView - android:id="@+id/person_icon" - android:layout_width="36dp" - android:layout_height="36dp" - android:scaleType="fitCenter" - /> - - <TextView - android:id="@+id/person_name" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:paddingTop="8dp" - android:ellipsize="end" - android:maxLines="2" - android:textAlignment="center" - /> - - </LinearLayout> - - <View - android:layout_width="8dp" - android:layout_height="match_parent" - android:layout_weight="1" + <ImageView + android:layout_width="48dp" + android:layout_height="48dp" + android:padding="8dp" + android:scaleType="fitCenter" /> - <View - android:layout_width="8dp" - android:layout_height="match_parent" - android:layout_weight="1" - /> - - <LinearLayout - android:layout_width="70dp" - android:layout_height="match_parent" - android:gravity="center_horizontal" - android:orientation="vertical" - android:visibility="invisible"> - - <ImageView - android:id="@+id/person_icon" - android:layout_width="36dp" - android:layout_height="36dp" - android:scaleType="fitCenter" - /> - - <TextView - android:id="@+id/person_name" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:paddingTop="8dp" - android:ellipsize="end" - android:maxLines="2" - android:textAlignment="center" - /> - - </LinearLayout> - - <View - android:layout_width="8dp" - android:layout_height="match_parent" - android:layout_weight="1" + <ImageView + android:layout_width="48dp" + android:layout_height="48dp" + android:padding="8dp" + android:scaleType="fitCenter" /> - <View - android:layout_width="8dp" - android:layout_height="match_parent" - android:layout_weight="1" - /> - - <LinearLayout - android:layout_width="70dp" - android:layout_height="match_parent" - android:gravity="center_horizontal" - android:orientation="vertical" - android:visibility="invisible"> - - <ImageView - android:id="@+id/person_icon" - android:layout_width="36dp" - android:layout_height="36dp" - android:scaleType="fitCenter" - /> - - <TextView - android:id="@+id/person_name" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:paddingTop="8dp" - android:ellipsize="end" - android:maxLines="2" - android:textAlignment="center" - /> - - </LinearLayout> - - <View - android:layout_width="8dp" - android:layout_height="match_parent" - android:layout_weight="1" - /> - - <View - android:layout_width="8dp" - android:layout_height="match_parent" - android:layout_weight="1" - /> - - <LinearLayout - android:layout_width="70dp" - android:layout_height="match_parent" - android:gravity="center_horizontal" - android:orientation="vertical" - android:visibility="invisible"> - - <ImageView - android:id="@+id/person_icon" - android:layout_width="36dp" - android:layout_height="36dp" - android:scaleType="fitCenter" - /> - - <TextView - android:id="@+id/person_name" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:paddingTop="8dp" - android:ellipsize="end" - android:maxLines="2" - android:textAlignment="center" - /> - - </LinearLayout> - - <View - android:layout_width="8dp" - android:layout_height="match_parent" - android:layout_weight="1" + <ImageView + android:layout_width="48dp" + android:layout_height="48dp" + android:layout_marginEnd="8dp" + android:padding="8dp" + android:scaleType="fitCenter" /> </LinearLayout> @@ -236,4 +96,4 @@ android:layout_width="match_parent" android:layout_height="match_parent" /> -</com.android.systemui.statusbar.notification.stack.PeopleHubView>
\ No newline at end of file +</com.android.systemui.statusbar.notification.stack.PeopleHubView> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 52c0a26f09ba..4f532b7b751d 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1163,6 +1163,9 @@ <!-- Section title for notifications that do not vibrate or make noise. [CHAR LIMIT=40] --> <string name="notification_section_header_gentle">Silent notifications</string> + <!-- Section title for conversational notifications. [CHAR LIMIT=40] --> + <string name="notification_section_header_conversations">Conversations</string> + <!-- Content description for accessibility: Tapping this button will dismiss all gentle notifications [CHAR LIMIT=NONE] --> <string name="accessibility_notification_section_header_gentle_clear_all">Clear all silent notifications</string> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java index 2c8f23889a17..ed1cd8191092 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java @@ -168,8 +168,9 @@ public class KeyguardHostView extends FrameLayout implements SecurityCallback { * * @param reason a flag indicating which string should be shown, see * {@link KeyguardSecurityView#PROMPT_REASON_NONE}, - * {@link KeyguardSecurityView#PROMPT_REASON_RESTART} and - * {@link KeyguardSecurityView#PROMPT_REASON_TIMEOUT}. + * {@link KeyguardSecurityView#PROMPT_REASON_RESTART}, + * {@link KeyguardSecurityView#PROMPT_REASON_TIMEOUT}, and + * {@link KeyguardSecurityView#PROMPT_REASON_PREPARE_FOR_UPDATE}. */ public void showPromptReason(int reason) { mSecurityContainer.showPromptReason(reason); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java index f8f3dc8d6ecd..718bcf16c832 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java @@ -137,6 +137,8 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView return R.string.kg_prompt_reason_device_admin; case PROMPT_REASON_USER_REQUEST: return R.string.kg_prompt_reason_user_request; + case PROMPT_REASON_PREPARE_FOR_UPDATE: + return R.string.kg_prompt_reason_prepare_for_update_password; case PROMPT_REASON_NONE: return 0; default: diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java index 9eb168a7b567..48c6bd114d4a 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java @@ -438,6 +438,10 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit case PROMPT_REASON_USER_REQUEST: mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_user_request); break; + case PROMPT_REASON_PREPARE_FOR_UPDATE: + mSecurityMessageDisplay.setMessage( + R.string.kg_prompt_reason_prepare_for_update_pattern); + break; case PROMPT_REASON_NONE: break; default: diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java index c67deccb1f62..6d865ab525f3 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java @@ -116,6 +116,8 @@ public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView return R.string.kg_prompt_reason_device_admin; case PROMPT_REASON_USER_REQUEST: return R.string.kg_prompt_reason_user_request; + case PROMPT_REASON_PREPARE_FOR_UPDATE: + return R.string.kg_prompt_reason_prepare_for_update_pin; case PROMPT_REASON_NONE: return 0; default: diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java index e10819473dea..09d4d5fcbde9 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java @@ -51,6 +51,11 @@ public interface KeyguardSecurityView { */ int PROMPT_REASON_AFTER_LOCKOUT = 5; + /*** + * Strong auth is require to prepare for an unattended update. + */ + int PROMPT_REASON_PREPARE_FOR_UPDATE = 6; + /** * Interface back to keyguard to tell it when security * @param callback diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 431862f1cc11..a58e3d78bb08 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -1638,7 +1638,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED); filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED); - broadcastDispatcher.registerReceiver(mBroadcastReceiver, filter, mHandler); + broadcastDispatcher.registerReceiverWithHandler(mBroadcastReceiver, filter, mHandler); final IntentFilter allUserFilter = new IntentFilter(); allUserFilter.addAction(Intent.ACTION_USER_INFO_CHANGED); @@ -1649,8 +1649,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab allUserFilter.addAction(ACTION_USER_UNLOCKED); allUserFilter.addAction(ACTION_USER_STOPPED); allUserFilter.addAction(ACTION_USER_REMOVED); - broadcastDispatcher.registerReceiver(mBroadcastAllReceiver, allUserFilter, mHandler, - UserHandle.ALL); + broadcastDispatcher.registerReceiverWithHandler(mBroadcastAllReceiver, allUserFilter, + mHandler, UserHandle.ALL); mSubscriptionManager.addOnSubscriptionsChangedListener(mSubscriptionListener); try { diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index eecc54c678c0..bbe972dea11f 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -121,6 +121,7 @@ import com.android.systemui.util.leak.GarbageMonitor; import com.android.systemui.util.leak.LeakDetector; import com.android.systemui.util.leak.LeakReporter; import com.android.systemui.util.sensors.AsyncSensorManager; +import com.android.systemui.wm.DisplayImeController; import com.android.systemui.wm.DisplayWindowController; import com.android.systemui.wm.SystemWindows; @@ -321,6 +322,7 @@ public class Dependency { @Inject Lazy<StatusBar> mStatusBar; @Inject Lazy<DisplayWindowController> mDisplayWindowController; @Inject Lazy<SystemWindows> mSystemWindows; + @Inject Lazy<DisplayImeController> mDisplayImeController; @Inject public Dependency() { @@ -509,6 +511,7 @@ public class Dependency { mProviders.put(StatusBar.class, mStatusBar::get); mProviders.put(DisplayWindowController.class, mDisplayWindowController::get); mProviders.put(SystemWindows.class, mSystemWindows::get); + mProviders.put(DisplayImeController.class, mDisplayImeController::get); // TODO(b/118592525): to support multi-display , we start to add something which is // per-display, while others may be global. I think it's time to add diff --git a/packages/SystemUI/src/com/android/systemui/DumpController.kt b/packages/SystemUI/src/com/android/systemui/DumpController.kt index 8c7075bee6cc..f14c4cd8e6c6 100644 --- a/packages/SystemUI/src/com/android/systemui/DumpController.kt +++ b/packages/SystemUI/src/com/android/systemui/DumpController.kt @@ -30,6 +30,14 @@ import javax.inject.Singleton /** * Controller that allows any [Dumpable] to subscribe and be dumped along with other SystemUI * dependencies. + * + * To dump a specific dumpable on-demand: + * + * ``` + * $ adb shell dumpsys activity service com.android.systemui/.SystemUIService dependency DumpController <tag1>,<tag2>,<tag3> + * ``` + * + * Where tag1, tag2, etc. are the tags of the dumpables you want to dump. */ @Singleton class DumpController @Inject constructor() : Dumpable { diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index 0e736dcd11a8..c533755c76da 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -266,7 +266,7 @@ public class ScreenDecorations extends SystemUI implements Tunable { IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_USER_SWITCHED); - mBroadcastDispatcher.registerReceiver(mIntentReceiver, filter, mHandler); + mBroadcastDispatcher.registerReceiverWithHandler(mIntentReceiver, filter, mHandler); mOverlay.addOnLayoutChangeListener(new OnLayoutChangeListener() { @Override diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt index 8cb0cc5db1d3..cedf7c354ccc 100644 --- a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt +++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt @@ -20,6 +20,7 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.IntentFilter import android.os.Handler +import android.os.HandlerExecutor import android.os.Looper import android.os.Message import android.os.UserHandle @@ -32,13 +33,14 @@ import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import java.io.FileDescriptor import java.io.PrintWriter +import java.util.concurrent.Executor import javax.inject.Inject import javax.inject.Singleton data class ReceiverData( val receiver: BroadcastReceiver, val filter: IntentFilter, - val handler: Handler, + val executor: Executor, val user: UserHandle ) @@ -76,24 +78,49 @@ open class BroadcastDispatcher @Inject constructor ( * @param filter A filter to determine what broadcasts should be dispatched to this receiver. * It will only take into account actions and categories for filtering. It must * have at least one action. - * @param handler A handler to dispatch [BroadcastReceiver.onReceive]. By default, it is the - * main handler. Pass `null` to use the default. + * @param handler A handler to dispatch [BroadcastReceiver.onReceive]. * @param user A user handle to determine which broadcast should be dispatched to this receiver. * By default, it is the current user. * @throws IllegalArgumentException if the filter has other constraints that are not actions or * categories or the filter has no actions. */ + @Deprecated(message = "Replacing Handler for Executor in SystemUI", + replaceWith = ReplaceWith("registerReceiver(receiver, filter, executor, user)")) @JvmOverloads - fun registerReceiver( + fun registerReceiverWithHandler( receiver: BroadcastReceiver, filter: IntentFilter, - handler: Handler? = mainHandler, + handler: Handler, user: UserHandle = context.user ) { + registerReceiver(receiver, filter, HandlerExecutor(handler), user) + } + + /** + * Register a receiver for broadcast with the dispatcher + * + * @param receiver A receiver to dispatch the [Intent] + * @param filter A filter to determine what broadcasts should be dispatched to this receiver. + * It will only take into account actions and categories for filtering. It must + * have at least one action. + * @param executor An executor to dispatch [BroadcastReceiver.onReceive]. Pass null to use an + * executor in the main thread (default). + * @param user A user handle to determine which broadcast should be dispatched to this receiver. + * By default, it is the current user. + * @throws IllegalArgumentException if the filter has other constraints that are not actions or + * categories or the filter has no actions. + */ + @JvmOverloads + fun registerReceiver( + receiver: BroadcastReceiver, + filter: IntentFilter, + executor: Executor? = context.mainExecutor, + user: UserHandle = context.user + ) { checkFilter(filter) this.handler .obtainMessage(MSG_ADD_RECEIVER, - ReceiverData(receiver, filter, handler ?: mainHandler, user)) + ReceiverData(receiver, filter, executor ?: context.mainExecutor, user)) .sendToTarget() } diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt index b2942bb14c6b..0c631aacab82 100644 --- a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt +++ b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt @@ -193,7 +193,7 @@ class UserBroadcastDispatcher( it.filter.hasAction(intent.action) && it.filter.matchCategories(intent.categories) == null } ?.forEach { - it.handler.post { + it.executor.execute { if (DEBUG) Log.w(TAG, "[$index] Dispatching ${intent.action} to ${it.receiver}") it.receiver.pendingResult = pendingResult diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index 7cd29ea48199..d835ee1865bf 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -344,11 +344,14 @@ public class BubbleController implements ConfigurationController.ConfigurationLi mSavedBubbleKeysPerUser = new SparseSetArray<>(); mCurrentUserId = mNotifUserManager.getCurrentUserId(); mNotifUserManager.addUserChangedListener( - newUserId -> { - saveBubbles(mCurrentUserId); - mBubbleData.dismissAll(DISMISS_USER_CHANGED); - restoreBubbles(newUserId); - mCurrentUserId = newUserId; + new NotificationLockscreenUserManager.UserChangedListener() { + @Override + public void onUserChanged(int newUserId) { + BubbleController.this.saveBubbles(mCurrentUserId); + mBubbleData.dismissAll(DISMISS_USER_CHANGED); + BubbleController.this.restoreBubbles(newUserId); + mCurrentUserId = newUserId; + } }); mUserCreatedBubbles = new HashSet<>(); @@ -743,9 +746,16 @@ public class BubbleController implements ConfigurationController.ConfigurationLi return !isAutogroupSummary; } else { // If it's not a user dismiss it's a cancel. + for (int i = 0; i < bubbleChildren.size(); i++) { + // First check if any of these are user-created (i.e. experimental bubbles) + if (mUserCreatedBubbles.contains(bubbleChildren.get(i).getKey())) { + // Experimental bubble! Intercept the removal. + return true; + } + } + // Not an experimental bubble, safe to remove. mBubbleData.removeSuppressedSummary(groupKey); - - // Remove any associated bubble children. + // Remove any associated bubble children with the summary. for (int i = 0; i < bubbleChildren.size(); i++) { Bubble bubbleChild = bubbleChildren.get(i); mBubbleData.notificationEntryRemoved(bubbleChild.getEntry(), diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java index 97224f1234dd..ccbbb2465742 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java @@ -111,7 +111,10 @@ public class BubbleData { } private final Context mContext; + /** Bubbles that are actively in the stack. */ private final List<Bubble> mBubbles; + /** Bubbles that are being loaded but haven't been added to the stack just yet. */ + private final List<Bubble> mPendingBubbles; private Bubble mSelectedBubble; private boolean mExpanded; private final int mMaxBubbles; @@ -143,6 +146,7 @@ public class BubbleData { public BubbleData(Context context) { mContext = context; mBubbles = new ArrayList<>(); + mPendingBubbles = new ArrayList<>(); mStateChange = new Update(mBubbles); mMaxBubbles = mContext.getResources().getInteger(R.integer.bubbles_max_rendered); } @@ -188,7 +192,15 @@ public class BubbleData { Bubble getOrCreateBubble(NotificationEntry entry) { Bubble bubble = getBubbleWithKey(entry.getKey()); if (bubble == null) { + // Check for it in pending + for (int i = 0; i < mPendingBubbles.size(); i++) { + Bubble b = mPendingBubbles.get(i); + if (b.getKey().equals(entry.getKey())) { + return b; + } + } bubble = new Bubble(entry); + mPendingBubbles.add(bubble); } else { bubble.setEntry(entry); } @@ -204,7 +216,7 @@ public class BubbleData { if (DEBUG_BUBBLE_DATA) { Log.d(TAG, "notificationEntryUpdated: " + bubble); } - + mPendingBubbles.remove(bubble); // No longer pending once we're here Bubble prevBubble = getBubbleWithKey(bubble.getKey()); suppressFlyout |= !shouldShowFlyout(bubble.getEntry()); @@ -377,6 +389,12 @@ public class BubbleData { } private void doRemove(String key, @DismissReason int reason) { + // If it was pending remove it + for (int i = 0; i < mPendingBubbles.size(); i++) { + if (mPendingBubbles.get(i).getKey().equals(key)) { + mPendingBubbles.remove(mPendingBubbles.get(i)); + } + } int indexToRemove = indexForKey(key); if (indexToRemove == -1) { return; diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java index 59d68bca93c3..6528f3762473 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java @@ -97,13 +97,11 @@ public class ExpandedAnimationController private boolean mSpringingBubbleToTouch = false; private int mExpandedViewPadding; - private float mLauncherGridDiff; public ExpandedAnimationController(Point displaySize, int expandedViewPadding, int orientation) { updateOrientation(orientation, displaySize); mExpandedViewPadding = expandedViewPadding; - mLauncherGridDiff = 30f; } /** @@ -569,15 +567,7 @@ public class ExpandedAnimationController * @return Space between bubbles in row above expanded view. */ private float getSpaceBetweenBubbles() { - /** - * Ordered left to right: - * Screen edge - * [mExpandedViewPadding] - * Expanded view edge - * [launcherGridDiff] --- arbitrary value until launcher exports widths - * Launcher's app icon grid edge that we must match - */ - final float rowMargins = (mExpandedViewPadding + mLauncherGridDiff) * 2; + final float rowMargins = mExpandedViewPadding * 2; final float maxRowWidth = getWidthForDisplayingBubbles() - rowMargins; final float totalBubbleWidth = mBubblesMaxRendered * mBubbleSizePx; diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java index 8d10552332ba..53a23b89f943 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java @@ -20,7 +20,6 @@ import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME; import android.app.INotificationManager; import android.content.Context; -import android.content.pm.IPackageManager; import android.hardware.display.AmbientDisplayConfiguration; import android.hardware.display.NightDisplayListener; import android.os.Handler; @@ -115,13 +114,6 @@ public class DependencyProvider { /** */ @Singleton @Provides - public IPackageManager provideIPackageManager() { - return IPackageManager.Stub.asInterface(ServiceManager.getService("package")); - } - - /** */ - @Singleton - @Provides public LayoutInflater providerLayoutInflater(Context context) { return LayoutInflater.from(context); } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java index bb12b53fec4c..26337b1f24b1 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java @@ -27,6 +27,7 @@ import android.app.NotificationManager; import android.app.WallpaperManager; import android.app.admin.DevicePolicyManager; import android.content.Context; +import android.content.pm.IPackageManager; import android.content.res.Resources; import android.hardware.SensorPrivacyManager; import android.os.Handler; @@ -123,6 +124,13 @@ public class SystemServicesModule { return WindowManagerGlobal.getWindowManagerService(); } + /** */ + @Singleton + @Provides + public IPackageManager provideIPackageManager() { + return IPackageManager.Stub.asInterface(ServiceManager.getService("package")); + } + @Singleton @Provides static KeyguardManager provideKeyguardManager(Context context) { diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java index c9faf69cfd6f..4e887262659e 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java @@ -91,7 +91,7 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi if (mDebuggable) { IntentFilter filter = new IntentFilter(); filter.addAction(ACTION_AOD_BRIGHTNESS); - mBroadcastDispatcher.registerReceiver(this, filter, handler, UserHandle.ALL); + mBroadcastDispatcher.registerReceiverWithHandler(this, filter, handler, UserHandle.ALL); } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index c1934c609f5a..e13c3e087893 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -22,6 +22,7 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOM import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT; +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE; import static com.android.systemui.DejankUtils.whitelistIpcs; import android.app.ActivityManager; @@ -670,6 +671,8 @@ public class KeyguardViewMediator extends SystemUI { return KeyguardSecurityView.PROMPT_REASON_USER_REQUEST; } else if (any && (strongAuth & STRONG_AUTH_REQUIRED_AFTER_LOCKOUT) != 0) { return KeyguardSecurityView.PROMPT_REASON_AFTER_LOCKOUT; + } else if (any && (strongAuth & STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE) != 0) { + return KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE; } return KeyguardSecurityView.PROMPT_REASON_NONE; } diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java index 59ac329e1983..48750fa5e769 100644 --- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java +++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java @@ -233,7 +233,7 @@ public class PowerUI extends SystemUI implements CommandQueue.Callbacks { filter.addAction(Intent.ACTION_SCREEN_OFF); filter.addAction(Intent.ACTION_SCREEN_ON); filter.addAction(Intent.ACTION_USER_SWITCHED); - mBroadcastDispatcher.registerReceiver(this, filter, mHandler); + mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mHandler); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt index f710f7fc47e2..f66a1ece1868 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt @@ -68,7 +68,7 @@ class DoubleLineTileLayout(context: Context) : ViewGroup(context), QSPanel.QSTil override fun updateResources(): Boolean { with(mContext.resources) { smallTileSize = getDimensionPixelSize(R.dimen.qs_quick_tile_size) - cellMarginHorizontal = getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal) + cellMarginHorizontal = getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal) / 2 cellMarginVertical = getDimensionPixelSize(R.dimen.new_qs_vertical_margin) } requestLayout() diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java index f7e4c794836e..1077834e7146 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java @@ -124,7 +124,7 @@ public class QSMediaPlayer { } } }); - btn.setImageDrawable(mContext.getResources().getDrawable(R.drawable.lb_ic_replay)); + btn.setImageDrawable(mContext.getResources().getDrawable(R.drawable.lb_ic_play)); btn.setImageTintList(ColorStateList.valueOf(mForegroundColor)); btn.setVisibility(View.VISIBLE); @@ -199,8 +199,7 @@ public class QSMediaPlayer { List<ResolveInfo> info = pm.queryBroadcastReceiversAsUser(it, 0, mContext.getUser()); if (info != null) { for (ResolveInfo inf : info) { - if (inf.activityInfo.packageName.equals(notif.contentIntent.getCreatorPackage())) { - Log.d(TAG, "Found receiver for package: " + inf); + if (inf.activityInfo.packageName.equals(mController.getPackageName())) { mRecvComponent = inf.getComponentInfo().getComponentName(); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 51e352b30019..35b8312ba25c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -184,21 +184,10 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne // Add media carousel if (useQsMediaPlayer(context)) { - HorizontalScrollView mediaScrollView = new HorizontalScrollView(mContext); - mediaScrollView.setHorizontalScrollBarEnabled(false); - int playerHeight = (int) getResources().getDimension(R.dimen.qs_media_height); - int padding = (int) getResources().getDimension(R.dimen.qs_media_padding); - LayoutParams lpView = new LayoutParams(LayoutParams.MATCH_PARENT, playerHeight); - lpView.setMarginStart(padding); - lpView.setMarginEnd(padding); - addView(mediaScrollView, lpView); - - LayoutParams lpCarousel = new LayoutParams(LayoutParams.MATCH_PARENT, - LayoutParams.WRAP_CONTENT); - mMediaCarousel = new LinearLayout(mContext); - mMediaCarousel.setOrientation(LinearLayout.HORIZONTAL); - mediaScrollView.addView(mMediaCarousel, lpCarousel); - mediaScrollView.setVisibility(View.GONE); + HorizontalScrollView mediaScrollView = (HorizontalScrollView) LayoutInflater.from( + mContext).inflate(R.layout.media_carousel, this, false); + mMediaCarousel = mediaScrollView.findViewById(R.id.media_carousel); + addView(mediaScrollView); } else { mMediaCarousel = null; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java index d40e25064352..cec1cb2fb53b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java @@ -106,7 +106,7 @@ public class QuickQSMediaPlayer { } } }); - btn.setImageDrawable(mContext.getResources().getDrawable(R.drawable.lb_ic_replay)); + btn.setImageDrawable(mContext.getResources().getDrawable(R.drawable.lb_ic_play)); btn.setImageTintList(ColorStateList.valueOf(mForegroundColor)); btn.setVisibility(View.VISIBLE); } @@ -136,14 +136,25 @@ public class QuickQSMediaPlayer { * @param actionsContainer a LinearLayout containing the media action buttons * @param actionsToShow indices of which actions to display in the mini player * (max 3: Notification.MediaStyle.MAX_MEDIA_BUTTONS_IN_COMPACT) + * @param contentIntent Intent to send when user taps on the view */ public void setMediaSession(MediaSession.Token token, Icon icon, int iconColor, int bgColor, - View actionsContainer, int[] actionsToShow) { - Log.d(TAG, "Setting media session: " + token); + View actionsContainer, int[] actionsToShow, PendingIntent contentIntent) { mToken = token; mForegroundColor = iconColor; mBackgroundColor = bgColor; - mController = new MediaController(mContext, token); + + String oldPackage = ""; + if (mController != null) { + oldPackage = mController.getPackageName(); + } + MediaController controller = new MediaController(mContext, token); + boolean samePlayer = mToken.equals(token) && oldPackage.equals(controller.getPackageName()); + if (mController != null && !samePlayer && !isPlaying(controller)) { + // Only update if this is a different session and currently playing + return; + } + mController = controller; MediaMetadata mMediaMetadata = mController.getMetadata(); // Try to find a receiver for the media button that matches this app @@ -153,7 +164,6 @@ public class QuickQSMediaPlayer { if (info != null) { for (ResolveInfo inf : info) { if (inf.activityInfo.packageName.equals(mController.getPackageName())) { - Log.d(TAG, "Found receiver for package: " + inf); mRecvComponent = inf.getComponentInfo().getComponentName(); } } @@ -165,6 +175,16 @@ public class QuickQSMediaPlayer { return; } + // Action + mMediaNotifView.setOnClickListener(v -> { + try { + contentIntent.send(); + mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); + } catch (PendingIntent.CanceledException e) { + Log.e(TAG, "Pending intent was canceled: " + e.getMessage()); + } + }); + // Album art addAlbumArtBackground(mMediaMetadata, mBackgroundColor); @@ -237,12 +257,12 @@ public class QuickQSMediaPlayer { * Check whether the media controlled by this player is currently playing * @return whether it is playing, or false if no controller information */ - public boolean isPlaying() { - if (mController == null) { + public boolean isPlaying(MediaController controller) { + if (controller == null) { return false; } - PlaybackState state = mController.getPlaybackState(); + PlaybackState state = controller.getPlaybackState(); if (state == null) { return false; } @@ -261,12 +281,11 @@ public class QuickQSMediaPlayer { private void addAlbumArtBackground(MediaMetadata metadata, int bgColor) { Bitmap albumArt = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART); float radius = mContext.getResources().getDimension(R.dimen.qs_media_corner_radius); - if (albumArt != null) { - Rect bounds = new Rect(); - mMediaNotifView.getBoundsOnScreen(bounds); - int width = bounds.width(); - int height = bounds.height(); - + Rect bounds = new Rect(); + mMediaNotifView.getBoundsOnScreen(bounds); + int width = bounds.width(); + int height = bounds.height(); + if (albumArt != null && width > 0 && height > 0) { Bitmap original = albumArt.copy(Bitmap.Config.ARGB_8888, true); Bitmap scaled = scaleBitmap(original, width, height); Canvas canvas = new Canvas(scaled); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java index db52e7d37a92..b05d4fdf7db7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java @@ -85,20 +85,19 @@ public class QuickQSPanel extends QSPanel { mHorizontalLinearLayout.setClipChildren(false); mHorizontalLinearLayout.setClipToPadding(false); - LayoutParams lp = new LayoutParams(0, LayoutParams.MATCH_PARENT, 1); - mTileLayout = new DoubleLineTileLayout(context); mMediaTileLayout = mTileLayout; mRegularTileLayout = new HeaderTileLayout(context); + LayoutParams lp = new LayoutParams(0, LayoutParams.MATCH_PARENT, 1); lp.setMarginEnd(10); lp.setMarginStart(0); mHorizontalLinearLayout.addView((View) mTileLayout, lp); mMediaPlayer = new QuickQSMediaPlayer(mContext, mHorizontalLinearLayout); - - lp.setMarginEnd(0); - lp.setMarginStart(10); - mHorizontalLinearLayout.addView(mMediaPlayer.getView(), lp); + LayoutParams lp2 = new LayoutParams(0, LayoutParams.MATCH_PARENT, 1); + lp2.setMarginEnd(0); + lp2.setMarginStart(25); + mHorizontalLinearLayout.addView(mMediaPlayer.getView(), lp2); sDefaultMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns); diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java index bbff117c6f81..ad79cadcc12d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java @@ -322,7 +322,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED); try { mUserReceiverRegistered.set(true); - mBroadcastDispatcher.registerReceiver(this, filter, mHandler, mUser); + mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mHandler, mUser); } catch (Exception ex) { mUserReceiverRegistered.set(false); Log.e(TAG, "Could not register unlock receiver", ex); diff --git a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java index 077d2602e43f..9599d77bf65a 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java +++ b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java @@ -98,7 +98,8 @@ public abstract class CurrentUserTracker { if (!mReceiverRegistered) { mCurrentUserId = ActivityManager.getCurrentUser(); IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED); - mBroadcastDispatcher.registerReceiver(this, filter, null, UserHandle.ALL); + mBroadcastDispatcher.registerReceiver(this, filter, null, + UserHandle.ALL); mReceiverRegistered = true; } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java index 2005d794c9d3..ac05c53c38dd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java @@ -40,7 +40,7 @@ import javax.inject.Singleton; * You will probably need to restart systemui for the changes to be picked up: * * {@code - * $ adb shell am crash com.android.systemui + * $ adb shell am restart com.android.systemui * } */ @Singleton @@ -59,6 +59,11 @@ public class FeatureFlags { return getDeviceConfigFlag("notification.newpipeline.enabled", false); } + public boolean isNewNotifPipelineRenderingEnabled() { + return isNewNotifPipelineEnabled() + && getDeviceConfigFlag("notification.newpipeline.rendering", false); + } + private void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) { synchronized (mCachedDeviceConfigFlags) { for (String key : properties.getKeyset()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java index ff4ce9492082..ebf7c2d58c2d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java @@ -49,6 +49,12 @@ public interface NotificationLockscreenUserManager { /** Adds a listener to be notified when the current user changes. */ void addUserChangedListener(UserChangedListener listener); + /** + * Removes a listener previously registered with + * {@link #addUserChangedListener(UserChangedListener)} + */ + void removeUserChangedListener(UserChangedListener listener); + SparseArray<UserInfo> getCurrentProfiles(); void setLockscreenPublicMode(boolean isProfilePublic, int userId); @@ -79,6 +85,7 @@ public interface NotificationLockscreenUserManager { /** Notified when the current user changes. */ interface UserChangedListener { - void onUserChanged(int userId); + default void onUserChanged(int userId) {} + default void onCurrentProfilesChanged(SparseArray<UserInfo> currentProfiles) {} } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java index 2e369b3295b8..976531d8b49d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java @@ -117,51 +117,63 @@ public class NotificationLockscreenUserManagerImpl implements @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); - if (Intent.ACTION_USER_SWITCHED.equals(action)) { - mCurrentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); - updateCurrentProfilesCache(); - Log.v(TAG, "userId " + mCurrentUserId + " is in the house"); - - updateLockscreenNotificationSetting(); - updatePublicMode(); - // The filtering needs to happen before the update call below in order to make sure - // the presenter has the updated notifications from the new user - getEntryManager().reapplyFilterAndSort("user switched"); - mPresenter.onUserSwitched(mCurrentUserId); - - for (UserChangedListener listener : mListeners) { - listener.onUserChanged(mCurrentUserId); - } - } else if (Intent.ACTION_USER_ADDED.equals(action)) { - updateCurrentProfilesCache(); - } else if (Intent.ACTION_USER_UNLOCKED.equals(action)) { - // Start the overview connection to the launcher service - Dependency.get(OverviewProxyService.class).startConnectionToCurrentUser(); - } else if (NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION.equals(action)) { - final IntentSender intentSender = intent.getParcelableExtra(Intent.EXTRA_INTENT); - final String notificationKey = intent.getStringExtra(Intent.EXTRA_INDEX); - if (intentSender != null) { - try { - mContext.startIntentSender(intentSender, null, 0, 0, 0); - } catch (IntentSender.SendIntentException e) { - /* ignore */ + switch (action) { + case Intent.ACTION_USER_SWITCHED: + mCurrentUserId = intent.getIntExtra( + Intent.EXTRA_USER_HANDLE, UserHandle.USER_ALL); + updateCurrentProfilesCache(); + + Log.v(TAG, "userId " + mCurrentUserId + " is in the house"); + + updateLockscreenNotificationSetting(); + updatePublicMode(); + // The filtering needs to happen before the update call below in order to + // make sure + // the presenter has the updated notifications from the new user + getEntryManager().reapplyFilterAndSort("user switched"); + mPresenter.onUserSwitched(mCurrentUserId); + + for (UserChangedListener listener : mListeners) { + listener.onUserChanged(mCurrentUserId); } - } - if (notificationKey != null) { - NotificationEntry entry = - getEntryManager().getActiveNotificationUnfiltered(notificationKey); - final int count = getEntryManager().getActiveNotificationsCount(); - final int rank = entry != null ? entry.getRanking().getRank() : 0; - NotificationVisibility.NotificationLocation location = - NotificationLogger.getNotificationLocation(entry); - final NotificationVisibility nv = NotificationVisibility.obtain(notificationKey, - rank, count, true, location); - try { - mBarService.onNotificationClick(notificationKey, nv); - } catch (RemoteException exception) { - /* ignore */ + break; + case Intent.ACTION_USER_ADDED: + case Intent.ACTION_MANAGED_PROFILE_AVAILABLE: + case Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE: + updateCurrentProfilesCache(); + break; + case Intent.ACTION_USER_UNLOCKED: + // Start the overview connection to the launcher service + Dependency.get(OverviewProxyService.class).startConnectionToCurrentUser(); + break; + case NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION: + final IntentSender intentSender = intent.getParcelableExtra( + Intent.EXTRA_INTENT); + final String notificationKey = intent.getStringExtra(Intent.EXTRA_INDEX); + if (intentSender != null) { + try { + mContext.startIntentSender(intentSender, null, 0, 0, 0); + } catch (IntentSender.SendIntentException e) { + /* ignore */ + } } - } + if (notificationKey != null) { + NotificationEntry entry = + getEntryManager().getActiveNotificationUnfiltered(notificationKey); + final int count = getEntryManager().getActiveNotificationsCount(); + final int rank = entry != null ? entry.getRanking().getRank() : 0; + NotificationVisibility.NotificationLocation location = + NotificationLogger.getNotificationLocation(entry); + final NotificationVisibility nv = NotificationVisibility.obtain( + notificationKey, + rank, count, true, location); + try { + mBarService.onNotificationClick(notificationKey, nv); + } catch (RemoteException exception) { + /* ignore */ + } + } + break; } } }; @@ -266,6 +278,8 @@ public class NotificationLockscreenUserManagerImpl implements filter.addAction(Intent.ACTION_USER_SWITCHED); filter.addAction(Intent.ACTION_USER_ADDED); filter.addAction(Intent.ACTION_USER_UNLOCKED); + filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE); + filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE); mBroadcastDispatcher.registerReceiver(mBaseBroadcastReceiver, filter); IntentFilter internalFilter = new IntentFilter(); @@ -489,6 +503,11 @@ public class NotificationLockscreenUserManagerImpl implements } } } + mMainHandler.post(() -> { + for (UserChangedListener listener : mListeners) { + listener.onCurrentProfilesChanged(mCurrentProfiles); + } + }); } public boolean isAnyProfilePublicMode() { @@ -555,6 +574,11 @@ public class NotificationLockscreenUserManagerImpl implements mListeners.add(listener); } + @Override + public void removeUserChangedListener(UserChangedListener listener) { + mListeners.remove(listener); + } + // public void updatePublicMode() { // //TODO: I think there may be a race condition where mKeyguardViewManager.isShowing() returns // // false when it should be true. Therefore, if we are not on the SHADE, don't even bother diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java index dc84b5785ad9..9c626f7b877d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java @@ -307,6 +307,7 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi case Icon.TYPE_RESOURCE: return a.getResPackage().equals(b.getResPackage()) && a.getResId() == b.getResId(); case Icon.TYPE_URI: + case Icon.TYPE_URI_ADAPTIVE_BITMAP: return a.getUriString().equals(b.getUriString()); default: return false; 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 33d97a150048..a2578ab3cfa6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -34,6 +34,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.NotificationVisibility; import com.android.systemui.Dependency; import com.android.systemui.Dumpable; +import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.NotificationLifetimeExtender; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationListener.NotificationHandler; @@ -131,6 +132,7 @@ public class NotificationEntryManager implements private final KeyguardEnvironment mKeyguardEnvironment; private final NotificationGroupManager mGroupManager; private final NotificationRankingManager mRankingManager; + private final FeatureFlags mFeatureFlags; private NotificationPresenter mPresenter; private RankingMap mLatestRankingMap; @@ -170,11 +172,13 @@ public class NotificationEntryManager implements NotifLog notifLog, NotificationGroupManager groupManager, NotificationRankingManager rankingManager, - KeyguardEnvironment keyguardEnvironment) { + KeyguardEnvironment keyguardEnvironment, + FeatureFlags featureFlags) { mNotifLog = notifLog; mGroupManager = groupManager; mRankingManager = rankingManager; mKeyguardEnvironment = keyguardEnvironment; + mFeatureFlags = featureFlags; } /** Once called, the NEM will start processing notification events from system server. */ @@ -290,6 +294,10 @@ public class NotificationEntryManager implements * WARNING: this will call back into us. Don't hold any locks. */ @Override + public void handleInflationException(NotificationEntry n, Exception e) { + handleInflationException(n.getSbn(), e); + } + public void handleInflationException(StatusBarNotification n, Exception e) { removeNotificationInternal( n.getKey(), null, null, true /* forceRemove */, false /* removedByUser */, @@ -529,9 +537,12 @@ public class NotificationEntryManager implements NotificationEntry entry = new NotificationEntry(notification, ranking); Dependency.get(LeakDetector.class).trackInstance(entry); + // Construct the expanded view. - requireBinder().inflateViews(entry, () -> performRemoveNotification(notification, - REASON_CANCEL)); + if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) { + requireBinder().inflateViews(entry, () -> performRemoveNotification(notification, + REASON_CANCEL)); + } abortExistingInflation(key, "addNotification"); mPendingNotifications.put(key, entry); @@ -574,8 +585,11 @@ public class NotificationEntryManager implements listener.onPreEntryUpdated(entry); } - requireBinder().inflateViews(entry, () -> performRemoveNotification(notification, - REASON_CANCEL)); + if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) { + requireBinder().inflateViews(entry, () -> performRemoveNotification(notification, + REASON_CANCEL)); + } + updateNotifications("updateNotificationInternal"); if (DEBUG) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java index ec1efa58868e..b960b42b3e2d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java @@ -24,7 +24,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; -import java.util.Objects; /** * Represents a set of grouped notifications. The final notification list is usually a mix of @@ -58,22 +57,15 @@ public class GroupEntry extends ListEntry { @VisibleForTesting public void setSummary(@Nullable NotificationEntry summary) { - if (!Objects.equals(mSummary, summary)) { - mSummary = summary; - onGroupingUpdated(); - } + mSummary = summary; } void clearChildren() { - if (mChildren.size() != 0) { - mChildren.clear(); - onGroupingUpdated(); - } + mChildren.clear(); } void addChild(NotificationEntry child) { mChildren.add(child); - onGroupingUpdated(); } void sortChildren(Comparator<? super NotificationEntry> c) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java index 601b3e053e8e..dc68c4bdba78 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java @@ -18,38 +18,20 @@ package com.android.systemui.statusbar.notification.collection; import android.annotation.Nullable; -import com.android.systemui.Dependency; -import com.android.systemui.statusbar.notification.collection.provider.DerivedMember; -import com.android.systemui.statusbar.notification.collection.provider.IsHighPriorityProvider; -import com.android.systemui.statusbar.phone.NotificationGroupManager; - -import java.util.Arrays; -import java.util.List; -import java.util.Objects; - /** * Abstract superclass for top-level entries, i.e. things that can appear in the final notification * list shown to users. In practice, this means either GroupEntries or NotificationEntries. */ public abstract class ListEntry { private final String mKey; - private final IsHighPriorityProvider mIsHighPriorityProvider = new IsHighPriorityProvider(); - private final List<DerivedMember> mDerivedMemberList = Arrays.asList(mIsHighPriorityProvider); @Nullable private GroupEntry mParent; @Nullable private GroupEntry mPreviousParent; private int mSection; int mFirstAddedIteration = -1; - // TODO: (b/145659174) remove groupManager when moving to NewNotifPipeline. Logic - // replaced in GroupEntry and NotifListBuilderImpl - private final NotificationGroupManager mGroupManager; - ListEntry(String key) { mKey = key; - - // TODO: (b/145659174) remove - mGroupManager = Dependency.get(NotificationGroupManager.class); } public String getKey() { @@ -68,11 +50,7 @@ public abstract class ListEntry { } void setParent(@Nullable GroupEntry parent) { - if (!Objects.equals(mParent, parent)) { - invalidateParent(); - mParent = parent; - onGroupingUpdated(); - } + mParent = parent; } @Nullable public GroupEntry getPreviousParent() { @@ -91,58 +69,4 @@ public abstract class ListEntry { void setSection(int section) { mSection = section; } - - /** - * Resets the cached values of DerivedMembers. - */ - void invalidateDerivedMembers() { - for (int i = 0; i < mDerivedMemberList.size(); i++) { - mDerivedMemberList.get(i).invalidate(); - } - } - - /** - * Whether this notification is shown to the user as a high priority notification: visible on - * the lock screen/status bar and in the top section in the shade. - */ - public boolean isHighPriority() { - return mIsHighPriorityProvider.get(this); - } - - private void invalidateParent() { - // invalidate our parent (GroupEntry) since DerivedMembers may be dependent on children - if (getParent() != null) { - getParent().invalidateDerivedMembers(); - } - - // TODO: (b/145659174) remove - final NotificationEntry notifEntry = getRepresentativeEntry(); - if (notifEntry != null && mGroupManager.isGroupChild(notifEntry.getSbn())) { - NotificationEntry summary = mGroupManager.getLogicalGroupSummary(notifEntry.getSbn()); - if (summary != null) { - summary.invalidateDerivedMembers(); - } - } - } - - void onGroupingUpdated() { - for (int i = 0; i < mDerivedMemberList.size(); i++) { - mDerivedMemberList.get(i).onGroupingUpdated(); - } - invalidateParent(); - } - - void onSbnUpdated() { - for (int i = 0; i < mDerivedMemberList.size(); i++) { - mDerivedMemberList.get(i).onSbnUpdated(); - } - invalidateParent(); - } - - void onRankingUpdated() { - for (int i = 0; i < mDerivedMemberList.size(); i++) { - mDerivedMemberList.get(i).onRankingUpdated(); - } - invalidateParent(); - } } 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 873cdbc91d25..856b75b7e36c 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 @@ -299,6 +299,14 @@ public class NotifCollection { if (!isLifetimeExtended(entry)) { Ranking ranking = requireRanking(rankingMap, entry.getKey()); entry.setRanking(ranking); + + // TODO: (b/145659174) update the sbn's overrideGroupKey in + // NotificationEntry.setRanking instead of here once we fully migrate to the + // NewNotifPipeline + final String newOverrideGroupKey = ranking.getOverrideGroupKey(); + if (!Objects.equals(entry.getSbn().getOverrideGroupKey(), newOverrideGroupKey)) { + entry.getSbn().setOverrideGroupKey(newOverrideGroupKey); + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflater.java new file mode 100644 index 000000000000..fc04827a9d6a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflater.java @@ -0,0 +1,55 @@ +/* + * 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; +import com.android.systemui.statusbar.notification.collection.coordinator.PreparationCoordinator; + +/** + * Used by the {@link PreparationCoordinator}. When notifications are added or updated, the + * NotifInflater is asked to (re)inflated and prepare their views. This inflation occurs off the + * main thread. When the inflation is finished, NotifInflater will trigger its InflationCallback. + */ +public interface NotifInflater { + + /** + * Callback used when inflation is finished. + */ + void setInflationCallback(InflationCallback callback); + + /** + * Called to rebind the entry's views. + */ + void rebindViews(NotificationEntry entry); + + /** + * Called to inflate the views of an entry. Views are not considered inflated until all of its + * views are bound. Once all views are inflated, the InflationCallback is triggered. + */ + void inflateViews(NotificationEntry entry); + + /** + * Request to stop the inflation of an entry. For example, called when a notification is + * removed and no longer needs to be inflated. + */ + void abortInflation(NotificationEntry entry); + + /** + * Callback once all the views are inflated and bound for a given NotificationEntry. + */ + interface InflationCallback { + void onInflationFinished(NotificationEntry entry); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java new file mode 100644 index 000000000000..0d175574f16b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java @@ -0,0 +1,158 @@ +/* + * 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; + +import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL; + +import android.os.RemoteException; +import android.service.notification.NotificationStats; +import android.service.notification.StatusBarNotification; + +import com.android.internal.statusbar.IStatusBarService; +import com.android.internal.statusbar.NotificationVisibility; +import com.android.systemui.statusbar.notification.InflationException; +import com.android.systemui.statusbar.notification.logging.NotificationLogger; +import com.android.systemui.statusbar.notification.row.NotificationContentInflater; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Handles notification inflating, rebinding, and inflation aborting. + * + * Currently a wrapper for NotificationRowBinderImpl. + */ +@Singleton +public class NotifInflaterImpl implements NotifInflater { + + private final IStatusBarService mStatusBarService; + private final NotifCollection mNotifCollection; + + private NotificationRowBinderImpl mNotificationRowBinder; + private InflationCallback mExternalInflationCallback; + + @Inject + public NotifInflaterImpl( + IStatusBarService statusBarService, + NotifCollection notifCollection) { + mStatusBarService = statusBarService; + mNotifCollection = notifCollection; + } + + /** + * Attaches the row binder for inflation. + */ + public void setRowBinder(NotificationRowBinderImpl rowBinder) { + mNotificationRowBinder = rowBinder; + mNotificationRowBinder.setInflationCallback(mInflationCallback); + } + + @Override + public void setInflationCallback(InflationCallback callback) { + mExternalInflationCallback = callback; + } + + @Override + public void rebindViews(NotificationEntry entry) { + inflateViews(entry); + } + + /** + * Called to inflate the views of an entry. Views are not considered inflated until all of its + * views are bound. + */ + @Override + public void inflateViews(NotificationEntry entry) { + try { + entry.setHasInflationError(false); + requireBinder().inflateViews(entry, getDismissCallback(entry)); + } catch (InflationException e) { + // logged in mInflationCallback.handleInflationException + } + } + + @Override + public void abortInflation(NotificationEntry entry) { + entry.abortTask(); + } + + private Runnable getDismissCallback(NotificationEntry entry) { + return new Runnable() { + @Override + public void run() { + int dismissalSurface = NotificationStats.DISMISSAL_SHADE; + /** + * TODO: determine dismissal surface (ie: shade / headsup / aod) + * see {@link NotificationLogger#logNotificationClear} + */ + mNotifCollection.dismissNotification( + entry, + 0, + new DismissedByUserStats( + dismissalSurface, + DISMISS_SENTIMENT_NEUTRAL, + NotificationVisibility.obtain(entry.getKey(), + entry.getRanking().getRank(), + mNotifCollection.getNotifs().size(), + true, + NotificationLogger.getNotificationLocation(entry)) + )); + } + }; + } + + private NotificationRowBinderImpl requireBinder() { + if (mNotificationRowBinder == null) { + throw new RuntimeException("NotificationRowBinder must be attached before using " + + "NotifInflaterImpl."); + } + return mNotificationRowBinder; + } + + private final NotificationContentInflater.InflationCallback mInflationCallback = + new NotificationContentInflater.InflationCallback() { + @Override + public void handleInflationException( + NotificationEntry entry, + Exception e) { + entry.setHasInflationError(true); + try { + final StatusBarNotification sbn = entry.getSbn(); + // report notification inflation errors back up + // to notification delegates + mStatusBarService.onNotificationError( + sbn.getPackageName(), + sbn.getTag(), + sbn.getId(), + sbn.getUid(), + sbn.getInitialPid(), + e.getMessage(), + sbn.getUserId()); + } catch (RemoteException ex) { + } + } + + @Override + public void onAsyncInflationFinished( + NotificationEntry entry, + int inflatedFlags) { + if (mExternalInflationCallback != null) { + mExternalInflationCallback.onInflationFinished(entry); + } + } + }; +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index 4f4fb240162d..7301fe1df398 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -102,6 +102,9 @@ public final class NotificationEntry extends ListEntry { /** If this was a group child that was promoted to the top level, then who did the promoting. */ @Nullable NotifPromoter mNotifPromoter; + /** If this notification had an issue with inflating. Only used with the NewNotifPipeline **/ + private boolean mHasInflationError; + /* * Old members @@ -201,11 +204,8 @@ public final class NotificationEntry extends ListEntry { + " doesn't match existing key " + mKey); } - if (!Objects.equals(mSbn, sbn)) { - mSbn = sbn; - mBubbleMetadata = mSbn.getNotification().getBubbleMetadata(); - onSbnUpdated(); - } + mSbn = sbn; + mBubbleMetadata = mSbn.getNotification().getBubbleMetadata(); } /** @@ -230,10 +230,7 @@ public final class NotificationEntry extends ListEntry { + " doesn't match existing key " + mKey); } - if (!Objects.equals(mRanking, ranking)) { - mRanking = ranking; - onRankingUpdated(); - } + mRanking = ranking; } /* @@ -576,6 +573,18 @@ public final class NotificationEntry extends ListEntry { remoteInputTextWhenReset = null; } + void setHasInflationError(boolean hasError) { + mHasInflationError = hasError; + } + + /** + * Whether this notification had an error when attempting to inflate. This is only used in + * the NewNotifPipeline + */ + public boolean hasInflationError() { + return mHasInflationError; + } + public void setHasSentReply() { hasSentReply = true; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt index 7010943559ba..3bbd722517f7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt @@ -24,6 +24,7 @@ import android.service.notification.StatusBarNotification import com.android.systemui.statusbar.NotificationMediaManager import com.android.systemui.statusbar.notification.NotificationFilter import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager +import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider import com.android.systemui.statusbar.notification.logging.NotifEvent import com.android.systemui.statusbar.notification.logging.NotifLog import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier @@ -54,7 +55,8 @@ open class NotificationRankingManager @Inject constructor( private val notifFilter: NotificationFilter, private val notifLog: NotifLog, sectionsFeatureManager: NotificationSectionsFeatureManager, - private val peopleNotificationIdentifier: PeopleNotificationIdentifier + private val peopleNotificationIdentifier: PeopleNotificationIdentifier, + private val highPriorityProvider: HighPriorityProvider ) { var rankingMap: RankingMap? = null @@ -81,6 +83,9 @@ open class NotificationRankingManager @Inject constructor( val aHeadsUp = a.isRowHeadsUp val bHeadsUp = b.isRowHeadsUp + val aIsHighPriority = a.isHighPriority() + val bIsHighPriority = b.isHighPriority() + when { usePeopleFiltering && aIsPeople != bIsPeople -> if (aIsPeople) -1 else 1 aHeadsUp != bHeadsUp -> if (aHeadsUp) -1 else 1 @@ -90,8 +95,8 @@ open class NotificationRankingManager @Inject constructor( aMedia != bMedia -> if (aMedia) -1 else 1 // Upsort PRIORITY_MAX system notifications aSystemMax != bSystemMax -> if (aSystemMax) -1 else 1 - a.isHighPriority != b.isHighPriority -> - -1 * a.isHighPriority.compareTo(b.isHighPriority) + aIsHighPriority != bIsHighPriority -> + -1 * aIsHighPriority.compareTo(bIsHighPriority) aRank != bRank -> aRank - bRank else -> nb.notification.`when`.compareTo(na.notification.`when`) } @@ -154,7 +159,7 @@ open class NotificationRankingManager @Inject constructor( ) { if (usePeopleFiltering && entry.isPeopleNotification()) { entry.bucket = BUCKET_PEOPLE - } else if (isHeadsUp || isMedia || isSystemMax || entry.isHighPriority) { + } else if (isHeadsUp || isMedia || isSystemMax || entry.isHighPriority()) { entry.bucket = BUCKET_ALERTING } else { entry.bucket = BUCKET_SILENT @@ -178,10 +183,6 @@ open class NotificationRankingManager @Inject constructor( // TODO: notify group manager here? groupManager.onEntryUpdated(entry, oldSbn) } - - // TODO: (b/145659174) remove after moving to new NotifPipeline - // (should be able to remove all groupManager code post-migration) - entry.invalidateDerivedMembers() } } } @@ -191,6 +192,9 @@ open class NotificationRankingManager @Inject constructor( sbn.isPeopleNotification() private fun StatusBarNotification.isPeopleNotification() = peopleNotificationIdentifier.isPeopleNotification(this) + + private fun NotificationEntry.isHighPriority() = + highPriorityProvider.isHighPriority(this) } // Convenience functions diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java index 6c93618e5395..6dc647d33046 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java @@ -109,16 +109,18 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { public void setUpWithPresenter(NotificationPresenter presenter, NotificationListContainer listContainer, HeadsUpManager headsUpManager, - NotificationContentInflater.InflationCallback inflationCallback, BindRowCallback bindRowCallback) { mPresenter = presenter; mListContainer = listContainer; mHeadsUpManager = headsUpManager; - mInflationCallback = inflationCallback; mBindRowCallback = bindRowCallback; mOnAppOpsClickListener = mGutsManager::openGuts; } + public void setInflationCallback(NotificationContentInflater.InflationCallback callback) { + mInflationCallback = callback; + } + public void setNotificationClicker(NotificationClicker clicker) { mNotificationClicker = clicker; } 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 9312c2260d46..db107f531e9e 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 @@ -43,6 +43,7 @@ import com.android.systemui.statusbar.notification.collection.NotifCollection; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; +import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; import com.android.systemui.statusbar.policy.KeyguardStateController; import javax.inject.Inject; @@ -62,6 +63,7 @@ public class KeyguardCoordinator implements Coordinator { private final BroadcastDispatcher mBroadcastDispatcher; private final StatusBarStateController mStatusBarStateController; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private final HighPriorityProvider mHighPriorityProvider; @Inject public KeyguardCoordinator( @@ -71,15 +73,16 @@ public class KeyguardCoordinator implements Coordinator { NotificationLockscreenUserManager lockscreenUserManager, BroadcastDispatcher broadcastDispatcher, StatusBarStateController statusBarStateController, - KeyguardUpdateMonitor keyguardUpdateMonitor) { + KeyguardUpdateMonitor keyguardUpdateMonitor, + HighPriorityProvider highPriorityProvider) { mContext = context; mMainHandler = mainThreadHandler; mKeyguardStateController = keyguardStateController; mLockscreenUserManager = lockscreenUserManager; - mBroadcastDispatcher = broadcastDispatcher; mStatusBarStateController = statusBarStateController; mKeyguardUpdateMonitor = keyguardUpdateMonitor; + mHighPriorityProvider = highPriorityProvider; } @Override @@ -151,7 +154,7 @@ public class KeyguardCoordinator implements Coordinator { } if (NotificationUtils.useNewInterruptionModel(mContext) && hideSilentNotificationsOnLockscreen()) { - return entry.isHighPriority(); + return mHighPriorityProvider.isHighPriority(entry); } else { return entry.getRepresentativeEntry() != null && !entry.getRepresentativeEntry().getRanking().isAmbient(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java index 132471933bb2..2436bb9f82f0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator; import com.android.systemui.Dumpable; +import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.notification.collection.NotifCollection; import com.android.systemui.statusbar.notification.collection.NotifCollectionListener; import com.android.systemui.statusbar.notification.collection.NotifLifetimeExtender; @@ -45,14 +46,19 @@ public class NotifCoordinators implements Dumpable { */ @Inject public NotifCoordinators( + FeatureFlags featureFlags, KeyguardCoordinator keyguardCoordinator, RankingCoordinator rankingCoordinator, ForegroundCoordinator foregroundCoordinator, - DeviceProvisionedCoordinator deviceProvisionedCoordinator) { + DeviceProvisionedCoordinator deviceProvisionedCoordinator, + PreparationCoordinator preparationCoordinator) { mCoordinators.add(keyguardCoordinator); mCoordinators.add(rankingCoordinator); mCoordinators.add(foregroundCoordinator); mCoordinators.add(deviceProvisionedCoordinator); + if (featureFlags.isNewNotifPipelineRenderingEnabled()) { + mCoordinators.add(preparationCoordinator); + } // TODO: add new Coordinators here! (b/145134683, b/112656837) } @@ -70,6 +76,7 @@ public class NotifCoordinators implements Dumpable { @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println(); pw.println(TAG + ":"); for (Coordinator c : mCoordinators) { pw.println("\t" + c.getClass()); 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 new file mode 100644 index 000000000000..a14f0e1cf631 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java @@ -0,0 +1,131 @@ +/* + * 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 com.android.systemui.statusbar.notification.collection.NotifCollection; +import com.android.systemui.statusbar.notification.collection.NotifCollectionListener; +import com.android.systemui.statusbar.notification.collection.NotifInflater; +import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder; +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; +import com.android.systemui.statusbar.notification.logging.NotifEvent; +import com.android.systemui.statusbar.notification.logging.NotifLog; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Kicks off notification inflation and view rebinding when a notification is added or updated. + * Aborts inflation when a notification is removed. + * + * If a notification is not done inflating, this coordinator will filter the notification out + * from the NotifListBuilder. + */ +@Singleton +public class PreparationCoordinator implements Coordinator { + private static final String TAG = "PreparationCoordinator"; + + private final NotifLog mNotifLog; + private final NotifInflater mNotifInflater; + private final List<NotificationEntry> mPendingNotifications = new ArrayList<>(); + + @Inject + public PreparationCoordinator(NotifLog notifLog, NotifInflaterImpl notifInflater) { + mNotifLog = notifLog; + mNotifInflater = notifInflater; + mNotifInflater.setInflationCallback(mInflationCallback); + } + + @Override + public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) { + notifCollection.addCollectionListener(mNotifCollectionListener); + notifListBuilder.addPreRenderFilter(mNotifInflationErrorFilter); + notifListBuilder.addPreRenderFilter(mNotifInflatingFilter); + } + + private final NotifCollectionListener mNotifCollectionListener = new NotifCollectionListener() { + @Override + public void onEntryAdded(NotificationEntry entry) { + inflateEntry(entry, "entryAdded"); + } + + @Override + public void onEntryUpdated(NotificationEntry entry) { + rebind(entry, "entryUpdated"); + } + + @Override + public void onEntryRemoved(NotificationEntry entry, int reason, boolean removedByUser) { + abortInflation(entry, "entryRemoved reason=" + reason); + } + }; + + private final NotifFilter mNotifInflationErrorFilter = new NotifFilter( + TAG + "InflationError") { + /** + * Filters out notifications that threw an error when attempting to inflate. + */ + @Override + public boolean shouldFilterOut(NotificationEntry entry, long now) { + if (entry.hasInflationError()) { + mPendingNotifications.remove(entry); + return true; + } + return false; + } + }; + + private final NotifFilter mNotifInflatingFilter = new NotifFilter(TAG + "Inflating") { + /** + * Filters out notifications that haven't been inflated yet + */ + @Override + public boolean shouldFilterOut(NotificationEntry entry, long now) { + return mPendingNotifications.contains(entry); + } + }; + + private final NotifInflater.InflationCallback mInflationCallback = + new NotifInflater.InflationCallback() { + @Override + public void onInflationFinished(NotificationEntry entry) { + mNotifLog.log(NotifEvent.INFLATED, entry); + mPendingNotifications.remove(entry); + mNotifInflatingFilter.invalidateList(); + } + }; + + private void inflateEntry(NotificationEntry entry, String reason) { + abortInflation(entry, reason); + mPendingNotifications.add(entry); + mNotifInflater.inflateViews(entry); + } + + private void rebind(NotificationEntry entry, String reason) { + mNotifInflater.rebindViews(entry); + } + + private void abortInflation(NotificationEntry entry, String reason) { + mNotifLog.log(NotifEvent.INFLATION_ABORTED, reason); + entry.abortTask(); + mPendingNotifications.remove(entry); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NewNotifPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NewNotifPipeline.java index 5e0bd4fdcf23..8d3d0ff43deb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NewNotifPipeline.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NewNotifPipeline.java @@ -20,9 +20,12 @@ import android.util.Log; import com.android.systemui.DumpController; import com.android.systemui.Dumpable; +import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.notification.collection.NotifCollection; +import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl; import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl; +import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl; import com.android.systemui.statusbar.notification.collection.coordinator.NotifCoordinators; import com.android.systemui.statusbar.notification.collection.notifcollection.GroupCoalescer; @@ -41,7 +44,9 @@ public class NewNotifPipeline implements Dumpable { private final NotifCollection mNotifCollection; private final NotifListBuilderImpl mNotifPipeline; private final NotifCoordinators mNotifPluggableCoordinators; + private final NotifInflaterImpl mNotifInflater; private final DumpController mDumpController; + private final FeatureFlags mFeatureFlags; private final FakePipelineConsumer mFakePipelineConsumer = new FakePipelineConsumer(); @@ -51,20 +56,30 @@ public class NewNotifPipeline implements Dumpable { NotifCollection notifCollection, NotifListBuilderImpl notifPipeline, NotifCoordinators notifCoordinators, - DumpController dumpController) { + NotifInflaterImpl notifInflater, + DumpController dumpController, + FeatureFlags featureFlags) { mGroupCoalescer = groupCoalescer; mNotifCollection = notifCollection; mNotifPipeline = notifPipeline; mNotifPluggableCoordinators = notifCoordinators; mDumpController = dumpController; + mNotifInflater = notifInflater; + mFeatureFlags = featureFlags; } /** Hooks the new pipeline up to NotificationManager */ public void initialize( - NotificationListener notificationService) { + NotificationListener notificationService, + NotificationRowBinderImpl rowBinder) { mDumpController.registerDumpable("NotifPipeline", this); + // Setup inflation + if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) { + mNotifInflater.setRowBinder(rowBinder); + } + // Wire up coordinators mFakePipelineConsumer.attach(mNotifPipeline); mNotifPluggableCoordinators.attach(mNotifCollection, mNotifPipeline); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/GroupCoalescer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/GroupCoalescer.java index 069c15f9d7a2..c3e3c5373b7b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/GroupCoalescer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/GroupCoalescer.java @@ -259,7 +259,7 @@ public class GroupCoalescer implements Dumpable { pw.println("Coalesced notifications:"); for (EventBatch batch : mBatches.values()) { pw.println(" Batch " + batch.mGroupKey + ":"); - pw.println(" Created" + (now - batch.mCreatedTimestamp) + "ms ago"); + pw.println(" Created " + (now - batch.mCreatedTimestamp) + "ms ago"); for (CoalescedEvent event : batch.mMembers) { pw.println(" " + event.getKey()); eventCount++; @@ -299,5 +299,5 @@ public class GroupCoalescer implements Dumpable { void onNotificationBatchPosted(List<CoalescedEvent> events); } - private static final int GROUP_LINGER_DURATION = 40; + private static final int GROUP_LINGER_DURATION = 500; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DerivedMember.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DerivedMember.java deleted file mode 100644 index 815e6f7eaa46..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DerivedMember.java +++ /dev/null @@ -1,62 +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.systemui.statusbar.notification.collection.provider; -/** - * Caches a computed value until invalidate() is called - * @param <Parent> Object used to computeValue - * @param <Value> type of value to cache until invalidate is called - */ -public abstract class DerivedMember<Parent, Value> { - private Value mValue; - protected abstract Value computeValue(Parent parent); - - /** - * Gets the last cached value, else recomputes the value. - */ - public Value get(Parent parent) { - if (mValue == null) { - mValue = computeValue(parent); - } - return mValue; - } - - /** - * Resets the cached value. - * Next time "get" is called, the value is recomputed. - */ - public void invalidate() { - mValue = null; - } - - /** - * Called when a NotificationEntry's status bar notification has updated. - * Derived members can invalidate here. - */ - public void onSbnUpdated() {} - - /** - * Called when a NotificationEntry's Ranking has updated. - * Derived members can invalidate here. - */ - public void onRankingUpdated() {} - - /** - * Called when a ListEntry's grouping information (parent or children) has changed. - * Derived members can invalidate here. - */ - public void onGroupingUpdated() {} -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java new file mode 100644 index 000000000000..3cc5e62c0bda --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java @@ -0,0 +1,109 @@ +/* + * 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.provider; + +import android.app.Notification; +import android.app.NotificationManager; + +import com.android.systemui.statusbar.notification.collection.GroupEntry; +import com.android.systemui.statusbar.notification.collection.ListEntry; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Determines whether a notification is considered 'high priority'. + * + * Notifications that are high priority are visible on the lock screen/status bar and in the top + * section in the shade. + */ +@Singleton +public class HighPriorityProvider { + private final PeopleNotificationIdentifier mPeopleNotificationIdentifier; + + @Inject + public HighPriorityProvider(PeopleNotificationIdentifier peopleNotificationIdentifier) { + mPeopleNotificationIdentifier = peopleNotificationIdentifier; + } + + /** + * @return true if the ListEntry is high priority, else false + * + * A NotificationEntry is considered high priority if it: + * - has importance greater than or equal to IMPORTANCE_DEFAULT + * OR + * - their importance has NOT been set to a low priority option by the user AND the + * notification fulfills one of the following: + * - has a person associated with it + * - has a media session associated with it + * - has messaging style + * + * A GroupEntry is considered high priority if its representativeEntry (summary) or children are + * high priority + */ + public boolean isHighPriority(ListEntry entry) { + if (entry == null) { + return false; + } + + final NotificationEntry notifEntry = entry.getRepresentativeEntry(); + return notifEntry.getRanking().getImportance() >= NotificationManager.IMPORTANCE_DEFAULT + || hasHighPriorityCharacteristics(notifEntry) + || hasHighPriorityChild(entry); + } + + + private boolean hasHighPriorityChild(ListEntry entry) { + if (entry instanceof GroupEntry) { + for (NotificationEntry child : ((GroupEntry) entry).getChildren()) { + if (isHighPriority(child)) { + return true; + } + } + } + return false; + } + + private boolean hasHighPriorityCharacteristics(NotificationEntry entry) { + return !hasUserSetImportance(entry) + && (isImportantOngoing(entry) + || entry.getSbn().getNotification().hasMediaSession() + || isPeopleNotification(entry) + || isMessagingStyle(entry)); + } + + private boolean isImportantOngoing(NotificationEntry entry) { + return entry.getSbn().getNotification().isForegroundService() + && entry.getRanking().getImportance() >= NotificationManager.IMPORTANCE_LOW; + } + + private boolean isMessagingStyle(NotificationEntry entry) { + return Notification.MessagingStyle.class.equals( + entry.getSbn().getNotification().getNotificationStyle()); + } + + private boolean isPeopleNotification(NotificationEntry entry) { + return mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn()); + } + + private boolean hasUserSetImportance(NotificationEntry entry) { + return entry.getRanking().getChannel() != null + && entry.getRanking().getChannel().hasUserSetImportance(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProvider.java deleted file mode 100644 index 76e256b9be2d..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProvider.java +++ /dev/null @@ -1,148 +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.systemui.statusbar.notification.collection.provider; - -import android.app.Notification; -import android.app.NotificationManager; -import android.app.Person; - -import com.android.systemui.Dependency; -import com.android.systemui.statusbar.notification.collection.GroupEntry; -import com.android.systemui.statusbar.notification.collection.ListEntry; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.phone.NotificationGroupManager; - -import java.util.ArrayList; -import java.util.List; - -/** - * Whether the ListEntry is shown to the user as a high priority notification: visible on - * the lock screen/status bar and in the top section in the shade. - * - * A NotificationEntry is considered high priority if it: - * - has importance greater than or equal to IMPORTANCE_DEFAULT - * OR - * - their importance has NOT been set to a low priority option by the user AND the notification - * fulfills one of the following: - * - has a person associated with it - * - has a media session associated with it - * - has messaging style - * - * A GroupEntry is considered high priority if its representativeEntry (summary) or children are - * high priority - */ -public class IsHighPriorityProvider extends DerivedMember<ListEntry, Boolean> { - // TODO: (b/145659174) remove groupManager when moving to NewNotifPipeline. Logic - // replaced in GroupEntry and NotifListBuilderImpl - private final NotificationGroupManager mGroupManager; - - - public IsHighPriorityProvider() { - // TODO: (b/145659174) remove - mGroupManager = Dependency.get(NotificationGroupManager.class); - } - - @Override - protected Boolean computeValue(ListEntry entry) { - if (entry == null) { - return false; - } - - return isHighPriority(entry); - } - - private boolean isHighPriority(ListEntry listEntry) { - // requires groups have been set (AFTER PipelineState.STATE_TRANSFORMING) - final NotificationEntry notifEntry = listEntry.getRepresentativeEntry(); - return notifEntry.getRanking().getImportance() >= NotificationManager.IMPORTANCE_DEFAULT - || hasHighPriorityCharacteristics(notifEntry) - || hasHighPriorityChild(listEntry); - - } - - private boolean hasHighPriorityChild(ListEntry entry) { - // TODO: (b/145659174) remove - if (entry instanceof NotificationEntry) { - NotificationEntry notifEntry = (NotificationEntry) entry; - if (mGroupManager.isSummaryOfGroup(notifEntry.getSbn())) { - List<NotificationEntry> logicalChildren = - mGroupManager.getLogicalChildren(notifEntry.getSbn()); - for (NotificationEntry child : logicalChildren) { - if (child.isHighPriority()) { - return true; - } - } - } - } - - if (entry instanceof GroupEntry) { - for (NotificationEntry child : ((GroupEntry) entry).getChildren()) { - if (child.isHighPriority()) { - return true; - } - } - } - return false; - } - - private boolean hasHighPriorityCharacteristics(NotificationEntry entry) { - return !hasUserSetImportance(entry) - && (isImportantOngoing(entry) - || entry.getSbn().getNotification().hasMediaSession() - || hasPerson(entry) - || isMessagingStyle(entry)); - } - - private boolean isImportantOngoing(NotificationEntry entry) { - return entry.getSbn().getNotification().isForegroundService() - && entry.getRanking().getImportance() >= NotificationManager.IMPORTANCE_LOW; - } - - private boolean isMessagingStyle(NotificationEntry entry) { - return Notification.MessagingStyle.class.equals( - entry.getSbn().getNotification().getNotificationStyle()); - } - - private boolean hasPerson(NotificationEntry entry) { - // TODO: cache favorite and recent contacts to check contact affinity - Notification notification = entry.getSbn().getNotification(); - ArrayList<Person> people = notification.extras != null - ? notification.extras.getParcelableArrayList(Notification.EXTRA_PEOPLE_LIST) - : new ArrayList<>(); - return people != null && !people.isEmpty(); - } - - private boolean hasUserSetImportance(NotificationEntry entry) { - return entry.getRanking().getChannel() != null - && entry.getRanking().getChannel().hasUserSetImportance(); - } - - @Override - public void onSbnUpdated() { - invalidate(); - } - - @Override - public void onRankingUpdated() { - invalidate(); - } - - @Override - public void onGroupingUpdated() { - invalidate(); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java index e4a57d72c501..c6c36ee17873 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java @@ -112,7 +112,12 @@ public class NotifEvent extends RichEvent { LIFETIME_EXTENDED, REMOVE_INTERCEPTED, INFLATION_ABORTED, - INFLATED + INFLATED, + + // GroupCoalescer + COALESCED_EVENT, + EARLY_BATCH_EMIT, + EMIT_EVENT_BATCH }) @Retention(RetentionPolicy.SOURCE) public @interface EventType {} @@ -147,8 +152,10 @@ public class NotifEvent extends RichEvent { "InflationAborted", "Inflated", + // GroupCoalescer labels: "CoalescedEvent", - "EarlyBatchEmit" + "EarlyBatchEmit", + "EmitEventBatch" }; private static final int TOTAL_EVENT_LABELS = EVENT_LABELS.length; @@ -174,25 +181,28 @@ public class NotifEvent extends RichEvent { /** * Events related to {@link NotificationEntryManager} */ - public static final int NOTIF_ADDED = TOTAL_LIST_BUILDER_EVENT_TYPES; - public static final int NOTIF_REMOVED = TOTAL_LIST_BUILDER_EVENT_TYPES + 1; - public static final int NOTIF_UPDATED = TOTAL_LIST_BUILDER_EVENT_TYPES + 2; - public static final int FILTER = TOTAL_LIST_BUILDER_EVENT_TYPES + 3; - public static final int SORT = TOTAL_LIST_BUILDER_EVENT_TYPES + 4; - public static final int FILTER_AND_SORT = TOTAL_LIST_BUILDER_EVENT_TYPES + 5; - public static final int NOTIF_VISIBILITY_CHANGED = TOTAL_LIST_BUILDER_EVENT_TYPES + 6; - public static final int LIFETIME_EXTENDED = TOTAL_LIST_BUILDER_EVENT_TYPES + 7; + private static final int NEM_EVENT_START_INDEX = TOTAL_LIST_BUILDER_EVENT_TYPES; + public static final int NOTIF_ADDED = NEM_EVENT_START_INDEX; + public static final int NOTIF_REMOVED = NEM_EVENT_START_INDEX + 1; + public static final int NOTIF_UPDATED = NEM_EVENT_START_INDEX + 2; + public static final int FILTER = NEM_EVENT_START_INDEX + 3; + public static final int SORT = NEM_EVENT_START_INDEX + 4; + public static final int FILTER_AND_SORT = NEM_EVENT_START_INDEX + 5; + public static final int NOTIF_VISIBILITY_CHANGED = NEM_EVENT_START_INDEX + 6; + public static final int LIFETIME_EXTENDED = NEM_EVENT_START_INDEX + 7; // unable to remove notif - removal intercepted by {@link NotificationRemoveInterceptor} - public static final int REMOVE_INTERCEPTED = TOTAL_LIST_BUILDER_EVENT_TYPES + 8; - public static final int INFLATION_ABORTED = TOTAL_LIST_BUILDER_EVENT_TYPES + 9; - public static final int INFLATED = TOTAL_LIST_BUILDER_EVENT_TYPES + 10; + public static final int REMOVE_INTERCEPTED = NEM_EVENT_START_INDEX + 8; + public static final int INFLATION_ABORTED = NEM_EVENT_START_INDEX + 9; + public static final int INFLATED = NEM_EVENT_START_INDEX + 10; private static final int TOTAL_NEM_EVENT_TYPES = 11; /** * Events related to {@link GroupCoalescer} */ - public static final int COALESCED_EVENT = TOTAL_NEM_EVENT_TYPES; - public static final int EARLY_BATCH_EMIT = TOTAL_NEM_EVENT_TYPES + 1; - public static final int EMIT_EVENT_BATCH = TOTAL_NEM_EVENT_TYPES + 2; - private static final int TOTAL_COALESCER_EVENT_TYPES = 2; + private static final int COALESCER_EVENT_START_INDEX = NEM_EVENT_START_INDEX + + TOTAL_NEM_EVENT_TYPES; + public static final int COALESCED_EVENT = COALESCER_EVENT_START_INDEX; + public static final int EARLY_BATCH_EMIT = COALESCER_EVENT_START_INDEX + 1; + public static final int EMIT_EVENT_BATCH = COALESCER_EVENT_START_INDEX + 2; + private static final int TOTAL_COALESCER_EVENT_TYPES = 3; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java index 3e1b5bd0571e..89e5f5596291 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java @@ -294,6 +294,9 @@ public class NotificationLogger implements StateListener { } } + /** + * Logs Notification inflation error + */ private void logNotificationError( StatusBarNotification notification, Exception exception) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHub.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHub.kt index 2c0c9420a8c4..e81d361f58f7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHub.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHub.kt @@ -37,7 +37,8 @@ data class PersonModel( val key: PersonKey, val name: CharSequence, val avatar: Drawable, - val clickIntent: PendingIntent + val clickIntent: PendingIntent, + val userId: Int ) /** Unique identifier for a Person in PeopleHub. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt index 784673e64ff8..88b41471a063 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt @@ -17,28 +17,27 @@ package com.android.systemui.statusbar.notification.people import android.app.Notification -import android.content.Context -import android.graphics.Canvas -import android.graphics.ColorFilter -import android.graphics.PixelFormat -import android.graphics.drawable.BitmapDrawable +import android.content.pm.UserInfo import android.graphics.drawable.Drawable -import android.os.UserHandle +import android.os.UserManager import android.service.notification.StatusBarNotification -import android.util.TypedValue +import android.util.SparseArray import android.view.View import android.view.ViewGroup import android.widget.ImageView import com.android.internal.statusbar.NotificationVisibility import com.android.internal.widget.MessagingGroup -import com.android.launcher3.icons.BaseIconFactory import com.android.systemui.R +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.NotificationPersonExtractorPlugin +import com.android.systemui.statusbar.NotificationLockscreenUserManager 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.policy.ExtensionController import java.util.ArrayDeque +import java.util.concurrent.Executor import javax.inject.Inject import javax.inject.Singleton @@ -52,8 +51,7 @@ interface NotificationPersonExtractor { @Singleton class NotificationPersonExtractorPluginBoundary @Inject constructor( - extensionController: ExtensionController, - private val context: Context + extensionController: ExtensionController ) : NotificationPersonExtractor { private var plugin: NotificationPersonExtractorPlugin? = null @@ -70,9 +68,8 @@ class NotificationPersonExtractorPluginBoundary @Inject constructor( } override fun extractPerson(sbn: StatusBarNotification) = - plugin?.extractPerson(sbn)?.let { data -> - val badged = addBadgeToDrawable(data.avatar, context, sbn.packageName, sbn.user) - PersonModel(data.key, data.name, badged, data.clickIntent) + plugin?.extractPerson(sbn)?.run { + PersonModel(key, name, avatar, clickIntent, sbn.user.identifier) } override fun extractPersonKey(sbn: StatusBarNotification) = plugin?.extractPersonKey(sbn) @@ -84,11 +81,16 @@ class NotificationPersonExtractorPluginBoundary @Inject constructor( @Singleton class PeopleHubDataSourceImpl @Inject constructor( private val notificationEntryManager: NotificationEntryManager, - private val peopleHubManager: PeopleHubManager, - private val extractor: NotificationPersonExtractor + private val extractor: NotificationPersonExtractor, + private val userManager: UserManager, + @Background private val bgExecutor: Executor, + @Main private val mainExecutor: Executor, + private val notifLockscreenUserMgr: NotificationLockscreenUserManager ) : DataSource<PeopleHubModel> { + private var userChangeSubscription: Subscription? = null private val dataListeners = mutableListOf<DataListener<PeopleHubModel>>() + private val peopleHubManagerForUser = SparseArray<PeopleHubManager>() private val notificationEntryListener = object : NotificationEntryListener { override fun onEntryInflated(entry: NotificationEntry, inflatedFlags: Int) = @@ -106,31 +108,56 @@ class PeopleHubDataSourceImpl @Inject constructor( } private fun removeVisibleEntry(entry: NotificationEntry) { - val key = extractor.extractPersonKey(entry.sbn) ?: entry.extractPersonKey() - if (key?.let(peopleHubManager::removeActivePerson) == true) { - updateUi() + (extractor.extractPersonKey(entry.sbn) ?: entry.extractPersonKey())?.let { key -> + val userId = entry.sbn.user.identifier + bgExecutor.execute { + val parentId = userManager.getProfileParent(userId)?.id ?: userId + mainExecutor.execute { + if (peopleHubManagerForUser[parentId]?.removeActivePerson(key) == true) { + updateUi() + } + } + } } } private fun addVisibleEntry(entry: NotificationEntry) { - val personModel = extractor.extractPerson(entry.sbn) ?: entry.extractPerson() - if (personModel?.let(peopleHubManager::addActivePerson) == true) { - updateUi() + (extractor.extractPerson(entry.sbn) ?: entry.extractPerson())?.let { personModel -> + val userId = entry.sbn.user.identifier + bgExecutor.execute { + val parentId = userManager.getProfileParent(userId)?.id ?: userId + mainExecutor.execute { + val manager = peopleHubManagerForUser[parentId] + ?: PeopleHubManager().also { peopleHubManagerForUser.put(parentId, it) } + if (manager.addActivePerson(personModel)) { + updateUi() + } + } + } } } override fun registerListener(listener: DataListener<PeopleHubModel>): Subscription { - val registerWithNotificationEntryManager = dataListeners.isEmpty() + val register = dataListeners.isEmpty() dataListeners.add(listener) - if (registerWithNotificationEntryManager) { + if (register) { + userChangeSubscription = notifLockscreenUserMgr.registerListener( + object : NotificationLockscreenUserManager.UserChangedListener { + override fun onUserChanged(userId: Int) = updateUi() + override fun onCurrentProfilesChanged( + currentProfiles: SparseArray<UserInfo>? + ) = updateUi() + }) notificationEntryManager.addNotificationEntryListener(notificationEntryListener) } else { - listener.onDataChanged(peopleHubManager.getPeopleHubModel()) + getPeopleHubModelForCurrentUser()?.let(listener::onDataChanged) } return object : Subscription { override fun unsubscribe() { dataListeners.remove(listener) if (dataListeners.isEmpty()) { + userChangeSubscription?.unsubscribe() + userChangeSubscription = null notificationEntryManager .removeNotificationEntryListener(notificationEntryListener) } @@ -138,16 +165,36 @@ class PeopleHubDataSourceImpl @Inject constructor( } } + private fun getPeopleHubModelForCurrentUser(): PeopleHubModel? { + val currentUserId = notifLockscreenUserMgr.currentUserId + val model = peopleHubManagerForUser[currentUserId]?.getPeopleHubModel() + ?: return null + val currentProfiles = notifLockscreenUserMgr.currentProfiles + return model.copy(people = model.people.filter { person -> + currentProfiles[person.userId]?.isQuietModeEnabled == false + }) + } + private fun updateUi() { - val model = peopleHubManager.getPeopleHubModel() + val model = getPeopleHubModelForCurrentUser() ?: return for (listener in dataListeners) { listener.onDataChanged(model) } } } -@Singleton -class PeopleHubManager @Inject constructor() { +private fun NotificationLockscreenUserManager.registerListener( + listener: NotificationLockscreenUserManager.UserChangedListener +): Subscription { + addUserChangedListener(listener) + return object : Subscription { + override fun unsubscribe() { + removeUserChangedListener(listener) + } + } +} + +class PeopleHubManager { private val activePeople = mutableMapOf<PersonKey, PersonModel>() private val inactivePeople = ArrayDeque<PersonModel>(MAX_STORED_INACTIVE_PEOPLE) @@ -157,7 +204,7 @@ class PeopleHubManager @Inject constructor() { if (inactivePeople.size >= MAX_STORED_INACTIVE_PEOPLE) { inactivePeople.removeLast() } - inactivePeople.push(data) + inactivePeople.add(data) return true } return false @@ -190,63 +237,7 @@ private fun NotificationEntry.extractPerson(): PersonModel? { ?: extras.getString(Notification.EXTRA_TITLE) ?: return null val drawable = extractAvatarFromRow(this) ?: return null - val badgedAvatar = addBadgeToDrawable(drawable, row.context, sbn.packageName, sbn.user) - return PersonModel(key, name, badgedAvatar, clickIntent) -} - -private fun addBadgeToDrawable( - drawable: Drawable, - context: Context, - packageName: String, - user: UserHandle -): Drawable { - val pm = context.packageManager - val appInfo = pm.getApplicationInfoAsUser(packageName, 0, user) - return object : Drawable() { - override fun draw(canvas: Canvas) { - val iconBounds = getBounds() - val factory = object : BaseIconFactory( - context, - 0 /* unused */, - iconBounds.width(), - true) {} - val badge = factory.createBadgedIconBitmap( - appInfo.loadIcon(pm), - user, - true, - appInfo.isInstantApp, - null) - val badgeDrawable = BitmapDrawable(context.resources, badge.icon) - .apply { - alpha = drawable.alpha - colorFilter = drawable.colorFilter - val badgeWidth = TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, - 15f, - context.resources.displayMetrics - ).toInt() - setBounds( - iconBounds.left + (iconBounds.width() - badgeWidth), - iconBounds.top + (iconBounds.height() - badgeWidth), - iconBounds.right, - iconBounds.bottom) - } - drawable.bounds = iconBounds - drawable.draw(canvas) - badgeDrawable.draw(canvas) - } - - override fun setAlpha(alpha: Int) { - drawable.alpha = alpha - } - - override fun setColorFilter(colorFilter: ColorFilter?) { - drawable.colorFilter = colorFilter - } - - @PixelFormat.Opacity - override fun getOpacity(): Int = PixelFormat.OPAQUE - } + return PersonModel(key, name, drawable, clickIntent, sbn.user.identifier) } fun extractAvatarFromRow(entry: NotificationEntry): Drawable? = @@ -272,4 +263,4 @@ private fun NotificationEntry.extractPersonKey(): PersonKey? = if (isMessagingNotification()) key else null private fun NotificationEntry.isMessagingNotification() = - sbn.notification.notificationStyle == Notification.MessagingStyle::class.java
\ No newline at end of file + sbn.notification.notificationStyle == Notification.MessagingStyle::class.java diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java index 172b72e9b0fc..30f22ac5e161 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java @@ -397,7 +397,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder existingWrapper.onReinflated(); } } catch (Exception e) { - handleInflationError(runningInflations, e, row.getEntry().getSbn(), callback); + handleInflationError(runningInflations, e, row.getEntry(), callback); // Add a running inflation to make sure we don't trigger callbacks. // Safe to do because only happens in tests. runningInflations.put(inflationId, new CancellationSignal()); @@ -448,7 +448,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder onViewApplied(newView); } catch (Exception anotherException) { runningInflations.remove(inflationId); - handleInflationError(runningInflations, e, row.getEntry().getSbn(), + handleInflationError(runningInflations, e, row.getEntry(), callback); } } @@ -474,7 +474,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder private static void handleInflationError( HashMap<Integer, CancellationSignal> runningInflations, Exception e, - StatusBarNotification notification, @Nullable InflationCallback callback) { + NotificationEntry notification, @Nullable InflationCallback callback) { Assert.isMainThread(); runningInflations.values().forEach(CancellationSignal::cancel); if (callback != null) { @@ -707,7 +707,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder + Integer.toHexString(sbn.getId()); Log.e(StatusBar.TAG, "couldn't inflate view for notification " + ident, e); if (mCallback != null) { - mCallback.handleInflationException(sbn, + mCallback.handleInflationException(mRow.getEntry(), new InflationException("Couldn't inflate contentViews" + e)); } } @@ -729,7 +729,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder } @Override - public void handleInflationException(StatusBarNotification notification, Exception e) { + public void handleInflationException(NotificationEntry entry, Exception e) { handleError(e); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java index 6f2abba128d6..779a224ecb62 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java @@ -53,6 +53,7 @@ import com.android.systemui.statusbar.StatusBarStateControllerImpl; import com.android.systemui.statusbar.notification.NotificationActivityStarter; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.phone.StatusBar; @@ -81,6 +82,7 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx private final Context mContext; private final VisualStabilityManager mVisualStabilityManager; private final AccessibilityManager mAccessibilityManager; + private final HighPriorityProvider mHighPriorityProvider; // Dependencies: private final NotificationLockscreenUserManager mLockscreenUserManager = @@ -109,12 +111,14 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx @Inject public NotificationGutsManager(Context context, VisualStabilityManager visualStabilityManager, Lazy<StatusBar> statusBarLazy, @Main Handler mainHandler, - AccessibilityManager accessibilityManager) { + AccessibilityManager accessibilityManager, + HighPriorityProvider highPriorityProvider) { mContext = context; mVisualStabilityManager = visualStabilityManager; mStatusBarLazy = statusBarLazy; mMainHandler = mainHandler; mAccessibilityManager = accessibilityManager; + mHighPriorityProvider = highPriorityProvider; } public void setUpWithPresenter(NotificationPresenter presenter, @@ -331,8 +335,7 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx row.getIsNonblockable(), isForBlockingHelper, row.getEntry().getImportance(), - row.getEntry().isHighPriority()); - + mHighPriorityProvider.isHighPriority(row.getEntry())); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java index b4ccb567504a..edfd1b4d3c85 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java @@ -46,6 +46,7 @@ import com.android.systemui.R; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.statusbar.AlphaOptimizedImageView; import com.android.systemui.statusbar.notification.row.NotificationGuts.GutsContent; +import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import java.util.ArrayList; @@ -269,7 +270,8 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl } mAppOpsItem = createAppOpsItem(mContext); if (mIsUsingBidirectionalSwipe) { - mInfoItem = createInfoItem(mContext, !mParent.getEntry().isHighPriority()); + mInfoItem = createInfoItem(mContext, + mParent.getEntry().getBucket() == NotificationSectionsManager.BUCKET_SILENT); } else { mInfoItem = createInfoItem(mContext); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java index 2fe54c00bb06..9b95bff4921c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java @@ -19,7 +19,6 @@ package com.android.systemui.statusbar.notification.row; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; -import android.service.notification.StatusBarNotification; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -138,10 +137,10 @@ public interface NotificationRowContentBinder { /** * Callback for when there is an inflation exception * - * @param notification notification which failed to inflate content + * @param entry notification which failed to inflate content * @param e exception */ - void handleInflationException(StatusBarNotification notification, Exception e); + void handleInflationException(NotificationEntry entry, Exception e); /** * Callback for after the content views finish inflating. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java index 352ba0f75a32..76fdfc6fbabc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java @@ -28,6 +28,7 @@ import android.media.session.MediaSession; import android.media.session.PlaybackState; import android.metrics.LogMaker; import android.os.Handler; +import android.service.notification.StatusBarNotification; import android.text.format.DateUtils; import android.view.LayoutInflater; import android.view.View; @@ -176,27 +177,30 @@ public class NotificationMediaTemplateViewWrapper extends NotificationTemplateVi final MediaSession.Token token = mRow.getEntry().getSbn().getNotification().extras .getParcelable(Notification.EXTRA_MEDIA_SESSION); - if (Utils.useQsMediaPlayer(mContext)) { + if (Utils.useQsMediaPlayer(mContext) && token != null) { final int[] compactActions = mRow.getEntry().getSbn().getNotification().extras .getIntArray(Notification.EXTRA_COMPACT_ACTIONS); int tintColor = getNotificationHeader().getOriginalIconColor(); StatusBarWindowController ctrl = Dependency.get(StatusBarWindowController.class); QuickQSPanel panel = ctrl.getStatusBarView().findViewById( com.android.systemui.R.id.quick_qs_panel); + StatusBarNotification sbn = mRow.getEntry().getSbn(); + Notification notif = sbn.getNotification(); panel.getMediaPlayer().setMediaSession(token, - mRow.getEntry().getSbn().getNotification().getSmallIcon(), + notif.getSmallIcon(), tintColor, mBackgroundColor, mActions, - compactActions); + compactActions, + notif.contentIntent); QSPanel bigPanel = ctrl.getStatusBarView().findViewById( com.android.systemui.R.id.quick_settings_panel); bigPanel.addMediaSession(token, - mRow.getEntry().getSbn().getNotification().getSmallIcon(), + notif.getSmallIcon(), tintColor, mBackgroundColor, mActions, - mRow.getEntry().getSbn()); + sbn); } boolean showCompactSeekbar = mMediaManager.getShowCompactMediaSeekbar(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java index 2761689ec409..09c1fad423d3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java @@ -187,18 +187,18 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section @Override public boolean beginsSection(@NonNull View view, @Nullable View previous) { boolean begin = false; - if (view instanceof ExpandableNotificationRow) { - if (previous instanceof ExpandableNotificationRow) { + if (view instanceof ActivatableNotificationView) { + if (previous instanceof ActivatableNotificationView) { // If we're drawing the first non-person notification, break out a section - ExpandableNotificationRow curr = (ExpandableNotificationRow) view; - ExpandableNotificationRow prev = (ExpandableNotificationRow) previous; + ActivatableNotificationView curr = (ActivatableNotificationView) view; + ActivatableNotificationView prev = (ActivatableNotificationView) previous; - begin = curr.getEntry().getBucket() != prev.getEntry().getBucket(); + begin = getBucket(curr) != getBucket(prev); } } if (!begin) { - begin = view == mGentleHeader || previous == mPeopleHubView; + begin = view == mGentleHeader || view == mPeopleHubView; } return begin; @@ -230,29 +230,42 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section return; } - int lastPersonIndex = -1; - int firstGentleNotifIndex = -1; + boolean peopleNotificationsPresent = false; + int firstNonHeadsUpIndex = -1; + int firstGentleIndex = -1; + int notifCount = 0; final int n = mParent.getChildCount(); for (int i = 0; i < n; i++) { View child = mParent.getChildAt(i); - if (child instanceof ExpandableNotificationRow - && child.getVisibility() != View.GONE) { + if (child instanceof ExpandableNotificationRow && child.getVisibility() != View.GONE) { + notifCount++; ExpandableNotificationRow row = (ExpandableNotificationRow) child; + if (firstNonHeadsUpIndex == -1 && !row.isHeadsUp()) { + firstNonHeadsUpIndex = i; + } if (row.getEntry().getBucket() == BUCKET_PEOPLE) { - lastPersonIndex = i; + peopleNotificationsPresent = true; } if (row.getEntry().getBucket() == BUCKET_SILENT) { - firstGentleNotifIndex = i; + firstGentleIndex = i; break; } } } + if (firstNonHeadsUpIndex == -1) { + firstNonHeadsUpIndex = firstGentleIndex != -1 ? firstGentleIndex : notifCount; + } + // make room for peopleHub - firstGentleNotifIndex += adjustPeopleHubVisibilityAndPosition(lastPersonIndex); + int offset = adjustPeopleHubVisibilityAndPosition( + firstNonHeadsUpIndex, peopleNotificationsPresent); + if (firstGentleIndex != -1) { + firstGentleIndex += offset; + } - adjustGentleHeaderVisibilityAndPosition(firstGentleNotifIndex); + adjustGentleHeaderVisibilityAndPosition(firstGentleIndex); mGentleHeader.setAreThereDismissableGentleNotifs( mParent.hasActiveClearableNotifications(ROWS_GENTLE)); @@ -294,13 +307,15 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section } } - private int adjustPeopleHubVisibilityAndPosition(int lastPersonIndex) { - final boolean showPeopleHeader = mPeopleHubVisible - && mNumberOfSections > 2 - && mStatusBarStateController.getState() != StatusBarState.KEYGUARD; + private int adjustPeopleHubVisibilityAndPosition( + int targetIndex, boolean peopleNotificationsPresent) { + final boolean showPeopleHeader = mNumberOfSections > 2 + && mStatusBarStateController.getState() != StatusBarState.KEYGUARD + && (peopleNotificationsPresent || mPeopleHubVisible); final int currentHubIndex = mParent.indexOfChild(mPeopleHubView); final boolean currentlyVisible = currentHubIndex >= 0; - int targetIndex = lastPersonIndex + 1; + + mPeopleHubView.setCanSwipe(showPeopleHeader && !peopleNotificationsPresent); if (!showPeopleHeader) { if (currentlyVisible) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java index 6d0fcc386281..4845ea16020b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java @@ -31,6 +31,7 @@ import com.android.systemui.SwipeHelper; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; class NotificationSwipeHelper extends SwipeHelper implements NotificationSwipeActionHelper { @@ -298,8 +299,8 @@ class NotificationSwipeHelper extends SwipeHelper implements NotificationSwipeAc @Override public Animator getViewTranslationAnimator(View v, float target, ValueAnimator.AnimatorUpdateListener listener) { - if (v instanceof SwipeableView) { - return ((SwipeableView) v).getTranslateViewAnimator(target, listener); + if (v instanceof ExpandableNotificationRow) { + return ((ExpandableNotificationRow) v).getTranslateViewAnimator(target, listener); } else { return superGetViewTranslationAnimator(v, target, listener); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/PeopleHubView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/PeopleHubView.kt index a0796060e9d8..e5717aeefdcb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/PeopleHubView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/PeopleHubView.kt @@ -16,38 +16,22 @@ package com.android.systemui.statusbar.notification.stack -import android.animation.Animator -import android.animation.AnimatorListenerAdapter -import android.animation.ObjectAnimator -import android.animation.ValueAnimator import android.content.Context import android.util.AttributeSet -import android.util.FloatProperty import android.view.View import android.view.ViewGroup import android.widget.ImageView -import android.widget.LinearLayout -import android.widget.TextView import com.android.systemui.R import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin import com.android.systemui.statusbar.notification.people.DataListener import com.android.systemui.statusbar.notification.people.PersonViewModel import com.android.systemui.statusbar.notification.row.ActivatableNotificationView -private val TRANSLATE_CONTENT = object : FloatProperty<PeopleHubView>("translate") { - override fun setValue(view: PeopleHubView, value: Float) { - view.translation = value - } - - override fun get(view: PeopleHubView) = view.translation -} - class PeopleHubView(context: Context, attrs: AttributeSet) : ActivatableNotificationView(context, attrs), SwipeableView { private lateinit var contents: ViewGroup private lateinit var personControllers: List<PersonDataListenerImpl> - private var translateAnim: ObjectAnimator? = null val personViewAdapters: Sequence<DataListener<PersonViewModel?>> get() = personControllers.asSequence() @@ -56,9 +40,10 @@ class PeopleHubView(context: Context, attrs: AttributeSet) : super.onFinishInflate() contents = requireViewById(R.id.people_list) personControllers = (0 until contents.childCount) + .reversed() .asSequence() .mapNotNull { idx -> - (contents.getChildAt(idx) as? LinearLayout)?.let(::PersonDataListenerImpl) + (contents.getChildAt(idx) as? ImageView)?.let(::PersonDataListenerImpl) } .toList() } @@ -69,41 +54,32 @@ class PeopleHubView(context: Context, attrs: AttributeSet) : override fun createMenu(): NotificationMenuRowPlugin? = null - override fun getTranslateViewAnimator( - leftTarget: Float, - listener: ValueAnimator.AnimatorUpdateListener? - ): Animator = - ObjectAnimator - .ofFloat(this, TRANSLATE_CONTENT, leftTarget) - .apply { - listener?.let { addUpdateListener(listener) } - addListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(anim: Animator) { - translateAnim = null - } - }) - } - .also { - translateAnim?.cancel() - translateAnim = it - } - override fun resetTranslation() { - translateAnim?.cancel() translationX = 0f } - private inner class PersonDataListenerImpl(val viewGroup: ViewGroup) : - DataListener<PersonViewModel?> { + override fun setTranslation(translation: Float) { + if (canSwipe) { + super.setTranslation(translation) + } + } - val nameView = viewGroup.requireViewById<TextView>(R.id.person_name) - val avatarView = viewGroup.requireViewById<ImageView>(R.id.person_icon) + var canSwipe: Boolean = true + set(value) { + if (field != value) { + if (field) { + resetTranslation() + } + field = value + } + } + + private inner class PersonDataListenerImpl(val avatarView: ImageView) : + DataListener<PersonViewModel?> { override fun onDataChanged(data: PersonViewModel?) { - viewGroup.visibility = data?.let { View.VISIBLE } ?: View.INVISIBLE - nameView.text = data?.name avatarView.setImageDrawable(data?.icon) - viewGroup.setOnClickListener { data?.onClick?.invoke() } + avatarView.setOnClickListener { data?.onClick?.invoke() } } } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SwipeableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SwipeableView.java index 6c6ef61cfdaf..49e59a2e7200 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SwipeableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SwipeableView.java @@ -16,9 +16,6 @@ package com.android.systemui.statusbar.notification.stack; -import android.animation.Animator; -import android.animation.ValueAnimator; - import androidx.annotation.Nullable; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; @@ -32,10 +29,6 @@ public interface SwipeableView { /** Optionally creates a menu for this view. */ @Nullable NotificationMenuRowPlugin createMenu(); - /** Animator for translating the view, simulating a swipe. */ - Animator getTranslateViewAnimator( - float leftTarget, ValueAnimator.AnimatorUpdateListener listener); - /** Sets the translation amount for an in-progress swipe. */ void setTranslation(float translate); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt index f7d52b5f5ab0..ad1aa8370495 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt @@ -98,7 +98,12 @@ class KeyguardBypassController : Dumpable { bypassEnabled = tunerService.getValue(key, dismissByDefault) != 0 } }, Settings.Secure.FACE_UNLOCK_DISMISSES_KEYGUARD) - lockscreenUserManager.addUserChangedListener { pendingUnlockType = null } + lockscreenUserManager.addUserChangedListener( + object : NotificationLockscreenUserManager.UserChangedListener { + override fun onUserChanged(userId: Int) { + pendingUnlockType = null + } + }) } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java index a3b1b5f8360b..a6842badc153 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java @@ -366,8 +366,8 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF); filter.addAction(Intent.ACTION_SCREEN_ON); filter.addAction(Intent.ACTION_USER_SWITCHED); - mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter, Handler.getMain(), - UserHandle.ALL); + mBroadcastDispatcher.registerReceiverWithHandler(mBroadcastReceiver, filter, + Handler.getMain(), UserHandle.ALL); notifyNavigationBarScreenOn(); mOverviewProxyService.addCallback(mOverviewProxyListener); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index 9dc8fbf900b6..0f3af095f7be 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -24,8 +24,6 @@ import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.util.AttributeSet; -import com.android.internal.annotations.VisibleForTesting; - public class NotificationPanelView extends PanelView { private static final boolean DEBUG = false; @@ -38,14 +36,10 @@ public class NotificationPanelView extends PanelView { static final String COUNTER_PANEL_OPEN = "panel_open"; static final String COUNTER_PANEL_OPEN_QS = "panel_open_qs"; - @VisibleForTesting - protected KeyguardAffordanceHelper mAffordanceHelper; - - private int mOldLayoutDirection; - private int mCurrentPanelAlpha; private final Paint mAlphaPaint = new Paint(); private boolean mDozing; + private RtlChangeListener mRtlChangeListener; public NotificationPanelView(Context context, AttributeSet attrs) { super(context, attrs); @@ -57,9 +51,8 @@ public class NotificationPanelView extends PanelView { @Override public void onRtlPropertiesChanged(int layoutDirection) { - if (layoutDirection != mOldLayoutDirection) { - mAffordanceHelper.onRtlPropertiesChanged(); - mOldLayoutDirection = layoutDirection; + if (mRtlChangeListener != null) { + mRtlChangeListener.onRtlPropertielsChanged(layoutDirection); } } @@ -95,4 +88,11 @@ public class NotificationPanelView extends PanelView { return !mDozing; } + void setRtlChangeListener(RtlChangeListener listener) { + mRtlChangeListener = listener; + } + + interface RtlChangeListener { + void onRtlPropertielsChanged(int layoutDirection); + } } 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 bbde25bb482e..90ec2a076e87 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -449,6 +449,7 @@ public class NotificationPanelViewController extends PanelViewController { private PluginManager mPluginManager; private FrameLayout mPluginFrame; private NPVPluginManager mNPVPluginManager; + private int mOldLayoutDirection; @Inject public NotificationPanelViewController(NotificationPanelView view, @@ -603,6 +604,13 @@ public class NotificationPanelViewController extends PanelViewController { } }, HomeControlsPlugin.class, false); + + mView.setRtlChangeListener(layoutDirection -> { + if (layoutDirection != mOldLayoutDirection) { + mAffordanceHelper.onRtlPropertiesChanged(); + mOldLayoutDirection = layoutDirection; + } + }); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java index 5b34aa7781c1..00e38f814dff 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java @@ -179,7 +179,7 @@ public class PhoneStatusBarPolicy filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE); filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE); filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED); - broadcastDispatcher.registerReceiver(mIntentReceiver, filter, mHandler); + broadcastDispatcher.registerReceiverWithHandler(mIntentReceiver, filter, mHandler); // listen for user / profile change. try { 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 277a7613aafb..ccc86b1f8c5f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -1272,7 +1272,11 @@ public class StatusBar extends SystemUI implements DemoMode, mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter); - mEntryManager.setRowBinder(rowBinder); + if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) { + mEntryManager.setRowBinder(rowBinder); + rowBinder.setInflationCallback(mEntryManager); + } + mRemoteInputUriController.attach(mEntryManager); rowBinder.setNotificationClicker(new NotificationClicker( @@ -1282,7 +1286,7 @@ public class StatusBar extends SystemUI implements DemoMode, mNotificationListController.bind(); if (mFeatureFlags.isNewNotifPipelineEnabled()) { - mNewNotifPipeline.get().initialize(mNotificationListener); + mNewNotifPipeline.get().initialize(mNotificationListener, rowBinder); } mEntryManager.attach(mNotificationListener); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java index 0fd0dabbc3f2..12a65169e1df 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java @@ -217,7 +217,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, mEntryManager.addNotificationLifetimeExtenders( remoteInputManager.getLifetimeExtenders()); notificationRowBinder.setUpWithPresenter(this, notifListContainer, mHeadsUpManager, - mEntryManager, this); + this); mNotificationInterruptionStateProvider.setUpWithPresenter( this, mHeadsUpManager, this::canHeadsUp); mLockscreenUserManager.setUpWithPresenter(this); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java index ddacc3aedce0..4f0af9e166c9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java @@ -188,7 +188,7 @@ public class Clock extends TextView implements DemoMode, Tunable, CommandQueue.C // NOTE: This receiver could run before this method returns, as it's not dispatching // on the main thread and BroadcastDispatcher may not need to register with Context. // The receiver will return immediately if the view does not have a Handler yet. - mBroadcastDispatcher.registerReceiver(mIntentReceiver, filter, + mBroadcastDispatcher.registerReceiverWithHandler(mIntentReceiver, filter, Dependency.get(Dependency.TIME_TICK_HANDLER), UserHandle.ALL); Dependency.get(TunerService.class).addTunable(this, CLOCK_SECONDS, StatusBarIconController.ICON_BLACKLIST); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java index 2e26711a3578..b4c154aa28cb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java @@ -96,7 +96,7 @@ public class DateView extends TextView { filter.addAction(Intent.ACTION_TIME_CHANGED); filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); filter.addAction(Intent.ACTION_LOCALE_CHANGED); - mBroadcastDispatcher.registerReceiver(mIntentReceiver, filter, + mBroadcastDispatcher.registerReceiverWithHandler(mIntentReceiver, filter, Dependency.get(Dependency.TIME_TICK_HANDLER)); updateClock(); 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 570f153a62c0..cb40d7752f53 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java @@ -79,7 +79,8 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio IntentFilter filter = new IntentFilter(); filter.addAction(LocationManager.HIGH_POWER_REQUEST_CHANGE_ACTION); filter.addAction(LocationManager.MODE_CHANGED_ACTION); - mBroadcastDispatcher.registerReceiver(this, filter, new Handler(bgLooper), UserHandle.ALL); + mBroadcastDispatcher.registerReceiverWithHandler(this, filter, + new Handler(bgLooper), UserHandle.ALL); mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); mStatusBarManager diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java index f640d039ad31..679fa7e2b016 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java @@ -326,7 +326,7 @@ public class NetworkControllerImpl extends BroadcastReceiver filter.addAction(ConnectivityManager.INET_CONDITION_ACTION); filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED); filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED); - mBroadcastDispatcher.registerReceiver(this, filter, mReceiverHandler); + mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mReceiverHandler); mListening = true; updateMobileControllers(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java index 019ef3bca709..312c4ac75bfa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java @@ -126,7 +126,8 @@ public class SecurityControllerImpl extends CurrentUserTracker implements Securi IntentFilter filter = new IntentFilter(); filter.addAction(KeyChain.ACTION_TRUST_STORE_CHANGED); filter.addAction(Intent.ACTION_USER_UNLOCKED); - broadcastDispatcher.registerReceiver(mBroadcastReceiver, filter, bgHandler, UserHandle.ALL); + broadcastDispatcher.registerReceiverWithHandler(mBroadcastReceiver, filter, bgHandler, + UserHandle.ALL); // TODO: re-register network callback on user change. mConnectivityManager.registerNetworkCallback(REQUEST, mNetworkCallback); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java index e7d1c95db0e3..718522c9908a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java @@ -85,7 +85,8 @@ public class WifiSignalController extends boolean visibleWhenEnabled = mContext.getResources().getBoolean( R.bool.config_showWifiIndicatorWhenEnabled); boolean wifiVisible = mCurrentState.enabled - && (mCurrentState.connected || !mHasMobileData || visibleWhenEnabled); + && ((mCurrentState.connected && mCurrentState.inetCondition == 1) + || !mHasMobileData || visibleWhenEnabled); String wifiDesc = wifiVisible ? mCurrentState.ssid : null; boolean ssidPresent = wifiVisible && mCurrentState.ssid != null; String contentDescription = getStringIfExists(getContentDescription()); diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java index 7758aba52918..31b9952afe94 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java @@ -88,7 +88,7 @@ public class ThemeOverlayController extends SystemUI { final IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_USER_SWITCHED); filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED); - mBroadcastDispatcher.registerReceiver(new BroadcastReceiver() { + mBroadcastDispatcher.registerReceiverWithHandler(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (DEBUG) Log.d(TAG, "Updating overlays for user switch / profile added."); diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java index 7cdba8607d86..cc6d607a60cf 100644 --- a/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java +++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java @@ -83,17 +83,19 @@ public abstract class ConcurrencyModule { * Provide a Background-Thread Executor by default. */ @Provides + @Singleton public static Executor provideExecutor(@Background Looper looper) { - return new ExecutorImpl(new Handler(looper)); + return new ExecutorImpl(looper); } /** * Provide a Background-Thread Executor. */ @Provides + @Singleton @Background public static Executor provideBackgroundExecutor(@Background Looper looper) { - return new ExecutorImpl(new Handler(looper)); + return new ExecutorImpl(looper); } /** @@ -109,26 +111,29 @@ public abstract class ConcurrencyModule { * Provide a Background-Thread Executor by default. */ @Provides + @Singleton public static DelayableExecutor provideDelayableExecutor(@Background Looper looper) { - return new ExecutorImpl(new Handler(looper)); + return new ExecutorImpl(looper); } /** * Provide a Background-Thread Executor. */ @Provides + @Singleton @Background public static DelayableExecutor provideBackgroundDelayableExecutor(@Background Looper looper) { - return new ExecutorImpl(new Handler(looper)); + return new ExecutorImpl(looper); } /** * Provide a Main-Thread Executor. */ @Provides + @Singleton @Main public static DelayableExecutor provideMainDelayableExecutor(@Main Looper looper) { - return new ExecutorImpl(new Handler(looper)); + return new ExecutorImpl(looper); } /** diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/ExecutorImpl.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/ExecutorImpl.java index 7e7732135e3a..2bbf9507122a 100644 --- a/packages/SystemUI/src/com/android/systemui/util/concurrency/ExecutorImpl.java +++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/ExecutorImpl.java @@ -17,37 +17,69 @@ package com.android.systemui.util.concurrency; import android.os.Handler; -import android.os.HandlerExecutor; +import android.os.Looper; import android.os.Message; +import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.TimeUnit; /** * Implementations of {@link DelayableExecutor} for SystemUI. */ -public class ExecutorImpl extends HandlerExecutor implements DelayableExecutor { +public class ExecutorImpl implements DelayableExecutor { private final Handler mHandler; - public ExecutorImpl(Handler handler) { - super(handler); - mHandler = handler; + ExecutorImpl(Looper looper) { + mHandler = new Handler(looper, this::onHandleMessage); + } + + @Override + public void execute(Runnable command) { + if (!mHandler.post(command)) { + throw new RejectedExecutionException(mHandler + " is shutting down"); + } } @Override public Runnable executeDelayed(Runnable r, long delay, TimeUnit unit) { - Object token = new Object(); - Message m = mHandler.obtainMessage(0, token); + ExecutionToken token = new ExecutionToken(r); + Message m = mHandler.obtainMessage(MSG_EXECUTE_RUNNABLE, token); mHandler.sendMessageDelayed(m, unit.toMillis(delay)); - return () -> mHandler.removeCallbacksAndMessages(token); + return token; } @Override public Runnable executeAtTime(Runnable r, long uptimeMillis, TimeUnit unit) { - Object token = new Object(); - Message m = mHandler.obtainMessage(0, token); + ExecutionToken token = new ExecutionToken(r); + Message m = mHandler.obtainMessage(MSG_EXECUTE_RUNNABLE, token); mHandler.sendMessageAtTime(m, unit.toMillis(uptimeMillis)); - return () -> mHandler.removeCallbacksAndMessages(token); + return token; + } + + private boolean onHandleMessage(Message msg) { + if (msg.what == MSG_EXECUTE_RUNNABLE) { + ExecutionToken token = (ExecutionToken) msg.obj; + token.runnable.run(); + } else { + throw new IllegalStateException("Unrecognized message: " + msg.what); + } + return true; } + + private class ExecutionToken implements Runnable { + public final Runnable runnable; + + private ExecutionToken(Runnable runnable) { + this.runnable = runnable; + } + + @Override + public void run() { + mHandler.removeCallbacksAndMessages(this); + } + } + + private static final int MSG_EXECUTE_RUNNABLE = 0; } diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java index a4ed31d95b2b..112ae6f3758a 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java @@ -1008,7 +1008,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); filter.addAction(Intent.ACTION_SCREEN_OFF); filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); - mBroadcastDispatcher.registerReceiver(this, filter, mWorker); + mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mWorker); } public void destroy() { diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java new file mode 100644 index 000000000000..d413308d4573 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java @@ -0,0 +1,333 @@ +/* + * 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.systemui.wm; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.content.res.Configuration; +import android.os.Handler; +import android.os.RemoteException; +import android.util.Slog; +import android.util.SparseArray; +import android.view.IDisplayWindowInsetsController; +import android.view.InsetsSource; +import android.view.InsetsSourceControl; +import android.view.InsetsState; +import android.view.Surface; +import android.view.SurfaceControl; +import android.view.WindowInsets; +import android.view.animation.Interpolator; +import android.view.animation.PathInterpolator; + +import com.android.systemui.dagger.qualifiers.Main; + +import java.util.ArrayList; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Manages IME control at the display-level. This occurs when IME comes up in multi-window mode. + */ +@Singleton +public class DisplayImeController implements DisplayWindowController.DisplayWindowListener { + private static final String TAG = "DisplayImeController"; + + static final int ANIMATION_DURATION_SHOW_MS = 275; + static final int ANIMATION_DURATION_HIDE_MS = 340; + static final Interpolator INTERPOLATOR = new PathInterpolator(0.4f, 0f, 0.2f, 1f); + private static final int DIRECTION_NONE = 0; + private static final int DIRECTION_SHOW = 1; + private static final int DIRECTION_HIDE = 2; + + SystemWindows mSystemWindows; + final Handler mHandler; + + final SparseArray<PerDisplay> mImePerDisplay = new SparseArray<>(); + + final ArrayList<ImePositionProcessor> mPositionProcessors = new ArrayList<>(); + + @Inject + DisplayImeController(SystemWindows syswin, DisplayWindowController displayController, + @Main Handler mainHandler) { + mHandler = mainHandler; + mSystemWindows = syswin; + displayController.addDisplayWindowListener(this); + } + + @Override + public void onDisplayAdded(int displayId) { + // Add's a system-ui window-manager specifically for ime. This type is special because + // WM will defer IME inset handling to it in multi-window scenarious. + PerDisplay pd = new PerDisplay(displayId, + mSystemWindows.mDisplayController.getDisplayLayout(displayId).rotation()); + try { + mSystemWindows.mWmService.setDisplayWindowInsetsController(displayId, pd); + } catch (RemoteException e) { + Slog.w(TAG, "Unable to set insets controller on display " + displayId); + } + mImePerDisplay.put(displayId, pd); + } + + @Override + public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) { + PerDisplay pd = mImePerDisplay.get(displayId); + if (pd == null) { + return; + } + if (mSystemWindows.mDisplayController.getDisplayLayout(displayId).rotation() + != pd.mRotation && isImeShowing(displayId)) { + pd.startAnimation(true); + } + } + + @Override + public void onDisplayRemoved(int displayId) { + try { + mSystemWindows.mWmService.setDisplayWindowInsetsController(displayId, null); + } catch (RemoteException e) { + Slog.w(TAG, "Unable to remove insets controller on display " + displayId); + } + mImePerDisplay.remove(displayId); + } + + private boolean isImeShowing(int displayId) { + PerDisplay pd = mImePerDisplay.get(displayId); + if (pd == null) { + return false; + } + final InsetsSource imeSource = pd.mInsetsState.getSource(InsetsState.ITYPE_IME); + return imeSource != null && pd.mImeSourceControl != null && imeSource.isVisible(); + } + + private void dispatchPositionChanged(int displayId, int imeTop, + SurfaceControl.Transaction t) { + synchronized (mPositionProcessors) { + for (ImePositionProcessor pp : mPositionProcessors) { + pp.onImePositionChanged(displayId, imeTop, t); + } + } + } + + private void dispatchStartPositioning(int displayId, int imeTop, int finalImeTop, + boolean show, SurfaceControl.Transaction t) { + synchronized (mPositionProcessors) { + for (ImePositionProcessor pp : mPositionProcessors) { + pp.onImeStartPositioning(displayId, imeTop, finalImeTop, show, t); + } + } + } + + private void dispatchEndPositioning(int displayId, int imeTop, boolean show, + SurfaceControl.Transaction t) { + synchronized (mPositionProcessors) { + for (ImePositionProcessor pp : mPositionProcessors) { + pp.onImeEndPositioning(displayId, imeTop, show, t); + } + } + } + + /** + * Adds an {@link ImePositionProcessor} to be called during ime position updates. + */ + public void addPositionProcessor(ImePositionProcessor processor) { + synchronized (mPositionProcessors) { + if (mPositionProcessors.contains(processor)) { + return; + } + mPositionProcessors.add(processor); + } + } + + /** + * Removes an {@link ImePositionProcessor} to be called during ime position updates. + */ + public void removePositionProcessor(ImePositionProcessor processor) { + synchronized (mPositionProcessors) { + mPositionProcessors.remove(processor); + } + } + + class PerDisplay extends IDisplayWindowInsetsController.Stub { + final int mDisplayId; + final InsetsState mInsetsState = new InsetsState(); + InsetsSourceControl mImeSourceControl = null; + int mAnimationDirection = DIRECTION_NONE; + ValueAnimator mAnimation = null; + int mRotation = Surface.ROTATION_0; + + PerDisplay(int displayId, int initialRotation) { + mDisplayId = displayId; + mRotation = initialRotation; + } + + @Override + public void insetsChanged(InsetsState insetsState) { + if (mInsetsState.equals(insetsState)) { + return; + } + mInsetsState.set(insetsState, true /* copySources */); + } + + @Override + public void insetsControlChanged(InsetsState insetsState, + InsetsSourceControl[] activeControls) { + insetsChanged(insetsState); + if (activeControls != null) { + for (InsetsSourceControl activeControl : activeControls) { + if (activeControl == null) { + continue; + } + if (activeControl.getType() == InsetsState.ITYPE_IME) { + mImeSourceControl = activeControl; + } + } + } + } + + @Override + public void showInsets(int types, boolean fromIme) { + if ((types & WindowInsets.Type.ime()) == 0) { + return; + } + startAnimation(true /* show */); + } + + @Override + public void hideInsets(int types, boolean fromIme) { + if ((types & WindowInsets.Type.ime()) == 0) { + return; + } + startAnimation(false /* show */); + } + + /** + * Sends the local visibility state back to window manager. Needed for legacy adjustForIme. + */ + private void setVisibleDirectly(boolean visible) { + mInsetsState.getSource(InsetsState.ITYPE_IME).setVisible(visible); + try { + mSystemWindows.mWmService.modifyDisplayWindowInsets(mDisplayId, mInsetsState); + } catch (RemoteException e) { + } + } + + private int imeTop(InsetsSource imeSource, float surfaceOffset) { + return imeSource.getFrame().top + (int) surfaceOffset; + } + + private void startAnimation(final boolean show) { + final InsetsSource imeSource = mInsetsState.getSource(InsetsState.ITYPE_IME); + if (imeSource == null || mImeSourceControl == null) { + return; + } + if ((mAnimationDirection == DIRECTION_SHOW && show) + || (mAnimationDirection == DIRECTION_HIDE && !show)) { + return; + } + if (mAnimationDirection != DIRECTION_NONE) { + mAnimation.end(); + mAnimationDirection = DIRECTION_NONE; + } + mAnimationDirection = show ? DIRECTION_SHOW : DIRECTION_HIDE; + mHandler.post(() -> { + final float defaultY = mImeSourceControl.getSurfacePosition().y; + final float x = mImeSourceControl.getSurfacePosition().x; + final float startY = show ? defaultY + imeSource.getFrame().height() : defaultY; + final float endY = show ? defaultY : defaultY + imeSource.getFrame().height(); + mAnimation = ValueAnimator.ofFloat(startY, endY); + mAnimation.setDuration( + show ? ANIMATION_DURATION_SHOW_MS : ANIMATION_DURATION_HIDE_MS); + + mAnimation.addUpdateListener(animation -> { + SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + float value = (float) animation.getAnimatedValue(); + t.setPosition(mImeSourceControl.getLeash(), x, value); + dispatchPositionChanged(mDisplayId, imeTop(imeSource, value), t); + t.apply(); + t.close(); + }); + mAnimation.setInterpolator(INTERPOLATOR); + mAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + t.setPosition(mImeSourceControl.getLeash(), x, startY); + dispatchStartPositioning(mDisplayId, imeTop(imeSource, startY), + imeTop(imeSource, endY), mAnimationDirection == DIRECTION_SHOW, + t); + if (mAnimationDirection == DIRECTION_SHOW) { + t.show(mImeSourceControl.getLeash()); + } + t.apply(); + t.close(); + } + @Override + public void onAnimationEnd(Animator animation) { + SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + t.setPosition(mImeSourceControl.getLeash(), x, endY); + dispatchEndPositioning(mDisplayId, imeTop(imeSource, endY), + mAnimationDirection == DIRECTION_SHOW, t); + if (mAnimationDirection == DIRECTION_HIDE) { + t.hide(mImeSourceControl.getLeash()); + } + t.apply(); + t.close(); + + mAnimationDirection = DIRECTION_NONE; + mAnimation = null; + } + }); + if (!show) { + // When going away, queue up insets change first, otherwise any bounds changes + // can have a "flicker" of ime-provided insets. + setVisibleDirectly(false /* visible */); + } + mAnimation.start(); + if (show) { + // When showing away, queue up insets change last, otherwise any bounds changes + // can have a "flicker" of ime-provided insets. + setVisibleDirectly(true /* visible */); + } + }); + } + } + + /** + * Allows other things to synchronize with the ime position + */ + public interface ImePositionProcessor { + /** + * Called when the IME position is starting to animate. + */ + void onImeStartPositioning(int displayId, int imeTop, int finalImeTop, boolean showing, + SurfaceControl.Transaction t); + + /** + * Called when the ime position changed. This is expected to be a synchronous call on the + * animation thread. Operations can be added to the transaction to be applied in sync. + */ + void onImePositionChanged(int displayId, int imeTop, SurfaceControl.Transaction t); + + /** + * Called when the IME position is done animating. + */ + void onImeEndPositioning(int displayId, int imeTop, boolean showing, + SurfaceControl.Transaction t); + } +} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java index b70fdbd4a2d8..eccf09633f16 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java @@ -19,7 +19,7 @@ package com.android.keyguard; import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE; import static android.telephony.SubscriptionManager.DATA_ROAMING_ENABLE; -import static android.telephony.SubscriptionManager.NAME_SOURCE_DEFAULT_SOURCE; +import static android.telephony.SubscriptionManager.NAME_SOURCE_DEFAULT; import static junit.framework.Assert.assertTrue; import static junit.framework.TestCase.assertFalse; @@ -83,14 +83,14 @@ public class CarrierTextControllerTest extends SysuiTestCase { private static final String TEST_CARRIER_2 = "TEST_CARRIER_2"; private static final int TEST_CARRIER_ID = 1; private static final SubscriptionInfo TEST_SUBSCRIPTION = new SubscriptionInfo(0, "", 0, - TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_DEFAULT_SOURCE, 0xFFFFFF, "", + TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_DEFAULT, 0xFFFFFF, "", DATA_ROAMING_DISABLE, null, null, null, null, false, null, "", false, null, TEST_CARRIER_ID, 0); private static final SubscriptionInfo TEST_SUBSCRIPTION_NULL = new SubscriptionInfo(0, "", 0, - TEST_CARRIER, null, NAME_SOURCE_DEFAULT_SOURCE, 0xFFFFFF, "", DATA_ROAMING_DISABLE, + TEST_CARRIER, null, NAME_SOURCE_DEFAULT, 0xFFFFFF, "", DATA_ROAMING_DISABLE, null, null, null, null, false, null, ""); private static final SubscriptionInfo TEST_SUBSCRIPTION_ROAMING = new SubscriptionInfo(0, "", 0, - TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_DEFAULT_SOURCE, 0xFFFFFF, "", + TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_DEFAULT, 0xFFFFFF, "", DATA_ROAMING_ENABLE, null, null, null, null, false, null, ""); @Mock private WifiManager mWifiManager; diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 40ea6ddbbe38..2e0fb3bc850d 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -17,7 +17,7 @@ package com.android.keyguard; import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE; -import static android.telephony.SubscriptionManager.NAME_SOURCE_DEFAULT_SOURCE; +import static android.telephony.SubscriptionManager.NAME_SOURCE_DEFAULT; import static com.google.common.truth.Truth.assertThat; @@ -84,11 +84,11 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { private static final int TEST_CARRIER_ID = 1; private static final String TEST_GROUP_UUID = "59b5c870-fc4c-47a4-a99e-9db826b48b24"; private static final SubscriptionInfo TEST_SUBSCRIPTION = new SubscriptionInfo(1, "", 0, - TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_DEFAULT_SOURCE, 0xFFFFFF, "", + TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_DEFAULT, 0xFFFFFF, "", DATA_ROAMING_DISABLE, null, null, null, null, false, null, "", false, TEST_GROUP_UUID, TEST_CARRIER_ID, 0); private static final SubscriptionInfo TEST_SUBSCRIPTION_2 = new SubscriptionInfo(2, "", 0, - TEST_CARRIER, TEST_CARRIER_2, NAME_SOURCE_DEFAULT_SOURCE, 0xFFFFFF, "", + TEST_CARRIER, TEST_CARRIER_2, NAME_SOURCE_DEFAULT, 0xFFFFFF, "", DATA_ROAMING_DISABLE, null, null, null, null, false, null, "", true, TEST_GROUP_UUID, TEST_CARRIER_ID, 0); @Mock @@ -150,10 +150,10 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testReceiversRegistered() { - verify(mBroadcastDispatcher, atLeastOnce()).registerReceiver( + verify(mBroadcastDispatcher, atLeastOnce()).registerReceiverWithHandler( eq(mKeyguardUpdateMonitor.mBroadcastReceiver), any(IntentFilter.class), any(Handler.class)); - verify(mBroadcastDispatcher, atLeastOnce()).registerReceiver( + verify(mBroadcastDispatcher, atLeastOnce()).registerReceiverWithHandler( eq(mKeyguardUpdateMonitor.mBroadcastAllReceiver), any(IntentFilter.class), any(Handler.class), eq(UserHandle.ALL)); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt index 42fbf59fef44..22b18373e81d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt @@ -27,6 +27,8 @@ import android.test.suitebuilder.annotation.SmallTest import android.testing.AndroidTestingRunner import android.testing.TestableLooper import com.android.systemui.SysuiTestCase +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.time.FakeSystemClock import junit.framework.Assert.assertSame import org.junit.Before import org.junit.Test @@ -39,6 +41,7 @@ import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations +import java.util.concurrent.Executor @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper @@ -73,6 +76,8 @@ class BroadcastDispatcherTest : SysuiTestCase() { @Mock private lateinit var mockHandler: Handler + private lateinit var executor: Executor + @Captor private lateinit var argumentCaptor: ArgumentCaptor<ReceiverData> @@ -83,6 +88,7 @@ class BroadcastDispatcherTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) testableLooper = TestableLooper.get(this) + executor = FakeExecutor(FakeSystemClock()) broadcastDispatcher = TestBroadcastDispatcher( mockContext, @@ -98,8 +104,9 @@ class BroadcastDispatcherTest : SysuiTestCase() { @Test fun testAddingReceiverToCorrectUBR() { - broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mockHandler, user0) - broadcastDispatcher.registerReceiver( + broadcastDispatcher.registerReceiverWithHandler(broadcastReceiver, intentFilter, + mockHandler, user0) + broadcastDispatcher.registerReceiverWithHandler( broadcastReceiverOther, intentFilterOther, mockHandler, user1) testableLooper.processAllMessages() @@ -115,9 +122,29 @@ class BroadcastDispatcherTest : SysuiTestCase() { } @Test + fun testAddingReceiverToCorrectUBR_executor() { + broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, executor, user0) + broadcastDispatcher.registerReceiver( + broadcastReceiverOther, intentFilterOther, executor, user1) + + testableLooper.processAllMessages() + + verify(mockUBRUser0).registerReceiver(capture(argumentCaptor)) + + assertSame(broadcastReceiver, argumentCaptor.value.receiver) + assertSame(intentFilter, argumentCaptor.value.filter) + + verify(mockUBRUser1).registerReceiver(capture(argumentCaptor)) + assertSame(broadcastReceiverOther, argumentCaptor.value.receiver) + assertSame(intentFilterOther, argumentCaptor.value.filter) + } + + @Test fun testRemovingReceiversRemovesFromAllUBR() { - broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mockHandler, user0) - broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mockHandler, user1) + broadcastDispatcher.registerReceiverWithHandler(broadcastReceiver, intentFilter, + mockHandler, user0) + broadcastDispatcher.registerReceiverWithHandler(broadcastReceiver, intentFilter, + mockHandler, user1) broadcastDispatcher.unregisterReceiver(broadcastReceiver) @@ -129,8 +156,10 @@ class BroadcastDispatcherTest : SysuiTestCase() { @Test fun testRemoveReceiverFromUser() { - broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mockHandler, user0) - broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mockHandler, user1) + broadcastDispatcher.registerReceiverWithHandler(broadcastReceiver, intentFilter, + mockHandler, user0) + broadcastDispatcher.registerReceiverWithHandler(broadcastReceiver, intentFilter, + mockHandler, user1) broadcastDispatcher.unregisterReceiverForUser(broadcastReceiver, user0) @@ -143,8 +172,8 @@ class BroadcastDispatcherTest : SysuiTestCase() { @Test fun testRegisterCurrentAsActualUser() { setUserMock(mockContext, user1) - broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mockHandler, - UserHandle.CURRENT) + broadcastDispatcher.registerReceiverWithHandler(broadcastReceiver, intentFilter, + mockHandler, UserHandle.CURRENT) testableLooper.processAllMessages() diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt index 21ed15517752..7821ae29592e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt @@ -26,6 +26,8 @@ import android.test.suitebuilder.annotation.SmallTest import android.testing.AndroidTestingRunner import android.testing.TestableLooper import com.android.systemui.SysuiTestCase +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.time.FakeSystemClock import junit.framework.Assert.assertEquals import junit.framework.Assert.assertFalse import junit.framework.Assert.assertTrue @@ -69,8 +71,6 @@ class UserBroadcastDispatcherTest : SysuiTestCase() { @Mock private lateinit var mockContext: Context @Mock - private lateinit var mockHandler: Handler - @Mock private lateinit var mPendingResult: BroadcastReceiver.PendingResult @Captor @@ -81,12 +81,14 @@ class UserBroadcastDispatcherTest : SysuiTestCase() { private lateinit var intentFilter: IntentFilter private lateinit var intentFilterOther: IntentFilter private lateinit var handler: Handler + private lateinit var fakeExecutor: FakeExecutor @Before fun setUp() { MockitoAnnotations.initMocks(this) testableLooper = TestableLooper.get(this) handler = Handler(testableLooper.looper) + fakeExecutor = FakeExecutor(FakeSystemClock()) userBroadcastDispatcher = UserBroadcastDispatcher( mockContext, USER_ID, handler, testableLooper.looper) @@ -108,7 +110,7 @@ class UserBroadcastDispatcherTest : SysuiTestCase() { intentFilter = IntentFilter(ACTION_1) userBroadcastDispatcher.registerReceiver( - ReceiverData(broadcastReceiver, intentFilter, mockHandler, USER_HANDLE)) + ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE)) testableLooper.processAllMessages() assertTrue(userBroadcastDispatcher.isRegistered()) @@ -128,7 +130,7 @@ class UserBroadcastDispatcherTest : SysuiTestCase() { intentFilter = IntentFilter(ACTION_1) userBroadcastDispatcher.registerReceiver( - ReceiverData(broadcastReceiver, intentFilter, mockHandler, USER_HANDLE)) + ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE)) testableLooper.processAllMessages() reset(mockContext) @@ -151,9 +153,9 @@ class UserBroadcastDispatcherTest : SysuiTestCase() { } userBroadcastDispatcher.registerReceiver( - ReceiverData(broadcastReceiver, intentFilter, mockHandler, USER_HANDLE)) + ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE)) userBroadcastDispatcher.registerReceiver( - ReceiverData(broadcastReceiverOther, intentFilterOther, mockHandler, USER_HANDLE)) + ReceiverData(broadcastReceiverOther, intentFilterOther, fakeExecutor, USER_HANDLE)) testableLooper.processAllMessages() assertTrue(userBroadcastDispatcher.isRegistered()) @@ -179,14 +181,15 @@ class UserBroadcastDispatcherTest : SysuiTestCase() { intentFilterOther = IntentFilter(ACTION_2) userBroadcastDispatcher.registerReceiver( - ReceiverData(broadcastReceiver, intentFilter, handler, USER_HANDLE)) + ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE)) userBroadcastDispatcher.registerReceiver( - ReceiverData(broadcastReceiverOther, intentFilterOther, handler, USER_HANDLE)) + ReceiverData(broadcastReceiverOther, intentFilterOther, fakeExecutor, USER_HANDLE)) val intent = Intent(ACTION_2) userBroadcastDispatcher.onReceive(mockContext, intent) testableLooper.processAllMessages() + fakeExecutor.runAllReady() verify(broadcastReceiver, never()).onReceive(any(), any()) verify(broadcastReceiverOther).onReceive(mockContext, intent) @@ -198,14 +201,15 @@ class UserBroadcastDispatcherTest : SysuiTestCase() { intentFilterOther = IntentFilter(ACTION_2) userBroadcastDispatcher.registerReceiver( - ReceiverData(broadcastReceiver, intentFilter, handler, USER_HANDLE)) + ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE)) userBroadcastDispatcher.registerReceiver( - ReceiverData(broadcastReceiver, intentFilterOther, handler, USER_HANDLE)) + ReceiverData(broadcastReceiver, intentFilterOther, fakeExecutor, USER_HANDLE)) val intent = Intent(ACTION_2) userBroadcastDispatcher.onReceive(mockContext, intent) testableLooper.processAllMessages() + fakeExecutor.runAllReady() verify(broadcastReceiver).onReceive(mockContext, intent) } @@ -218,14 +222,15 @@ class UserBroadcastDispatcherTest : SysuiTestCase() { intentFilterOther.addCategory(CATEGORY_2) userBroadcastDispatcher.registerReceiver( - ReceiverData(broadcastReceiver, intentFilter, handler, USER_HANDLE)) + ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE)) userBroadcastDispatcher.registerReceiver( - ReceiverData(broadcastReceiverOther, intentFilterOther, handler, USER_HANDLE)) + ReceiverData(broadcastReceiverOther, intentFilterOther, fakeExecutor, USER_HANDLE)) val intent = Intent(ACTION_1) userBroadcastDispatcher.onReceive(mockContext, intent) testableLooper.processAllMessages() + fakeExecutor.runAllReady() verify(broadcastReceiver).onReceive(mockContext, intent) verify(broadcastReceiverOther).onReceive(mockContext, intent) @@ -235,12 +240,13 @@ class UserBroadcastDispatcherTest : SysuiTestCase() { fun testPendingResult() { intentFilter = IntentFilter(ACTION_1) userBroadcastDispatcher.registerReceiver( - ReceiverData(broadcastReceiver, intentFilter, handler, USER_HANDLE)) + ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE)) val intent = Intent(ACTION_1) userBroadcastDispatcher.onReceive(mockContext, intent) testableLooper.processAllMessages() + fakeExecutor.runAllReady() verify(broadcastReceiver).onReceive(mockContext, intent) verify(broadcastReceiver).pendingResult = mPendingResult @@ -250,15 +256,16 @@ class UserBroadcastDispatcherTest : SysuiTestCase() { fun testRemoveReceiverReferences() { intentFilter = IntentFilter(ACTION_1) userBroadcastDispatcher.registerReceiver( - ReceiverData(broadcastReceiver, intentFilter, handler, USER_HANDLE)) + ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE)) intentFilterOther = IntentFilter(ACTION_1) intentFilterOther.addAction(ACTION_2) userBroadcastDispatcher.registerReceiver( - ReceiverData(broadcastReceiverOther, intentFilterOther, handler, USER_HANDLE)) + ReceiverData(broadcastReceiverOther, intentFilterOther, fakeExecutor, USER_HANDLE)) userBroadcastDispatcher.unregisterReceiver(broadcastReceiver) testableLooper.processAllMessages() + fakeExecutor.runAllReady() assertFalse(userBroadcastDispatcher.isReceiverReferenceHeld(broadcastReceiver)) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java index 167f361b341a..548da8e1f2aa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java @@ -110,7 +110,7 @@ public class PowerUITest extends SysuiTestCase { @Test public void testReceiverIsRegisteredToDispatcherOnStart() { mPowerUI.start(); - verify(mBroadcastDispatcher).registerReceiver( + verify(mBroadcastDispatcher).registerReceiverWithHandler( any(BroadcastReceiver.class), any(IntentFilter.class), any(Handler.class)); //PowerUI does not call with User diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java index 40b0ba9bf633..77659df738c3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java @@ -345,7 +345,7 @@ public class NotificationTestHelper { NotificationContentInflater.InflationCallback callback = new NotificationContentInflater.InflationCallback() { @Override - public void handleInflationException(StatusBarNotification notification, + public void handleInflationException(NotificationEntry entry, Exception e) { countDownLatch.countDown(); } 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 f55ea4ff8ef2..cd33cf922482 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 @@ -65,6 +65,7 @@ import com.android.systemui.ForegroundServiceController; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.NotificationLifetimeExtender; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationLockscreenUserManager; @@ -82,6 +83,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntryB import com.android.systemui.statusbar.notification.collection.NotificationRankingManager; import com.android.systemui.statusbar.notification.collection.NotificationRowBinder; import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl; +import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; import com.android.systemui.statusbar.notification.logging.NotifLog; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; @@ -143,6 +145,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase { @Mock private RowInflaterTask mAsyncInflationTask; @Mock private NotificationRowBinder mMockedRowBinder; @Mock private NotifLog mNotifLog; + @Mock private FeatureFlags mFeatureFlags; private int mId; private NotificationEntry mEntry; @@ -219,6 +222,8 @@ public class NotificationEntryManagerTest extends SysuiTestCase { mEntry.expandedIcon = mock(StatusBarIconView.class); + when(mFeatureFlags.isNewNotifPipelineEnabled()).thenReturn(false); + when(mFeatureFlags.isNewNotifPipelineRenderingEnabled()).thenReturn(false); mEntryManager = new TestableNotificationEntryManager( mNotifLog, mGroupManager, @@ -229,8 +234,10 @@ public class NotificationEntryManagerTest extends SysuiTestCase { mock(NotificationFilter.class), mNotifLog, mock(NotificationSectionsFeatureManager.class), - mock(PeopleNotificationIdentifier.class)), - mEnvironment + mock(PeopleNotificationIdentifier.class), + mock(HighPriorityProvider.class)), + mEnvironment, + mFeatureFlags ); mEntryManager.setUpWithPresenter(mPresenter, mListContainer, mHeadsUpManager); mEntryManager.addNotificationEntryListener(mEntryListener); @@ -242,7 +249,8 @@ public class NotificationEntryManagerTest extends SysuiTestCase { mock(StatusBarStateController.class), mock(NotificationLogger.class)); notificationRowBinder.setUpWithPresenter( - mPresenter, mListContainer, mHeadsUpManager, mEntryManager, mBindCallback); + mPresenter, mListContainer, mHeadsUpManager, mBindCallback); + notificationRowBinder.setInflationCallback(mEntryManager); notificationRowBinder.setNotificationClicker(mock(NotificationClicker.class)); mEntryManager.setRowBinder(notificationRowBinder); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt index 34beefe9843b..1afee120e495 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification +import com.android.systemui.statusbar.FeatureFlags import com.android.systemui.statusbar.NotificationPresenter import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.NotificationRankingManager @@ -33,8 +34,9 @@ class TestableNotificationEntryManager( log: NotifLog, gm: NotificationGroupManager, rm: NotificationRankingManager, - ke: KeyguardEnvironment -) : NotificationEntryManager(log, gm, rm, ke) { + ke: KeyguardEnvironment, + ff: FeatureFlags +) : NotificationEntryManager(log, gm, rm, ke, ff) { public var countDownLatch: CountDownLatch = CountDownLatch(1) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryTest.java deleted file mode 100644 index a06d6c1e593b..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryTest.java +++ /dev/null @@ -1,148 +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.systemui.statusbar.notification.collection; - -import static android.app.NotificationManager.IMPORTANCE_HIGH; -import static android.app.NotificationManager.IMPORTANCE_MIN; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import android.testing.AndroidTestingRunner; - -import androidx.test.filters.SmallTest; - -import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.RankingBuilder; - -import org.junit.Test; -import org.junit.runner.RunWith; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -public class GroupEntryTest extends SysuiTestCase { - @Test - public void testIsHighPriority_addChild() { - // GIVEN a GroupEntry with a lowPrioritySummary and no children - final GroupEntry parentEntry = new GroupEntry("test_group_key"); - final NotificationEntry lowPrioritySummary = createNotifEntry(false); - setSummary(parentEntry, lowPrioritySummary); - assertFalse(parentEntry.isHighPriority()); - - // WHEN we add a high priority child and invalidate derived members - addChild(parentEntry, createNotifEntry(true)); - parentEntry.invalidateDerivedMembers(); - - // THEN the GroupEntry's priority is updated to high even though the summary is still low - // priority - assertTrue(parentEntry.isHighPriority()); - assertFalse(lowPrioritySummary.isHighPriority()); - } - - @Test - public void testIsHighPriority_clearChildren() { - // GIVEN a GroupEntry with a lowPrioritySummary and high priority children - final GroupEntry parentEntry = new GroupEntry("test_group_key"); - setSummary(parentEntry, createNotifEntry(false)); - addChild(parentEntry, createNotifEntry(true)); - addChild(parentEntry, createNotifEntry(true)); - addChild(parentEntry, createNotifEntry(true)); - assertTrue(parentEntry.isHighPriority()); - - // WHEN we clear the children and invalidate derived members - parentEntry.clearChildren(); - parentEntry.invalidateDerivedMembers(); - - // THEN the parentEntry isn't high priority anymore - assertFalse(parentEntry.isHighPriority()); - } - - @Test - public void testIsHighPriority_summaryUpdated() { - // GIVEN a GroupEntry with a lowPrioritySummary and no children - final GroupEntry parentEntry = new GroupEntry("test_group_key"); - final NotificationEntry lowPrioritySummary = createNotifEntry(false); - setSummary(parentEntry, lowPrioritySummary); - assertFalse(parentEntry.isHighPriority()); - - // WHEN the summary changes to high priority and invalidates its derived members - lowPrioritySummary.setRanking( - new RankingBuilder() - .setKey(lowPrioritySummary.getKey()) - .setImportance(IMPORTANCE_HIGH) - .build()); - lowPrioritySummary.invalidateDerivedMembers(); - assertTrue(lowPrioritySummary.isHighPriority()); - - // THEN the GroupEntry's priority is updated to high - assertTrue(parentEntry.isHighPriority()); - } - - @Test - public void testIsHighPriority_checkChildrenToCalculatePriority() { - // GIVEN: - // GroupEntry = parentEntry, summary = lowPrioritySummary - // NotificationEntry = lowPriorityChild - // NotificationEntry = highPriorityChild - final GroupEntry parentEntry = new GroupEntry("test_group_key"); - setSummary(parentEntry, createNotifEntry(false)); - addChild(parentEntry, createNotifEntry(false)); - addChild(parentEntry, createNotifEntry(true)); - - // THEN the GroupEntry parentEntry is high priority since it has a high priority child - assertTrue(parentEntry.isHighPriority()); - } - - @Test - public void testIsHighPriority_childEntryRankingUpdated() { - // GIVEN: - // GroupEntry = parentEntry, summary = lowPrioritySummary - // NotificationEntry = lowPriorityChild - final GroupEntry parentEntry = new GroupEntry("test_group_key"); - final NotificationEntry lowPriorityChild = createNotifEntry(false); - setSummary(parentEntry, createNotifEntry(false)); - addChild(parentEntry, lowPriorityChild); - - // WHEN the child entry ranking changes to high priority and invalidates its derived members - lowPriorityChild.setRanking( - new RankingBuilder() - .setKey(lowPriorityChild.getKey()) - .setImportance(IMPORTANCE_HIGH) - .build()); - lowPriorityChild.invalidateDerivedMembers(); - - // THEN the parent entry's high priority value is updated - but not the parent's summary - assertTrue(parentEntry.isHighPriority()); - assertFalse(parentEntry.getSummary().isHighPriority()); - } - - private NotificationEntry createNotifEntry(boolean highPriority) { - return new NotificationEntryBuilder() - .setImportance(highPriority ? IMPORTANCE_HIGH : IMPORTANCE_MIN) - .build(); - } - - private void setSummary(GroupEntry parent, NotificationEntry summary) { - parent.setSummary(summary); - summary.setParent(parent); - } - - private void addChild(GroupEntry parent, NotificationEntry child) { - parent.addChild(child); - child.setParent(parent); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java new file mode 100644 index 000000000000..93909dc4d330 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java @@ -0,0 +1,225 @@ +/* + * 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; + +import static android.app.NotificationManager.IMPORTANCE_HIGH; +import static android.app.NotificationManager.IMPORTANCE_LOW; +import static android.app.NotificationManager.IMPORTANCE_MIN; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.app.Notification; +import android.app.NotificationChannel; +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.RankingBuilder; +import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; + +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 HighPriorityProviderTest extends SysuiTestCase { + @Mock private PeopleNotificationIdentifier mPeopleNotificationIdentifier; + private HighPriorityProvider mHighPriorityProvider; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mHighPriorityProvider = new HighPriorityProvider(mPeopleNotificationIdentifier); + } + + @Test + public void highImportance() { + // GIVEN notification has high importance + final NotificationEntry entry = new NotificationEntryBuilder() + .setImportance(IMPORTANCE_HIGH) + .build(); + when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn())).thenReturn(false); + + // THEN it has high priority + assertTrue(mHighPriorityProvider.isHighPriority(entry)); + } + + @Test + public void peopleNotification() { + // GIVEN notification is low importance and is a people notification + final Notification notification = new Notification.Builder(mContext, "test") + .build(); + final NotificationEntry entry = new NotificationEntryBuilder() + .setNotification(notification) + .setImportance(IMPORTANCE_LOW) + .build(); + when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn())).thenReturn(true); + + // THEN it has high priority + assertTrue(mHighPriorityProvider.isHighPriority(entry)); + } + + @Test + public void messagingStyle() { + // GIVEN notification is low importance but has messaging style + final Notification notification = new Notification.Builder(mContext, "test") + .setStyle(new Notification.MessagingStyle("")) + .build(); + final NotificationEntry entry = new NotificationEntryBuilder() + .setNotification(notification) + .build(); + when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn())).thenReturn(false); + + // THEN it has high priority + assertTrue(mHighPriorityProvider.isHighPriority(entry)); + } + + @Test + public void lowImportanceForeground() { + // GIVEN notification is low importance and is associated with a foreground service + final Notification notification = mock(Notification.class); + when(notification.isForegroundService()).thenReturn(true); + + final NotificationEntry entry = new NotificationEntryBuilder() + .setNotification(notification) + .setImportance(IMPORTANCE_LOW) + .build(); + when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn())).thenReturn(false); + + // THEN it has high priority + assertTrue(mHighPriorityProvider.isHighPriority(entry)); + } + + @Test + public void minImportanceForeground() { + // GIVEN notification is low importance and is associated with a foreground service + final Notification notification = mock(Notification.class); + when(notification.isForegroundService()).thenReturn(true); + + final NotificationEntry entry = new NotificationEntryBuilder() + .setNotification(notification) + .setImportance(IMPORTANCE_MIN) + .build(); + when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn())).thenReturn(false); + + // THEN it does NOT have high priority + assertFalse(mHighPriorityProvider.isHighPriority(entry)); + } + + @Test + public void userChangeTrumpsHighPriorityCharacteristics() { + // GIVEN notification has high priority characteristics but the user changed the importance + // to less than IMPORTANCE_DEFAULT (ie: IMPORTANCE_LOW or IMPORTANCE_MIN) + final Notification notification = new Notification.Builder(mContext, "test") + .setStyle(new Notification.MessagingStyle("")) + .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true) + .build(); + final NotificationChannel channel = new NotificationChannel("a", "a", + IMPORTANCE_LOW); + channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE); + + final NotificationEntry entry = new NotificationEntryBuilder() + .setNotification(notification) + .setChannel(channel) + .build(); + when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn())).thenReturn(true); + + // THEN it does NOT have high priority + assertFalse(mHighPriorityProvider.isHighPriority(entry)); + } + + @Test + public void testIsHighPriority_summaryUpdated() { + // GIVEN a GroupEntry with a lowPrioritySummary and no children + final GroupEntry parentEntry = new GroupEntry("test_group_key"); + final NotificationEntry lowPrioritySummary = createNotifEntry(false); + setSummary(parentEntry, lowPrioritySummary); + assertFalse(mHighPriorityProvider.isHighPriority(parentEntry)); + + // WHEN the summary changes to high priority + lowPrioritySummary.setRanking( + new RankingBuilder() + .setKey(lowPrioritySummary.getKey()) + .setImportance(IMPORTANCE_HIGH) + .build()); + assertTrue(mHighPriorityProvider.isHighPriority(lowPrioritySummary)); + + // THEN the GroupEntry's priority is updated to high + assertTrue(mHighPriorityProvider.isHighPriority(parentEntry)); + } + + @Test + public void testIsHighPriority_checkChildrenToCalculatePriority() { + // GIVEN: + // GroupEntry = parentEntry, summary = lowPrioritySummary + // NotificationEntry = lowPriorityChild + // NotificationEntry = highPriorityChild + final GroupEntry parentEntry = new GroupEntry("test_group_key"); + setSummary(parentEntry, createNotifEntry(false)); + addChild(parentEntry, createNotifEntry(false)); + addChild(parentEntry, createNotifEntry(true)); + + // THEN the GroupEntry parentEntry is high priority since it has a high priority child + assertTrue(mHighPriorityProvider.isHighPriority(parentEntry)); + } + + @Test + public void testIsHighPriority_childEntryRankingUpdated() { + // GIVEN: + // GroupEntry = parentEntry, summary = lowPrioritySummary + // NotificationEntry = lowPriorityChild + final GroupEntry parentEntry = new GroupEntry("test_group_key"); + final NotificationEntry lowPriorityChild = createNotifEntry(false); + setSummary(parentEntry, createNotifEntry(false)); + addChild(parentEntry, lowPriorityChild); + + // WHEN the child entry ranking changes to high priority + lowPriorityChild.setRanking( + new RankingBuilder() + .setKey(lowPriorityChild.getKey()) + .setImportance(IMPORTANCE_HIGH) + .build()); + + // THEN the parent entry's high priority value is updated - but not the parent's summary + assertTrue(mHighPriorityProvider.isHighPriority(parentEntry)); + assertFalse(mHighPriorityProvider.isHighPriority(parentEntry.getSummary())); + } + + private NotificationEntry createNotifEntry(boolean highPriority) { + return new NotificationEntryBuilder() + .setImportance(highPriority ? IMPORTANCE_HIGH : IMPORTANCE_MIN) + .build(); + } + + private void setSummary(GroupEntry parent, NotificationEntry summary) { + parent.setSummary(summary); + summary.setParent(parent); + } + + private void addChild(GroupEntry parent, NotificationEntry child) { + parent.addChild(child); + child.setParent(parent); + } +} 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 0837a42ae700..28feacac8c44 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 @@ -24,6 +24,7 @@ import static com.android.systemui.statusbar.notification.collection.NotifCollec import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; @@ -261,9 +262,13 @@ public class NotifCollectionTest extends SysuiTestCase { .setRank(5) .setExplanation("baz buzz") .build(); + + // WHEN entry3's ranking update includes an update to its overrideGroupKey + final String newOverrideGroupKey = "newOverrideGroupKey"; Ranking newRanking3 = new RankingBuilder(notif3.ranking) .setRank(6) .setExplanation("Penguin pizza") + .setOverrideGroupKey(newOverrideGroupKey) .build(); mNoMan.setRanking(notif1.sbn.getKey(), newRanking1); @@ -275,6 +280,10 @@ public class NotifCollectionTest extends SysuiTestCase { assertEquals(newRanking1, entry1.getRanking()); assertEquals(newRanking2, entry2.getRanking()); assertEquals(newRanking3, entry3.getRanking()); + + // THEN the entry3's overrideGroupKey is updated along with its groupKey + assertEquals(newOverrideGroupKey, entry3.getSbn().getOverrideGroupKey()); + assertNotNull(entry3.getSbn().getGroupKey()); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java index 39ae68a40291..5b0b66849027 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java @@ -21,8 +21,6 @@ import static android.app.Notification.CATEGORY_CALL; import static android.app.Notification.CATEGORY_EVENT; import static android.app.Notification.CATEGORY_MESSAGE; import static android.app.Notification.CATEGORY_REMINDER; -import static android.app.NotificationManager.IMPORTANCE_HIGH; -import static android.app.NotificationManager.IMPORTANCE_MIN; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT; import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking; @@ -92,44 +90,6 @@ public class NotificationEntryTest extends SysuiTestCase { } @Test - public void testIsHighPriority_notificationUpdates() { - // GIVEN a notification with high importance - final NotificationEntry entryHigh = new NotificationEntryBuilder() - .setImportance(IMPORTANCE_HIGH) - .build(); - - // WHEN we get the value for the high priority entry, we're caching the high priority value - assertTrue(entryHigh.isHighPriority()); - - // WHEN we change the ranking and derived members (high priority) are invalidated - entryHigh.setRanking( - new RankingBuilder() - .setKey(entryHigh.getKey()) - .setImportance(IMPORTANCE_MIN) - .build()); - entryHigh.invalidateDerivedMembers(); - - // THEN the priority is recalculated and is now low - assertFalse(entryHigh.isHighPriority()); - - // WHEN the sbn is updated to have messaging style (high priority characteristic) - // AND the entry invalidates its derived members - final Notification notification = - new Notification.Builder(mContext, "test") - .setStyle(new Notification.MessagingStyle("")) - .build(); - final StatusBarNotification sbn = entryHigh.getSbn(); - entryHigh.setSbn(new StatusBarNotification( - sbn.getPackageName(), sbn.getPackageName(), - sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(), - notification, sbn.getUser(), sbn.getOverrideGroupKey(), 0)); - entryHigh.invalidateDerivedMembers(); - - // THEN the priority is recalculated and is now high - assertTrue(entryHigh.isHighPriority()); - } - - @Test public void testIsExemptFromDndVisualSuppression_foreground() { mEntry.getSbn().getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt index 10450fa82cde..e27319103525 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt @@ -28,6 +28,7 @@ import com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking import com.android.systemui.statusbar.NotificationMediaManager import com.android.systemui.statusbar.notification.NotificationFilter import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager +import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider import com.android.systemui.statusbar.notification.logging.NotifLog import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING @@ -62,7 +63,8 @@ class NotificationRankingManagerTest : SysuiTestCase() { mock(NotificationFilter::class.java), mock(NotifLog::class.java), mock(NotificationSectionsFeatureManager::class.java), - personNotificationIdentifier + personNotificationIdentifier, + HighPriorityProvider(personNotificationIdentifier) ) } @@ -182,7 +184,8 @@ class NotificationRankingManagerTest : SysuiTestCase() { filter: NotificationFilter, notifLog: NotifLog, sectionsFeatureManager: NotificationSectionsFeatureManager, - peopleNotificationIdentifier: PeopleNotificationIdentifier + peopleNotificationIdentifier: PeopleNotificationIdentifier, + highPriorityProvider: HighPriorityProvider ) : NotificationRankingManager( mediaManager, groupManager, @@ -190,7 +193,8 @@ class NotificationRankingManagerTest : SysuiTestCase() { filter, notifLog, sectionsFeatureManager, - peopleNotificationIdentifier + peopleNotificationIdentifier, + highPriorityProvider ) { fun applyTestRankingMap(r: RankingMap) { rankingMap = r 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 979b8a906ef0..f921cf969e61 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 @@ -45,6 +45,7 @@ import com.android.systemui.statusbar.notification.collection.NotifListBuilderIm 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.NotifFilter; +import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; import com.android.systemui.statusbar.policy.KeyguardStateController; import org.junit.Before; @@ -66,6 +67,7 @@ public class KeyguardCoordinatorTest extends SysuiTestCase { @Mock private BroadcastDispatcher mBroadcastDispatcher; @Mock private StatusBarStateController mStatusBarStateController; @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; + @Mock private HighPriorityProvider mHighPriorityProvider; @Mock private NotifListBuilderImpl mNotifListBuilder; private NotificationEntry mEntry; @@ -78,7 +80,7 @@ public class KeyguardCoordinatorTest extends SysuiTestCase { mKeyguardCoordinator = new KeyguardCoordinator( mContext, mMainHandler, mKeyguardStateController, mLockscreenUserManager, mBroadcastDispatcher, mStatusBarStateController, - mKeyguardUpdateMonitor); + mKeyguardUpdateMonitor, mHighPriorityProvider); mEntry = new NotificationEntryBuilder() .setUser(new UserHandle(NOTIF_USER_ID)) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProviderTest.java deleted file mode 100644 index 6fa1a89515c3..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProviderTest.java +++ /dev/null @@ -1,184 +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.systemui.statusbar.notification.collection.provider; - -import static android.app.NotificationManager.IMPORTANCE_HIGH; -import static android.app.NotificationManager.IMPORTANCE_LOW; -import static android.app.NotificationManager.IMPORTANCE_MIN; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import android.app.Notification; -import android.app.NotificationChannel; -import android.app.Person; -import android.testing.AndroidTestingRunner; - -import androidx.test.filters.SmallTest; - -import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -public class IsHighPriorityProviderTest extends SysuiTestCase { - private IsHighPriorityProvider mIsHighPriorityProvider; - - @Before - public void setup() { - mIsHighPriorityProvider = new IsHighPriorityProvider(); - } - - @Test - public void testCache() { - // GIVEN a notification with high importance - final NotificationEntry entryHigh = new NotificationEntryBuilder() - .setImportance(IMPORTANCE_HIGH) - .build(); - - // GIVEN notification with min importance - final NotificationEntry entryMin = new NotificationEntryBuilder() - .setImportance(IMPORTANCE_MIN) - .build(); - - // WHEN we get the value for the high priority entry - assertTrue(mIsHighPriorityProvider.get(entryHigh)); - - // THEN the value is cached, so even when passed an entryMin, we still get high priority - assertTrue(mIsHighPriorityProvider.get(entryMin)); - - // UNTIL the provider is invalidated - mIsHighPriorityProvider.invalidate(); - - // THEN the priority is recalculated - assertFalse(mIsHighPriorityProvider.get(entryMin)); - } - - @Test - public void highImportance() { - // GIVEN notification has high importance - final NotificationEntry entry = new NotificationEntryBuilder() - .setImportance(IMPORTANCE_HIGH) - .build(); - - // THEN it has high priority - assertTrue(mIsHighPriorityProvider.get(entry)); - } - - @Test - public void peopleNotification() { - // GIVEN notification is low importance but has a person associated with it - final Notification notification = new Notification.Builder(mContext, "test") - .addPerson( - new Person.Builder() - .setName("name") - .setKey("abc") - .setUri("uri") - .setBot(true) - .build()) - .build(); - - final NotificationEntry entry = new NotificationEntryBuilder() - .setNotification(notification) - .setImportance(IMPORTANCE_LOW) - .build(); - - // THEN it has high priority - assertTrue(mIsHighPriorityProvider.get(entry)); - } - - @Test - public void messagingStyle() { - // GIVEN notification is low importance but has messaging style - final Notification notification = new Notification.Builder(mContext, "test") - .setStyle(new Notification.MessagingStyle("")) - .build(); - - final NotificationEntry entry = new NotificationEntryBuilder() - .setNotification(notification) - .build(); - - // THEN it has high priority - assertTrue(mIsHighPriorityProvider.get(entry)); - } - - @Test - public void lowImportanceForeground() { - // GIVEN notification is low importance and is associated with a foreground service - final Notification notification = mock(Notification.class); - when(notification.isForegroundService()).thenReturn(true); - - final NotificationEntry entry = new NotificationEntryBuilder() - .setNotification(notification) - .setImportance(IMPORTANCE_LOW) - .build(); - - // THEN it has high priority - assertTrue(mIsHighPriorityProvider.get(entry)); - } - - @Test - public void minImportanceForeground() { - // GIVEN notification is low importance and is associated with a foreground service - final Notification notification = mock(Notification.class); - when(notification.isForegroundService()).thenReturn(true); - - final NotificationEntry entry = new NotificationEntryBuilder() - .setNotification(notification) - .setImportance(IMPORTANCE_MIN) - .build(); - - // THEN it does NOT have high priority - assertFalse(mIsHighPriorityProvider.get(entry)); - } - - @Test - public void userChangeTrumpsHighPriorityCharacteristics() { - // GIVEN notification has high priority characteristics but the user changed the importance - // to less than IMPORTANCE_DEFAULT (ie: IMPORTANCE_LOW or IMPORTANCE_MIN) - final Notification notification = new Notification.Builder(mContext, "test") - .addPerson( - new Person.Builder() - .setName("name") - .setKey("abc") - .setUri("uri") - .setBot(true) - .build()) - .setStyle(new Notification.MessagingStyle("")) - .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true) - .build(); - - final NotificationChannel channel = new NotificationChannel("a", "a", - IMPORTANCE_LOW); - channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE); - - final NotificationEntry entry = new NotificationEntryBuilder() - .setNotification(notification) - .setChannel(channel) - .build(); - - // THEN it does NOT have high priority - assertFalse(mIsHighPriorityProvider.get(entry)); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/people/PeopleHubViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/people/PeopleHubViewControllerTest.kt index 0764d0cd4b88..867a9b97d622 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/people/PeopleHubViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/people/PeopleHubViewControllerTest.kt @@ -178,9 +178,10 @@ private fun <T> castNull(): T = null as T private fun fakePersonModel( id: String, name: CharSequence, - clickIntent: PendingIntent + clickIntent: PendingIntent, + userId: Int = 0 ): PersonModel = - PersonModel(id, name, mock(Drawable::class.java), clickIntent) + PersonModel(id, name, mock(Drawable::class.java), clickIntent, userId) private fun fakePersonViewModel(name: CharSequence): PersonViewModel = PersonViewModel(name, mock(Drawable::class.java), mock({}.javaClass)) @@ -207,4 +208,4 @@ class FakeDataListener<T> : DataListener<T> { override fun onDataChanged(data: T) { lastSeen = Maybe.Just(data) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java index dc4e4980e443..f916fe5ad7fb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java @@ -33,7 +33,6 @@ import android.content.Context; import android.os.CancellationSignal; import android.os.Handler; import android.os.Looper; -import android.service.notification.StatusBarNotification; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; import android.util.ArrayMap; @@ -179,7 +178,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase { true /* isNewView */, (v, p, r) -> true, new InflationCallback() { @Override - public void handleInflationException(StatusBarNotification notification, + public void handleInflationException(NotificationEntry entry, Exception e) { countDownLatch.countDown(); throw new RuntimeException("No Exception expected"); @@ -261,7 +260,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase { inflater.setInflateSynchronously(true); InflationCallback callback = new InflationCallback() { @Override - public void handleInflationException(StatusBarNotification notification, + public void handleInflationException(NotificationEntry entry, Exception e) { if (!expectingException) { exceptionHolder.setException(e); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java index ccc9496368e9..4e27770982e5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java @@ -70,6 +70,7 @@ import com.android.systemui.statusbar.NotificationTestHelper; import com.android.systemui.statusbar.notification.NotificationActivityStarter; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.phone.StatusBar; @@ -113,6 +114,7 @@ public class NotificationGutsManagerTest extends SysuiTestCase { @Mock private DeviceProvisionedController mDeviceProvisionedController; @Mock private StatusBar mStatusBar; @Mock private AccessibilityManager mAccessibilityManager; + @Mock private HighPriorityProvider mHighPriorityProvider; @Before public void setUp() { @@ -128,7 +130,7 @@ public class NotificationGutsManagerTest extends SysuiTestCase { when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false); mGutsManager = new NotificationGutsManager(mContext, mVisualStabilityManager, - () -> mStatusBar, mHandler, mAccessibilityManager); + () -> mStatusBar, mHandler, mAccessibilityManager, mHighPriorityProvider); mGutsManager.setUpWithPresenter(mPresenter, mStackScroller, mCheckSaveListener, mOnSettingsClickListener); mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter); @@ -391,6 +393,7 @@ public class NotificationGutsManagerTest extends SysuiTestCase { .build(); when(row.getIsNonblockable()).thenReturn(false); + when(mHighPriorityProvider.isHighPriority(entry)).thenReturn(true); StatusBarNotification statusBarNotification = entry.getSbn(); mGutsManager.initializeNotificationInfo(row, notificationInfoView); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java index 003d80376c40..518b6703391e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java @@ -267,8 +267,6 @@ public class NotificationSectionsManagerTest extends SysuiTestCase { ExpandableNotificationRow notifRow = mock(ExpandableNotificationRow.class, RETURNS_DEEP_STUBS); when(notifRow.getVisibility()).thenReturn(View.VISIBLE); - when(notifRow.getEntry().isHighPriority()) - .thenReturn(children[i] == ChildType.HIPRI); when(notifRow.getEntry().getBucket()).thenReturn( children[i] == ChildType.HIPRI ? BUCKET_ALERTING : BUCKET_SILENT); when(notifRow.getParent()).thenReturn(mNssl); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index 77a6a2602d8e..ea8d4ee20aab 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -53,6 +53,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.statusbar.EmptyShadeView; +import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationPresenter; @@ -70,6 +71,7 @@ import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.collection.NotificationRankingManager; +import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; import com.android.systemui.statusbar.notification.logging.NotifLog; import com.android.systemui.statusbar.notification.people.PeopleHubSectionFooterViewAdapter; import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; @@ -164,9 +166,11 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { mock(NotificationFilter.class), mock(NotifLog.class), mock(NotificationSectionsFeatureManager.class), - mock(PeopleNotificationIdentifier.class) + mock(PeopleNotificationIdentifier.class), + mock(HighPriorityProvider.class) ), - mock(NotificationEntryManager.KeyguardEnvironment.class)); + mock(NotificationEntryManager.KeyguardEnvironment.class), + mock(FeatureFlags.class)); mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager); mEntryManager.setUpForTest(mock(NotificationPresenter.class), null, mHeadsUpManager); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java index 39afbe0a1a23..8f645b6bd3c8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java @@ -190,7 +190,7 @@ public class NavigationBarFragmentTest extends SysuiBaseFragmentTest { mFragments.dispatchResume(); processAllMessages(); - verify(mBroadcastDispatcher).registerReceiver( + verify(mBroadcastDispatcher).registerReceiverWithHandler( any(BroadcastReceiver.class), any(IntentFilter.class), any(Handler.class), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java index 34511831247e..32da4c9650fb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java @@ -39,7 +39,8 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest { setWifiState(true, testSsid); setWifiLevel(0); - verifyLastWifiIcon(true, WifiIcons.WIFI_SIGNAL_STRENGTH[0][0]); + // Connected, but still not validated - does not show + verifyLastWifiIcon(false, WifiIcons.WIFI_SIGNAL_STRENGTH[0][0]); for (int testLevel = 0; testLevel < WifiIcons.WIFI_LEVEL_COUNT; testLevel++) { setWifiLevel(testLevel); @@ -47,7 +48,8 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest { setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_WIFI, true, true); verifyLastWifiIcon(true, WifiIcons.WIFI_SIGNAL_STRENGTH[1][testLevel]); setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_WIFI, false, true); - verifyLastWifiIcon(true, WifiIcons.WIFI_SIGNAL_STRENGTH[0][testLevel]); + // Icon does not show if not validated + verifyLastWifiIcon(false, WifiIcons.WIFI_SIGNAL_STRENGTH[0][testLevel]); } } @@ -70,7 +72,7 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest { testSsid); setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_WIFI, false, true); verifyLastQsWifiIcon(true, true, WifiIcons.QS_WIFI_SIGNAL_STRENGTH[0][testLevel], - testSsid); + null); } } @@ -132,7 +134,7 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest { verifyLastWifiIcon(true, WifiIcons.WIFI_SIGNAL_STRENGTH[1][testLevel]); setConnectivityViaCallback(NetworkCapabilities.TRANSPORT_WIFI, false, true); - verifyLastWifiIcon(true, WifiIcons.WIFI_SIGNAL_STRENGTH[0][testLevel]); + verifyLastWifiIcon(false, WifiIcons.WIFI_SIGNAL_STRENGTH[0][testLevel]); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java index 2854665aedb1..80aa6f6c49bc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java @@ -69,7 +69,7 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase { @Test public void testRegisteredWithDispatcher() { - verify(mBroadcastDispatcher).registerReceiver(any(BroadcastReceiver.class), + verify(mBroadcastDispatcher).registerReceiverWithHandler(any(BroadcastReceiver.class), any(IntentFilter.class), any(Handler.class)); // VolumeDialogControllerImpl does not call with user } diff --git a/packages/Tethering/Android.bp b/packages/Tethering/Android.bp index 0be853a81fb2..d297f3f84189 100644 --- a/packages/Tethering/Android.bp +++ b/packages/Tethering/Android.bp @@ -20,7 +20,7 @@ java_defaults { srcs: [ "src/**/*.java", ":framework-tethering-shared-srcs", - ":net-module-utils-srcs", + ":tethering-module-utils-srcs", ":services-tethering-shared-srcs", ], static_libs: [ @@ -123,4 +123,5 @@ android_app { use_embedded_native_libs: true, // The permission configuration *must* be included to ensure security of the device required: ["NetworkPermissionConfig"], + apex_available: ["com.android.tethering"], } diff --git a/packages/Tethering/AndroidManifest.xml b/packages/Tethering/AndroidManifest.xml index 87a8c3f5c68a..e99c2c529bd2 100644 --- a/packages/Tethering/AndroidManifest.xml +++ b/packages/Tethering/AndroidManifest.xml @@ -33,6 +33,7 @@ <uses-permission android:name="android.permission.MANAGE_USB" /> <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" /> <uses-permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY" /> + <uses-permission android:name="android.permission.TETHER_PRIVILEGED" /> <uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" /> <uses-permission android:name="android.permission.WRITE_SETTINGS" /> diff --git a/packages/Tethering/common/TetheringLib/Android.bp b/packages/Tethering/common/TetheringLib/Android.bp index 5785707cb9c5..264ce440f59f 100644 --- a/packages/Tethering/common/TetheringLib/Android.bp +++ b/packages/Tethering/common/TetheringLib/Android.bp @@ -47,6 +47,16 @@ java_library { libs: [ "android_system_stubs_current", ], + + hostdex: true, // for hiddenapi check + visibility: [ + "//frameworks/base/packages/Tethering:__subpackages__", + //TODO(b/147200698) remove below lines when the platform is built with stubs + "//frameworks/base", + "//frameworks/base/services", + "//frameworks/base/services/core", + ], + apex_available: ["com.android.tethering"], } filegroup { diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java index a49ab85d2faf..11e57186c666 100644 --- a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java +++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java @@ -15,8 +15,6 @@ */ package android.net; -import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR; - import android.annotation.NonNull; import android.content.Context; import android.net.ConnectivityManager.OnTetheringEventCallback; @@ -52,6 +50,103 @@ public class TetheringManager { private TetheringConfigurationParcel mTetheringConfiguration; private TetherStatesParcel mTetherStatesParcel; + /** + * Broadcast Action: A tetherable connection has come or gone. + * Uses {@code TetheringManager.EXTRA_AVAILABLE_TETHER}, + * {@code TetheringManager.EXTRA_ACTIVE_LOCAL_ONLY}, + * {@code TetheringManager.EXTRA_ACTIVE_TETHER}, and + * {@code TetheringManager.EXTRA_ERRORED_TETHER} to indicate + * the current state of tethering. Each include a list of + * interface names in that state (may be empty). + */ + public static final String ACTION_TETHER_STATE_CHANGED = + "android.net.conn.TETHER_STATE_CHANGED"; + + /** + * gives a String[] listing all the interfaces configured for + * tethering and currently available for tethering. + */ + public static final String EXTRA_AVAILABLE_TETHER = "availableArray"; + + /** + * gives a String[] listing all the interfaces currently in local-only + * mode (ie, has DHCPv4+IPv6-ULA support and no packet forwarding) + */ + public static final String EXTRA_ACTIVE_LOCAL_ONLY = "localOnlyArray"; + + /** + * gives a String[] listing all the interfaces currently tethered + * (ie, has DHCPv4 support and packets potentially forwarded/NATed) + */ + public static final String EXTRA_ACTIVE_TETHER = "tetherArray"; + + /** + * gives a String[] listing all the interfaces we tried to tether and + * failed. Use {@link #getLastTetherError} to find the error code + * for any interfaces listed here. + */ + public static final String EXTRA_ERRORED_TETHER = "erroredArray"; + + /** + * Invalid tethering type. + * @see #startTethering. + */ + public static final int TETHERING_INVALID = -1; + + /** + * Wifi tethering type. + * @see #startTethering. + */ + public static final int TETHERING_WIFI = 0; + + /** + * USB tethering type. + * @see #startTethering. + */ + public static final int TETHERING_USB = 1; + + /** + * Bluetooth tethering type. + * @see #startTethering. + */ + public static final int TETHERING_BLUETOOTH = 2; + + /** + * Wifi P2p tethering type. + * Wifi P2p tethering is set through events automatically, and don't + * need to start from #startTethering. + */ + public static final int TETHERING_WIFI_P2P = 3; + + /** + * Extra used for communicating with the TetherService. Includes the type of tethering to + * enable if any. + */ + public static final String EXTRA_ADD_TETHER_TYPE = "extraAddTetherType"; + + /** + * Extra used for communicating with the TetherService. Includes the type of tethering for + * which to cancel provisioning. + */ + public static final String EXTRA_REM_TETHER_TYPE = "extraRemTetherType"; + + /** + * Extra used for communicating with the TetherService. True to schedule a recheck of tether + * provisioning. + */ + public static final String EXTRA_SET_ALARM = "extraSetAlarm"; + + /** + * Tells the TetherService to run a provision check now. + */ + public static final String EXTRA_RUN_PROVISION = "extraRunProvision"; + + /** + * Extra used for communicating with the TetherService. Contains the {@link ResultReceiver} + * which will receive provisioning results. Can be left empty. + */ + public static final String EXTRA_PROVISION_CALLBACK = "extraProvisionCallback"; + public static final int TETHER_ERROR_NO_ERROR = 0; public static final int TETHER_ERROR_UNKNOWN_IFACE = 1; public static final int TETHER_ERROR_SERVICE_UNAVAIL = 2; @@ -470,7 +565,7 @@ public class TetheringManager { * failed. Re-attempting to tether may cause them to reset to the Tethered * state. Alternatively, causing the interface to be destroyed and recreated * may cause them to reset to the available state. - * {@link ConnectivityManager#getLastTetherError} can be used to get more + * {@link TetheringManager#getLastTetherError} can be used to get more * information on the cause of the errors. * * @return an array of 0 or more String indicating the interface names diff --git a/packages/Tethering/jarjar-rules.txt b/packages/Tethering/jarjar-rules.txt index dd9eab74e851..c6efa41e580a 100644 --- a/packages/Tethering/jarjar-rules.txt +++ b/packages/Tethering/jarjar-rules.txt @@ -11,5 +11,8 @@ rule com.android.internal.util.MessageUtils* com.android.networkstack.tethering. rule com.android.internal.util.Preconditions* com.android.networkstack.tethering.util.Preconditions@1 rule com.android.internal.util.State* com.android.networkstack.tethering.util.State@1 rule com.android.internal.util.StateMachine* com.android.networkstack.tethering.util.StateMachine@1 +rule com.android.internal.util.TrafficStatsConstants* com.android.networkstack.tethering.util.TrafficStatsConstants@1 rule android.net.LocalLog* com.android.networkstack.tethering.LocalLog@1 + +rule android.net.shared.Inet4AddressUtils* com.android.networkstack.tethering.shared.Inet4AddressUtils@1 diff --git a/packages/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java b/packages/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java index 1fe2328f1cdb..d6bc063210b3 100644 --- a/packages/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java +++ b/packages/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java @@ -20,11 +20,11 @@ import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTH; import android.annotation.NonNull; import android.net.LinkAddress; - -import com.google.android.collect.Sets; +import android.util.ArraySet; import java.net.Inet4Address; import java.util.Collection; +import java.util.Collections; import java.util.Set; /** @@ -68,7 +68,7 @@ public class DhcpServingParamsParcelExt extends DhcpServingParamsParcel { * but it must always be set explicitly. */ public DhcpServingParamsParcelExt setDefaultRouters(@NonNull Inet4Address... defaultRouters) { - return setDefaultRouters(Sets.newArraySet(defaultRouters)); + return setDefaultRouters(newArraySet(defaultRouters)); } /** @@ -96,7 +96,7 @@ public class DhcpServingParamsParcelExt extends DhcpServingParamsParcel { * <p>This may be an empty list of servers, but it must always be set explicitly. */ public DhcpServingParamsParcelExt setDnsServers(@NonNull Inet4Address... dnsServers) { - return setDnsServers(Sets.newArraySet(dnsServers)); + return setDnsServers(newArraySet(dnsServers)); } /** @@ -126,7 +126,7 @@ public class DhcpServingParamsParcelExt extends DhcpServingParamsParcel { * and do not need to be set here. */ public DhcpServingParamsParcelExt setExcludedAddrs(@NonNull Inet4Address... excludedAddrs) { - return setExcludedAddrs(Sets.newArraySet(excludedAddrs)); + return setExcludedAddrs(newArraySet(excludedAddrs)); } /** @@ -169,4 +169,10 @@ public class DhcpServingParamsParcelExt extends DhcpServingParamsParcel { } return res; } + + private static ArraySet<Inet4Address> newArraySet(Inet4Address... addrs) { + ArraySet<Inet4Address> addrSet = new ArraySet<>(addrs.length); + Collections.addAll(addrSet, addrs); + return addrSet; + } } diff --git a/packages/Tethering/src/android/net/ip/IpServer.java b/packages/Tethering/src/android/net/ip/IpServer.java index 8fde52040ecc..6ac467e39a9d 100644 --- a/packages/Tethering/src/android/net/ip/IpServer.java +++ b/packages/Tethering/src/android/net/ip/IpServer.java @@ -17,40 +17,39 @@ package android.net.ip; import static android.net.InetAddresses.parseNumericAddress; +import static android.net.RouteInfo.RTN_UNICAST; import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS; import static android.net.util.NetworkConstants.FF; import static android.net.util.NetworkConstants.RFC7421_PREFIX_LENGTH; import static android.net.util.NetworkConstants.asByte; +import static android.net.util.TetheringMessageBase.BASE_IPSERVER; -import android.net.ConnectivityManager; import android.net.INetd; import android.net.INetworkStackStatusCallback; import android.net.INetworkStatsService; -import android.net.InterfaceConfiguration; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties; import android.net.RouteInfo; +import android.net.TetheringManager; import android.net.dhcp.DhcpServerCallbacks; import android.net.dhcp.DhcpServingParamsParcel; import android.net.dhcp.DhcpServingParamsParcelExt; import android.net.dhcp.IDhcpServer; import android.net.ip.RouterAdvertisementDaemon.RaParams; +import android.net.shared.NetdUtils; +import android.net.shared.RouteUtils; import android.net.util.InterfaceParams; import android.net.util.InterfaceSet; -import android.net.util.NetdService; import android.net.util.SharedLog; -import android.os.INetworkManagementService; import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.os.ServiceSpecificException; import android.util.Log; -import android.util.Slog; import android.util.SparseArray; import com.android.internal.util.MessageUtils; -import com.android.internal.util.Protocol; import com.android.internal.util.State; import com.android.internal.util.StateMachine; @@ -119,7 +118,7 @@ public class IpServer extends StateMachine { * * @param who the calling instance of IpServer * @param state one of STATE_* - * @param lastError one of ConnectivityManager.TETHER_ERROR_* + * @param lastError one of TetheringManager.TETHER_ERROR_* */ public void updateInterfaceState(IpServer who, int state, int lastError) { } @@ -144,36 +143,31 @@ public class IpServer extends StateMachine { return InterfaceParams.getByName(ifName); } - public INetd getNetdService() { - return NetdService.getInstance(); - } - /** Create a DhcpServer instance to be used by IpServer. */ public abstract void makeDhcpServer(String ifName, DhcpServingParamsParcel params, DhcpServerCallbacks cb); } - private static final int BASE_IFACE = Protocol.BASE_TETHERING + 100; // request from the user that it wants to tether - public static final int CMD_TETHER_REQUESTED = BASE_IFACE + 2; + public static final int CMD_TETHER_REQUESTED = BASE_IPSERVER + 1; // request from the user that it wants to untether - public static final int CMD_TETHER_UNREQUESTED = BASE_IFACE + 3; + public static final int CMD_TETHER_UNREQUESTED = BASE_IPSERVER + 2; // notification that this interface is down - public static final int CMD_INTERFACE_DOWN = BASE_IFACE + 4; + public static final int CMD_INTERFACE_DOWN = BASE_IPSERVER + 3; // notification from the master SM that it had trouble enabling IP Forwarding - public static final int CMD_IP_FORWARDING_ENABLE_ERROR = BASE_IFACE + 7; + public static final int CMD_IP_FORWARDING_ENABLE_ERROR = BASE_IPSERVER + 4; // notification from the master SM that it had trouble disabling IP Forwarding - public static final int CMD_IP_FORWARDING_DISABLE_ERROR = BASE_IFACE + 8; + public static final int CMD_IP_FORWARDING_DISABLE_ERROR = BASE_IPSERVER + 5; // notification from the master SM that it had trouble starting tethering - public static final int CMD_START_TETHERING_ERROR = BASE_IFACE + 9; + public static final int CMD_START_TETHERING_ERROR = BASE_IPSERVER + 6; // notification from the master SM that it had trouble stopping tethering - public static final int CMD_STOP_TETHERING_ERROR = BASE_IFACE + 10; + public static final int CMD_STOP_TETHERING_ERROR = BASE_IPSERVER + 7; // notification from the master SM that it had trouble setting the DNS forwarders - public static final int CMD_SET_DNS_FORWARDERS_ERROR = BASE_IFACE + 11; + public static final int CMD_SET_DNS_FORWARDERS_ERROR = BASE_IPSERVER + 8; // the upstream connection has changed - public static final int CMD_TETHER_CONNECTION_CHANGED = BASE_IFACE + 12; + public static final int CMD_TETHER_CONNECTION_CHANGED = BASE_IPSERVER + 9; // new IPv6 tethering parameters need to be processed - public static final int CMD_IPV6_TETHER_UPDATE = BASE_IFACE + 13; + public static final int CMD_IPV6_TETHER_UPDATE = BASE_IPSERVER + 10; private final State mInitialState; private final State mLocalHotspotState; @@ -181,7 +175,6 @@ public class IpServer extends StateMachine { private final State mUnavailableState; private final SharedLog mLog; - private final INetworkManagementService mNMService; private final INetd mNetd; private final INetworkStatsService mStatsService; private final Callback mCallback; @@ -211,15 +204,15 @@ public class IpServer extends StateMachine { private int mDhcpServerStartIndex = 0; private IDhcpServer mDhcpServer; private RaParams mLastRaParams; + private LinkAddress mIpv4Address; public IpServer( String ifaceName, Looper looper, int interfaceType, SharedLog log, - INetworkManagementService nMService, INetworkStatsService statsService, - Callback callback, boolean usingLegacyDhcp, Dependencies deps) { + INetd netd, INetworkStatsService statsService, Callback callback, + boolean usingLegacyDhcp, Dependencies deps) { super(ifaceName, looper); mLog = log.forSubComponent(ifaceName); - mNMService = nMService; - mNetd = deps.getNetdService(); + mNetd = netd; mStatsService = statsService; mCallback = callback; mInterfaceCtrl = new InterfaceController(ifaceName, mNetd, mLog); @@ -229,7 +222,7 @@ public class IpServer extends StateMachine { mUsingLegacyDhcp = usingLegacyDhcp; mDeps = deps; resetLinkProperties(); - mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR; + mLastError = TetheringManager.TETHER_ERROR_NO_ERROR; mServingMode = STATE_AVAILABLE; mInitialState = new InitialState(); @@ -250,7 +243,7 @@ public class IpServer extends StateMachine { } /** - * Tethering downstream type. It would be one of ConnectivityManager#TETHERING_*. + * Tethering downstream type. It would be one of TetheringManager#TETHERING_*. */ public int interfaceType() { return mInterfaceType; @@ -348,13 +341,13 @@ public class IpServer extends StateMachine { } }); } catch (RemoteException e) { - e.rethrowFromSystemServer(); + throw new IllegalStateException(e); } }); } private void handleError() { - mLastError = ConnectivityManager.TETHER_ERROR_DHCPSERVER_ERROR; + mLastError = TetheringManager.TETHER_ERROR_DHCPSERVER_ERROR; transitionTo(mInitialState); } } @@ -389,14 +382,15 @@ public class IpServer extends StateMachine { public void callback(int statusCode) { if (statusCode != STATUS_SUCCESS) { mLog.e("Error stopping DHCP server: " + statusCode); - mLastError = ConnectivityManager.TETHER_ERROR_DHCPSERVER_ERROR; + mLastError = TetheringManager.TETHER_ERROR_DHCPSERVER_ERROR; // Not much more we can do here } } }); mDhcpServer = null; } catch (RemoteException e) { - e.rethrowFromSystemServer(); + mLog.e("Error stopping DHCP", e); + // Not much more we can do here } } } @@ -415,83 +409,69 @@ public class IpServer extends StateMachine { // NOTE: All of configureIPv4() will be refactored out of existence // into calls to InterfaceController, shared with startIPv4(). mInterfaceCtrl.clearIPv4Address(); + mIpv4Address = null; } - // TODO: Refactor this in terms of calls to InterfaceController. private boolean configureIPv4(boolean enabled) { if (VDBG) Log.d(TAG, "configureIPv4(" + enabled + ")"); // TODO: Replace this hard-coded information with dynamically selected // config passed down to us by a higher layer IP-coordinating element. - String ipAsString = null; + final Inet4Address srvAddr; int prefixLen = 0; - if (mInterfaceType == ConnectivityManager.TETHERING_USB) { - ipAsString = USB_NEAR_IFACE_ADDR; - prefixLen = USB_PREFIX_LENGTH; - } else if (mInterfaceType == ConnectivityManager.TETHERING_WIFI) { - ipAsString = getRandomWifiIPv4Address(); - prefixLen = WIFI_HOST_IFACE_PREFIX_LENGTH; - } else if (mInterfaceType == ConnectivityManager.TETHERING_WIFI_P2P) { - ipAsString = WIFI_P2P_IFACE_ADDR; - prefixLen = WIFI_P2P_IFACE_PREFIX_LENGTH; - } else { - // BT configures the interface elsewhere: only start DHCP. - final Inet4Address srvAddr = (Inet4Address) parseNumericAddress(BLUETOOTH_IFACE_ADDR); - return configureDhcp(enabled, srvAddr, BLUETOOTH_DHCP_PREFIX_LENGTH); - } - - final LinkAddress linkAddr; try { - final InterfaceConfiguration ifcg = mNMService.getInterfaceConfig(mIfaceName); - if (ifcg == null) { - mLog.e("Received null interface config"); - return false; - } - - InetAddress addr = parseNumericAddress(ipAsString); - linkAddr = new LinkAddress(addr, prefixLen); - ifcg.setLinkAddress(linkAddr); - if (mInterfaceType == ConnectivityManager.TETHERING_WIFI) { - // The WiFi stack has ownership of the interface up/down state. - // It is unclear whether the Bluetooth or USB stacks will manage their own - // state. - ifcg.ignoreInterfaceUpDownStatus(); + if (mInterfaceType == TetheringManager.TETHERING_USB) { + srvAddr = (Inet4Address) parseNumericAddress(USB_NEAR_IFACE_ADDR); + prefixLen = USB_PREFIX_LENGTH; + } else if (mInterfaceType == TetheringManager.TETHERING_WIFI) { + srvAddr = (Inet4Address) parseNumericAddress(getRandomWifiIPv4Address()); + prefixLen = WIFI_HOST_IFACE_PREFIX_LENGTH; + } else if (mInterfaceType == TetheringManager.TETHERING_WIFI_P2P) { + srvAddr = (Inet4Address) parseNumericAddress(WIFI_P2P_IFACE_ADDR); + prefixLen = WIFI_P2P_IFACE_PREFIX_LENGTH; } else { - if (enabled) { - ifcg.setInterfaceUp(); - } else { - ifcg.setInterfaceDown(); - } + // BT configures the interface elsewhere: only start DHCP. + // TODO: make all tethering types behave the same way, and delete the bluetooth + // code that calls into NetworkManagementService directly. + srvAddr = (Inet4Address) parseNumericAddress(BLUETOOTH_IFACE_ADDR); + mIpv4Address = new LinkAddress(srvAddr, BLUETOOTH_DHCP_PREFIX_LENGTH); + return configureDhcp(enabled, srvAddr, BLUETOOTH_DHCP_PREFIX_LENGTH); } - ifcg.clearFlag("running"); + mIpv4Address = new LinkAddress(srvAddr, prefixLen); + } catch (IllegalArgumentException e) { + mLog.e("Error selecting ipv4 address", e); + if (!enabled) stopDhcp(); + return false; + } - // TODO: this may throw if the interface is already gone. Do proper handling and - // simplify the DHCP server start/stop. - mNMService.setInterfaceConfig(mIfaceName, ifcg); + final Boolean setIfaceUp; + if (mInterfaceType == TetheringManager.TETHERING_WIFI) { + // The WiFi stack has ownership of the interface up/down state. + // It is unclear whether the Bluetooth or USB stacks will manage their own + // state. + setIfaceUp = null; + } else { + setIfaceUp = enabled; + } + if (!mInterfaceCtrl.setInterfaceConfiguration(mIpv4Address, setIfaceUp)) { + mLog.e("Error configuring interface"); + if (!enabled) stopDhcp(); + return false; + } - if (!configureDhcp(enabled, (Inet4Address) addr, prefixLen)) { - return false; - } - } catch (Exception e) { - mLog.e("Error configuring interface " + e); - if (!enabled) { - try { - // Calling stopDhcp several times is fine - stopDhcp(); - } catch (Exception dhcpError) { - mLog.e("Error stopping DHCP", dhcpError); - } - } + if (!configureDhcp(enabled, srvAddr, prefixLen)) { return false; } // Directly-connected route. - final RouteInfo route = new RouteInfo(linkAddr); + final IpPrefix ipv4Prefix = new IpPrefix(mIpv4Address.getAddress(), + mIpv4Address.getPrefixLength()); + final RouteInfo route = new RouteInfo(ipv4Prefix, null, null, RTN_UNICAST); if (enabled) { - mLinkProperties.addLinkAddress(linkAddr); + mLinkProperties.addLinkAddress(mIpv4Address); mLinkProperties.addRoute(route); } else { - mLinkProperties.removeLinkAddress(linkAddr); + mLinkProperties.removeLinkAddress(mIpv4Address); mLinkProperties.removeRoute(route); } return true; @@ -583,14 +563,12 @@ public class IpServer extends StateMachine { if (!deprecatedPrefixes.isEmpty()) { final ArrayList<RouteInfo> toBeRemoved = getLocalRoutesFor(mIfaceName, deprecatedPrefixes); - try { - final int removalFailures = mNMService.removeRoutesFromLocalNetwork(toBeRemoved); - if (removalFailures > 0) { - mLog.e(String.format("Failed to remove %d IPv6 routes from local table.", - removalFailures)); - } - } catch (RemoteException e) { - mLog.e("Failed to remove IPv6 routes from local table: " + e); + // Remove routes from local network. + final int removalFailures = RouteUtils.removeRoutesFromLocalNetwork( + mNetd, toBeRemoved); + if (removalFailures > 0) { + mLog.e(String.format("Failed to remove %d IPv6 routes from local table.", + removalFailures)); } for (RouteInfo route : toBeRemoved) mLinkProperties.removeRoute(route); @@ -607,13 +585,18 @@ public class IpServer extends StateMachine { final ArrayList<RouteInfo> toBeAdded = getLocalRoutesFor(mIfaceName, addedPrefixes); try { - // It's safe to call addInterfaceToLocalNetwork() even if - // the interface is already in the local_network. Note also - // that adding routes that already exist does not cause an - // error (EEXIST is silently ignored). - mNMService.addInterfaceToLocalNetwork(mIfaceName, toBeAdded); - } catch (Exception e) { - mLog.e("Failed to add IPv6 routes to local table: " + e); + // It's safe to call networkAddInterface() even if + // the interface is already in the local_network. + mNetd.networkAddInterface(INetd.LOCAL_NET_ID, mIfaceName); + try { + // Add routes from local network. Note that adding routes that + // already exist does not cause an error (EEXIST is silently ignored). + RouteUtils.addRoutesToLocalNetwork(mNetd, mIfaceName, toBeAdded); + } catch (IllegalStateException e) { + mLog.e("Failed to add IPv6 routes to local table: " + e); + } + } catch (ServiceSpecificException | RemoteException e) { + mLog.e("Failed to add " + mIfaceName + " to local table: ", e); } for (RouteInfo route : toBeAdded) mLinkProperties.addRoute(route); @@ -727,7 +710,7 @@ public class IpServer extends StateMachine { logMessage(this, message.what); switch (message.what) { case CMD_TETHER_REQUESTED: - mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR; + mLastError = TetheringManager.TETHER_ERROR_NO_ERROR; switch (message.arg1) { case STATE_LOCAL_ONLY: transitionTo(mLocalHotspotState); @@ -756,15 +739,17 @@ public class IpServer extends StateMachine { @Override public void enter() { if (!startIPv4()) { - mLastError = ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR; + mLastError = TetheringManager.TETHER_ERROR_IFACE_CFG_ERROR; return; } try { - mNMService.tetherInterface(mIfaceName); - } catch (Exception e) { + final IpPrefix ipv4Prefix = new IpPrefix(mIpv4Address.getAddress(), + mIpv4Address.getPrefixLength()); + NetdUtils.tetherInterface(mNetd, mIfaceName, ipv4Prefix); + } catch (RemoteException | ServiceSpecificException e) { mLog.e("Error Tethering: " + e); - mLastError = ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR; + mLastError = TetheringManager.TETHER_ERROR_TETHER_IFACE_ERROR; return; } @@ -783,9 +768,9 @@ public class IpServer extends StateMachine { stopIPv6(); try { - mNMService.untetherInterface(mIfaceName); - } catch (Exception e) { - mLastError = ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR; + NetdUtils.untetherInterface(mNetd, mIfaceName); + } catch (RemoteException | ServiceSpecificException e) { + mLastError = TetheringManager.TETHER_ERROR_UNTETHER_IFACE_ERROR; mLog.e("Failed to untether interface: " + e); } @@ -815,7 +800,7 @@ public class IpServer extends StateMachine { case CMD_START_TETHERING_ERROR: case CMD_STOP_TETHERING_ERROR: case CMD_SET_DNS_FORWARDERS_ERROR: - mLastError = ConnectivityManager.TETHER_ERROR_MASTER_ERROR; + mLastError = TetheringManager.TETHER_ERROR_MASTER_ERROR; transitionTo(mInitialState); break; default: @@ -834,7 +819,7 @@ public class IpServer extends StateMachine { @Override public void enter() { super.enter(); - if (mLastError != ConnectivityManager.TETHER_ERROR_NO_ERROR) { + if (mLastError != TetheringManager.TETHER_ERROR_NO_ERROR) { transitionTo(mInitialState); } @@ -870,7 +855,7 @@ public class IpServer extends StateMachine { @Override public void enter() { super.enter(); - if (mLastError != ConnectivityManager.TETHER_ERROR_NO_ERROR) { + if (mLastError != TetheringManager.TETHER_ERROR_NO_ERROR) { transitionTo(mInitialState); } @@ -900,17 +885,17 @@ public class IpServer extends StateMachine { // About to tear down NAT; gather remaining statistics. mStatsService.forceUpdate(); } catch (Exception e) { - if (VDBG) Log.e(TAG, "Exception in forceUpdate: " + e.toString()); + mLog.e("Exception in forceUpdate: " + e.toString()); } try { - mNMService.stopInterfaceForwarding(mIfaceName, upstreamIface); - } catch (Exception e) { - if (VDBG) Log.e(TAG, "Exception in removeInterfaceForward: " + e.toString()); + mNetd.ipfwdRemoveInterfaceForward(mIfaceName, upstreamIface); + } catch (RemoteException | ServiceSpecificException e) { + mLog.e("Exception in ipfwdRemoveInterfaceForward: " + e.toString()); } try { - mNMService.disableNat(mIfaceName, upstreamIface); - } catch (Exception e) { - if (VDBG) Log.e(TAG, "Exception in disableNat: " + e.toString()); + mNetd.tetherRemoveForward(mIfaceName, upstreamIface); + } catch (RemoteException | ServiceSpecificException e) { + mLog.e("Exception in disableNat: " + e.toString()); } } @@ -946,12 +931,12 @@ public class IpServer extends StateMachine { for (String ifname : added) { try { - mNMService.enableNat(mIfaceName, ifname); - mNMService.startInterfaceForwarding(mIfaceName, ifname); - } catch (Exception e) { - mLog.e("Exception enabling NAT: " + e); + mNetd.tetherAddForward(mIfaceName, ifname); + mNetd.ipfwdAddInterfaceForward(mIfaceName, ifname); + } catch (RemoteException | ServiceSpecificException e) { + mLog.e("Exception enabling NAT: " + e.toString()); cleanupUpstream(); - mLastError = ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR; + mLastError = TetheringManager.TETHER_ERROR_ENABLE_NAT_ERROR; transitionTo(mInitialState); return true; } @@ -996,7 +981,7 @@ public class IpServer extends StateMachine { class UnavailableState extends State { @Override public void enter() { - mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR; + mLastError = TetheringManager.TETHER_ERROR_NO_ERROR; sendInterfaceState(STATE_UNAVAILABLE); } } @@ -1007,7 +992,7 @@ public class IpServer extends StateMachine { String ifname, HashSet<IpPrefix> prefixes) { final ArrayList<RouteInfo> localRoutes = new ArrayList<RouteInfo>(); for (IpPrefix ipp : prefixes) { - localRoutes.add(new RouteInfo(ipp, null, ifname)); + localRoutes.add(new RouteInfo(ipp, null, ifname, RTN_UNICAST)); } return localRoutes; } @@ -1019,7 +1004,7 @@ public class IpServer extends StateMachine { try { return Inet6Address.getByAddress(null, dnsBytes, 0); } catch (UnknownHostException e) { - Slog.wtf(TAG, "Failed to construct Inet6Address from: " + localPrefix); + Log.wtf(TAG, "Failed to construct Inet6Address from: " + localPrefix); return null; } } diff --git a/packages/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java b/packages/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java index 4396cdb92621..6f017dcb623f 100644 --- a/packages/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java +++ b/packages/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java @@ -22,14 +22,13 @@ import static android.system.OsConstants.AF_INET6; import static android.system.OsConstants.IPPROTO_ICMPV6; import static android.system.OsConstants.SOCK_RAW; import static android.system.OsConstants.SOL_SOCKET; -import static android.system.OsConstants.SO_BINDTODEVICE; import static android.system.OsConstants.SO_SNDTIMEO; import android.net.IpPrefix; import android.net.LinkAddress; -import android.net.NetworkUtils; import android.net.TrafficStats; import android.net.util.InterfaceParams; +import android.net.util.SocketUtils; import android.net.util.TetheringUtils; import android.system.ErrnoException; import android.system.Os; @@ -39,8 +38,6 @@ import android.util.Log; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.TrafficStatsConstants; -import libcore.io.IoBridge; - import java.io.FileDescriptor; import java.io.IOException; import java.net.Inet6Address; @@ -612,8 +609,7 @@ public class RouterAdvertisementDaemon { // Setting SNDTIMEO is purely for defensive purposes. Os.setsockoptTimeval( mSocket, SOL_SOCKET, SO_SNDTIMEO, StructTimeval.fromMillis(send_timout_ms)); - Os.setsockoptIfreq(mSocket, SOL_SOCKET, SO_BINDTODEVICE, mInterface.name); - NetworkUtils.protectFromVpn(mSocket); + SocketUtils.bindSocketToInterface(mSocket, mInterface.name); TetheringUtils.setupRaSocket(mSocket, mInterface.index); } catch (ErrnoException | IOException e) { Log.e(TAG, "Failed to create RA daemon socket: " + e); @@ -628,7 +624,7 @@ public class RouterAdvertisementDaemon { private void closeSocket() { if (mSocket != null) { try { - IoBridge.closeAndSignalBlockedThreads(mSocket); + SocketUtils.closeSocket(mSocket); } catch (IOException ignored) { } } mSocket = null; @@ -672,7 +668,7 @@ public class RouterAdvertisementDaemon { } private final class UnicastResponder extends Thread { - private final InetSocketAddress mSolicitor = new InetSocketAddress(); + private final InetSocketAddress mSolicitor = new InetSocketAddress(0); // The recycled buffer for receiving Router Solicitations from clients. // If the RS is larger than IPV6_MIN_MTU the packets are truncated. // This is fine since currently only byte 0 is examined anyway. diff --git a/packages/Tethering/src/android/net/util/TetheringMessageBase.java b/packages/Tethering/src/android/net/util/TetheringMessageBase.java new file mode 100644 index 000000000000..1b763ce920da --- /dev/null +++ b/packages/Tethering/src/android/net/util/TetheringMessageBase.java @@ -0,0 +1,25 @@ +/* + * 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.net.util; + +/** + * This class defines Message.what base addresses for various state machine. + */ +public class TetheringMessageBase { + public static final int BASE_MASTER = 0; + public static final int BASE_IPSERVER = 100; + +} diff --git a/packages/Tethering/src/android/net/util/TetheringUtils.java b/packages/Tethering/src/android/net/util/TetheringUtils.java index 2fb73ced8884..fa543bdce735 100644 --- a/packages/Tethering/src/android/net/util/TetheringUtils.java +++ b/packages/Tethering/src/android/net/util/TetheringUtils.java @@ -39,4 +39,11 @@ public class TetheringUtils { */ public static native void setupRaSocket(FileDescriptor fd, int ifIndex) throws SocketException; + + /** + * Read s as an unsigned 16-bit integer. + */ + public static int uint16(short s) { + return s & 0xffff; + } } diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java b/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java index ba5d08dce4ca..4e2a2c1c7af7 100644 --- a/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java +++ b/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java @@ -16,16 +16,16 @@ package com.android.server.connectivity.tethering; -import static android.net.ConnectivityManager.EXTRA_ADD_TETHER_TYPE; -import static android.net.ConnectivityManager.EXTRA_PROVISION_CALLBACK; -import static android.net.ConnectivityManager.EXTRA_RUN_PROVISION; -import static android.net.ConnectivityManager.TETHERING_BLUETOOTH; -import static android.net.ConnectivityManager.TETHERING_INVALID; -import static android.net.ConnectivityManager.TETHERING_USB; -import static android.net.ConnectivityManager.TETHERING_WIFI; -import static android.net.ConnectivityManager.TETHER_ERROR_ENTITLEMENT_UNKONWN; -import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR; -import static android.net.ConnectivityManager.TETHER_ERROR_PROVISION_FAILED; +import static android.net.TetheringManager.EXTRA_ADD_TETHER_TYPE; +import static android.net.TetheringManager.EXTRA_PROVISION_CALLBACK; +import static android.net.TetheringManager.EXTRA_RUN_PROVISION; +import static android.net.TetheringManager.TETHERING_BLUETOOTH; +import static android.net.TetheringManager.TETHERING_INVALID; +import static android.net.TetheringManager.TETHERING_USB; +import static android.net.TetheringManager.TETHERING_WIFI; +import static android.net.TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKONWN; +import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR; +import static android.net.TetheringManager.TETHER_ERROR_PROVISION_FAILED; import static com.android.internal.R.string.config_wifi_tether_enable; @@ -38,7 +38,6 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.res.Resources; import android.net.util.SharedLog; -import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -48,7 +47,6 @@ import android.os.PersistableBundle; import android.os.ResultReceiver; import android.os.SystemClock; import android.os.SystemProperties; -import android.os.UserHandle; import android.provider.Settings; import android.telephony.CarrierConfigManager; import android.util.ArraySet; @@ -61,7 +59,7 @@ import java.io.PrintWriter; /** * Re-check tethering provisioning for enabled downstream tether types. - * Reference ConnectivityManager.TETHERING_{@code *} for each tether type. + * Reference TetheringManager.TETHERING_{@code *} for each tether type. * * All methods of this class must be accessed from the thread of tethering * state machine. @@ -88,9 +86,9 @@ public class EntitlementManager { private static final int EVENT_GET_ENTITLEMENT_VALUE = 4; // The ArraySet contains enabled downstream types, ex: - // {@link ConnectivityManager.TETHERING_WIFI} - // {@link ConnectivityManager.TETHERING_USB} - // {@link ConnectivityManager.TETHERING_BLUETOOTH} + // {@link TetheringManager.TETHERING_WIFI} + // {@link TetheringManager.TETHERING_USB} + // {@link TetheringManager.TETHERING_BLUETOOTH} private final ArraySet<Integer> mCurrentTethers; private final Context mContext; private final int mPermissionChangeMessageCode; @@ -98,8 +96,8 @@ public class EntitlementManager { private final SparseIntArray mEntitlementCacheValue; private final EntitlementHandler mHandler; private final StateMachine mTetherMasterSM; - // Key: ConnectivityManager.TETHERING_*(downstream). - // Value: ConnectivityManager.TETHER_ERROR_{NO_ERROR or PROVISION_FAILED}(provisioning result). + // Key: TetheringManager.TETHERING_*(downstream). + // Value: TetheringManager.TETHER_ERROR_{NO_ERROR or PROVISION_FAILED}(provisioning result). private final SparseIntArray mCellularPermitted; private PendingIntent mProvisioningRecheckAlarm; private boolean mCellularUpstreamPermitted = true; @@ -135,7 +133,7 @@ public class EntitlementManager { /** * Ui entitlement check fails in |downstream|. * - * @param downstream tethering type from ConnectivityManager.TETHERING_{@code *}. + * @param downstream tethering type from TetheringManager.TETHERING_{@code *}. */ void onUiEntitlementFailed(int downstream); } @@ -171,7 +169,7 @@ public class EntitlementManager { * This is called when tethering starts. * Launch provisioning app if upstream is cellular. * - * @param downstreamType tethering type from ConnectivityManager.TETHERING_{@code *} + * @param downstreamType tethering type from TetheringManager.TETHERING_{@code *} * @param showProvisioningUi a boolean indicating whether to show the * provisioning app UI if there is one. */ @@ -196,9 +194,9 @@ public class EntitlementManager { // till upstream change to cellular. if (mUsingCellularAsUpstream) { if (showProvisioningUi) { - runUiTetherProvisioning(type, config.subId); + runUiTetherProvisioning(type, config.activeDataSubId); } else { - runSilentTetherProvisioning(type, config.subId); + runSilentTetherProvisioning(type, config.activeDataSubId); } mNeedReRunProvisioningUi = false; } else { @@ -212,7 +210,7 @@ public class EntitlementManager { /** * Tell EntitlementManager that a given type of tethering has been disabled * - * @param type tethering type from ConnectivityManager.TETHERING_{@code *} + * @param type tethering type from TetheringManager.TETHERING_{@code *} */ public void stopProvisioningIfNeeded(int type) { mHandler.sendMessage(mHandler.obtainMessage(EVENT_STOP_PROVISIONING, type, 0)); @@ -270,9 +268,9 @@ public class EntitlementManager { if (mCellularPermitted.indexOfKey(downstream) < 0) { if (mNeedReRunProvisioningUi) { mNeedReRunProvisioningUi = false; - runUiTetherProvisioning(downstream, config.subId); + runUiTetherProvisioning(downstream, config.activeDataSubId); } else { - runSilentTetherProvisioning(downstream, config.subId); + runSilentTetherProvisioning(downstream, config.activeDataSubId); } } } @@ -298,7 +296,7 @@ public class EntitlementManager { /** * Re-check tethering provisioning for all enabled tether types. - * Reference ConnectivityManager.TETHERING_{@code *} for each tether type. + * Reference TetheringManager.TETHERING_{@code *} for each tether type. * * @param config an object that encapsulates the various tethering configuration elements. * Note: this method is only called from TetherMaster on the handler thread. @@ -336,7 +334,8 @@ public class EntitlementManager { .getSystemService(Context.CARRIER_CONFIG_SERVICE); if (configManager == null) return null; - final PersistableBundle carrierConfig = configManager.getConfigForSubId(config.subId); + final PersistableBundle carrierConfig = configManager.getConfigForSubId( + config.activeDataSubId); if (CarrierConfigManager.isConfigForIdentifiedCarrier(carrierConfig)) { return carrierConfig; @@ -364,7 +363,7 @@ public class EntitlementManager { /** * Run no UI tethering provisioning check. - * @param type tethering type from ConnectivityManager.TETHERING_{@code *} + * @param type tethering type from TetheringManager.TETHERING_{@code *} * @param subId default data subscription ID. */ @VisibleForTesting @@ -379,12 +378,9 @@ public class EntitlementManager { intent.putExtra(EXTRA_PROVISION_CALLBACK, receiver); intent.putExtra(EXTRA_SUBID, subId); intent.setComponent(TETHER_SERVICE); - final long ident = Binder.clearCallingIdentity(); - try { - mContext.startServiceAsUser(intent, UserHandle.CURRENT); - } finally { - Binder.restoreCallingIdentity(ident); - } + // Only admin user can change tethering and SilentTetherProvisioning don't need to + // show UI, it is fine to always start setting's background service as system user. + mContext.startService(intent); } private void runUiTetherProvisioning(int type, int subId) { @@ -394,7 +390,7 @@ public class EntitlementManager { /** * Run the UI-enabled tethering provisioning check. - * @param type tethering type from ConnectivityManager.TETHERING_{@code *} + * @param type tethering type from TetheringManager.TETHERING_{@code *} * @param subId default data subscription ID. * @param receiver to receive entitlement check result. */ @@ -402,17 +398,14 @@ public class EntitlementManager { protected void runUiTetherProvisioning(int type, int subId, ResultReceiver receiver) { if (DBG) mLog.i("runUiTetherProvisioning: " + type); - Intent intent = new Intent(Settings.ACTION_TETHER_PROVISIONING); + Intent intent = new Intent(Settings.ACTION_TETHER_PROVISIONING_UI); intent.putExtra(EXTRA_ADD_TETHER_TYPE, type); intent.putExtra(EXTRA_PROVISION_CALLBACK, receiver); intent.putExtra(EXTRA_SUBID, subId); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - final long ident = Binder.clearCallingIdentity(); - try { - mContext.startActivityAsUser(intent, UserHandle.CURRENT); - } finally { - Binder.restoreCallingIdentity(ident); - } + // Only launch entitlement UI for system user. Entitlement UI should not appear for other + // user because only admin user is allowed to change tethering. + mContext.startActivity(intent); } // Not needed to check if this don't run on the handler thread because it's private. @@ -468,7 +461,7 @@ public class EntitlementManager { * Add the mapping between provisioning result and tethering type. * Notify UpstreamNetworkMonitor if Cellular permission changes. * - * @param type tethering type from ConnectivityManager.TETHERING_{@code *} + * @param type tethering type from TetheringManager.TETHERING_{@code *} * @param resultCode Provisioning result */ protected void addDownstreamMapping(int type, int resultCode) { @@ -483,7 +476,7 @@ public class EntitlementManager { /** * Remove the mapping for input tethering type. - * @param type tethering type from ConnectivityManager.TETHERING_{@code *} + * @param type tethering type from TetheringManager.TETHERING_{@code *} */ protected void removeDownstreamMapping(int type) { mLog.i("removeDownstreamMapping: " + type); @@ -632,7 +625,7 @@ public class EntitlementManager { /** * Update the last entitlement value to internal cache * - * @param type tethering type from ConnectivityManager.TETHERING_{@code *} + * @param type tethering type from TetheringManager.TETHERING_{@code *} * @param resultCode last entitlement value * @return the last updated entitlement value */ @@ -671,7 +664,7 @@ public class EntitlementManager { receiver.send(cacheValue, null); } else { ResultReceiver proxy = buildProxyReceiver(downstream, false/* notifyFail */, receiver); - runUiTetherProvisioning(downstream, config.subId, proxy); + runUiTetherProvisioning(downstream, config.activeDataSubId, proxy); } } } diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java b/packages/Tethering/src/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java index 93054140213d..66b9ade81019 100644 --- a/packages/Tethering/src/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java +++ b/packages/Tethering/src/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java @@ -29,6 +29,7 @@ import android.util.Log; import java.net.Inet6Address; import java.net.InetAddress; +import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedList; @@ -257,7 +258,7 @@ public class IPv6TetheringCoordinator { final LinkProperties lp = new LinkProperties(); final IpPrefix local48 = makeUniqueLocalPrefix(ulp, (short) 0, 48); - lp.addRoute(new RouteInfo(local48, null, null)); + lp.addRoute(new RouteInfo(local48, null, null, RouteInfo.RTN_UNICAST)); final IpPrefix local64 = makeUniqueLocalPrefix(ulp, subnetId, 64); // Because this is a locally-generated ULA, we don't have an upstream @@ -273,7 +274,13 @@ public class IPv6TetheringCoordinator { final byte[] bytes = Arrays.copyOf(in6addr, in6addr.length); bytes[7] = (byte) (subnetId >> 8); bytes[8] = (byte) subnetId; - return new IpPrefix(bytes, prefixlen); + final InetAddress addr; + try { + addr = InetAddress.getByAddress(bytes); + } catch (UnknownHostException e) { + throw new IllegalStateException("Invalid address length: " + bytes.length, e); + } + return new IpPrefix(addr, prefixlen); } // Generates a Unique Locally-assigned Prefix: diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadController.java b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadController.java index 16734d83e3aa..ce7c2a669f0a 100644 --- a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadController.java +++ b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadController.java @@ -25,6 +25,7 @@ import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED; import android.content.ContentResolver; import android.net.ITetheringStatsProvider; +import android.net.InetAddresses; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties; @@ -33,7 +34,6 @@ import android.net.RouteInfo; import android.net.netlink.ConntrackMessage; import android.net.netlink.NetlinkConstants; import android.net.netlink.NetlinkSocket; -import android.net.util.IpUtils; import android.net.util.SharedLog; import android.os.Handler; import android.os.INetworkManagementService; @@ -279,7 +279,7 @@ public class OffloadController { entry.iface = kv.getKey(); entry.rxBytes = value.rxBytes; entry.txBytes = value.txBytes; - stats.addValues(entry); + stats.addEntry(entry); } return stats; @@ -477,9 +477,10 @@ public class OffloadController { if (!ri.hasGateway()) continue; final String gateway = ri.getGateway().getHostAddress(); - if (ri.isIPv4Default()) { + final InetAddress address = ri.getDestination().getAddress(); + if (ri.isDefaultRoute() && address instanceof Inet4Address) { v4gateway = gateway; - } else if (ri.isIPv6Default()) { + } else if (ri.isDefaultRoute() && address instanceof Inet6Address) { v6gateways.add(gateway); } } @@ -547,7 +548,10 @@ public class OffloadController { private static boolean shouldIgnoreDownstreamRoute(RouteInfo route) { // Ignore any link-local routes. - if (!route.getDestinationLinkAddress().isGlobalPreferred()) return true; + final IpPrefix destination = route.getDestination(); + final LinkAddress linkAddr = new LinkAddress(destination.getAddress(), + destination.getPrefixLength()); + if (!linkAddr.isGlobalPreferred()) return true; return false; } @@ -588,7 +592,7 @@ public class OffloadController { return; } - if (!IpUtils.isValidUdpOrTcpPort(srcPort)) { + if (!isValidUdpOrTcpPort(srcPort)) { mLog.e("Invalid src port: " + srcPort); return; } @@ -599,7 +603,7 @@ public class OffloadController { return; } - if (!IpUtils.isValidUdpOrTcpPort(dstPort)) { + if (!isValidUdpOrTcpPort(dstPort)) { mLog.e("Invalid dst port: " + dstPort); return; } @@ -628,7 +632,7 @@ public class OffloadController { private static Inet4Address parseIPv4Address(String addrString) { try { - final InetAddress ip = InetAddress.parseNumericAddress(addrString); + final InetAddress ip = InetAddresses.parseNumericAddress(addrString); // TODO: Consider other sanitization steps here, including perhaps: // not eql to 0.0.0.0 // not within 169.254.0.0/16 @@ -668,4 +672,8 @@ public class OffloadController { return 180; } } + + private static boolean isValidUdpOrTcpPort(int port) { + return port > 0 && port < 65536; + } } diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java index 85164edf3c5b..4a8ef1f92754 100644 --- a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java +++ b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java @@ -16,7 +16,7 @@ package com.android.server.connectivity.tethering; -import static com.android.internal.util.BitUtils.uint16; +import static android.net.util.TetheringUtils.uint16; import android.hardware.tetheroffload.control.V1_0.IOffloadControl; import android.hardware.tetheroffload.control.V1_0.ITetheringOffloadCallback; diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java index 0b5759ba539f..d6abfb922a9c 100644 --- a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java +++ b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java @@ -20,23 +20,24 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.hardware.usb.UsbManager.USB_CONFIGURED; import static android.hardware.usb.UsbManager.USB_CONNECTED; import static android.hardware.usb.UsbManager.USB_FUNCTION_RNDIS; -import static android.net.ConnectivityManager.ACTION_TETHER_STATE_CHANGED; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; -import static android.net.ConnectivityManager.EXTRA_ACTIVE_LOCAL_ONLY; -import static android.net.ConnectivityManager.EXTRA_ACTIVE_TETHER; -import static android.net.ConnectivityManager.EXTRA_AVAILABLE_TETHER; -import static android.net.ConnectivityManager.EXTRA_ERRORED_TETHER; import static android.net.ConnectivityManager.EXTRA_NETWORK_INFO; -import static android.net.ConnectivityManager.TETHERING_BLUETOOTH; -import static android.net.ConnectivityManager.TETHERING_INVALID; -import static android.net.ConnectivityManager.TETHERING_USB; -import static android.net.ConnectivityManager.TETHERING_WIFI; -import static android.net.ConnectivityManager.TETHERING_WIFI_P2P; -import static android.net.ConnectivityManager.TETHER_ERROR_MASTER_ERROR; -import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR; -import static android.net.ConnectivityManager.TETHER_ERROR_SERVICE_UNAVAIL; -import static android.net.ConnectivityManager.TETHER_ERROR_UNAVAIL_IFACE; -import static android.net.ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE; +import static android.net.TetheringManager.ACTION_TETHER_STATE_CHANGED; +import static android.net.TetheringManager.EXTRA_ACTIVE_LOCAL_ONLY; +import static android.net.TetheringManager.EXTRA_ACTIVE_TETHER; +import static android.net.TetheringManager.EXTRA_AVAILABLE_TETHER; +import static android.net.TetheringManager.EXTRA_ERRORED_TETHER; +import static android.net.TetheringManager.TETHERING_BLUETOOTH; +import static android.net.TetheringManager.TETHERING_INVALID; +import static android.net.TetheringManager.TETHERING_USB; +import static android.net.TetheringManager.TETHERING_WIFI; +import static android.net.TetheringManager.TETHERING_WIFI_P2P; +import static android.net.TetheringManager.TETHER_ERROR_MASTER_ERROR; +import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR; +import static android.net.TetheringManager.TETHER_ERROR_SERVICE_UNAVAIL; +import static android.net.TetheringManager.TETHER_ERROR_UNAVAIL_IFACE; +import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_IFACE; +import static android.net.util.TetheringMessageBase.BASE_MASTER; import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME; import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_MODE; import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_STATE; @@ -70,10 +71,10 @@ import android.net.LinkAddress; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkInfo; -import android.net.NetworkUtils; import android.net.TetherStatesParcel; import android.net.TetheringConfigurationParcel; import android.net.ip.IpServer; +import android.net.shared.NetdUtils; import android.net.util.BaseNetdUnsolicitedEventListener; import android.net.util.InterfaceSet; import android.net.util.PrefixUtils; @@ -86,12 +87,12 @@ import android.net.wifi.p2p.WifiP2pManager; import android.os.Binder; import android.os.Bundle; import android.os.Handler; -import android.os.INetworkManagementService; import android.os.Looper; import android.os.Message; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.ResultReceiver; +import android.os.ServiceSpecificException; import android.os.UserHandle; import android.os.UserManager; import android.telephony.PhoneStateListener; @@ -101,12 +102,14 @@ import android.util.ArrayMap; import android.util.Log; import android.util.SparseArray; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import com.android.internal.annotations.VisibleForTesting; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.MessageUtils; -import com.android.internal.util.Protocol; import com.android.internal.util.State; import com.android.internal.util.StateMachine; import com.android.networkstack.tethering.R; @@ -120,6 +123,8 @@ import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.Set; +import java.util.concurrent.Executor; +import java.util.concurrent.RejectedExecutionException; /** * @@ -137,6 +142,8 @@ public class Tethering { }; private static final SparseArray<String> sMagicDecoderRing = MessageUtils.findMessageNames(sMessageClasses); + // Keep in sync with NETID_UNSET in system/netd/include/netid_client.h + private static final int NETID_UNSET = 0; private static class TetherState { public final IpServer ipServer; @@ -170,8 +177,6 @@ public class Tethering { private final Context mContext; private final ArrayMap<String, TetherState> mTetherStates; private final BroadcastReceiver mStateReceiver; - // Stopship: replace mNMService before production. - private final INetworkManagementService mNMService; private final INetworkStatsService mStatsService; private final INetworkPolicyManager mPolicyManager; private final Looper mLooper; @@ -185,10 +190,10 @@ public class Tethering { private final TetheringDependencies mDeps; private final EntitlementManager mEntitlementMgr; private final Handler mHandler; - private final PhoneStateListener mPhoneStateListener; private final INetd mNetd; private final NetdCallback mNetdCallback; private final UserRestrictionActionListener mTetheringRestriction; + private final ActiveDataSubIdListener mActiveDataSubIdListener; private int mActiveDataSubId = INVALID_SUBSCRIPTION_ID; // All the usage of mTetheringEventCallback should run in the same thread. private ITetheringEventCallback mTetheringEventCallback = null; @@ -208,7 +213,6 @@ public class Tethering { mLog.mark("Tethering.constructed"); mDeps = deps; mContext = mDeps.getContext(); - mNMService = mDeps.getINetworkManagementService(); mStatsService = mDeps.getINetworkStatsService(); mPolicyManager = mDeps.getINetworkPolicyManager(); mNetd = mDeps.getINetd(mContext); @@ -223,10 +227,9 @@ public class Tethering { mHandler = mTetherMasterSM.getHandler(); mOffloadController = new OffloadController(mHandler, - mDeps.getOffloadHardwareInterface(mHandler, mLog), - mContext.getContentResolver(), mNMService, - mLog); - mUpstreamNetworkMonitor = deps.getUpstreamNetworkMonitor(mContext, mTetherMasterSM, mLog, + mDeps.getOffloadHardwareInterface(mHandler, mLog), mContext.getContentResolver(), + mDeps.getINetworkManagementService(), mLog); + mUpstreamNetworkMonitor = mDeps.getUpstreamNetworkMonitor(mContext, mTetherMasterSM, mLog, TetherMasterSM.EVENT_UPSTREAM_CALLBACK); mForwardedDownstreams = new HashSet<>(); @@ -252,26 +255,6 @@ public class Tethering { mEntitlementMgr.reevaluateSimCardProvisioning(mConfig); }); - mPhoneStateListener = new PhoneStateListener(mLooper) { - @Override - public void onActiveDataSubscriptionIdChanged(int subId) { - mLog.log("OBSERVED active data subscription change, from " + mActiveDataSubId - + " to " + subId); - if (subId == mActiveDataSubId) return; - - mActiveDataSubId = subId; - updateConfiguration(); - // To avoid launching unexpected provisioning checks, ignore re-provisioning when - // no CarrierConfig loaded yet. Assume reevaluateSimCardProvisioning() will be - // triggered again when CarrierConfig is loaded. - if (mEntitlementMgr.getCarrierConfig(mConfig) != null) { - mEntitlementMgr.reevaluateSimCardProvisioning(mConfig); - } else { - mLog.log("IGNORED reevaluate provisioning due to no carrier config loaded"); - } - } - }; - mStateReceiver = new StateReceiver(); mNetdCallback = new NetdCallback(); @@ -284,6 +267,8 @@ public class Tethering { final UserManager userManager = (UserManager) mContext.getSystemService( Context.USER_SERVICE); mTetheringRestriction = new UserRestrictionActionListener(userManager, this); + final TetheringThreadExecutor executor = new TetheringThreadExecutor(mHandler); + mActiveDataSubIdListener = new ActiveDataSubIdListener(executor); // Load tethering configuration. updateConfiguration(); @@ -294,8 +279,8 @@ public class Tethering { private void startStateMachineUpdaters(Handler handler) { mCarrierConfigChange.startListening(); - mContext.getSystemService(TelephonyManager.class).listen( - mPhoneStateListener, PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE); + mContext.getSystemService(TelephonyManager.class).listen(mActiveDataSubIdListener, + PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE); IntentFilter filter = new IntentFilter(); filter.addAction(UsbManager.ACTION_USB_STATE); @@ -305,13 +290,43 @@ public class Tethering { filter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION); filter.addAction(UserManager.ACTION_USER_RESTRICTIONS_CHANGED); mContext.registerReceiver(mStateReceiver, filter, null, handler); + } - filter = new IntentFilter(); - filter.addAction(Intent.ACTION_MEDIA_SHARED); - filter.addAction(Intent.ACTION_MEDIA_UNSHARED); - filter.addDataScheme("file"); - mContext.registerReceiver(mStateReceiver, filter, null, handler); + private class TetheringThreadExecutor implements Executor { + private final Handler mTetherHandler; + TetheringThreadExecutor(Handler handler) { + mTetherHandler = handler; + } + @Override + public void execute(Runnable command) { + if (!mTetherHandler.post(command)) { + throw new RejectedExecutionException(mTetherHandler + " is shutting down"); + } + } + } + private class ActiveDataSubIdListener extends PhoneStateListener { + ActiveDataSubIdListener(Executor executor) { + super(executor); + } + + @Override + public void onActiveDataSubscriptionIdChanged(int subId) { + mLog.log("OBSERVED active data subscription change, from " + mActiveDataSubId + + " to " + subId); + if (subId == mActiveDataSubId) return; + + mActiveDataSubId = subId; + updateConfiguration(); + // To avoid launching unexpected provisioning checks, ignore re-provisioning + // when no CarrierConfig loaded yet. Assume reevaluateSimCardProvisioning() + // ill be triggered again when CarrierConfig is loaded. + if (mEntitlementMgr.getCarrierConfig(mConfig) != null) { + mEntitlementMgr.reevaluateSimCardProvisioning(mConfig); + } else { + mLog.log("IGNORED reevaluate provisioning, no carrier config loaded"); + } + } } private WifiManager getWifiManager() { @@ -326,8 +341,7 @@ public class Tethering { } private void maybeDunSettingChanged() { - final boolean isDunRequired = TetheringConfiguration.checkDunRequired( - mContext, mActiveDataSubId); + final boolean isDunRequired = TetheringConfiguration.checkDunRequired(mContext); if (isDunRequired == mConfig.isDunRequired) return; updateConfiguration(); } @@ -401,7 +415,6 @@ public class Tethering { } } - void interfaceRemoved(String iface) { if (VDBG) Log.d(TAG, "interfaceRemoved " + iface); synchronized (mPublicSync) { @@ -1002,8 +1015,8 @@ public class Tethering { String[] ifaces = null; try { - ifaces = mNMService.listInterfaces(); - } catch (Exception e) { + ifaces = mNetd.interfaceGetList(); + } catch (RemoteException | ServiceSpecificException e) { Log.e(TAG, "Error listing Interfaces", e); return; } @@ -1162,7 +1175,6 @@ public class Tethering { } class TetherMasterSM extends StateMachine { - private static final int BASE_MASTER = Protocol.BASE_TETHERING; // an interface SM has requested Tethering/Local Hotspot static final int EVENT_IFACE_SERVING_STATE_ACTIVE = BASE_MASTER + 1; // an interface SM has unrequested Tethering/Local Hotspot @@ -1179,7 +1191,6 @@ public class Tethering { static final int EVENT_IFACE_UPDATE_LINKPROPERTIES = BASE_MASTER + 7; // Events from EntitlementManager to choose upstream again. static final int EVENT_UPSTREAM_PERMISSION_CHANGED = BASE_MASTER + 8; - private final State mInitialState; private final State mTetherModeAliveState; @@ -1264,25 +1275,25 @@ public class Tethering { protected boolean turnOnMasterTetherSettings() { final TetheringConfiguration cfg = mConfig; try { - mNMService.setIpForwardingEnabled(true); - } catch (Exception e) { + mNetd.ipfwdEnableForwarding(TAG); + } catch (RemoteException | ServiceSpecificException e) { mLog.e(e); transitionTo(mSetIpForwardingEnabledErrorState); return false; } + // TODO: Randomize DHCPv4 ranges, especially in hotspot mode. // Legacy DHCP server is disabled if passed an empty ranges array final String[] dhcpRanges = cfg.enableLegacyDhcpServer - ? cfg.legacyDhcpRanges - : new String[0]; + ? cfg.legacyDhcpRanges : new String[0]; try { - // TODO: Find a more accurate method name (startDHCPv4()?). - mNMService.startTethering(dhcpRanges); - } catch (Exception e) { + NetdUtils.tetherStart(mNetd, true /** usingLegacyDnsProxy */, dhcpRanges); + } catch (RemoteException | ServiceSpecificException e) { try { - mNMService.stopTethering(); - mNMService.startTethering(dhcpRanges); - } catch (Exception ee) { + // Stop and retry. + mNetd.tetherStop(); + NetdUtils.tetherStart(mNetd, true /** usingLegacyDnsProxy */, dhcpRanges); + } catch (RemoteException | ServiceSpecificException ee) { mLog.e(ee); transitionTo(mStartTetheringErrorState); return false; @@ -1294,15 +1305,15 @@ public class Tethering { protected boolean turnOffMasterTetherSettings() { try { - mNMService.stopTethering(); - } catch (Exception e) { + mNetd.tetherStop(); + } catch (RemoteException | ServiceSpecificException e) { mLog.e(e); transitionTo(mStopTetheringErrorState); return false; } try { - mNMService.setIpForwardingEnabled(false); - } catch (Exception e) { + mNetd.ipfwdDisableForwarding(TAG); + } catch (RemoteException | ServiceSpecificException e) { mLog.e(e); transitionTo(mSetIpForwardingDisabledErrorState); return false; @@ -1365,19 +1376,25 @@ public class Tethering { protected void setDnsForwarders(final Network network, final LinkProperties lp) { // TODO: Set v4 and/or v6 DNS per available connectivity. - String[] dnsServers = mConfig.defaultIPv4DNS; final Collection<InetAddress> dnses = lp.getDnsServers(); // TODO: Properly support the absence of DNS servers. + final String[] dnsServers; if (dnses != null && !dnses.isEmpty()) { - // TODO: remove this invocation of NetworkUtils.makeStrings(). - dnsServers = NetworkUtils.makeStrings(dnses); + dnsServers = new String[dnses.size()]; + int i = 0; + for (InetAddress dns : dnses) { + dnsServers[i++] = dns.getHostAddress(); + } + } else { + dnsServers = mConfig.defaultIPv4DNS; } + final int netId = (network != null) ? network.netId : NETID_UNSET; try { - mNMService.setDnsForwarders(network, dnsServers); + mNetd.tetherDnsSet(netId, dnsServers); mLog.log(String.format( "SET DNS forwarders: network=%s dnsServers=%s", network, Arrays.toString(dnsServers))); - } catch (Exception e) { + } catch (RemoteException | ServiceSpecificException e) { // TODO: Investigate how this can fail and what exactly // happens if/when such failures occur. mLog.e("setting DNS forwarders failed, " + e); @@ -1680,8 +1697,8 @@ public class Tethering { Log.e(TAG, "Error in startTethering"); notify(IpServer.CMD_START_TETHERING_ERROR); try { - mNMService.setIpForwardingEnabled(false); - } catch (Exception e) { } + mNetd.ipfwdDisableForwarding(TAG); + } catch (RemoteException | ServiceSpecificException e) { } } } @@ -1691,8 +1708,8 @@ public class Tethering { Log.e(TAG, "Error in stopTethering"); notify(IpServer.CMD_STOP_TETHERING_ERROR); try { - mNMService.setIpForwardingEnabled(false); - } catch (Exception e) { } + mNetd.ipfwdDisableForwarding(TAG); + } catch (RemoteException | ServiceSpecificException e) { } } } @@ -1702,11 +1719,11 @@ public class Tethering { Log.e(TAG, "Error in setDnsForwarders"); notify(IpServer.CMD_SET_DNS_FORWARDERS_ERROR); try { - mNMService.stopTethering(); - } catch (Exception e) { } + mNetd.tetherStop(); + } catch (RemoteException | ServiceSpecificException e) { } try { - mNMService.setIpForwardingEnabled(false); - } catch (Exception e) { } + mNetd.ipfwdDisableForwarding(TAG); + } catch (RemoteException | ServiceSpecificException e) { } } } @@ -1866,7 +1883,7 @@ public class Tethering { } } - void dump(FileDescriptor fd, PrintWriter writer, String[] args) { + void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer, @Nullable String[] args) { // Binder.java closes the resource for us. @SuppressWarnings("resource") final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); @@ -2047,7 +2064,7 @@ public class Tethering { mLog.log("adding TetheringInterfaceStateMachine for: " + iface); final TetherState tetherState = new TetherState( - new IpServer(iface, mLooper, interfaceType, mLog, mNMService, mStatsService, + new IpServer(iface, mLooper, interfaceType, mLog, mNetd, mStatsService, makeControlCallback(), mConfig.enableLegacyDhcpServer, mDeps.getIpServerDependencies())); mTetherStates.put(iface, tetherState); diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java index 0ab4d634e84d..397ba8ada551 100644 --- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java +++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java @@ -37,7 +37,6 @@ import static com.android.internal.R.string.config_mobile_hotspot_provision_app_ import android.content.ContentResolver; import android.content.Context; import android.content.res.Resources; -import android.net.ConnectivityManager; import android.net.TetheringConfigurationParcel; import android.net.util.SharedLog; import android.provider.Settings; @@ -100,13 +99,13 @@ public class TetheringConfiguration { public final String provisioningAppNoUi; public final int provisioningCheckPeriod; - public final int subId; + public final int activeDataSubId; public TetheringConfiguration(Context ctx, SharedLog log, int id) { final SharedLog configLog = log.forSubComponent("config"); - subId = id; - Resources res = getResources(ctx, subId); + activeDataSubId = id; + Resources res = getResources(ctx, activeDataSubId); tetherableUsbRegexs = getResourceStringArray(res, config_tether_usb_regexs); // TODO: Evaluate deleting this altogether now that Wi-Fi always passes @@ -116,7 +115,7 @@ public class TetheringConfiguration { tetherableWifiP2pRegexs = getResourceStringArray(res, config_tether_wifi_p2p_regexs); tetherableBluetoothRegexs = getResourceStringArray(res, config_tether_bluetooth_regexs); - isDunRequired = checkDunRequired(ctx, subId); + isDunRequired = checkDunRequired(ctx); chooseUpstreamAutomatically = getResourceBoolean(res, config_tether_upstream_automatic); preferredUpstreamIfaceTypes = getUpstreamIfaceTypes(res, isDunRequired); @@ -166,8 +165,8 @@ public class TetheringConfiguration { /** Does the dumping.*/ public void dump(PrintWriter pw) { - pw.print("subId: "); - pw.println(subId); + pw.print("activeDataSubId: "); + pw.println(activeDataSubId); dumpStringArray(pw, "tetherableUsbRegexs", tetherableUsbRegexs); dumpStringArray(pw, "tetherableWifiRegexs", tetherableWifiRegexs); @@ -179,8 +178,8 @@ public class TetheringConfiguration { pw.print("chooseUpstreamAutomatically: "); pw.println(chooseUpstreamAutomatically); - dumpStringArray(pw, "preferredUpstreamIfaceTypes", - preferredUpstreamNames(preferredUpstreamIfaceTypes)); + pw.print("legacyPreredUpstreamIfaceTypes: "); + pw.println(Arrays.toString(toIntArray(preferredUpstreamIfaceTypes))); dumpStringArray(pw, "legacyDhcpRanges", legacyDhcpRanges); dumpStringArray(pw, "defaultIPv4DNS", defaultIPv4DNS); @@ -196,7 +195,7 @@ public class TetheringConfiguration { /** Returns the string representation of this object.*/ public String toString() { final StringJoiner sj = new StringJoiner(" "); - sj.add(String.format("subId:%d", subId)); + sj.add(String.format("activeDataSubId:%d", activeDataSubId)); sj.add(String.format("tetherableUsbRegexs:%s", makeString(tetherableUsbRegexs))); sj.add(String.format("tetherableWifiRegexs:%s", makeString(tetherableWifiRegexs))); sj.add(String.format("tetherableWifiP2pRegexs:%s", makeString(tetherableWifiP2pRegexs))); @@ -205,7 +204,7 @@ public class TetheringConfiguration { sj.add(String.format("isDunRequired:%s", isDunRequired)); sj.add(String.format("chooseUpstreamAutomatically:%s", chooseUpstreamAutomatically)); sj.add(String.format("preferredUpstreamIfaceTypes:%s", - makeString(preferredUpstreamNames(preferredUpstreamIfaceTypes)))); + toIntArray(preferredUpstreamIfaceTypes))); sj.add(String.format("provisioningApp:%s", makeString(provisioningApp))); sj.add(String.format("provisioningAppNoUi:%s", provisioningAppNoUi)); sj.add(String.format("enableLegacyDhcpServer:%s", enableLegacyDhcpServer)); @@ -234,25 +233,12 @@ public class TetheringConfiguration { return sj.toString(); } - private static String[] preferredUpstreamNames(Collection<Integer> upstreamTypes) { - String[] upstreamNames = null; - - if (upstreamTypes != null) { - upstreamNames = new String[upstreamTypes.size()]; - int i = 0; - for (Integer netType : upstreamTypes) { - upstreamNames[i] = ConnectivityManager.getNetworkTypeName(netType); - i++; - } - } - - return upstreamNames; - } - /** Check whether dun is required. */ - public static boolean checkDunRequired(Context ctx, int id) { + public static boolean checkDunRequired(Context ctx) { final TelephonyManager tm = (TelephonyManager) ctx.getSystemService(TELEPHONY_SERVICE); - return (tm != null) ? tm.isTetheringApnRequired(id) : false; + // TelephonyManager would uses the active data subscription, which should be the one used + // by tethering. + return (tm != null) ? tm.isTetheringApnRequired() : false; } private static Collection<Integer> getUpstreamIfaceTypes(Resources res, boolean dunRequired) { @@ -386,24 +372,28 @@ public class TetheringConfiguration { return false; } + private static int[] toIntArray(Collection<Integer> values) { + final int[] result = new int[values.size()]; + int index = 0; + for (Integer value : values) { + result[index++] = value; + } + return result; + } + /** * Convert this TetheringConfiguration to a TetheringConfigurationParcel. */ public TetheringConfigurationParcel toStableParcelable() { final TetheringConfigurationParcel parcel = new TetheringConfigurationParcel(); - parcel.subId = subId; + parcel.subId = activeDataSubId; parcel.tetherableUsbRegexs = tetherableUsbRegexs; parcel.tetherableWifiRegexs = tetherableWifiRegexs; parcel.tetherableBluetoothRegexs = tetherableBluetoothRegexs; parcel.isDunRequired = isDunRequired; parcel.chooseUpstreamAutomatically = chooseUpstreamAutomatically; - int[] preferredTypes = new int[preferredUpstreamIfaceTypes.size()]; - int index = 0; - for (Integer type : preferredUpstreamIfaceTypes) { - preferredTypes[index++] = type; - } - parcel.preferredUpstreamIfaceTypes = preferredTypes; + parcel.preferredUpstreamIfaceTypes = toIntArray(preferredUpstreamIfaceTypes); parcel.legacyDhcpRanges = legacyDhcpRanges; parcel.defaultIPv4DNS = defaultIPv4DNS; diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java index 6334c20c2acc..d5cdd8a004dc 100644 --- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java +++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java @@ -22,14 +22,17 @@ import android.net.NetworkCapabilities; import android.net.RouteInfo; import android.net.util.InterfaceSet; -import java.net.Inet4Address; -import java.net.Inet6Address; import java.net.InetAddress; +import java.net.UnknownHostException; /** * @hide */ public final class TetheringInterfaceUtils { + private static final InetAddress IN6ADDR_ANY = getByAddress( + new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}); + private static final InetAddress INADDR_ANY = getByAddress(new byte[] {0, 0, 0, 0}); + /** * Get upstream interfaces for tethering based on default routes for IPv4/IPv6. * @return null if there is no usable interface, or a set of at least one interface otherwise. @@ -40,7 +43,7 @@ public final class TetheringInterfaceUtils { } final LinkProperties lp = ns.linkProperties; - final String if4 = getInterfaceForDestination(lp, Inet4Address.ANY); + final String if4 = getInterfaceForDestination(lp, INADDR_ANY); final String if6 = getIPv6Interface(ns); return (if4 == null && if6 == null) ? null : new InterfaceSet(if4, if6); @@ -76,7 +79,7 @@ public final class TetheringInterfaceUtils { && ns.networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR); return canTether - ? getInterfaceForDestination(ns.linkProperties, Inet6Address.ANY) + ? getInterfaceForDestination(ns.linkProperties, IN6ADDR_ANY) : null; } @@ -86,4 +89,12 @@ public final class TetheringInterfaceUtils { : null; return (ri != null) ? ri.getInterface() : null; } + + private static InetAddress getByAddress(final byte[] addr) { + try { + return InetAddress.getByAddress(null, addr); + } catch (UnknownHostException e) { + throw new AssertionError("illegal address length" + addr.length); + } + } } diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java index 1d5998680287..e4e4a090603d 100644 --- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java +++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java @@ -21,15 +21,16 @@ import static android.net.TetheringManager.TETHER_ERROR_NO_ACCESS_TETHERING_PERM import static android.net.TetheringManager.TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION; import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR; import static android.net.TetheringManager.TETHER_ERROR_UNSUPPORTED; +import static android.net.dhcp.IDhcpServer.STATUS_UNKNOWN_ERROR; import android.app.Service; import android.content.Context; import android.content.Intent; -import android.net.ConnectivityManager; import android.net.IIntResultListener; import android.net.INetworkStackConnector; import android.net.ITetheringConnector; import android.net.ITetheringEventCallback; +import android.net.NetworkCapabilities; import android.net.NetworkRequest; import android.net.dhcp.DhcpServerCallbacks; import android.net.dhcp.DhcpServingParamsParcel; @@ -306,9 +307,15 @@ public class TetheringService extends Service { mDeps = new TetheringDependencies() { @Override public NetworkRequest getDefaultNetworkRequest() { - ConnectivityManager cm = (ConnectivityManager) mContext.getSystemService( - Context.CONNECTIVITY_SERVICE); - return cm.getDefaultRequest(); + // TODO: b/147280869, add a proper system API to replace this. + final NetworkRequest trackDefaultRequest = new NetworkRequest.Builder() + .clearCapabilities() + .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) + .addCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED) + .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) + .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + .build(); + return trackDefaultRequest; } @Override @@ -340,7 +347,10 @@ public class TetheringService extends Service { service.makeDhcpServer(ifName, params, cb); } catch (RemoteException e) { - e.rethrowFromSystemServer(); + Log.e(TAG, "Fail to make dhcp server"); + try { + cb.onDhcpServerCreated(STATUS_UNKNOWN_ERROR, null); + } catch (RemoteException re) { } } } }; diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java b/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java index dc38c49a0c12..5692a6fc5c80 100644 --- a/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java +++ b/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java @@ -16,10 +16,12 @@ package com.android.server.connectivity.tethering; +import static android.net.ConnectivityManager.TYPE_BLUETOOTH; +import static android.net.ConnectivityManager.TYPE_ETHERNET; +import static android.net.ConnectivityManager.TYPE_MOBILE; import static android.net.ConnectivityManager.TYPE_MOBILE_DUN; import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI; -import static android.net.ConnectivityManager.TYPE_NONE; -import static android.net.ConnectivityManager.getNetworkTypeName; +import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; @@ -35,10 +37,11 @@ import android.net.NetworkRequest; import android.net.util.PrefixUtils; import android.net.util.SharedLog; import android.os.Handler; -import android.os.Process; import android.util.Log; +import android.util.SparseIntArray; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.Preconditions; import com.android.internal.util.StateMachine; import java.util.HashMap; @@ -79,11 +82,25 @@ public class UpstreamNetworkMonitor { public static final int EVENT_ON_LINKPROPERTIES = 2; public static final int EVENT_ON_LOST = 3; public static final int NOTIFY_LOCAL_PREFIXES = 10; + // This value is used by deprecated preferredUpstreamIfaceTypes selection which is default + // disabled. + @VisibleForTesting + public static final int TYPE_NONE = -1; private static final int CALLBACK_LISTEN_ALL = 1; private static final int CALLBACK_DEFAULT_INTERNET = 2; private static final int CALLBACK_MOBILE_REQUEST = 3; + private static final SparseIntArray sLegacyTypeToTransport = new SparseIntArray(); + static { + sLegacyTypeToTransport.put(TYPE_MOBILE, NetworkCapabilities.TRANSPORT_CELLULAR); + sLegacyTypeToTransport.put(TYPE_MOBILE_DUN, NetworkCapabilities.TRANSPORT_CELLULAR); + sLegacyTypeToTransport.put(TYPE_MOBILE_HIPRI, NetworkCapabilities.TRANSPORT_CELLULAR); + sLegacyTypeToTransport.put(TYPE_WIFI, NetworkCapabilities.TRANSPORT_WIFI); + sLegacyTypeToTransport.put(TYPE_BLUETOOTH, NetworkCapabilities.TRANSPORT_BLUETOOTH); + sLegacyTypeToTransport.put(TYPE_ETHERNET, NetworkCapabilities.TRANSPORT_ETHERNET); + } + private final Context mContext; private final SharedLog mLog; private final StateMachine mTarget; @@ -130,15 +147,15 @@ public class UpstreamNetworkMonitor { */ public void startTrackDefaultNetwork(NetworkRequest defaultNetworkRequest, EntitlementManager entitle) { - // This is not really a "request", just a way of tracking the system default network. - // It's guaranteed not to actually bring up any networks because it's the same request - // as the ConnectivityService default request, and thus shares fate with it. We can't - // use registerDefaultNetworkCallback because it will not track the system default - // network if there is a VPN that applies to our UID. + + // defaultNetworkRequest is not really a "request", just a way of tracking the system + // default network. It's guaranteed not to actually bring up any networks because it's + // the should be the same request as the ConnectivityService default request, and thus + // shares fate with it. We can't use registerDefaultNetworkCallback because it will not + // track the system default network if there is a VPN that applies to our UID. if (mDefaultNetworkCallback == null) { - final NetworkRequest trackDefaultRequest = new NetworkRequest(defaultNetworkRequest); mDefaultNetworkCallback = new UpstreamNetworkCallback(CALLBACK_DEFAULT_INTERNET); - cm().requestNetwork(trackDefaultRequest, mDefaultNetworkCallback, mHandler); + cm().requestNetwork(defaultNetworkRequest, mDefaultNetworkCallback, mHandler); } if (mEntitlementMgr == null) { mEntitlementMgr = entitle; @@ -204,7 +221,7 @@ public class UpstreamNetworkMonitor { final int legacyType = mDunRequired ? TYPE_MOBILE_DUN : TYPE_MOBILE_HIPRI; final NetworkRequest mobileUpstreamRequest = new NetworkRequest.Builder() - .setCapabilities(ConnectivityManager.networkCapabilitiesForType(legacyType)) + .setCapabilities(networkCapabilitiesForType(legacyType)) .build(); // The existing default network and DUN callbacks will be notified. @@ -239,7 +256,7 @@ public class UpstreamNetworkMonitor { final TypeStatePair typeStatePair = findFirstAvailableUpstreamByType( mNetworkMap.values(), preferredTypes, isCellularUpstreamPermitted()); - mLog.log("preferred upstream type: " + getNetworkTypeName(typeStatePair.type)); + mLog.log("preferred upstream type: " + typeStatePair.type); switch (typeStatePair.type) { case TYPE_MOBILE_DUN: @@ -328,13 +345,6 @@ public class UpstreamNetworkMonitor { network, newNc)); } - // Log changes in upstream network signal strength, if available. - if (network.equals(mTetheringUpstreamNetwork) && newNc.hasSignalStrength()) { - final int newSignal = newNc.getSignalStrength(); - final String prevSignal = getSignalStrength(prev.networkCapabilities); - mLog.logf("upstream network signal strength: %s -> %s", prevSignal, newSignal); - } - mNetworkMap.put(network, new UpstreamNetworkState( prev.linkProperties, newNc, network)); // TODO: If sufficient information is available to select a more @@ -363,16 +373,6 @@ public class UpstreamNetworkMonitor { notifyTarget(EVENT_ON_LINKPROPERTIES, network); } - private void handleSuspended(Network network) { - if (!network.equals(mTetheringUpstreamNetwork)) return; - mLog.log("SUSPENDED current upstream: " + network); - } - - private void handleResumed(Network network) { - if (!network.equals(mTetheringUpstreamNetwork)) return; - mLog.log("RESUMED current upstream: " + network); - } - private void handleLost(Network network) { // There are few TODOs within ConnectivityService's rematching code // pertaining to spurious onLost() notifications. @@ -462,20 +462,6 @@ public class UpstreamNetworkMonitor { } @Override - public void onNetworkSuspended(Network network) { - if (mCallbackType == CALLBACK_LISTEN_ALL) { - handleSuspended(network); - } - } - - @Override - public void onNetworkResumed(Network network) { - if (mCallbackType == CALLBACK_LISTEN_ALL) { - handleResumed(network); - } - } - - @Override public void onLost(Network network) { if (mCallbackType == CALLBACK_DEFAULT_INTERNET) { mDefaultInternetNetwork = null; @@ -519,18 +505,15 @@ public class UpstreamNetworkMonitor { for (int type : preferredTypes) { NetworkCapabilities nc; try { - nc = ConnectivityManager.networkCapabilitiesForType(type); + nc = networkCapabilitiesForType(type); } catch (IllegalArgumentException iae) { - Log.e(TAG, "No NetworkCapabilities mapping for legacy type: " - + ConnectivityManager.getNetworkTypeName(type)); + Log.e(TAG, "No NetworkCapabilities mapping for legacy type: " + type); continue; } if (!isCellularUpstreamPermitted && isCellular(nc)) { continue; } - nc.setSingleUid(Process.myUid()); - for (UpstreamNetworkState value : netStates) { if (!nc.satisfiedByNetworkCapabilities(value.networkCapabilities)) { continue; @@ -557,11 +540,6 @@ public class UpstreamNetworkMonitor { return prefixSet; } - private static String getSignalStrength(NetworkCapabilities nc) { - if (nc == null || !nc.hasSignalStrength()) return "unknown"; - return Integer.toString(nc.getSignalStrength()); - } - private static boolean isCellular(UpstreamNetworkState ns) { return (ns != null) && isCellular(ns.networkCapabilities); } @@ -589,4 +567,28 @@ public class UpstreamNetworkMonitor { return null; } + + /** + * Given a legacy type (TYPE_WIFI, ...) returns the corresponding NetworkCapabilities instance. + * This function is used for deprecated legacy type and be disabled by default. + */ + @VisibleForTesting + public static NetworkCapabilities networkCapabilitiesForType(int type) { + final NetworkCapabilities nc = new NetworkCapabilities(); + + // Map from type to transports. + final int notFound = -1; + final int transport = sLegacyTypeToTransport.get(type, notFound); + Preconditions.checkArgument(transport != notFound, "unknown legacy type: " + type); + nc.addTransportType(transport); + + if (type == TYPE_MOBILE_DUN) { + nc.addCapability(NetworkCapabilities.NET_CAPABILITY_DUN); + // DUN is restricted network, see NetworkCapabilities#FORCE_RESTRICTED_CAPABILITIES. + nc.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED); + } else { + nc.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); + } + return nc; + } } diff --git a/packages/Tethering/tests/unit/Android.bp b/packages/Tethering/tests/unit/Android.bp index 81a0548cd718..53782fed1c50 100644 --- a/packages/Tethering/tests/unit/Android.bp +++ b/packages/Tethering/tests/unit/Android.bp @@ -20,7 +20,11 @@ android_test { srcs: [ "src/**/*.java", ], - test_suites: ["device-tests"], + test_suites: [ + "device-tests", + "mts", + ], + compile_multilib: "both", static_libs: [ "androidx.test.rules", "frameworks-base-testutils", diff --git a/packages/Tethering/tests/unit/jarjar-rules.txt b/packages/Tethering/tests/unit/jarjar-rules.txt index 64fdebd92726..921fbed373b0 100644 --- a/packages/Tethering/tests/unit/jarjar-rules.txt +++ b/packages/Tethering/tests/unit/jarjar-rules.txt @@ -7,5 +7,6 @@ rule com.android.internal.util.MessageUtils* com.android.networkstack.tethering. rule com.android.internal.util.Preconditions* com.android.networkstack.tethering.util.Preconditions@1 rule com.android.internal.util.State* com.android.networkstack.tethering.util.State@1 rule com.android.internal.util.StateMachine* com.android.networkstack.tethering.util.StateMachine@1 +rule com.android.internal.util.TrafficStatsConstants* com.android.networkstack.tethering.util.TrafficStatsConstants@1 rule android.net.LocalLog* com.android.networkstack.tethering.LocalLog@1 diff --git a/packages/Tethering/tests/unit/src/android/net/dhcp/DhcpServingParamsParcelExtTest.java b/packages/Tethering/tests/unit/src/android/net/dhcp/DhcpServingParamsParcelExtTest.java index e01ac7f08c2f..e8add9830b5f 100644 --- a/packages/Tethering/tests/unit/src/android/net/dhcp/DhcpServingParamsParcelExtTest.java +++ b/packages/Tethering/tests/unit/src/android/net/dhcp/DhcpServingParamsParcelExtTest.java @@ -18,8 +18,6 @@ package android.net.dhcp; import static android.net.InetAddresses.parseNumericAddress; -import static com.google.android.collect.Sets.newHashSet; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -34,6 +32,8 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.net.Inet4Address; +import java.util.Arrays; +import java.util.HashSet; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -47,9 +47,10 @@ public class DhcpServingParamsParcelExtTest { private static final int TEST_LEASE_TIME_SECS = 120; private static final int TEST_MTU = 1000; private static final Set<Inet4Address> TEST_ADDRESS_SET = - newHashSet(inet4Addr("192.168.1.123"), inet4Addr("192.168.1.124")); + new HashSet<Inet4Address>(Arrays.asList( + new Inet4Address[] {inet4Addr("192.168.1.123"), inet4Addr("192.168.1.124")})); private static final Set<Integer> TEST_ADDRESS_SET_PARCELED = - newHashSet(0xc0a8017b, 0xc0a8017c); + new HashSet<Integer>(Arrays.asList(new Integer[] {0xc0a8017b, 0xc0a8017c})); private DhcpServingParamsParcelExt mParcel; diff --git a/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java index 4358cd6966f1..65a0ac13a84b 100644 --- a/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java +++ b/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java @@ -16,13 +16,14 @@ package android.net.ip; -import static android.net.ConnectivityManager.TETHERING_BLUETOOTH; -import static android.net.ConnectivityManager.TETHERING_USB; -import static android.net.ConnectivityManager.TETHERING_WIFI; -import static android.net.ConnectivityManager.TETHERING_WIFI_P2P; -import static android.net.ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR; -import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR; -import static android.net.ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR; +import static android.net.INetd.IF_STATE_UP; +import static android.net.TetheringManager.TETHERING_BLUETOOTH; +import static android.net.TetheringManager.TETHERING_USB; +import static android.net.TetheringManager.TETHERING_WIFI; +import static android.net.TetheringManager.TETHERING_WIFI_P2P; +import static android.net.TetheringManager.TETHER_ERROR_ENABLE_NAT_ERROR; +import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR; +import static android.net.TetheringManager.TETHER_ERROR_TETHER_IFACE_ERROR; import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS; import static android.net.ip.IpServer.STATE_AVAILABLE; import static android.net.ip.IpServer.STATE_LOCAL_ONLY; @@ -52,7 +53,7 @@ import static org.mockito.Mockito.when; import android.net.INetd; import android.net.INetworkStatsService; -import android.net.InterfaceConfiguration; +import android.net.InterfaceConfigurationParcel; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties; @@ -64,7 +65,6 @@ import android.net.dhcp.IDhcpServerCallbacks; import android.net.util.InterfaceParams; import android.net.util.InterfaceSet; import android.net.util.SharedLog; -import android.os.INetworkManagementService; import android.os.RemoteException; import android.os.test.TestLooper; import android.text.TextUtils; @@ -89,6 +89,8 @@ public class IpServerTest { private static final String IFACE_NAME = "testnet1"; private static final String UPSTREAM_IFACE = "upstream0"; private static final String UPSTREAM_IFACE2 = "upstream1"; + private static final String BLUETOOTH_IFACE_ADDR = "192.168.42.1"; + private static final int BLUETOOTH_DHCP_PREFIX_LENGTH = 24; private static final int DHCP_LEASE_TIME_SECS = 3600; private static final InterfaceParams TEST_IFACE_PARAMS = new InterfaceParams( @@ -96,11 +98,9 @@ public class IpServerTest { private static final int MAKE_DHCPSERVER_TIMEOUT_MS = 1000; - @Mock private INetworkManagementService mNMService; @Mock private INetd mNetd; @Mock private INetworkStatsService mStatsService; @Mock private IpServer.Callback mCallback; - @Mock private InterfaceConfiguration mInterfaceConfiguration; @Mock private SharedLog mSharedLog; @Mock private IDhcpServer mDhcpServer; @Mock private RouterAdvertisementDaemon mRaDaemon; @@ -112,6 +112,7 @@ public class IpServerTest { private final ArgumentCaptor<LinkProperties> mLinkPropertiesCaptor = ArgumentCaptor.forClass(LinkProperties.class); private IpServer mIpServer; + private InterfaceConfigurationParcel mInterfaceConfiguration; private void initStateMachine(int interfaceType) throws Exception { initStateMachine(interfaceType, false /* usingLegacyDhcp */); @@ -131,17 +132,20 @@ public class IpServerTest { }).when(mDependencies).makeDhcpServer(any(), mDhcpParamsCaptor.capture(), any()); when(mDependencies.getRouterAdvertisementDaemon(any())).thenReturn(mRaDaemon); when(mDependencies.getInterfaceParams(IFACE_NAME)).thenReturn(TEST_IFACE_PARAMS); - when(mDependencies.getNetdService()).thenReturn(mNetd); - + mInterfaceConfiguration = new InterfaceConfigurationParcel(); + mInterfaceConfiguration.flags = new String[0]; + if (interfaceType == TETHERING_BLUETOOTH) { + mInterfaceConfiguration.ipv4Addr = BLUETOOTH_IFACE_ADDR; + mInterfaceConfiguration.prefixLength = BLUETOOTH_DHCP_PREFIX_LENGTH; + } mIpServer = new IpServer( - IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog, - mNMService, mStatsService, mCallback, usingLegacyDhcp, mDependencies); + IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog, mNetd, mStatsService, + mCallback, usingLegacyDhcp, mDependencies); mIpServer.start(); // Starting the state machine always puts us in a consistent state and notifies // the rest of the world that we've changed from an unknown to available state. mLooper.dispatchAll(); - reset(mNMService, mStatsService, mCallback); - when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration); + reset(mNetd, mStatsService, mCallback); when(mRaDaemon.start()).thenReturn(true); } @@ -158,8 +162,7 @@ public class IpServerTest { if (upstreamIface != null) { dispatchTetherConnectionChanged(upstreamIface); } - reset(mNMService, mStatsService, mCallback); - when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration); + reset(mNetd, mStatsService, mCallback); } @Before public void setUp() throws Exception { @@ -169,15 +172,14 @@ public class IpServerTest { @Test public void startsOutAvailable() { - mIpServer = new IpServer(IFACE_NAME, mLooper.getLooper(), - TETHERING_BLUETOOTH, mSharedLog, mNMService, mStatsService, mCallback, - false /* usingLegacyDhcp */, mDependencies); + mIpServer = new IpServer(IFACE_NAME, mLooper.getLooper(), TETHERING_BLUETOOTH, mSharedLog, + mNetd, mStatsService, mCallback, false /* usingLegacyDhcp */, mDependencies); mIpServer.start(); mLooper.dispatchAll(); verify(mCallback).updateInterfaceState( mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR); verify(mCallback).updateLinkProperties(eq(mIpServer), any(LinkProperties.class)); - verifyNoMoreInteractions(mCallback, mNMService, mStatsService); + verifyNoMoreInteractions(mCallback, mNetd, mStatsService); } @Test @@ -196,7 +198,7 @@ public class IpServerTest { // None of these commands should trigger us to request action from // the rest of the system. dispatchCommand(command); - verifyNoMoreInteractions(mNMService, mStatsService, mCallback); + verifyNoMoreInteractions(mNetd, mStatsService, mCallback); } } @@ -208,7 +210,7 @@ public class IpServerTest { verify(mCallback).updateInterfaceState( mIpServer, STATE_UNAVAILABLE, TETHER_ERROR_NO_ERROR); verify(mCallback).updateLinkProperties(eq(mIpServer), any(LinkProperties.class)); - verifyNoMoreInteractions(mNMService, mStatsService, mCallback); + verifyNoMoreInteractions(mNetd, mStatsService, mCallback); } @Test @@ -216,13 +218,17 @@ public class IpServerTest { initStateMachine(TETHERING_BLUETOOTH); dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED); - InOrder inOrder = inOrder(mCallback, mNMService); - inOrder.verify(mNMService).tetherInterface(IFACE_NAME); + InOrder inOrder = inOrder(mCallback, mNetd); + inOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME); + inOrder.verify(mNetd).networkAddInterface(INetd.LOCAL_NET_ID, IFACE_NAME); + // One for ipv4 route, one for ipv6 link local route. + inOrder.verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(IFACE_NAME), + any(), any()); inOrder.verify(mCallback).updateInterfaceState( mIpServer, STATE_TETHERED, TETHER_ERROR_NO_ERROR); inOrder.verify(mCallback).updateLinkProperties( eq(mIpServer), any(LinkProperties.class)); - verifyNoMoreInteractions(mNMService, mStatsService, mCallback); + verifyNoMoreInteractions(mNetd, mStatsService, mCallback); } @Test @@ -230,14 +236,16 @@ public class IpServerTest { initTetheredStateMachine(TETHERING_BLUETOOTH, null); dispatchCommand(IpServer.CMD_TETHER_UNREQUESTED); - InOrder inOrder = inOrder(mNMService, mNetd, mStatsService, mCallback); - inOrder.verify(mNMService).untetherInterface(IFACE_NAME); + InOrder inOrder = inOrder(mNetd, mStatsService, mCallback); + inOrder.verify(mNetd).tetherApplyDnsInterfaces(); + inOrder.verify(mNetd).tetherInterfaceRemove(IFACE_NAME); + inOrder.verify(mNetd).networkRemoveInterface(INetd.LOCAL_NET_ID, IFACE_NAME); inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg -> IFACE_NAME.equals(cfg.ifName))); inOrder.verify(mCallback).updateInterfaceState( mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR); inOrder.verify(mCallback).updateLinkProperties( eq(mIpServer), any(LinkProperties.class)); - verifyNoMoreInteractions(mNMService, mStatsService, mCallback); + verifyNoMoreInteractions(mNetd, mStatsService, mCallback); } @Test @@ -245,16 +253,19 @@ public class IpServerTest { initStateMachine(TETHERING_USB); dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED); - InOrder inOrder = inOrder(mCallback, mNMService); - inOrder.verify(mNMService).getInterfaceConfig(IFACE_NAME); - inOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration); - inOrder.verify(mNMService).tetherInterface(IFACE_NAME); + InOrder inOrder = inOrder(mCallback, mNetd); + inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg -> + IFACE_NAME.equals(cfg.ifName) && assertContainsFlag(cfg.flags, IF_STATE_UP))); + inOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME); + inOrder.verify(mNetd).networkAddInterface(INetd.LOCAL_NET_ID, IFACE_NAME); + inOrder.verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(IFACE_NAME), + any(), any()); inOrder.verify(mCallback).updateInterfaceState( mIpServer, STATE_TETHERED, TETHER_ERROR_NO_ERROR); inOrder.verify(mCallback).updateLinkProperties( eq(mIpServer), mLinkPropertiesCaptor.capture()); assertIPv4AddressAndDirectlyConnectedRoute(mLinkPropertiesCaptor.getValue()); - verifyNoMoreInteractions(mNMService, mStatsService, mCallback); + verifyNoMoreInteractions(mNetd, mStatsService, mCallback); } @Test @@ -262,16 +273,19 @@ public class IpServerTest { initStateMachine(TETHERING_WIFI_P2P); dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_LOCAL_ONLY); - InOrder inOrder = inOrder(mCallback, mNMService); - inOrder.verify(mNMService).getInterfaceConfig(IFACE_NAME); - inOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration); - inOrder.verify(mNMService).tetherInterface(IFACE_NAME); + InOrder inOrder = inOrder(mCallback, mNetd); + inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg -> + IFACE_NAME.equals(cfg.ifName) && assertContainsFlag(cfg.flags, IF_STATE_UP))); + inOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME); + inOrder.verify(mNetd).networkAddInterface(INetd.LOCAL_NET_ID, IFACE_NAME); + inOrder.verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(IFACE_NAME), + any(), any()); inOrder.verify(mCallback).updateInterfaceState( mIpServer, STATE_LOCAL_ONLY, TETHER_ERROR_NO_ERROR); inOrder.verify(mCallback).updateLinkProperties( eq(mIpServer), mLinkPropertiesCaptor.capture()); assertIPv4AddressAndDirectlyConnectedRoute(mLinkPropertiesCaptor.getValue()); - verifyNoMoreInteractions(mNMService, mStatsService, mCallback); + verifyNoMoreInteractions(mNetd, mStatsService, mCallback); } @Test @@ -281,10 +295,10 @@ public class IpServerTest { // Telling the state machine about its upstream interface triggers // a little more configuration. dispatchTetherConnectionChanged(UPSTREAM_IFACE); - InOrder inOrder = inOrder(mNMService); - inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE); - inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE); - verifyNoMoreInteractions(mNMService, mStatsService, mCallback); + InOrder inOrder = inOrder(mNetd); + inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE); + inOrder.verify(mNetd).ipfwdAddInterfaceForward(IFACE_NAME, UPSTREAM_IFACE); + verifyNoMoreInteractions(mNetd, mStatsService, mCallback); } @Test @@ -292,49 +306,49 @@ public class IpServerTest { initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE); dispatchTetherConnectionChanged(UPSTREAM_IFACE2); - InOrder inOrder = inOrder(mNMService, mStatsService); + InOrder inOrder = inOrder(mNetd, mStatsService); inOrder.verify(mStatsService).forceUpdate(); - inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE); - inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE); - inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2); - inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2); - verifyNoMoreInteractions(mNMService, mStatsService, mCallback); + inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE); + inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE); + inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2); + inOrder.verify(mNetd).ipfwdAddInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2); + verifyNoMoreInteractions(mNetd, mStatsService, mCallback); } @Test public void handlesChangingUpstreamNatFailure() throws Exception { initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE); - doThrow(RemoteException.class).when(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2); + doThrow(RemoteException.class).when(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2); dispatchTetherConnectionChanged(UPSTREAM_IFACE2); - InOrder inOrder = inOrder(mNMService, mStatsService); + InOrder inOrder = inOrder(mNetd, mStatsService); inOrder.verify(mStatsService).forceUpdate(); - inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE); - inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE); - inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2); + inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE); + inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE); + inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2); inOrder.verify(mStatsService).forceUpdate(); - inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2); - inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE2); + inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2); + inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE2); } @Test public void handlesChangingUpstreamInterfaceForwardingFailure() throws Exception { initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE); - doThrow(RemoteException.class).when(mNMService).startInterfaceForwarding( + doThrow(RemoteException.class).when(mNetd).ipfwdAddInterfaceForward( IFACE_NAME, UPSTREAM_IFACE2); dispatchTetherConnectionChanged(UPSTREAM_IFACE2); - InOrder inOrder = inOrder(mNMService, mStatsService); + InOrder inOrder = inOrder(mNetd, mStatsService); inOrder.verify(mStatsService).forceUpdate(); - inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE); - inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE); - inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2); - inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2); + inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE); + inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE); + inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2); + inOrder.verify(mNetd).ipfwdAddInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2); inOrder.verify(mStatsService).forceUpdate(); - inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2); - inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE2); + inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2); + inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE2); } @Test @@ -342,17 +356,19 @@ public class IpServerTest { initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE); dispatchCommand(IpServer.CMD_TETHER_UNREQUESTED); - InOrder inOrder = inOrder(mNMService, mNetd, mStatsService, mCallback); + InOrder inOrder = inOrder(mNetd, mStatsService, mCallback); inOrder.verify(mStatsService).forceUpdate(); - inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE); - inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE); - inOrder.verify(mNMService).untetherInterface(IFACE_NAME); + inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE); + inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE); + inOrder.verify(mNetd).tetherApplyDnsInterfaces(); + inOrder.verify(mNetd).tetherInterfaceRemove(IFACE_NAME); + inOrder.verify(mNetd).networkRemoveInterface(INetd.LOCAL_NET_ID, IFACE_NAME); inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg -> IFACE_NAME.equals(cfg.ifName))); inOrder.verify(mCallback).updateInterfaceState( mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR); inOrder.verify(mCallback).updateLinkProperties( eq(mIpServer), any(LinkProperties.class)); - verifyNoMoreInteractions(mNMService, mStatsService, mCallback); + verifyNoMoreInteractions(mNetd, mStatsService, mCallback); } @Test @@ -361,13 +377,14 @@ public class IpServerTest { initTetheredStateMachine(TETHERING_USB, null); if (shouldThrow) { - doThrow(RemoteException.class).when(mNMService).untetherInterface(IFACE_NAME); + doThrow(RemoteException.class).when(mNetd).tetherInterfaceRemove(IFACE_NAME); } dispatchCommand(IpServer.CMD_INTERFACE_DOWN); - InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration, mCallback); - usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown(); - usbTeardownOrder.verify(mNMService).setInterfaceConfig( - IFACE_NAME, mInterfaceConfiguration); + InOrder usbTeardownOrder = inOrder(mNetd, mCallback); + // Currently IpServer interfaceSetCfg twice to stop IPv4. One just set interface down + // Another one is set IPv4 to 0.0.0.0/0 as clearng ipv4 address. + usbTeardownOrder.verify(mNetd, times(2)).interfaceSetCfg( + argThat(cfg -> IFACE_NAME.equals(cfg.ifName))); usbTeardownOrder.verify(mCallback).updateInterfaceState( mIpServer, STATE_UNAVAILABLE, TETHER_ERROR_NO_ERROR); usbTeardownOrder.verify(mCallback).updateLinkProperties( @@ -380,12 +397,15 @@ public class IpServerTest { public void usbShouldBeTornDownOnTetherError() throws Exception { initStateMachine(TETHERING_USB); - doThrow(RemoteException.class).when(mNMService).tetherInterface(IFACE_NAME); + doThrow(RemoteException.class).when(mNetd).tetherInterfaceAdd(IFACE_NAME); dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED); - InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration, mCallback); - usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown(); - usbTeardownOrder.verify(mNMService).setInterfaceConfig( - IFACE_NAME, mInterfaceConfiguration); + InOrder usbTeardownOrder = inOrder(mNetd, mCallback); + usbTeardownOrder.verify(mNetd).interfaceSetCfg( + argThat(cfg -> IFACE_NAME.equals(cfg.ifName))); + usbTeardownOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME); + + usbTeardownOrder.verify(mNetd, times(2)).interfaceSetCfg( + argThat(cfg -> IFACE_NAME.equals(cfg.ifName))); usbTeardownOrder.verify(mCallback).updateInterfaceState( mIpServer, STATE_AVAILABLE, TETHER_ERROR_TETHER_IFACE_ERROR); usbTeardownOrder.verify(mCallback).updateLinkProperties( @@ -397,11 +417,13 @@ public class IpServerTest { public void shouldTearDownUsbOnUpstreamError() throws Exception { initTetheredStateMachine(TETHERING_USB, null); - doThrow(RemoteException.class).when(mNMService).enableNat(anyString(), anyString()); + doThrow(RemoteException.class).when(mNetd).tetherAddForward(anyString(), anyString()); dispatchTetherConnectionChanged(UPSTREAM_IFACE); - InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration, mCallback); - usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown(); - usbTeardownOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration); + InOrder usbTeardownOrder = inOrder(mNetd, mCallback); + usbTeardownOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE); + + usbTeardownOrder.verify(mNetd, times(2)).interfaceSetCfg( + argThat(cfg -> IFACE_NAME.equals(cfg.ifName))); usbTeardownOrder.verify(mCallback).updateInterfaceState( mIpServer, STATE_AVAILABLE, TETHER_ERROR_ENABLE_NAT_ERROR); usbTeardownOrder.verify(mCallback).updateLinkProperties( @@ -413,11 +435,11 @@ public class IpServerTest { public void ignoresDuplicateUpstreamNotifications() throws Exception { initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE); - verifyNoMoreInteractions(mNMService, mStatsService, mCallback); + verifyNoMoreInteractions(mNetd, mStatsService, mCallback); for (int i = 0; i < 5; i++) { dispatchTetherConnectionChanged(UPSTREAM_IFACE); - verifyNoMoreInteractions(mNMService, mStatsService, mCallback); + verifyNoMoreInteractions(mNetd, mStatsService, mCallback); } } @@ -510,8 +532,10 @@ public class IpServerTest { } assertNotNull("missing IPv4 address", addr4); + final IpPrefix destination = new IpPrefix(addr4.getAddress(), addr4.getPrefixLength()); // Assert the presence of the associated directly connected route. - final RouteInfo directlyConnected = new RouteInfo(addr4, null, lp.getInterfaceName()); + final RouteInfo directlyConnected = new RouteInfo(destination, null, lp.getInterfaceName(), + RouteInfo.RTN_UNICAST); assertTrue("missing directly connected route: '" + directlyConnected.toString() + "'", lp.getRoutes().contains(directlyConnected)); } @@ -523,4 +547,12 @@ public class IpServerTest { // never see an empty interface name in any LinkProperties update. assertFalse(TextUtils.isEmpty(lp.getInterfaceName())); } + + private boolean assertContainsFlag(String[] flags, String match) { + for (String flag : flags) { + if (flag.equals(match)) return true; + } + fail("Missing flag: " + match); + return false; + } } diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java index 99cf9e90d912..66eba9ae3b7a 100644 --- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java +++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java @@ -16,12 +16,12 @@ package com.android.server.connectivity.tethering; -import static android.net.ConnectivityManager.TETHERING_BLUETOOTH; -import static android.net.ConnectivityManager.TETHERING_USB; -import static android.net.ConnectivityManager.TETHERING_WIFI; -import static android.net.ConnectivityManager.TETHER_ERROR_ENTITLEMENT_UNKONWN; -import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR; -import static android.net.ConnectivityManager.TETHER_ERROR_PROVISION_FAILED; +import static android.net.TetheringManager.TETHERING_BLUETOOTH; +import static android.net.TetheringManager.TETHERING_USB; +import static android.net.TetheringManager.TETHERING_WIFI; +import static android.net.TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKONWN; +import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR; +import static android.net.TetheringManager.TETHER_ERROR_PROVISION_FAILED; import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/OffloadControllerTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/OffloadControllerTest.java index 8574f5401496..7886ca6c132d 100644 --- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/OffloadControllerTest.java +++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/OffloadControllerTest.java @@ -21,6 +21,7 @@ import static android.net.NetworkStats.STATS_PER_IFACE; import static android.net.NetworkStats.STATS_PER_UID; import static android.net.NetworkStats.TAG_NONE; import static android.net.NetworkStats.UID_ALL; +import static android.net.RouteInfo.RTN_UNICAST; import static android.net.TrafficStats.UID_TETHERING; import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED; @@ -269,7 +270,7 @@ public class OffloadControllerTest { final String ipv4Addr = "192.0.2.5"; final String linkAddr = ipv4Addr + "/24"; lp.addLinkAddress(new LinkAddress(linkAddr)); - lp.addRoute(new RouteInfo(new IpPrefix("192.0.2.0/24"))); + lp.addRoute(new RouteInfo(new IpPrefix("192.0.2.0/24"), null, null, RTN_UNICAST)); offload.setUpstreamLinkProperties(lp); // IPv4 prefixes and addresses on the upstream are simply left as whole // prefixes (already passed in from UpstreamNetworkMonitor code). If a @@ -285,7 +286,7 @@ public class OffloadControllerTest { inOrder.verifyNoMoreInteractions(); final String ipv4Gateway = "192.0.2.1"; - lp.addRoute(new RouteInfo(InetAddress.getByName(ipv4Gateway))); + lp.addRoute(new RouteInfo(null, InetAddress.getByName(ipv4Gateway), null, RTN_UNICAST)); offload.setUpstreamLinkProperties(lp); // No change in local addresses means no call to setLocalPrefixes(). inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture()); @@ -296,7 +297,7 @@ public class OffloadControllerTest { inOrder.verifyNoMoreInteractions(); final String ipv6Gw1 = "fe80::cafe"; - lp.addRoute(new RouteInfo(InetAddress.getByName(ipv6Gw1))); + lp.addRoute(new RouteInfo(null, InetAddress.getByName(ipv6Gw1), null, RTN_UNICAST)); offload.setUpstreamLinkProperties(lp); // No change in local addresses means no call to setLocalPrefixes(). inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture()); @@ -310,7 +311,7 @@ public class OffloadControllerTest { inOrder.verifyNoMoreInteractions(); final String ipv6Gw2 = "fe80::d00d"; - lp.addRoute(new RouteInfo(InetAddress.getByName(ipv6Gw2))); + lp.addRoute(new RouteInfo(null, InetAddress.getByName(ipv6Gw2), null, RTN_UNICAST)); offload.setUpstreamLinkProperties(lp); // No change in local addresses means no call to setLocalPrefixes(). inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture()); @@ -327,8 +328,10 @@ public class OffloadControllerTest { final LinkProperties stacked = new LinkProperties(); stacked.setInterfaceName("stacked"); stacked.addLinkAddress(new LinkAddress("192.0.2.129/25")); - stacked.addRoute(new RouteInfo(InetAddress.getByName("192.0.2.254"))); - stacked.addRoute(new RouteInfo(InetAddress.getByName("fe80::bad:f00"))); + stacked.addRoute(new RouteInfo(null, InetAddress.getByName("192.0.2.254"), null, + RTN_UNICAST)); + stacked.addRoute(new RouteInfo(null, InetAddress.getByName("fe80::bad:f00"), null, + RTN_UNICAST)); assertTrue(lp.addStackedLink(stacked)); offload.setUpstreamLinkProperties(lp); // No change in local addresses means no call to setLocalPrefixes(). @@ -348,7 +351,7 @@ public class OffloadControllerTest { // removed from "local prefixes" and /128s added for the upstream IPv6 // addresses. This is not yet implemented, and for now we simply // expect to see these /128s. - lp.addRoute(new RouteInfo(new IpPrefix("2001:db8::/64"))); + lp.addRoute(new RouteInfo(new IpPrefix("2001:db8::/64"), null, null, RTN_UNICAST)); // "2001:db8::/64" plus "assigned" ASCII in hex lp.addLinkAddress(new LinkAddress("2001:db8::6173:7369:676e:6564/64")); // "2001:db8::/64" plus "random" ASCII in hex @@ -574,13 +577,15 @@ public class OffloadControllerTest { final LinkProperties usbLinkProperties = new LinkProperties(); usbLinkProperties.setInterfaceName(RNDIS0); usbLinkProperties.addLinkAddress(new LinkAddress("192.168.42.1/24")); - usbLinkProperties.addRoute(new RouteInfo(new IpPrefix(USB_PREFIX))); + usbLinkProperties.addRoute( + new RouteInfo(new IpPrefix(USB_PREFIX), null, null, RTN_UNICAST)); offload.notifyDownstreamLinkProperties(usbLinkProperties); inOrder.verify(mHardware, times(1)).addDownstreamPrefix(RNDIS0, USB_PREFIX); inOrder.verifyNoMoreInteractions(); // [2] Routes for IPv6 link-local prefixes should never be added. - usbLinkProperties.addRoute(new RouteInfo(new IpPrefix(IPV6_LINKLOCAL))); + usbLinkProperties.addRoute( + new RouteInfo(new IpPrefix(IPV6_LINKLOCAL), null, null, RTN_UNICAST)); offload.notifyDownstreamLinkProperties(usbLinkProperties); inOrder.verify(mHardware, never()).addDownstreamPrefix(eq(RNDIS0), anyString()); inOrder.verifyNoMoreInteractions(); @@ -588,7 +593,8 @@ public class OffloadControllerTest { // [3] Add an IPv6 prefix for good measure. Only new offload-able // prefixes should be passed to the HAL. usbLinkProperties.addLinkAddress(new LinkAddress("2001:db8::1/64")); - usbLinkProperties.addRoute(new RouteInfo(new IpPrefix(IPV6_DOC_PREFIX))); + usbLinkProperties.addRoute( + new RouteInfo(new IpPrefix(IPV6_DOC_PREFIX), null, null, RTN_UNICAST)); offload.notifyDownstreamLinkProperties(usbLinkProperties); inOrder.verify(mHardware, times(1)).addDownstreamPrefix(RNDIS0, IPV6_DOC_PREFIX); inOrder.verifyNoMoreInteractions(); @@ -601,8 +607,10 @@ public class OffloadControllerTest { // [5] Differences in local routes are converted into addDownstream() // and removeDownstream() invocations accordingly. - usbLinkProperties.removeRoute(new RouteInfo(new IpPrefix(IPV6_DOC_PREFIX), null, RNDIS0)); - usbLinkProperties.addRoute(new RouteInfo(new IpPrefix(IPV6_DISCARD_PREFIX))); + usbLinkProperties.removeRoute( + new RouteInfo(new IpPrefix(IPV6_DOC_PREFIX), null, RNDIS0, RTN_UNICAST)); + usbLinkProperties.addRoute( + new RouteInfo(new IpPrefix(IPV6_DISCARD_PREFIX), null, null, RTN_UNICAST)); offload.notifyDownstreamLinkProperties(usbLinkProperties); inOrder.verify(mHardware, times(1)).removeDownstreamPrefix(RNDIS0, IPV6_DOC_PREFIX); inOrder.verify(mHardware, times(1)).addDownstreamPrefix(RNDIS0, IPV6_DISCARD_PREFIX); @@ -680,19 +688,23 @@ public class OffloadControllerTest { final LinkProperties usbLinkProperties = new LinkProperties(); usbLinkProperties.setInterfaceName(RNDIS0); usbLinkProperties.addLinkAddress(new LinkAddress("192.168.42.1/24")); - usbLinkProperties.addRoute(new RouteInfo(new IpPrefix(USB_PREFIX))); + usbLinkProperties.addRoute( + new RouteInfo(new IpPrefix(USB_PREFIX), null, null, RTN_UNICAST)); offload.notifyDownstreamLinkProperties(usbLinkProperties); final LinkProperties wifiLinkProperties = new LinkProperties(); wifiLinkProperties.setInterfaceName(WLAN0); wifiLinkProperties.addLinkAddress(new LinkAddress("192.168.43.1/24")); - wifiLinkProperties.addRoute(new RouteInfo(new IpPrefix(WIFI_PREFIX))); - wifiLinkProperties.addRoute(new RouteInfo(new IpPrefix(IPV6_LINKLOCAL))); + wifiLinkProperties.addRoute( + new RouteInfo(new IpPrefix(WIFI_PREFIX), null, null, RTN_UNICAST)); + wifiLinkProperties.addRoute( + new RouteInfo(new IpPrefix(IPV6_LINKLOCAL), null, null, RTN_UNICAST)); // Use a benchmark prefix (RFC 5180 + erratum), since the documentation // prefix is included in the excluded prefix list. wifiLinkProperties.addLinkAddress(new LinkAddress("2001:2::1/64")); wifiLinkProperties.addLinkAddress(new LinkAddress("2001:2::2/64")); - wifiLinkProperties.addRoute(new RouteInfo(new IpPrefix("2001:2::/64"))); + wifiLinkProperties.addRoute( + new RouteInfo(new IpPrefix("2001:2::/64"), null, null, RTN_UNICAST)); offload.notifyDownstreamLinkProperties(wifiLinkProperties); offload.removeDownstreamInterface(RNDIS0); diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java index 30bff3560955..7799da4b94a7 100644 --- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java +++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java @@ -34,7 +34,6 @@ import static com.android.internal.R.array.config_tether_wifi_regexs; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.anyInt; import static org.mockito.Mockito.when; import android.content.ContentResolver; @@ -145,7 +144,7 @@ public class TetheringConfigurationTest { @Test public void testDunFromTelephonyManagerMeansDun() { - when(mTelephonyManager.isTetheringApnRequired(anyInt())).thenReturn(true); + when(mTelephonyManager.isTetheringApnRequired()).thenReturn(true); final TetheringConfiguration cfgWifi = getTetheringConfiguration(TYPE_WIFI); final TetheringConfiguration cfgMobileWifiHipri = getTetheringConfiguration( @@ -169,7 +168,7 @@ public class TetheringConfigurationTest { @Test public void testDunNotRequiredFromTelephonyManagerMeansNoDun() { - when(mTelephonyManager.isTetheringApnRequired(anyInt())).thenReturn(false); + when(mTelephonyManager.isTetheringApnRequired()).thenReturn(false); final TetheringConfiguration cfgWifi = getTetheringConfiguration(TYPE_WIFI); final TetheringConfiguration cfgMobileWifiHipri = getTetheringConfiguration( @@ -212,7 +211,7 @@ public class TetheringConfigurationTest { @Test public void testNoDefinedUpstreamTypesAddsEthernet() { when(mResources.getIntArray(config_tether_upstream_types)).thenReturn(new int[]{}); - when(mTelephonyManager.isTetheringApnRequired(anyInt())).thenReturn(false); + when(mTelephonyManager.isTetheringApnRequired()).thenReturn(false); final TetheringConfiguration cfg = new TetheringConfiguration( mMockContext, mLog, INVALID_SUBSCRIPTION_ID); @@ -235,7 +234,7 @@ public class TetheringConfigurationTest { public void testDefinedUpstreamTypesSansEthernetAddsEthernet() { when(mResources.getIntArray(config_tether_upstream_types)).thenReturn( new int[]{TYPE_WIFI, TYPE_MOBILE_HIPRI}); - when(mTelephonyManager.isTetheringApnRequired(anyInt())).thenReturn(false); + when(mTelephonyManager.isTetheringApnRequired()).thenReturn(false); final TetheringConfiguration cfg = new TetheringConfiguration( mMockContext, mLog, INVALID_SUBSCRIPTION_ID); @@ -253,7 +252,7 @@ public class TetheringConfigurationTest { public void testDefinedUpstreamTypesWithEthernetDoesNotAddEthernet() { when(mResources.getIntArray(config_tether_upstream_types)) .thenReturn(new int[]{TYPE_WIFI, TYPE_ETHERNET, TYPE_MOBILE_HIPRI}); - when(mTelephonyManager.isTetheringApnRequired(anyInt())).thenReturn(false); + when(mTelephonyManager.isTetheringApnRequired()).thenReturn(false); final TetheringConfiguration cfg = new TetheringConfiguration( mMockContext, mLog, INVALID_SUBSCRIPTION_ID); diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java index 9d9ad10af5b3..7af48a89d87c 100644 --- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java +++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java @@ -19,15 +19,15 @@ package com.android.server.connectivity.tethering; import static android.hardware.usb.UsbManager.USB_CONFIGURED; import static android.hardware.usb.UsbManager.USB_CONNECTED; import static android.hardware.usb.UsbManager.USB_FUNCTION_RNDIS; -import static android.net.ConnectivityManager.ACTION_TETHER_STATE_CHANGED; -import static android.net.ConnectivityManager.EXTRA_ACTIVE_LOCAL_ONLY; -import static android.net.ConnectivityManager.EXTRA_ACTIVE_TETHER; -import static android.net.ConnectivityManager.EXTRA_AVAILABLE_TETHER; -import static android.net.ConnectivityManager.TETHERING_USB; -import static android.net.ConnectivityManager.TETHERING_WIFI; -import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR; -import static android.net.ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE; -import static android.net.ConnectivityManager.TYPE_WIFI_P2P; +import static android.net.RouteInfo.RTN_UNICAST; +import static android.net.TetheringManager.ACTION_TETHER_STATE_CHANGED; +import static android.net.TetheringManager.EXTRA_ACTIVE_LOCAL_ONLY; +import static android.net.TetheringManager.EXTRA_ACTIVE_TETHER; +import static android.net.TetheringManager.EXTRA_AVAILABLE_TETHER; +import static android.net.TetheringManager.TETHERING_USB; +import static android.net.TetheringManager.TETHERING_WIFI; +import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR; +import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_IFACE; import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS; import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME; import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_MODE; @@ -49,7 +49,6 @@ import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.any; -import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -72,16 +71,15 @@ import android.net.INetd; import android.net.INetworkPolicyManager; import android.net.INetworkStatsService; import android.net.ITetheringEventCallback; -import android.net.InterfaceConfiguration; +import android.net.InetAddresses; +import android.net.InterfaceConfigurationParcel; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties; import android.net.MacAddress; import android.net.Network; import android.net.NetworkCapabilities; -import android.net.NetworkInfo; import android.net.NetworkRequest; -import android.net.NetworkUtils; import android.net.RouteInfo; import android.net.TetherStatesParcel; import android.net.TetheringConfigurationParcel; @@ -146,6 +144,7 @@ public class TetheringTest { private static final String TEST_USB_IFNAME = "test_rndis0"; private static final String TEST_WLAN_IFNAME = "test_wlan0"; private static final String TEST_P2P_IFNAME = "test_p2p-p2p0-0"; + private static final String TETHERING_NAME = "Tethering"; private static final int DHCPSERVER_START_TIMEOUT_MS = 1000; @@ -184,6 +183,7 @@ public class TetheringTest { private BroadcastReceiver mBroadcastReceiver; private Tethering mTethering; private PhoneStateListener mPhoneStateListener; + private InterfaceConfigurationParcel mInterfaceConfiguration; private class TestContext extends BroadcastInterceptingContext { TestContext(Context base) { @@ -247,11 +247,6 @@ public class TetheringTest { } @Override - public INetd getNetdService() { - return mNetd; - } - - @Override public void makeDhcpServer(String ifName, DhcpServingParamsParcel params, DhcpServerCallbacks cb) { new Thread(() -> { @@ -365,23 +360,26 @@ public class TetheringTest { if (withIPv4) { prop.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), - NetworkUtils.numericToInetAddress("10.0.0.1"), TEST_MOBILE_IFNAME)); + InetAddresses.parseNumericAddress("10.0.0.1"), + TEST_MOBILE_IFNAME, RTN_UNICAST)); } if (withIPv6) { - prop.addDnsServer(NetworkUtils.numericToInetAddress("2001:db8::2")); + prop.addDnsServer(InetAddresses.parseNumericAddress("2001:db8::2")); prop.addLinkAddress( - new LinkAddress(NetworkUtils.numericToInetAddress("2001:db8::"), + new LinkAddress(InetAddresses.parseNumericAddress("2001:db8::"), NetworkConstants.RFC7421_PREFIX_LENGTH)); prop.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), - NetworkUtils.numericToInetAddress("2001:db8::1"), TEST_MOBILE_IFNAME)); + InetAddresses.parseNumericAddress("2001:db8::1"), + TEST_MOBILE_IFNAME, RTN_UNICAST)); } if (with464xlat) { final LinkProperties stackedLink = new LinkProperties(); stackedLink.setInterfaceName(TEST_XLAT_MOBILE_IFNAME); stackedLink.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), - NetworkUtils.numericToInetAddress("192.0.0.1"), TEST_XLAT_MOBILE_IFNAME)); + InetAddresses.parseNumericAddress("192.0.0.1"), + TEST_XLAT_MOBILE_IFNAME, RTN_UNICAST)); prop.addStackedLink(stackedLink); } @@ -425,11 +423,11 @@ public class TetheringTest { .thenReturn(new int[0]); when(mResources.getBoolean(com.android.internal.R.bool.config_tether_upstream_automatic)) .thenReturn(false); - when(mNMService.listInterfaces()) + when(mNetd.interfaceGetList()) .thenReturn(new String[] { TEST_MOBILE_IFNAME, TEST_WLAN_IFNAME, TEST_USB_IFNAME, TEST_P2P_IFNAME}); - when(mNMService.getInterfaceConfig(anyString())) - .thenReturn(new InterfaceConfiguration()); + mInterfaceConfiguration = new InterfaceConfigurationParcel(); + mInterfaceConfiguration.flags = new String[0]; when(mRouterAdvertisementDaemon.start()) .thenReturn(true); @@ -491,15 +489,12 @@ public class TetheringTest { p2pInfo.groupFormed = isGroupFormed; p2pInfo.isGroupOwner = isGroupOwner; - NetworkInfo networkInfo = new NetworkInfo(TYPE_WIFI_P2P, 0, null, null); - WifiP2pGroup group = new WifiP2pGroup(); group.setIsGroupOwner(isGroupOwner); group.setInterface(ifname); final Intent intent = new Intent(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION); intent.putExtra(WifiP2pManager.EXTRA_WIFI_P2P_INFO, p2pInfo); - intent.putExtra(WifiP2pManager.EXTRA_NETWORK_INFO, networkInfo); intent.putExtra(WifiP2pManager.EXTRA_WIFI_P2P_GROUP, group); mServiceContext.sendBroadcastAsUserMultiplePermissions(intent, UserHandle.ALL, P2P_RECEIVER_PERMISSIONS_FOR_BROADCAST); @@ -519,10 +514,11 @@ public class TetheringTest { } private void verifyInterfaceServingModeStarted(String ifname) throws Exception { - verify(mNMService, times(1)).getInterfaceConfig(ifname); - verify(mNMService, times(1)) - .setInterfaceConfig(eq(ifname), any(InterfaceConfiguration.class)); - verify(mNMService, times(1)).tetherInterface(ifname); + verify(mNetd, times(1)).interfaceSetCfg(any(InterfaceConfigurationParcel.class)); + verify(mNetd, times(1)).tetherInterfaceAdd(ifname); + verify(mNetd, times(1)).networkAddInterface(INetd.LOCAL_NET_ID, ifname); + verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(ifname), + anyString(), anyString()); } private void verifyTetheringBroadcast(String ifname, String whichExtra) { @@ -554,7 +550,7 @@ public class TetheringTest { verify(mWifiManager).updateInterfaceIpState( TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED); } - verifyNoMoreInteractions(mNMService); + verifyNoMoreInteractions(mNetd); verifyNoMoreInteractions(mWifiManager); } @@ -577,14 +573,14 @@ public class TetheringTest { prepareUsbTethering(upstreamState); // This should produce no activity of any kind. - verifyNoMoreInteractions(mNMService); + verifyNoMoreInteractions(mNetd); // Pretend we then receive USB configured broadcast. sendUsbBroadcast(true, true, true); mLooper.dispatchAll(); // Now we should see the start of tethering mechanics (in this case: // tetherMatchingInterfaces() which starts by fetching all interfaces). - verify(mNMService, times(1)).listInterfaces(); + verify(mNetd, times(1)).interfaceGetList(); // UpstreamNetworkMonitor should receive selected upstream verify(mUpstreamNetworkMonitor, times(1)).selectPreferredUpstreamType(any()); @@ -614,9 +610,9 @@ public class TetheringTest { verifyInterfaceServingModeStarted(TEST_WLAN_IFNAME); verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER); - verify(mNMService, times(1)).setIpForwardingEnabled(true); - verify(mNMService, times(1)).startTethering(any(String[].class)); - verifyNoMoreInteractions(mNMService); + verify(mNetd, times(1)).ipfwdEnableForwarding(TETHERING_NAME); + verify(mNetd, times(1)).tetherStartWithConfiguration(any()); + verifyNoMoreInteractions(mNetd); verify(mWifiManager).updateInterfaceIpState( TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED); verify(mWifiManager).updateInterfaceIpState( @@ -634,16 +630,16 @@ public class TetheringTest { mTethering.interfaceRemoved(TEST_WLAN_IFNAME); mLooper.dispatchAll(); - verify(mNMService, times(1)).untetherInterface(TEST_WLAN_IFNAME); - // {g,s}etInterfaceConfig() called twice for enabling and disabling IPv4. - verify(mNMService, times(2)).getInterfaceConfig(TEST_WLAN_IFNAME); - verify(mNMService, times(2)) - .setInterfaceConfig(eq(TEST_WLAN_IFNAME), any(InterfaceConfiguration.class)); - verify(mNMService, times(1)).stopTethering(); - verify(mNMService, times(1)).setIpForwardingEnabled(false); + verify(mNetd, times(1)).tetherApplyDnsInterfaces(); + verify(mNetd, times(1)).tetherInterfaceRemove(TEST_WLAN_IFNAME); + verify(mNetd, times(1)).networkRemoveInterface(INetd.LOCAL_NET_ID, TEST_WLAN_IFNAME); + // interfaceSetCfg() called once for enabling and twice disabling IPv4. + verify(mNetd, times(3)).interfaceSetCfg(any(InterfaceConfigurationParcel.class)); + verify(mNetd, times(1)).tetherStop(); + verify(mNetd, times(1)).ipfwdDisableForwarding(TETHERING_NAME); verify(mWifiManager, times(3)).updateInterfaceIpState( TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED); - verifyNoMoreInteractions(mNMService); + verifyNoMoreInteractions(mNetd); verifyNoMoreInteractions(mWifiManager); // Asking for the last error after the per-interface state machine // has been reaped yields an unknown interface error. @@ -680,8 +676,8 @@ public class TetheringTest { UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState(); runUsbTethering(upstreamState); - verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); - verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); + verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); + verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); sendIPv6TetherUpdates(upstreamState); verify(mRouterAdvertisementDaemon, never()).buildNewRa(any(), notNull()); @@ -704,8 +700,8 @@ public class TetheringTest { UpstreamNetworkState upstreamState = buildMobileIPv6UpstreamState(); runUsbTethering(upstreamState); - verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); - verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); + verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); + verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); sendIPv6TetherUpdates(upstreamState); verify(mRouterAdvertisementDaemon, times(1)).buildNewRa(any(), notNull()); @@ -717,8 +713,8 @@ public class TetheringTest { UpstreamNetworkState upstreamState = buildMobileDualStackUpstreamState(); runUsbTethering(upstreamState); - verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); - verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); + verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); + verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); verify(mRouterAdvertisementDaemon, times(1)).start(); verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any()); @@ -732,12 +728,11 @@ public class TetheringTest { UpstreamNetworkState upstreamState = buildMobile464xlatUpstreamState(); runUsbTethering(upstreamState); - verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_XLAT_MOBILE_IFNAME); - verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); + verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_XLAT_MOBILE_IFNAME); + verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any()); - verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); - verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, - TEST_XLAT_MOBILE_IFNAME); + verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_XLAT_MOBILE_IFNAME); + verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); sendIPv6TetherUpdates(upstreamState); verify(mRouterAdvertisementDaemon, times(1)).buildNewRa(any(), notNull()); @@ -750,9 +745,9 @@ public class TetheringTest { UpstreamNetworkState upstreamState = buildMobileIPv6UpstreamState(); runUsbTethering(upstreamState); - verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); + verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any()); - verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); + verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); // Then 464xlat comes up upstreamState = buildMobile464xlatUpstreamState(); @@ -768,12 +763,11 @@ public class TetheringTest { mLooper.dispatchAll(); // Forwarding is added for 464xlat - verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_XLAT_MOBILE_IFNAME); - verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, - TEST_XLAT_MOBILE_IFNAME); + verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_XLAT_MOBILE_IFNAME); + verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_XLAT_MOBILE_IFNAME); // Forwarding was not re-added for v6 (still times(1)) - verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); - verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); + verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); + verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); // DHCP not restarted on downstream (still times(1)) verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any()); } @@ -816,7 +810,7 @@ public class TetheringTest { mLooper.dispatchAll(); verify(mWifiManager, times(1)).startTetheredHotspot(null); verifyNoMoreInteractions(mWifiManager); - verifyNoMoreInteractions(mNMService); + verifyNoMoreInteractions(mNetd); // Emulate externally-visible WifiManager effects, causing the // per-interface state machine to start up, and telling us that @@ -829,7 +823,7 @@ public class TetheringTest { verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER); verify(mWifiManager).updateInterfaceIpState( TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED); - verifyNoMoreInteractions(mNMService); + verifyNoMoreInteractions(mNetd); verifyNoMoreInteractions(mWifiManager); } @@ -843,7 +837,7 @@ public class TetheringTest { mLooper.dispatchAll(); verify(mWifiManager, times(1)).startTetheredHotspot(null); verifyNoMoreInteractions(mWifiManager); - verifyNoMoreInteractions(mNMService); + verifyNoMoreInteractions(mNetd); // Emulate externally-visible WifiManager effects, causing the // per-interface state machine to start up, and telling us that @@ -854,9 +848,11 @@ public class TetheringTest { verifyInterfaceServingModeStarted(TEST_WLAN_IFNAME); verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER); - verify(mNMService, times(1)).setIpForwardingEnabled(true); - verify(mNMService, times(1)).startTethering(any(String[].class)); - verifyNoMoreInteractions(mNMService); + verify(mNetd, times(1)).ipfwdEnableForwarding(TETHERING_NAME); + verify(mNetd, times(1)).tetherStartWithConfiguration(any()); + verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(TEST_WLAN_IFNAME), + anyString(), anyString()); + verifyNoMoreInteractions(mNetd); verify(mWifiManager).updateInterfaceIpState( TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED); verify(mWifiManager).updateInterfaceIpState( @@ -874,8 +870,8 @@ public class TetheringTest { ///// // We do not currently emulate any upstream being found. // - // This is why there are no calls to verify mNMService.enableNat() or - // mNMService.startInterfaceForwarding(). + // This is why there are no calls to verify mNetd.tetherAddForward() or + // mNetd.ipfwdAddInterfaceForward(). ///// // Emulate pressing the WiFi tethering button. @@ -883,7 +879,7 @@ public class TetheringTest { mLooper.dispatchAll(); verify(mWifiManager, times(1)).stopSoftAp(); verifyNoMoreInteractions(mWifiManager); - verifyNoMoreInteractions(mNMService); + verifyNoMoreInteractions(mNetd); // Emulate externally-visible WifiManager effects, when tethering mode // is being torn down. @@ -891,16 +887,16 @@ public class TetheringTest { mTethering.interfaceRemoved(TEST_WLAN_IFNAME); mLooper.dispatchAll(); - verify(mNMService, times(1)).untetherInterface(TEST_WLAN_IFNAME); - // {g,s}etInterfaceConfig() called twice for enabling and disabling IPv4. - verify(mNMService, atLeastOnce()).getInterfaceConfig(TEST_WLAN_IFNAME); - verify(mNMService, atLeastOnce()) - .setInterfaceConfig(eq(TEST_WLAN_IFNAME), any(InterfaceConfiguration.class)); - verify(mNMService, times(1)).stopTethering(); - verify(mNMService, times(1)).setIpForwardingEnabled(false); + verify(mNetd, times(1)).tetherApplyDnsInterfaces(); + verify(mNetd, times(1)).tetherInterfaceRemove(TEST_WLAN_IFNAME); + verify(mNetd, times(1)).networkRemoveInterface(INetd.LOCAL_NET_ID, TEST_WLAN_IFNAME); + // interfaceSetCfg() called once for enabling and twice for disabling IPv4. + verify(mNetd, times(3)).interfaceSetCfg(any(InterfaceConfigurationParcel.class)); + verify(mNetd, times(1)).tetherStop(); + verify(mNetd, times(1)).ipfwdDisableForwarding(TETHERING_NAME); verify(mWifiManager, times(3)).updateInterfaceIpState( TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED); - verifyNoMoreInteractions(mNMService); + verifyNoMoreInteractions(mNetd); verifyNoMoreInteractions(mWifiManager); // Asking for the last error after the per-interface state machine // has been reaped yields an unknown interface error. @@ -911,14 +907,14 @@ public class TetheringTest { @Test public void failureEnablingIpForwarding() throws Exception { when(mWifiManager.startTetheredHotspot(any(SoftApConfiguration.class))).thenReturn(true); - doThrow(new RemoteException()).when(mNMService).setIpForwardingEnabled(true); + doThrow(new RemoteException()).when(mNetd).ipfwdEnableForwarding(TETHERING_NAME); // Emulate pressing the WiFi tethering button. mTethering.startTethering(TETHERING_WIFI, null, false); mLooper.dispatchAll(); verify(mWifiManager, times(1)).startTetheredHotspot(null); verifyNoMoreInteractions(mWifiManager); - verifyNoMoreInteractions(mNMService); + verifyNoMoreInteractions(mNetd); // Emulate externally-visible WifiManager effects, causing the // per-interface state machine to start up, and telling us that @@ -927,15 +923,15 @@ public class TetheringTest { sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED); mLooper.dispatchAll(); - // We verify get/set called thrice here: twice for setup (on NMService) and once during - // teardown (on Netd) because all events happen over the course of the single + // We verify get/set called three times here: twice for setup and once during + // teardown because all events happen over the course of the single // dispatchAll() above. Note that once the IpServer IPv4 address config // code is refactored the two calls during shutdown will revert to one. - verify(mNMService, times(2)).getInterfaceConfig(TEST_WLAN_IFNAME); - verify(mNMService, times(2)) - .setInterfaceConfig(eq(TEST_WLAN_IFNAME), any(InterfaceConfiguration.class)); - verify(mNetd, times(1)).interfaceSetCfg(argThat(p -> TEST_WLAN_IFNAME.equals(p.ifName))); - verify(mNMService, times(1)).tetherInterface(TEST_WLAN_IFNAME); + verify(mNetd, times(3)).interfaceSetCfg(argThat(p -> TEST_WLAN_IFNAME.equals(p.ifName))); + verify(mNetd, times(1)).tetherInterfaceAdd(TEST_WLAN_IFNAME); + verify(mNetd, times(1)).networkAddInterface(INetd.LOCAL_NET_ID, TEST_WLAN_IFNAME); + verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(TEST_WLAN_IFNAME), + anyString(), anyString()); verify(mWifiManager).updateInterfaceIpState( TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED); verify(mWifiManager).updateInterfaceIpState( @@ -945,18 +941,20 @@ public class TetheringTest { assertEquals(3, mTetheringDependencies.mIsTetheringSupportedCalls); verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER); // This is called, but will throw. - verify(mNMService, times(1)).setIpForwardingEnabled(true); + verify(mNetd, times(1)).ipfwdEnableForwarding(TETHERING_NAME); // This never gets called because of the exception thrown above. - verify(mNMService, times(0)).startTethering(any(String[].class)); + verify(mNetd, times(0)).tetherStartWithConfiguration(any()); // When the master state machine transitions to an error state it tells // downstream interfaces, which causes us to tell Wi-Fi about the error // so it can take down AP mode. - verify(mNMService, times(1)).untetherInterface(TEST_WLAN_IFNAME); + verify(mNetd, times(1)).tetherApplyDnsInterfaces(); + verify(mNetd, times(1)).tetherInterfaceRemove(TEST_WLAN_IFNAME); + verify(mNetd, times(1)).networkRemoveInterface(INetd.LOCAL_NET_ID, TEST_WLAN_IFNAME); verify(mWifiManager).updateInterfaceIpState( TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_CONFIGURATION_ERROR); verifyNoMoreInteractions(mWifiManager); - verifyNoMoreInteractions(mNMService); + verifyNoMoreInteractions(mNetd); } private void runUserRestrictionsChange( @@ -1210,12 +1208,12 @@ public class TetheringTest { @Test public void testMultiSimAware() throws Exception { final TetheringConfiguration initailConfig = mTethering.getTetheringConfiguration(); - assertEquals(INVALID_SUBSCRIPTION_ID, initailConfig.subId); + assertEquals(INVALID_SUBSCRIPTION_ID, initailConfig.activeDataSubId); final int fakeSubId = 1234; mPhoneStateListener.onActiveDataSubscriptionIdChanged(fakeSubId); final TetheringConfiguration newConfig = mTethering.getTetheringConfiguration(); - assertEquals(fakeSubId, newConfig.subId); + assertEquals(fakeSubId, newConfig.activeDataSubId); } private void workingWifiP2pGroupOwner( @@ -1228,9 +1226,9 @@ public class TetheringTest { verifyInterfaceServingModeStarted(TEST_P2P_IFNAME); verifyTetheringBroadcast(TEST_P2P_IFNAME, EXTRA_AVAILABLE_TETHER); - verify(mNMService, times(1)).setIpForwardingEnabled(true); - verify(mNMService, times(1)).startTethering(any(String[].class)); - verifyNoMoreInteractions(mNMService); + verify(mNetd, times(1)).ipfwdEnableForwarding(TETHERING_NAME); + verify(mNetd, times(1)).tetherStartWithConfiguration(any()); + verifyNoMoreInteractions(mNetd); verifyTetheringBroadcast(TEST_P2P_IFNAME, EXTRA_ACTIVE_LOCAL_ONLY); verify(mUpstreamNetworkMonitor, times(1)).startObserveAllNetworks(); // This will be called twice, one is on entering IpServer.STATE_AVAILABLE, @@ -1245,16 +1243,16 @@ public class TetheringTest { mTethering.interfaceRemoved(TEST_P2P_IFNAME); mLooper.dispatchAll(); - verify(mNMService, times(1)).untetherInterface(TEST_P2P_IFNAME); - // {g,s}etInterfaceConfig() called twice for enabling and disabling IPv4. - verify(mNMService, times(2)).getInterfaceConfig(TEST_P2P_IFNAME); - verify(mNMService, times(2)) - .setInterfaceConfig(eq(TEST_P2P_IFNAME), any(InterfaceConfiguration.class)); - verify(mNMService, times(1)).stopTethering(); - verify(mNMService, times(1)).setIpForwardingEnabled(false); + verify(mNetd, times(1)).tetherApplyDnsInterfaces(); + verify(mNetd, times(1)).tetherInterfaceRemove(TEST_P2P_IFNAME); + verify(mNetd, times(1)).networkRemoveInterface(INetd.LOCAL_NET_ID, TEST_P2P_IFNAME); + // interfaceSetCfg() called once for enabling and twice for disabling IPv4. + verify(mNetd, times(3)).interfaceSetCfg(any(InterfaceConfigurationParcel.class)); + verify(mNetd, times(1)).tetherStop(); + verify(mNetd, times(1)).ipfwdDisableForwarding(TETHERING_NAME); verify(mUpstreamNetworkMonitor, never()).getCurrentPreferredUpstream(); verify(mUpstreamNetworkMonitor, never()).selectPreferredUpstreamType(any()); - verifyNoMoreInteractions(mNMService); + verifyNoMoreInteractions(mNetd); // Asking for the last error after the per-interface state machine // has been reaped yields an unknown interface error. assertEquals(TETHER_ERROR_UNKNOWN_IFACE, mTethering.getLastTetherError(TEST_P2P_IFNAME)); @@ -1268,12 +1266,11 @@ public class TetheringTest { sendWifiP2pConnectionChanged(true, false, TEST_P2P_IFNAME); mLooper.dispatchAll(); - verify(mNMService, never()).getInterfaceConfig(TEST_P2P_IFNAME); - verify(mNMService, never()) - .setInterfaceConfig(eq(TEST_P2P_IFNAME), any(InterfaceConfiguration.class)); - verify(mNMService, never()).tetherInterface(TEST_P2P_IFNAME); - verify(mNMService, never()).setIpForwardingEnabled(true); - verify(mNMService, never()).startTethering(any(String[].class)); + verify(mNetd, never()).interfaceSetCfg(any(InterfaceConfigurationParcel.class)); + verify(mNetd, never()).tetherInterfaceAdd(TEST_P2P_IFNAME); + verify(mNetd, never()).networkAddInterface(INetd.LOCAL_NET_ID, TEST_P2P_IFNAME); + verify(mNetd, never()).ipfwdEnableForwarding(TETHERING_NAME); + verify(mNetd, never()).tetherStartWithConfiguration(any()); // Emulate externally-visible WifiP2pManager effects, when wifi p2p group // is being removed. @@ -1281,13 +1278,13 @@ public class TetheringTest { mTethering.interfaceRemoved(TEST_P2P_IFNAME); mLooper.dispatchAll(); - verify(mNMService, never()).untetherInterface(TEST_P2P_IFNAME); - verify(mNMService, never()).getInterfaceConfig(TEST_P2P_IFNAME); - verify(mNMService, never()) - .setInterfaceConfig(eq(TEST_P2P_IFNAME), any(InterfaceConfiguration.class)); - verify(mNMService, never()).stopTethering(); - verify(mNMService, never()).setIpForwardingEnabled(false); - verifyNoMoreInteractions(mNMService); + verify(mNetd, never()).tetherApplyDnsInterfaces(); + verify(mNetd, never()).tetherInterfaceRemove(TEST_P2P_IFNAME); + verify(mNetd, never()).networkRemoveInterface(INetd.LOCAL_NET_ID, TEST_P2P_IFNAME); + verify(mNetd, never()).interfaceSetCfg(any(InterfaceConfigurationParcel.class)); + verify(mNetd, never()).tetherStop(); + verify(mNetd, never()).ipfwdDisableForwarding(TETHERING_NAME); + verifyNoMoreInteractions(mNetd); // Asking for the last error after the per-interface state machine // has been reaped yields an unknown interface error. assertEquals(TETHER_ERROR_UNKNOWN_IFACE, mTethering.getLastTetherError(TEST_P2P_IFNAME)); @@ -1317,12 +1314,11 @@ public class TetheringTest { sendWifiP2pConnectionChanged(true, true, TEST_P2P_IFNAME); mLooper.dispatchAll(); - verify(mNMService, never()).getInterfaceConfig(TEST_P2P_IFNAME); - verify(mNMService, never()) - .setInterfaceConfig(eq(TEST_P2P_IFNAME), any(InterfaceConfiguration.class)); - verify(mNMService, never()).tetherInterface(TEST_P2P_IFNAME); - verify(mNMService, never()).setIpForwardingEnabled(true); - verify(mNMService, never()).startTethering(any(String[].class)); + verify(mNetd, never()).interfaceSetCfg(any(InterfaceConfigurationParcel.class)); + verify(mNetd, never()).tetherInterfaceAdd(TEST_P2P_IFNAME); + verify(mNetd, never()).networkAddInterface(INetd.LOCAL_NET_ID, TEST_P2P_IFNAME); + verify(mNetd, never()).ipfwdEnableForwarding(TETHERING_NAME); + verify(mNetd, never()).tetherStartWithConfiguration(any()); assertEquals(TETHER_ERROR_UNKNOWN_IFACE, mTethering.getLastTetherError(TEST_P2P_IFNAME)); } @Test diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java index c90abbbedb5f..5ed75bf26f8b 100644 --- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java +++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java @@ -18,13 +18,14 @@ package com.android.server.connectivity.tethering; import static android.net.ConnectivityManager.TYPE_MOBILE_DUN; import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI; -import static android.net.ConnectivityManager.TYPE_NONE; import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN; import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; +import static com.android.server.connectivity.tethering.UpstreamNetworkMonitor.TYPE_NONE; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -538,13 +539,15 @@ public class UpstreamNetworkMonitorTest { mUNM.selectPreferredUpstreamType(preferredTypes)); verify(mEntitleMgr, times(1)).maybeRunProvisioning(); } + private void assertSatisfiesLegacyType(int legacyType, UpstreamNetworkState ns) { if (legacyType == TYPE_NONE) { assertTrue(ns == null); return; } - final NetworkCapabilities nc = ConnectivityManager.networkCapabilitiesForType(legacyType); + final NetworkCapabilities nc = + UpstreamNetworkMonitor.networkCapabilitiesForType(legacyType); assertTrue(nc.satisfiedByNetworkCapabilities(ns.networkCapabilities)); } diff --git a/packages/services/PacProcessor/jni/Android.bp b/packages/services/PacProcessor/jni/Android.bp index 2a94237f2aed..61f8143e68b5 100644 --- a/packages/services/PacProcessor/jni/Android.bp +++ b/packages/services/PacProcessor/jni/Android.bp @@ -37,4 +37,10 @@ cc_library_shared { "-Wunused", "-Wunreachable-code", ], + sanitize: { + cfi: true, + diag: { + cfi: true, + }, + }, } diff --git a/rs/java/android/renderscript/BaseObj.java b/rs/java/android/renderscript/BaseObj.java index b7e05d9c984c..7b5514b8a0d1 100644 --- a/rs/java/android/renderscript/BaseObj.java +++ b/rs/java/android/renderscript/BaseObj.java @@ -16,8 +16,10 @@ package android.renderscript; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; + import dalvik.system.CloseGuard; + import java.util.concurrent.locks.ReentrantReadWriteLock; /** diff --git a/rs/java/android/renderscript/Element.java b/rs/java/android/renderscript/Element.java index b8eb3a1d7a40..0941907d35f8 100644 --- a/rs/java/android/renderscript/Element.java +++ b/rs/java/android/renderscript/Element.java @@ -16,7 +16,7 @@ package android.renderscript; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; /** * <p>An Element represents one item within an {@link diff --git a/rs/java/android/renderscript/FileA3D.java b/rs/java/android/renderscript/FileA3D.java index 9a6b0bcd4544..7cc2825ae565 100644 --- a/rs/java/android/renderscript/FileA3D.java +++ b/rs/java/android/renderscript/FileA3D.java @@ -16,13 +16,13 @@ package android.renderscript; -import java.io.File; -import java.io.InputStream; - -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.res.AssetManager; import android.content.res.Resources; +import java.io.File; +import java.io.InputStream; + /** * @hide * @deprecated in API 16 diff --git a/rs/java/android/renderscript/Font.java b/rs/java/android/renderscript/Font.java index 583350e91795..df9d8019f28d 100644 --- a/rs/java/android/renderscript/Font.java +++ b/rs/java/android/renderscript/Font.java @@ -16,17 +16,16 @@ package android.renderscript; +import android.compat.annotation.UnsupportedAppUsage; +import android.content.res.AssetManager; +import android.content.res.Resources; +import android.os.Environment; + import java.io.File; import java.io.InputStream; import java.util.HashMap; import java.util.Map; -import android.os.Environment; - -import android.annotation.UnsupportedAppUsage; -import android.content.res.AssetManager; -import android.content.res.Resources; - /** * @hide * @deprecated in API 16 diff --git a/rs/java/android/renderscript/Matrix4f.java b/rs/java/android/renderscript/Matrix4f.java index 026c9fbd7d5e..a9469c979494 100644 --- a/rs/java/android/renderscript/Matrix4f.java +++ b/rs/java/android/renderscript/Matrix4f.java @@ -16,8 +16,7 @@ package android.renderscript; -import android.annotation.UnsupportedAppUsage; -import java.lang.Math; +import android.compat.annotation.UnsupportedAppUsage; /** diff --git a/rs/java/android/renderscript/Mesh.java b/rs/java/android/renderscript/Mesh.java index 5321dcb957dc..826225a70d86 100644 --- a/rs/java/android/renderscript/Mesh.java +++ b/rs/java/android/renderscript/Mesh.java @@ -16,7 +16,8 @@ package android.renderscript; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; + import java.util.Vector; /** diff --git a/rs/java/android/renderscript/Program.java b/rs/java/android/renderscript/Program.java index e28d646f5f1c..ff072183e927 100644 --- a/rs/java/android/renderscript/Program.java +++ b/rs/java/android/renderscript/Program.java @@ -17,14 +17,14 @@ package android.renderscript; +import android.compat.annotation.UnsupportedAppUsage; +import android.content.res.Resources; +import android.util.Log; + import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; -import android.annotation.UnsupportedAppUsage; -import android.content.res.Resources; -import android.util.Log; - /** * @hide diff --git a/rs/java/android/renderscript/ProgramFragment.java b/rs/java/android/renderscript/ProgramFragment.java index 3dde9b6d6400..880531207b4d 100644 --- a/rs/java/android/renderscript/ProgramFragment.java +++ b/rs/java/android/renderscript/ProgramFragment.java @@ -16,7 +16,7 @@ package android.renderscript; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; /** diff --git a/rs/java/android/renderscript/ProgramFragmentFixedFunction.java b/rs/java/android/renderscript/ProgramFragmentFixedFunction.java index d05d41da8b6f..c741ce6e77ed 100644 --- a/rs/java/android/renderscript/ProgramFragmentFixedFunction.java +++ b/rs/java/android/renderscript/ProgramFragmentFixedFunction.java @@ -16,7 +16,7 @@ package android.renderscript; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; /** diff --git a/rs/java/android/renderscript/ProgramRaster.java b/rs/java/android/renderscript/ProgramRaster.java index 33000acb4eb0..a21696c82161 100644 --- a/rs/java/android/renderscript/ProgramRaster.java +++ b/rs/java/android/renderscript/ProgramRaster.java @@ -16,7 +16,7 @@ package android.renderscript; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; /** diff --git a/rs/java/android/renderscript/ProgramStore.java b/rs/java/android/renderscript/ProgramStore.java index 622fe21be47a..7e61347ee218 100644 --- a/rs/java/android/renderscript/ProgramStore.java +++ b/rs/java/android/renderscript/ProgramStore.java @@ -16,7 +16,7 @@ package android.renderscript; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; /** diff --git a/rs/java/android/renderscript/ProgramVertex.java b/rs/java/android/renderscript/ProgramVertex.java index 83d9ea7be645..9257234de42c 100644 --- a/rs/java/android/renderscript/ProgramVertex.java +++ b/rs/java/android/renderscript/ProgramVertex.java @@ -38,7 +38,7 @@ **/ package android.renderscript; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; /** diff --git a/rs/java/android/renderscript/ProgramVertexFixedFunction.java b/rs/java/android/renderscript/ProgramVertexFixedFunction.java index 579d3bb507e8..03c2eaf91242 100644 --- a/rs/java/android/renderscript/ProgramVertexFixedFunction.java +++ b/rs/java/android/renderscript/ProgramVertexFixedFunction.java @@ -16,7 +16,7 @@ package android.renderscript; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; /** diff --git a/rs/java/android/renderscript/RSSurfaceView.java b/rs/java/android/renderscript/RSSurfaceView.java index 561373cef625..6bdde387b334 100644 --- a/rs/java/android/renderscript/RSSurfaceView.java +++ b/rs/java/android/renderscript/RSSurfaceView.java @@ -16,7 +16,7 @@ package android.renderscript; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.util.AttributeSet; import android.view.SurfaceHolder; diff --git a/rs/java/android/renderscript/RenderScript.java b/rs/java/android/renderscript/RenderScript.java index f4c27771c846..39efe731ce8a 100644 --- a/rs/java/android/renderscript/RenderScript.java +++ b/rs/java/android/renderscript/RenderScript.java @@ -16,7 +16,7 @@ package android.renderscript; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.res.AssetManager; import android.graphics.Bitmap; @@ -447,44 +447,33 @@ public class RenderScript { validate(); return rsnAllocationCreateTyped(mContext, type, mip, usage, pointer); } - native long rsnAllocationCreateFromBitmap(long con, long type, int mip, long bitmapHandle, + + native long rsnAllocationCreateFromBitmap(long con, long type, int mip, Bitmap bmp, int usage); synchronized long nAllocationCreateFromBitmap(long type, int mip, Bitmap bmp, int usage) { validate(); - return rsnAllocationCreateFromBitmap(mContext, type, mip, bmp.getNativeInstance(), usage); + return rsnAllocationCreateFromBitmap(mContext, type, mip, bmp, usage); } - native long rsnAllocationCreateBitmapBackedAllocation(long con, long type, int mip, long bitmapHandle, + native long rsnAllocationCreateBitmapBackedAllocation(long con, long type, int mip, Bitmap bmp, int usage); synchronized long nAllocationCreateBitmapBackedAllocation(long type, int mip, Bitmap bmp, int usage) { validate(); - return rsnAllocationCreateBitmapBackedAllocation(mContext, type, mip, bmp.getNativeInstance(), - usage); + return rsnAllocationCreateBitmapBackedAllocation(mContext, type, mip, bmp, usage); } - native long rsnAllocationCubeCreateFromBitmap(long con, long type, int mip, long bitmapHandle, + native long rsnAllocationCubeCreateFromBitmap(long con, long type, int mip, Bitmap bmp, int usage); synchronized long nAllocationCubeCreateFromBitmap(long type, int mip, Bitmap bmp, int usage) { validate(); - return rsnAllocationCubeCreateFromBitmap(mContext, type, mip, bmp.getNativeInstance(), - usage); - } - native long rsnAllocationCreateBitmapRef(long con, long type, long bitmapHandle); - synchronized long nAllocationCreateBitmapRef(long type, Bitmap bmp) { - validate(); - return rsnAllocationCreateBitmapRef(mContext, type, bmp.getNativeInstance()); - } - native long rsnAllocationCreateFromAssetStream(long con, int mips, int assetStream, int usage); - synchronized long nAllocationCreateFromAssetStream(int mips, int assetStream, int usage) { - validate(); - return rsnAllocationCreateFromAssetStream(mContext, mips, assetStream, usage); + return rsnAllocationCubeCreateFromBitmap(mContext, type, mip, bmp, usage); } - native void rsnAllocationCopyToBitmap(long con, long alloc, long bitmapHandle); + native void rsnAllocationCopyToBitmap(long con, long alloc, Bitmap bmp); synchronized void nAllocationCopyToBitmap(long alloc, Bitmap bmp) { validate(); - rsnAllocationCopyToBitmap(mContext, alloc, bmp.getNativeInstance()); + rsnAllocationCopyToBitmap(mContext, alloc, bmp); } native void rsnAllocationSyncAll(long con, long alloc, int src); @@ -537,10 +526,10 @@ public class RenderScript { validate(); rsnAllocationGenerateMipmaps(mContext, alloc); } - native void rsnAllocationCopyFromBitmap(long con, long alloc, long bitmapHandle); + native void rsnAllocationCopyFromBitmap(long con, long alloc, Bitmap bmp); synchronized void nAllocationCopyFromBitmap(long alloc, Bitmap bmp) { validate(); - rsnAllocationCopyFromBitmap(mContext, alloc, bmp.getNativeInstance()); + rsnAllocationCopyFromBitmap(mContext, alloc, bmp); } diff --git a/rs/java/android/renderscript/RenderScriptCacheDir.java b/rs/java/android/renderscript/RenderScriptCacheDir.java index 1797bef4be8d..862d032d6987 100644 --- a/rs/java/android/renderscript/RenderScriptCacheDir.java +++ b/rs/java/android/renderscript/RenderScriptCacheDir.java @@ -16,7 +16,8 @@ package android.renderscript; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; + import java.io.File; /** diff --git a/rs/java/android/renderscript/RenderScriptGL.java b/rs/java/android/renderscript/RenderScriptGL.java index 6fac83e8c4a8..dafaf367364d 100644 --- a/rs/java/android/renderscript/RenderScriptGL.java +++ b/rs/java/android/renderscript/RenderScriptGL.java @@ -16,7 +16,7 @@ package android.renderscript; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.graphics.SurfaceTexture; import android.view.Surface; diff --git a/rs/java/android/renderscript/Script.java b/rs/java/android/renderscript/Script.java index 9ad9aea9d7aa..d1d3a7642382 100644 --- a/rs/java/android/renderscript/Script.java +++ b/rs/java/android/renderscript/Script.java @@ -16,7 +16,7 @@ package android.renderscript; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.util.SparseArray; /** diff --git a/rs/jni/Android.mk b/rs/jni/Android.mk index 0854b9582187..f9ef0b7b8e9d 100644 --- a/rs/jni/Android.mk +++ b/rs/jni/Android.mk @@ -12,7 +12,6 @@ LOCAL_SHARED_LIBRARIES := \ libRS \ libcutils \ liblog \ - libhwui \ libutils \ libui \ libgui \ diff --git a/rs/jni/android_renderscript_RenderScript.cpp b/rs/jni/android_renderscript_RenderScript.cpp index dfee96182a48..5ae895dbbce6 100644 --- a/rs/jni/android_renderscript_RenderScript.cpp +++ b/rs/jni/android_renderscript_RenderScript.cpp @@ -32,10 +32,10 @@ #include "jni.h" #include <nativehelper/JNIHelp.h> +#include <android/graphics/bitmap.h> #include "android_runtime/AndroidRuntime.h" #include "android_runtime/android_view_Surface.h" #include "android_runtime/android_util_AssetManager.h" -#include "android/graphics/GraphicsJNI.h" #include "android/native_window.h" #include "android/native_window_jni.h" @@ -1319,27 +1319,28 @@ nAllocationGenerateMipmaps(JNIEnv *_env, jobject _this, jlong con, jlong alloc) rsAllocationGenerateMipmaps((RsContext)con, (RsAllocation)alloc); } +static size_t computeByteSize(const android::graphics::Bitmap& bitmap) { + AndroidBitmapInfo info = bitmap.getInfo(); + return info.height * info.stride; +} + static jlong nAllocationCreateFromBitmap(JNIEnv *_env, jobject _this, jlong con, jlong type, jint mip, - jlong bitmapPtr, jint usage) + jobject jbitmap, jint usage) { - SkBitmap bitmap; - bitmap::toBitmap(bitmapPtr).getSkBitmap(&bitmap); - + android::graphics::Bitmap bitmap(_env, jbitmap); const void* ptr = bitmap.getPixels(); jlong id = (jlong)(uintptr_t)rsAllocationCreateFromBitmap((RsContext)con, (RsType)type, (RsAllocationMipmapControl)mip, - ptr, bitmap.computeByteSize(), usage); + ptr, computeByteSize(bitmap), usage); return id; } static jlong nAllocationCreateBitmapBackedAllocation(JNIEnv *_env, jobject _this, jlong con, jlong type, - jint mip, jlong bitmapPtr, jint usage) + jint mip, jobject jbitmap, jint usage) { - SkBitmap bitmap; - bitmap::toBitmap(bitmapPtr).getSkBitmap(&bitmap); - + android::graphics::Bitmap bitmap(_env, jbitmap); const void* ptr = bitmap.getPixels(); jlong id = (jlong)(uintptr_t)rsAllocationCreateTyped((RsContext)con, (RsType)type, (RsAllocationMipmapControl)mip, @@ -1349,40 +1350,35 @@ nAllocationCreateBitmapBackedAllocation(JNIEnv *_env, jobject _this, jlong con, static jlong nAllocationCubeCreateFromBitmap(JNIEnv *_env, jobject _this, jlong con, jlong type, jint mip, - jlong bitmapPtr, jint usage) + jobject jbitmap, jint usage) { - SkBitmap bitmap; - bitmap::toBitmap(bitmapPtr).getSkBitmap(&bitmap); - + android::graphics::Bitmap bitmap(_env, jbitmap); const void* ptr = bitmap.getPixels(); jlong id = (jlong)(uintptr_t)rsAllocationCubeCreateFromBitmap((RsContext)con, (RsType)type, (RsAllocationMipmapControl)mip, - ptr, bitmap.computeByteSize(), usage); + ptr, computeByteSize(bitmap), usage); return id; } static void -nAllocationCopyFromBitmap(JNIEnv *_env, jobject _this, jlong con, jlong alloc, jlong bitmapPtr) +nAllocationCopyFromBitmap(JNIEnv *_env, jobject _this, jlong con, jlong alloc, jobject jbitmap) { - SkBitmap bitmap; - bitmap::toBitmap(bitmapPtr).getSkBitmap(&bitmap); - int w = bitmap.width(); - int h = bitmap.height(); + android::graphics::Bitmap bitmap(_env, jbitmap); + int w = bitmap.getInfo().width; + int h = bitmap.getInfo().height; const void* ptr = bitmap.getPixels(); rsAllocation2DData((RsContext)con, (RsAllocation)alloc, 0, 0, 0, RS_ALLOCATION_CUBEMAP_FACE_POSITIVE_X, - w, h, ptr, bitmap.computeByteSize(), 0); + w, h, ptr, computeByteSize(bitmap), 0); } static void -nAllocationCopyToBitmap(JNIEnv *_env, jobject _this, jlong con, jlong alloc, jlong bitmapPtr) +nAllocationCopyToBitmap(JNIEnv *_env, jobject _this, jlong con, jlong alloc, jobject jbitmap) { - SkBitmap bitmap; - bitmap::toBitmap(bitmapPtr).getSkBitmap(&bitmap); - + android::graphics::Bitmap bitmap(_env, jbitmap); void* ptr = bitmap.getPixels(); - rsAllocationCopyToBitmap((RsContext)con, (RsAllocation)alloc, ptr, bitmap.computeByteSize()); + rsAllocationCopyToBitmap((RsContext)con, (RsAllocation)alloc, ptr, computeByteSize(bitmap)); bitmap.notifyPixelsChanged(); } @@ -2867,12 +2863,12 @@ static const JNINativeMethod methods[] = { {"rsnTypeGetNativeData", "(JJ[J)V", (void*)nTypeGetNativeData }, {"rsnAllocationCreateTyped", "(JJIIJ)J", (void*)nAllocationCreateTyped }, -{"rsnAllocationCreateFromBitmap", "(JJIJI)J", (void*)nAllocationCreateFromBitmap }, -{"rsnAllocationCreateBitmapBackedAllocation", "(JJIJI)J", (void*)nAllocationCreateBitmapBackedAllocation }, -{"rsnAllocationCubeCreateFromBitmap","(JJIJI)J", (void*)nAllocationCubeCreateFromBitmap }, +{"rsnAllocationCreateFromBitmap", "(JJILandroid/graphics/Bitmap;I)J", (void*)nAllocationCreateFromBitmap }, +{"rsnAllocationCreateBitmapBackedAllocation", "(JJILandroid/graphics/Bitmap;I)J", (void*)nAllocationCreateBitmapBackedAllocation }, +{"rsnAllocationCubeCreateFromBitmap","(JJILandroid/graphics/Bitmap;I)J", (void*)nAllocationCubeCreateFromBitmap }, -{"rsnAllocationCopyFromBitmap", "(JJJ)V", (void*)nAllocationCopyFromBitmap }, -{"rsnAllocationCopyToBitmap", "(JJJ)V", (void*)nAllocationCopyToBitmap }, +{"rsnAllocationCopyFromBitmap", "(JJLandroid/graphics/Bitmap;)V", (void*)nAllocationCopyFromBitmap }, +{"rsnAllocationCopyToBitmap", "(JJLandroid/graphics/Bitmap;)V", (void*)nAllocationCopyToBitmap }, {"rsnAllocationSyncAll", "(JJI)V", (void*)nAllocationSyncAll }, {"rsnAllocationSetupBufferQueue", "(JJI)V", (void*)nAllocationSetupBufferQueue }, diff --git a/services/Android.bp b/services/Android.bp index ede4c3e7a6be..5afed6c4fd19 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -61,6 +61,7 @@ java_library { "services.devicepolicy", "services.midi", "services.net", + "services.people", "services.print", "services.restrictions", "services.startop", @@ -112,7 +113,7 @@ droidstubs { srcs: [":services-sources"], installable: false, // TODO: remove the --hide options below - args: " --show-single-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES,process=android.annotation.SystemApi.Process.SYSTEM_SERVER\\)" + + args: " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES,process=android.annotation.SystemApi.Process.SYSTEM_SERVER\\)" + " --hide-annotation android.annotation.Hide" + " --hide-package com.google.android.startop.iorap" + " --hide ReferencesHidden" + diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 6a6e2b2f3467..58d3489a8cc8 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -35,6 +35,7 @@ import android.annotation.Nullable; import android.app.ActivityOptions; import android.app.AlertDialog; import android.app.PendingIntent; +import android.app.RemoteAction; import android.appwidget.AppWidgetManagerInternal; import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; @@ -148,6 +149,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub // their capabilities are ready. private static final int WAIT_MOTION_INJECTOR_TIMEOUT_MILLIS = 1000; + static final String FUNCTION_REGISTER_SYSTEM_ACTION = "registerSystemAction"; + static final String FUNCTION_UNREGISTER_SYSTEM_ACTION = "unregisterSystemAction"; private static final String FUNCTION_REGISTER_UI_TEST_AUTOMATION_SERVICE = "registerUiTestAutomationService"; @@ -253,6 +256,27 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } + @VisibleForTesting + AccessibilityManagerService( + Context context, + PackageManager packageManager, + AccessibilitySecurityPolicy securityPolicy, + SystemActionPerformer systemActionPerformer, + AccessibilityWindowManager a11yWindowManager, + AccessibilityDisplayListener a11yDisplayListener) { + mContext = context; + mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + mWindowManagerService = LocalServices.getService(WindowManagerInternal.class); + mMainHandler = new MainHandler(mContext.getMainLooper()); + mActivityTaskManagerService = LocalServices.getService(ActivityTaskManagerInternal.class); + mPackageManager = packageManager; + mSecurityPolicy = securityPolicy; + mSystemActionPerformer = systemActionPerformer; + mA11yWindowManager = a11yWindowManager; + mA11yDisplayListener = a11yDisplayListener; + init(); + } + /** * Creates a new instance. * @@ -260,21 +284,24 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub */ public AccessibilityManagerService(Context context) { mContext = context; - mPackageManager = mContext.getPackageManager(); - mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); mWindowManagerService = LocalServices.getService(WindowManagerInternal.class); - mSecurityPolicy = new AccessibilitySecurityPolicy(mContext, this); mMainHandler = new MainHandler(mContext.getMainLooper()); + mActivityTaskManagerService = LocalServices.getService(ActivityTaskManagerInternal.class); + mPackageManager = mContext.getPackageManager(); + mSecurityPolicy = new AccessibilitySecurityPolicy(mContext, this); mSystemActionPerformer = new SystemActionPerformer(mContext, mWindowManagerService); mA11yWindowManager = new AccessibilityWindowManager(mLock, mMainHandler, mWindowManagerService, this, mSecurityPolicy, this); mA11yDisplayListener = new AccessibilityDisplayListener(mContext, mMainHandler); - mSecurityPolicy.setAccessibilityWindowManager(mA11yWindowManager); - mActivityTaskManagerService = LocalServices.getService(ActivityTaskManagerInternal.class); + init(); + } + private void init() { + mSecurityPolicy.setAccessibilityWindowManager(mA11yWindowManager); registerBroadcastReceivers(); new AccessibilityContentObserver(mMainHandler).register( - context.getContentResolver()); + mContext.getContentResolver()); } @Override @@ -623,6 +650,30 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub event.recycle(); } + /** + * This is the implementation of AccessibilityManager system API. + * System UI calls into this method through AccessibilityManager system API to register a + * system action. + */ + @Override + public void registerSystemAction(RemoteAction action, int actionId) { + mSecurityPolicy.enforceCallingPermission(Manifest.permission.MANAGE_ACCESSIBILITY, + FUNCTION_REGISTER_SYSTEM_ACTION); + mSystemActionPerformer.registerSystemAction(actionId, action); + } + + /** + * This is the implementation of AccessibilityManager system API. + * System UI calls into this method through AccessibilityManager system API to unregister a + * system action. + */ + @Override + public void unregisterSystemAction(int actionId) { + mSecurityPolicy.enforceCallingPermission(Manifest.permission.MANAGE_ACCESSIBILITY, + FUNCTION_UNREGISTER_SYSTEM_ACTION); + mSystemActionPerformer.unregisterSystemAction(actionId); + } + @Override public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(int userId) { synchronized (mLock) { diff --git a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java index 17549268503e..11dcfefd7e3b 100644 --- a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java +++ b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java @@ -157,8 +157,13 @@ public class SystemActionPerformer { /** * This method is called to register a system action. If a system action is already registered * with the given id, the existing system action will be overwritten. + * + * This method is supposed to be package internal since this class is meant to be used by + * AccessibilityManagerService only. But Mockito has a bug which requiring this to be public + * to be mocked. */ - void registerSystemAction(int id, RemoteAction action) { + @VisibleForTesting + public void registerSystemAction(int id, RemoteAction action) { synchronized (mSystemActionLock) { mRegisteredSystemActions.put(id, action); } @@ -170,8 +175,13 @@ public class SystemActionPerformer { /** * This method is called to unregister a system action previously registered through * registerSystemAction. + * + * This method is supposed to be package internal since this class is meant to be used by + * AccessibilityManagerService only. But Mockito has a bug which requiring this to be public + * to be mocked. */ - void unregisterSystemAction(int id) { + @VisibleForTesting + public void unregisterSystemAction(int id) { synchronized (mSystemActionLock) { mRegisteredSystemActions.remove(id); } diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 95cd8fc47944..f34b5e71ad7b 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -112,7 +112,6 @@ import com.android.server.autofill.ui.PendingUi; import com.android.server.inputmethod.InputMethodManagerInternal; import java.io.PrintWriter; -import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -311,20 +310,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @NonNull private final InputMethodManagerInternal mInputMethodManagerInternal; - @GuardedBy("mLock") - @Nullable - private CompletableFuture<InlineSuggestionsRequest> mSuggestionsRequestFuture; - - @GuardedBy("mLock") - @Nullable - private CompletableFuture<IInlineSuggestionsResponseCallback> - mInlineSuggestionsResponseCallbackFuture; - @Nullable private InlineSuggestionsRequestCallbackImpl mInlineSuggestionsRequestCallback; - private static final int INLINE_REQUEST_TIMEOUT_MS = 1000; - /** * Receiver of assist data from the app's {@link Activity}. */ @@ -336,7 +324,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState + "mForAugmentedAutofillOnly: %s", mForAugmentedAutofillOnly); return; } - if (mCurrentViewId == null) { + // Keeps to prevent it is cleared on multiple threads. + final AutofillId currentViewId = mCurrentViewId; + if (currentViewId == null) { Slog.w(TAG, "No current view id - session might have finished"); return; } @@ -410,7 +400,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (mContexts == null) { mContexts = new ArrayList<>(1); } - mContexts.add(new FillContext(requestId, structure, mCurrentViewId)); + mContexts.add(new FillContext(requestId, structure, currentViewId)); cancelCurrentRequestLocked(); @@ -422,7 +412,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final ArrayList<FillContext> contexts = mergePreviousSessionLocked(/* forSave= */ false); - final InlineSuggestionsRequest suggestionsRequest = getInlineSuggestionsRequest(); + final InlineSuggestionsRequest suggestionsRequest = + mInlineSuggestionsRequestCallback != null + ? mInlineSuggestionsRequestCallback.getRequest() : null; request = new FillRequest(requestId, contexts, mClientState, flags, suggestionsRequest); @@ -620,13 +612,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState private void maybeRequestInlineSuggestionsRequestThenFillLocked(@NonNull ViewState viewState, int newState, int flags) { if (isInlineSuggestionsEnabled()) { - mSuggestionsRequestFuture = new CompletableFuture<>(); - mInlineSuggestionsResponseCallbackFuture = new CompletableFuture<>(); - - if (mInlineSuggestionsRequestCallback == null) { - mInlineSuggestionsRequestCallback = new InlineSuggestionsRequestCallbackImpl(this); - } - + mInlineSuggestionsRequestCallback = new InlineSuggestionsRequestCallbackImpl(); mInputMethodManagerInternal.onCreateInlineSuggestionsRequest( mComponentName, mCurrentViewId, mInlineSuggestionsRequestCallback); } @@ -636,10 +622,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState private static final class InlineSuggestionsRequestCallbackImpl extends IInlineSuggestionsRequestCallback.Stub { - private final WeakReference<Session> mSession; + private static final int INLINE_REQUEST_TIMEOUT_MS = 1000; + + private final CompletableFuture<InlineSuggestionsRequest> mRequest; + private final CompletableFuture<IInlineSuggestionsResponseCallback> mResponseCallback; - private InlineSuggestionsRequestCallbackImpl(Session session) { - mSession = new WeakReference<>(session); + private InlineSuggestionsRequestCallbackImpl() { + mRequest = new CompletableFuture<>(); + mResponseCallback = new CompletableFuture<>(); } @Override @@ -648,13 +638,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState Log.d(TAG, "inline suggestions request unsupported, " + "falling back to regular autofill"); } - final Session session = mSession.get(); - if (session != null) { - synchronized (session.mLock) { - session.mSuggestionsRequestFuture.cancel(true); - session.mInlineSuggestionsResponseCallbackFuture.cancel(true); - } - } + mRequest.cancel(true); + mResponseCallback.cancel(true); } @Override @@ -663,13 +648,36 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (sDebug) { Log.d(TAG, "onInlineSuggestionsRequest() received: " + request); } - final Session session = mSession.get(); - if (session != null) { - synchronized (session.mLock) { - session.mSuggestionsRequestFuture.complete(request); - session.mInlineSuggestionsResponseCallbackFuture.complete(callback); - } + mRequest.complete(request); + mResponseCallback.complete(callback); + } + + @Nullable + private InlineSuggestionsRequest getRequest() { + try { + return mRequest.get(INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS); + } catch (TimeoutException e) { + Log.w(TAG, "Exception getting inline suggestions request in time: " + e); + } catch (CancellationException e) { + Log.w(TAG, "Inline suggestions request cancelled"); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + return null; + } + + @Nullable + private IInlineSuggestionsResponseCallback getResponseCallback() { + try { + return mResponseCallback.get(INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS); + } catch (TimeoutException e) { + Log.w(TAG, "Exception getting inline suggestions callback in time: " + e); + } catch (CancellationException e) { + Log.w(TAG, "Inline suggestions callback cancelled"); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); } + return null; } } @@ -2678,22 +2686,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * Returns whether we made a request to show inline suggestions. */ private boolean requestShowInlineSuggestions(FillResponse response) { - IInlineSuggestionsResponseCallback inlineContentCallback = null; - synchronized (mLock) { - if (mInlineSuggestionsResponseCallbackFuture != null) { - try { - inlineContentCallback = mInlineSuggestionsResponseCallbackFuture.get( - INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS); - } catch (TimeoutException e) { - Log.w(TAG, "Exception getting inline suggestions callback in time: " + e); - } catch (CancellationException e) { - Log.w(TAG, "Inline suggestions callback cancelled"); - } catch (InterruptedException | ExecutionException e) { - throw new RuntimeException(e); - } - } - } - + final IInlineSuggestionsResponseCallback inlineContentCallback = + mInlineSuggestionsRequestCallback != null + ? mInlineSuggestionsRequestCallback.getResponseCallback() : null; if (inlineContentCallback == null) { Log.w(TAG, "Session input method callback is not set yet"); return false; @@ -3015,10 +3010,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final AutofillId focusedId = AutofillId.withoutSession(mCurrentViewId); - final InlineSuggestionsRequest inlineSuggestionsRequest = getInlineSuggestionsRequest(); + final InlineSuggestionsRequest inlineSuggestionsRequest = + mInlineSuggestionsRequestCallback != null + ? mInlineSuggestionsRequestCallback.getRequest() : null; final IInlineSuggestionsResponseCallback inlineSuggestionsResponseCallback = - getInlineSuggestionsResponseCallback(); - + mInlineSuggestionsRequestCallback != null + ? mInlineSuggestionsRequestCallback.getResponseCallback() : null; remoteService.onRequestAutofillLocked(id, mClient, taskId, mComponentName, focusedId, currentValue, inlineSuggestionsRequest, inlineSuggestionsResponseCallback); @@ -3028,40 +3025,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return mAugmentedAutofillDestroyer; } - @Nullable - private InlineSuggestionsRequest getInlineSuggestionsRequest() { - if (mSuggestionsRequestFuture != null) { - try { - return mSuggestionsRequestFuture.get( - INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS); - } catch (TimeoutException e) { - Log.w(TAG, "Exception getting inline suggestions request in time: " + e); - } catch (CancellationException e) { - Log.w(TAG, "Inline suggestions request cancelled"); - } catch (InterruptedException | ExecutionException e) { - throw new RuntimeException(e); - } - } - return null; - } - - @Nullable - private IInlineSuggestionsResponseCallback getInlineSuggestionsResponseCallback() { - if (mInlineSuggestionsResponseCallbackFuture != null) { - try { - return mInlineSuggestionsResponseCallbackFuture.get( - INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS); - } catch (TimeoutException e) { - Log.w(TAG, "Exception getting inline suggestions callback in time: " + e); - } catch (CancellationException e) { - Log.w(TAG, "Inline suggestions callback cancelled"); - } catch (InterruptedException | ExecutionException e) { - throw new RuntimeException(e); - } - } - return null; - } - @GuardedBy("mLock") private void cancelAugmentedAutofillLocked() { final RemoteAugmentedAutofillService remoteService = mService diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java index 3bce322a7655..d45a54e0ff28 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerService.java +++ b/services/backup/java/com/android/server/backup/BackupManagerService.java @@ -1452,6 +1452,11 @@ public class BackupManagerService extends IBackupManager.Stub { if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) { return; } + dumpWithoutCheckingPermission(fd, pw, args); + } + + @VisibleForTesting + void dumpWithoutCheckingPermission(FileDescriptor fd, PrintWriter pw, String[] args) { int userId = binderGetCallingUserId(); if (!isUserReadyForBackup(userId)) { pw.println("Inactive"); @@ -1460,7 +1465,16 @@ public class BackupManagerService extends IBackupManager.Stub { if (args != null) { for (String arg : args) { - if ("users".equals(arg.toLowerCase())) { + if ("-h".equals(arg)) { + pw.println("'dumpsys backup' optional arguments:"); + pw.println(" -h : this help text"); + pw.println(" a[gents] : dump information about defined backup agents"); + pw.println(" transportclients : dump information about transport clients"); + pw.println(" transportstats : dump transport statts"); + pw.println(" users : dump the list of users for which backup service " + + "is running"); + return; + } else if ("users".equals(arg.toLowerCase())) { pw.print(DUMP_RUNNING_USERS_MESSAGE); for (int i = 0; i < mUserServices.size(); i++) { pw.print(" " + mUserServices.keyAt(i)); @@ -1471,11 +1485,12 @@ public class BackupManagerService extends IBackupManager.Stub { } } - UserBackupManagerService userBackupManagerService = - getServiceForUserIfCallerHasPermission(UserHandle.USER_SYSTEM, "dump()"); - - if (userBackupManagerService != null) { - userBackupManagerService.dump(fd, pw, args); + for (int i = 0; i < mUserServices.size(); i++) { + UserBackupManagerService userBackupManagerService = + getServiceForUserIfCallerHasPermission(mUserServices.keyAt(i), "dump()"); + if (userBackupManagerService != null) { + userBackupManagerService.dump(fd, pw, args); + } } } diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java index 064cd060528d..7b95ab526b41 100644 --- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java @@ -3545,14 +3545,7 @@ public class UserBackupManagerService { try { if (args != null) { for (String arg : args) { - if ("-h".equals(arg)) { - pw.println("'dumpsys backup' optional arguments:"); - pw.println(" -h : this help text"); - pw.println(" a[gents] : dump information about defined backup agents"); - pw.println(" users : dump the list of users for which backup service " - + "is running"); - return; - } else if ("agents".startsWith(arg)) { + if ("agents".startsWith(arg)) { dumpAgents(pw); return; } else if ("transportclients".equals(arg.toLowerCase())) { @@ -3583,8 +3576,10 @@ public class UserBackupManagerService { } private void dumpInternal(PrintWriter pw) { + // Add prefix for only non-system users so that system user dumpsys is the same as before + String userPrefix = mUserId == UserHandle.USER_SYSTEM ? "" : "User " + mUserId + ":"; synchronized (mQueueLock) { - pw.println("Backup Manager is " + (mEnabled ? "enabled" : "disabled") + pw.println(userPrefix + "Backup Manager is " + (mEnabled ? "enabled" : "disabled") + " / " + (!mSetupComplete ? "not " : "") + "setup complete / " + (this.mPendingInits.size() == 0 ? "not " : "") + "pending init"); pw.println("Auto-restore is " + (mAutoRestore ? "enabled" : "disabled")); @@ -3594,13 +3589,13 @@ public class UserBackupManagerService { + " (now = " + System.currentTimeMillis() + ')'); pw.println(" next scheduled: " + KeyValueBackupJob.nextScheduled(mUserId)); - pw.println("Transport whitelist:"); + pw.println(userPrefix + "Transport whitelist:"); for (ComponentName transport : mTransportManager.getTransportWhitelist()) { pw.print(" "); pw.println(transport.flattenToShortString()); } - pw.println("Available transports:"); + pw.println(userPrefix + "Available transports:"); final String[] transports = listAllTransports(); if (transports != null) { for (String t : transports) { @@ -3626,18 +3621,18 @@ public class UserBackupManagerService { mTransportManager.dumpTransportClients(pw); - pw.println("Pending init: " + mPendingInits.size()); + pw.println(userPrefix + "Pending init: " + mPendingInits.size()); for (String s : mPendingInits) { pw.println(" " + s); } - pw.print("Ancestral: "); + pw.print(userPrefix + "Ancestral: "); pw.println(Long.toHexString(mAncestralToken)); - pw.print("Current: "); + pw.print(userPrefix + "Current: "); pw.println(Long.toHexString(mCurrentToken)); int numPackages = mBackupParticipants.size(); - pw.println("Participants:"); + pw.println(userPrefix + "Participants:"); for (int i = 0; i < numPackages; i++) { int uid = mBackupParticipants.keyAt(i); pw.print(" uid: "); @@ -3648,7 +3643,7 @@ public class UserBackupManagerService { } } - pw.println("Ancestral packages: " + pw.println(userPrefix + "Ancestral packages: " + (mAncestralPackages == null ? "none" : mAncestralPackages.size())); if (mAncestralPackages != null) { for (String pkg : mAncestralPackages) { @@ -3657,17 +3652,17 @@ public class UserBackupManagerService { } Set<String> processedPackages = mProcessedPackagesJournal.getPackagesCopy(); - pw.println("Ever backed up: " + processedPackages.size()); + pw.println(userPrefix + "Ever backed up: " + processedPackages.size()); for (String pkg : processedPackages) { pw.println(" " + pkg); } - pw.println("Pending key/value backup: " + mPendingBackups.size()); + pw.println(userPrefix + "Pending key/value backup: " + mPendingBackups.size()); for (BackupRequest req : mPendingBackups.values()) { pw.println(" " + req); } - pw.println("Full backup queue:" + mFullBackupQueue.size()); + pw.println(userPrefix + "Full backup queue:" + mFullBackupQueue.size()); for (FullBackupEntry entry : mFullBackupQueue) { pw.print(" "); pw.print(entry.lastBackup); diff --git a/services/core/Android.bp b/services/core/Android.bp index 8d223003fe5b..b2fba730fac1 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -117,6 +117,7 @@ java_library_static { "android.hardware.oemlock-V1.0-java", "android.hardware.configstore-V1.0-java", "android.hardware.contexthub-V1.0-java", + "android.hardware.rebootescrow-java", "android.hardware.soundtrigger-V2.3-java", "android.hidl.manager-V1.2-java", "dnsresolver_aidl_interface-V2-java", @@ -164,9 +165,3 @@ prebuilt_etc { name: "protolog.conf.json.gz", src: ":services.core.json.gz", } - -platform_compat_config { - name: "services-core-platform-compat-config", - src: ":services.core.unboosted", -} - diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java index 40a7dfcfc18d..76572d35d516 100644 --- a/services/core/java/android/content/pm/PackageManagerInternal.java +++ b/services/core/java/android/content/pm/PackageManagerInternal.java @@ -319,6 +319,12 @@ public abstract class PackageManagerInternal { int deviceOwnerUserId, String deviceOwner, SparseArray<String> profileOwners); /** + * Called by DevicePolicyManagerService to set the package names protected by the device + * owner. + */ + public abstract void setDeviceOwnerProtectedPackages(List<String> packageNames); + + /** * Returns {@code true} if a given package can't be wiped. Otherwise, returns {@code false}. */ public abstract boolean isPackageDataProtected(int userId, String packageName); @@ -805,6 +811,12 @@ public abstract class PackageManagerInternal { public abstract boolean isApexPackage(String packageName); /** + * Returns list of {@code packageName} of apks inside the given apex. + * @param apexPackageName Package name of the apk container of apex + */ + public abstract List<String> getApksInApex(String apexPackageName); + + /** * Uninstalls given {@code packageName}. * * @param packageName apex package to uninstall. diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index bd8a3618733f..c2e32d332f50 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -92,6 +92,7 @@ import android.net.NetworkInfo.DetailedState; import android.net.NetworkMisc; import android.net.NetworkMonitorManager; import android.net.NetworkPolicyManager; +import android.net.NetworkProvider; import android.net.NetworkQuotaInfo; import android.net.NetworkRequest; import android.net.NetworkScore; @@ -219,6 +220,7 @@ import java.util.Objects; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; +import java.util.concurrent.atomic.AtomicInteger; /** * @hide @@ -595,6 +597,10 @@ public class ConnectivityService extends IConnectivityManager.Stub // sequence number of NetworkRequests private int mNextNetworkRequestId = 1; + // Sequence number for NetworkProvider IDs. + private final AtomicInteger mNextNetworkProviderId = new AtomicInteger( + NetworkProvider.FIRST_PROVIDER_ID); + // NetworkRequest activity String log entries. private static final int MAX_NETWORK_REQUEST_LOGS = 20; private final LocalLog mNetworkRequestInfoLogs = new LocalLog(MAX_NETWORK_REQUEST_LOGS); @@ -3029,25 +3035,9 @@ public class ConnectivityService extends IConnectivityManager.Stub if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) { if (VDBG) log("NetworkFactory connected"); // Finish setting up the full connection - mNetworkFactoryInfos.get(msg.replyTo).asyncChannel.sendMessage( - AsyncChannel.CMD_CHANNEL_FULL_CONNECTION); - // A network factory has connected. Send it all current NetworkRequests. - for (NetworkRequestInfo nri : mNetworkRequests.values()) { - if (nri.request.isListen()) continue; - ensureRunningOnConnectivityServiceThread(); - NetworkAgentInfo nai = nri.mSatisfier; - final int score; - final int serial; - if (nai != null) { - score = nai.getCurrentScore(); - serial = nai.factorySerialNumber; - } else { - score = 0; - serial = NetworkFactory.SerialNumber.NONE; - } - ac.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK, score, serial, - nri.request); - } + NetworkFactoryInfo nfi = mNetworkFactoryInfos.get(msg.replyTo); + nfi.completeConnection(); + sendAllRequestsToFactory(nfi); } else { loge("Error connecting NetworkFactory"); mNetworkFactoryInfos.remove(msg.obj); @@ -3430,8 +3420,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) { - nfi.asyncChannel.sendMessage(android.net.NetworkFactory.CMD_CANCEL_REQUEST, - nri.request); + nfi.cancelRequest(nri.request); } } else { // listens don't have a singular affectedNetwork. Check all networks to see @@ -4927,15 +4916,70 @@ public class ConnectivityService extends IConnectivityManager.Stub private static class NetworkFactoryInfo { public final String name; public final Messenger messenger; - public final AsyncChannel asyncChannel; + private final AsyncChannel mAsyncChannel; + private final IBinder.DeathRecipient mDeathRecipient; public final int factorySerialNumber; NetworkFactoryInfo(String name, Messenger messenger, AsyncChannel asyncChannel, - int factorySerialNumber) { + int factorySerialNumber, IBinder.DeathRecipient deathRecipient) { this.name = name; this.messenger = messenger; - this.asyncChannel = asyncChannel; this.factorySerialNumber = factorySerialNumber; + mAsyncChannel = asyncChannel; + mDeathRecipient = deathRecipient; + + if ((mAsyncChannel == null) == (mDeathRecipient == null)) { + throw new AssertionError("Must pass exactly one of asyncChannel or deathRecipient"); + } + } + + boolean isLegacyNetworkFactory() { + return mAsyncChannel != null; + } + + void sendMessageToNetworkProvider(int what, int arg1, int arg2, Object obj) { + try { + messenger.send(Message.obtain(null /* handler */, what, arg1, arg2, obj)); + } catch (RemoteException e) { + // Remote process died. Ignore; the death recipient will remove this + // NetworkFactoryInfo from mNetworkFactoryInfos. + } + } + + void requestNetwork(NetworkRequest request, int score, int servingSerialNumber) { + if (isLegacyNetworkFactory()) { + mAsyncChannel.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK, score, + servingSerialNumber, request); + } else { + sendMessageToNetworkProvider(NetworkProvider.CMD_REQUEST_NETWORK, score, + servingSerialNumber, request); + } + } + + void cancelRequest(NetworkRequest request) { + if (isLegacyNetworkFactory()) { + mAsyncChannel.sendMessage(android.net.NetworkFactory.CMD_CANCEL_REQUEST, request); + } else { + sendMessageToNetworkProvider(NetworkProvider.CMD_CANCEL_REQUEST, 0, 0, request); + } + } + + void connect(Context context, Handler handler) { + if (isLegacyNetworkFactory()) { + mAsyncChannel.connect(context, handler, messenger); + } else { + try { + messenger.getBinder().linkToDeath(mDeathRecipient, 0); + } catch (RemoteException e) { + mDeathRecipient.binderDied(); + } + } + } + + void completeConnection() { + if (isLegacyNetworkFactory()) { + mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION); + } } } @@ -5306,6 +5350,11 @@ public class ConnectivityService extends IConnectivityManager.Stub mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_LISTENER, nri)); } + /** Returns the next Network provider ID. */ + public final int nextNetworkProviderId() { + return mNextNetworkProviderId.getAndIncrement(); + } + @Override public void releaseNetworkRequest(NetworkRequest networkRequest) { ensureNetworkRequestHasType(networkRequest); @@ -5317,23 +5366,51 @@ public class ConnectivityService extends IConnectivityManager.Stub public int registerNetworkFactory(Messenger messenger, String name) { enforceNetworkFactoryPermission(); NetworkFactoryInfo nfi = new NetworkFactoryInfo(name, messenger, new AsyncChannel(), - NetworkFactory.SerialNumber.nextSerialNumber()); + nextNetworkProviderId(), null /* deathRecipient */); mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_FACTORY, nfi)); return nfi.factorySerialNumber; } private void handleRegisterNetworkFactory(NetworkFactoryInfo nfi) { + if (mNetworkFactoryInfos.containsKey(nfi.messenger)) { + // Avoid creating duplicates. even if an app makes a direct AIDL call. + // This will never happen if an app calls ConnectivityManager#registerNetworkProvider, + // as that will throw if a duplicate provider is registered. + Slog.e(TAG, "Attempt to register existing NetworkFactoryInfo " + + mNetworkFactoryInfos.get(nfi.messenger).name); + return; + } + if (DBG) log("Got NetworkFactory Messenger for " + nfi.name); mNetworkFactoryInfos.put(nfi.messenger, nfi); - nfi.asyncChannel.connect(mContext, mTrackerHandler, nfi.messenger); + nfi.connect(mContext, mTrackerHandler); + if (!nfi.isLegacyNetworkFactory()) { + // Legacy NetworkFactories get their requests when their AsyncChannel connects. + sendAllRequestsToFactory(nfi); + } } @Override - public void unregisterNetworkFactory(Messenger messenger) { + public int registerNetworkProvider(Messenger messenger, String name) { + enforceNetworkFactoryPermission(); + NetworkFactoryInfo nfi = new NetworkFactoryInfo(name, messenger, + null /* asyncChannel */, nextNetworkProviderId(), + () -> unregisterNetworkProvider(messenger)); + mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_FACTORY, nfi)); + return nfi.factorySerialNumber; + } + + @Override + public void unregisterNetworkProvider(Messenger messenger) { enforceNetworkFactoryPermission(); mHandler.sendMessage(mHandler.obtainMessage(EVENT_UNREGISTER_NETWORK_FACTORY, messenger)); } + @Override + public void unregisterNetworkFactory(Messenger messenger) { + unregisterNetworkProvider(messenger); + } + private void handleUnregisterNetworkFactory(Messenger messenger) { NetworkFactoryInfo nfi = mNetworkFactoryInfos.remove(messenger); if (nfi == null) { @@ -5343,6 +5420,12 @@ public class ConnectivityService extends IConnectivityManager.Stub if (DBG) log("unregisterNetworkFactory for " + nfi.name); } + @Override + public void declareNetworkRequestUnfulfillable(NetworkRequest request) { + enforceNetworkFactoryPermission(); + mHandler.post(() -> handleReleaseNetworkRequest(request, Binder.getCallingUid(), true)); + } + // NOTE: Accessed on multiple threads, must be synchronized on itself. @GuardedBy("mNetworkForNetId") private final SparseArray<NetworkAgentInfo> mNetworkForNetId = new SparseArray<>(); @@ -5961,8 +6044,26 @@ public class ConnectivityService extends IConnectivityManager.Stub log("sending new Min Network Score(" + score + "): " + networkRequest.toString()); } for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) { - nfi.asyncChannel.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK, score, - serial, networkRequest); + nfi.requestNetwork(networkRequest, score, serial); + } + } + + /** Sends all current NetworkRequests to the specified factory. */ + private void sendAllRequestsToFactory(NetworkFactoryInfo nfi) { + ensureRunningOnConnectivityServiceThread(); + for (NetworkRequestInfo nri : mNetworkRequests.values()) { + if (nri.request.isListen()) continue; + NetworkAgentInfo nai = nri.mSatisfier; + final int score; + final int serial; + if (nai != null) { + score = nai.getCurrentScore(); + serial = nai.factorySerialNumber; + } else { + score = 0; + serial = NetworkFactory.SerialNumber.NONE; + } + nfi.requestNetwork(nri.request, score, serial); } } diff --git a/services/core/java/com/android/server/CountryDetectorService.java b/services/core/java/com/android/server/CountryDetectorService.java index 861c731c69e0..b0132d35fa3b 100644 --- a/services/core/java/com/android/server/CountryDetectorService.java +++ b/services/core/java/com/android/server/CountryDetectorService.java @@ -24,21 +24,29 @@ import android.location.ICountryListener; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; +import android.text.TextUtils; import android.util.PrintWriterPrinter; import android.util.Printer; import android.util.Slog; +import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.BackgroundThread; import com.android.internal.util.DumpUtils; import com.android.server.location.ComprehensiveCountryDetector; +import com.android.server.location.CountryDetectorBase; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.lang.reflect.InvocationTargetException; import java.util.HashMap; /** - * This class detects the country that the user is in through {@link ComprehensiveCountryDetector}. + * This class detects the country that the user is in. The default country detection is made through + * {@link com.android.server.location.ComprehensiveCountryDetector}. It is possible to overlay the + * detection algorithm by overlaying the attribute R.string.config_customCountryDetector with the + * custom class name to use instead. The custom class must extend + * {@link com.android.server.location.CountryDetectorBase} * * @hide */ @@ -88,7 +96,7 @@ public class CountryDetectorService extends ICountryDetector.Stub { private final HashMap<IBinder, Receiver> mReceivers; private final Context mContext; - private ComprehensiveCountryDetector mCountryDetector; + private CountryDetectorBase mCountryDetector; private boolean mSystemReady; private Handler mHandler; private CountryListener mLocationBasedDetectorListener; @@ -184,8 +192,17 @@ public class CountryDetectorService extends ICountryDetector.Stub { }); } - private void initialize() { - mCountryDetector = new ComprehensiveCountryDetector(mContext); + @VisibleForTesting + void initialize() { + final String customCountryClass = mContext.getString(R.string.config_customCountryDetector); + if (!TextUtils.isEmpty(customCountryClass)) { + mCountryDetector = loadCustomCountryDetectorIfAvailable(customCountryClass); + } + + if (mCountryDetector == null) { + Slog.d(TAG, "Using default country detector"); + mCountryDetector = new ComprehensiveCountryDetector(mContext); + } mLocationBasedDetectorListener = country -> mHandler.post(() -> notifyReceivers(country)); } @@ -194,10 +211,32 @@ public class CountryDetectorService extends ICountryDetector.Stub { } @VisibleForTesting + CountryDetectorBase getCountryDetector() { + return mCountryDetector; + } + + @VisibleForTesting boolean isSystemReady() { return mSystemReady; } + private CountryDetectorBase loadCustomCountryDetectorIfAvailable( + final String customCountryClass) { + CountryDetectorBase customCountryDetector = null; + + Slog.d(TAG, "Using custom country detector class: " + customCountryClass); + try { + customCountryDetector = Class.forName(customCountryClass).asSubclass( + CountryDetectorBase.class).getConstructor(Context.class).newInstance( + mContext); + } catch (ClassNotFoundException | InstantiationException | IllegalAccessException + | NoSuchMethodException | InvocationTargetException e) { + Slog.e(TAG, "Could not instantiate the custom country detector class"); + } + + return customCountryDetector; + } + @SuppressWarnings("unused") @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { @@ -206,9 +245,10 @@ public class CountryDetectorService extends ICountryDetector.Stub { try { final Printer p = new PrintWriterPrinter(fout); p.println("CountryDetectorService state:"); + p.println("Country detector class=" + mCountryDetector.getClass().getName()); p.println(" Number of listeners=" + mReceivers.keySet().size()); if (mCountryDetector == null) { - p.println(" ComprehensiveCountryDetector not initialized"); + p.println(" CountryDetector not initialized"); } else { p.println(" " + mCountryDetector.toString()); } diff --git a/services/core/java/com/android/server/GnssManagerService.java b/services/core/java/com/android/server/GnssManagerService.java index bbcfdc63f3f1..32cdc41472c9 100644 --- a/services/core/java/com/android/server/GnssManagerService.java +++ b/services/core/java/com/android/server/GnssManagerService.java @@ -47,7 +47,6 @@ import com.android.internal.util.DumpUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.server.LocationManagerServiceUtils.LinkedListener; import com.android.server.LocationManagerServiceUtils.LinkedListenerBase; -import com.android.server.location.AbstractLocationProvider; import com.android.server.location.CallerIdentity; import com.android.server.location.GnssBatchingProvider; import com.android.server.location.GnssCapabilitiesProvider; @@ -116,11 +115,9 @@ public class GnssManagerService { private final Handler mHandler; public GnssManagerService(LocationManagerService locationManagerService, - Context context, - AbstractLocationProvider.LocationProviderManager gnssProviderManager, - LocationUsageLogger locationUsageLogger) { - this(locationManagerService, context, new GnssLocationProvider(context, gnssProviderManager, - FgThread.getHandler().getLooper()), locationUsageLogger); + Context context, LocationUsageLogger locationUsageLogger) { + this(locationManagerService, context, + new GnssLocationProvider(context, FgThread.getHandler()), locationUsageLogger); } // Can use this constructor to inject GnssLocationProvider for testing diff --git a/services/core/java/com/android/server/GraphicsStatsService.java b/services/core/java/com/android/server/GraphicsStatsService.java index 70569db5e2d3..5179fa7a6eb5 100644 --- a/services/core/java/com/android/server/GraphicsStatsService.java +++ b/services/core/java/com/android/server/GraphicsStatsService.java @@ -38,11 +38,13 @@ import android.view.IGraphicsStats; import android.view.IGraphicsStatsCallback; import com.android.internal.util.DumpUtils; +import com.android.internal.util.FastPrintWriter; import java.io.File; import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; +import java.io.StringWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; @@ -78,6 +80,8 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { private static final int SAVE_BUFFER = 1; private static final int DELETE_OLD = 2; + private static final int AID_STATSD = 1066; // Statsd uid is set to 1066 forever. + // This isn't static because we need this to happen after registerNativeMethods, however // the class is loaded (and thus static ctor happens) before that occurs. private final int ASHMEM_SIZE = nGetAshmemSize(); @@ -121,6 +125,7 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { return true; } }); + nativeInit(); } /** @@ -186,6 +191,86 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { return pfd; } + // If lastFullDay is true, pullGraphicsStats returns stats for the last complete day/24h period + // that does not include today. If lastFullDay is false, pullGraphicsStats returns stats for the + // current day. + // This method is invoked from native code only. + @SuppressWarnings({"UnusedDeclaration"}) + private long pullGraphicsStats(boolean lastFullDay) throws RemoteException { + int uid = Binder.getCallingUid(); + + // DUMP and PACKAGE_USAGE_STATS permissions are required to invoke this method. + // TODO: remove exception for statsd daemon after required permissions are granted. statsd + // TODO: should have these permissions granted by data/etc/platform.xml, but it does not. + if (uid != AID_STATSD) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new FastPrintWriter(sw); + if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) { + pw.flush(); + throw new RemoteException(sw.toString()); + } + } + + long callingIdentity = Binder.clearCallingIdentity(); + try { + return pullGraphicsStatsImpl(lastFullDay); + } finally { + Binder.restoreCallingIdentity(callingIdentity); + } + } + + private long pullGraphicsStatsImpl(boolean lastFullDay) { + long targetDay; + if (lastFullDay) { + // Get stats from yesterday. Stats stay constant, because the day is over. + targetDay = normalizeDate(System.currentTimeMillis() - 86400000).getTimeInMillis(); + } else { + // Get stats from today. Stats may change as more apps are run today. + targetDay = normalizeDate(System.currentTimeMillis()).getTimeInMillis(); + } + + // Find active buffers for targetDay. + ArrayList<HistoricalBuffer> buffers; + synchronized (mLock) { + buffers = new ArrayList<>(mActive.size()); + for (int i = 0; i < mActive.size(); i++) { + ActiveBuffer buffer = mActive.get(i); + if (buffer.mInfo.startTime == targetDay) { + try { + buffers.add(new HistoricalBuffer(buffer)); + } catch (IOException ex) { + // Ignore + } + } + } + } + + // Dump active and historic buffers for targetDay in a serialized + // GraphicsStatsServiceDumpProto proto. + long dump = nCreateDump(-1, true); + try { + synchronized (mFileAccessLock) { + HashSet<File> skipList = dumpActiveLocked(dump, buffers); + buffers.clear(); + String subPath = String.format("%d", targetDay); + File dateDir = new File(mGraphicsStatsDir, subPath); + if (dateDir.exists()) { + for (File pkg : dateDir.listFiles()) { + for (File version : pkg.listFiles()) { + File data = new File(version, "total"); + if (skipList.contains(data)) { + continue; + } + nAddToDump(dump, data.getAbsolutePath()); + } + } + } + } + } finally { + return nFinishDumpInMemory(dump); + } + } + private ParcelFileDescriptor getPfd(MemoryFile file) { try { if (!file.getFileDescriptor().valid()) { @@ -379,12 +464,21 @@ public class GraphicsStatsService extends IGraphicsStats.Stub { } } + @Override + protected void finalize() throws Throwable { + nativeDestructor(); + } + + private native void nativeInit(); + private static native void nativeDestructor(); + private static native int nGetAshmemSize(); private static native long nCreateDump(int outFd, boolean isProto); private static native void nAddToDump(long dump, String path, String packageName, long versionCode, long startTime, long endTime, byte[] data); private static native void nAddToDump(long dump, String path); private static native void nFinishDump(long dump); + private static native long nFinishDumpInMemory(long dump); private static native void nSaveBuffer(String path, String packageName, long versionCode, long startTime, long endTime, byte[] data); diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java index c5f1923b0b98..32128d5f26f8 100644 --- a/services/core/java/com/android/server/LocationManagerService.java +++ b/services/core/java/com/android/server/LocationManagerService.java @@ -23,8 +23,6 @@ import static android.location.LocationManager.NETWORK_PROVIDER; import static android.location.LocationManager.PASSIVE_PROVIDER; import static android.os.PowerManager.locationPowerSaveModeToString; -import static com.android.internal.util.Preconditions.checkState; - import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; @@ -74,7 +72,6 @@ import android.os.UserHandle; import android.os.UserManager; import android.os.WorkSource; import android.os.WorkSource.WorkChain; -import android.provider.Settings; import android.stats.location.LocationStatsEnums; import android.text.TextUtils; import android.util.EventLog; @@ -92,6 +89,7 @@ import com.android.internal.util.DumpUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.server.location.AbstractLocationProvider; +import com.android.server.location.AbstractLocationProvider.State; import com.android.server.location.ActivityRecognitionProxy; import com.android.server.location.CallerIdentity; import com.android.server.location.GeocoderProxy; @@ -105,6 +103,7 @@ import com.android.server.location.LocationRequestStatistics.PackageStatistics; import com.android.server.location.LocationSettingsStore; import com.android.server.location.LocationUsageLogger; import com.android.server.location.MockProvider; +import com.android.server.location.MockableLocationProvider; import com.android.server.location.PassiveProvider; import com.android.server.pm.permission.PermissionManagerServiceInternal; @@ -121,6 +120,8 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; /** @@ -196,6 +197,8 @@ public class LocationManagerService extends ILocationManager.Stub { private final LocationSettingsStore mSettingsStore; private final LocationUsageLogger mLocationUsageLogger; + private final PassiveLocationProviderManager mPassiveManager; + private AppOpsManager mAppOps; private PackageManager mPackageManager; private PowerManager mPowerManager; @@ -205,21 +208,17 @@ public class LocationManagerService extends ILocationManager.Stub { private GeofenceManager mGeofenceManager; private LocationFudger mLocationFudger; private GeocoderProxy mGeocodeProvider; - @Nullable - private GnssManagerService mGnssManagerService; - private PassiveProvider mPassiveProvider; // track passive provider for special cases + @Nullable private GnssManagerService mGnssManagerService; + @GuardedBy("mLock") private String mExtraLocationControllerPackage; - private boolean mExtraLocationControllerPackageEnabled; - - // list of currently active providers @GuardedBy("mLock") - private final ArrayList<LocationProviderManager> mProviders = new ArrayList<>(); + private boolean mExtraLocationControllerPackageEnabled; - // list of non-mock providers, so that when mock providers replace real providers, they can be - // later re-replaced - @GuardedBy("mLock") - private final ArrayList<LocationProviderManager> mRealProviders = new ArrayList<>(); + // @GuardedBy("mLock") + // hold lock for write or to prevent write, no lock for read + private final CopyOnWriteArrayList<LocationProviderManager> mProviderManagers = + new CopyOnWriteArrayList<>(); @GuardedBy("mLock") private final HashMap<Object, Receiver> mReceivers = new HashMap<>(); @@ -238,9 +237,9 @@ public class LocationManagerService extends ILocationManager.Stub { private final HashMap<String, Location> mLastLocationCoarseInterval = new HashMap<>(); - // current active user on the device - other users are denied location data - private int mCurrentUserId = UserHandle.USER_SYSTEM; - private int[] mCurrentUserProfiles = new int[]{UserHandle.USER_SYSTEM}; + // current active user on the device + private int mCurrentUserId; + private int[] mCurrentUserProfiles; @GuardedBy("mLock") @PowerManager.LocationPowerSaveMode @@ -252,6 +251,17 @@ public class LocationManagerService extends ILocationManager.Stub { mSettingsStore = new LocationSettingsStore(mContext, mHandler); mLocationUsageLogger = new LocationUsageLogger(); + mCurrentUserId = UserHandle.USER_NULL; + mCurrentUserProfiles = new int[]{UserHandle.USER_NULL}; + + // set up passive provider - we do this early because it has no dependencies on system + // services or external code that isn't ready yet, and because this allows the variable to + // be final. other more complex providers are initialized later, when system services are + // ready + mPassiveManager = new PassiveLocationProviderManager(); + mProviderManagers.add(mPassiveManager); + mPassiveManager.setRealProvider(new PassiveProvider(mContext)); + // Let the package manager query which are the default location // providers as they get certain permissions granted by default. PermissionManagerServiceInternal permissionManagerInternal = LocalServices.getService( @@ -415,15 +425,15 @@ public class LocationManagerService extends ILocationManager.Stub { for (Receiver receiver : mReceivers.values()) { receiver.updateMonitoring(true); } - for (LocationProviderManager p : mProviders) { - applyRequirementsLocked(p); + for (LocationProviderManager manager : mProviderManagers) { + applyRequirementsLocked(manager); } } @GuardedBy("mLock") private void onPermissionsChangedLocked() { - for (LocationProviderManager p : mProviders) { - applyRequirementsLocked(p); + for (LocationProviderManager manager : mProviderManagers) { + applyRequirementsLocked(manager); } } @@ -442,16 +452,16 @@ public class LocationManagerService extends ILocationManager.Stub { mBatterySaverMode = newLocationMode; - for (LocationProviderManager p : mProviders) { - applyRequirementsLocked(p); + for (LocationProviderManager manager : mProviderManagers) { + applyRequirementsLocked(manager); } } @GuardedBy("mLock") private void onScreenStateChangedLocked() { if (mBatterySaverMode == PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF) { - for (LocationProviderManager p : mProviders) { - applyRequirementsLocked(p); + for (LocationProviderManager manager : mProviderManagers) { + applyRequirementsLocked(manager); } } } @@ -466,8 +476,8 @@ public class LocationManagerService extends ILocationManager.Stub { intent.putExtra(LocationManager.EXTRA_LOCATION_ENABLED, isLocationEnabledForUser(userId)); mContext.sendBroadcastAsUser(intent, UserHandle.of(userId)); - for (LocationProviderManager p : mProviders) { - p.onUseableChangedLocked(userId); + for (LocationProviderManager manager : mProviderManagers) { + manager.onUseableChangedLocked(userId); } } @@ -521,22 +531,22 @@ public class LocationManagerService extends ILocationManager.Stub { @GuardedBy("mLock") private void onBackgroundThrottleIntervalChangedLocked() { - for (LocationProviderManager provider : mProviders) { - applyRequirementsLocked(provider); + for (LocationProviderManager manager : mProviderManagers) { + applyRequirementsLocked(manager); } } @GuardedBy("mLock") private void onBackgroundThrottleWhitelistChangedLocked() { - for (LocationProviderManager p : mProviders) { - applyRequirementsLocked(p); + for (LocationProviderManager manager : mProviderManagers) { + applyRequirementsLocked(manager); } } @GuardedBy("lock") private void onIgnoreSettingsWhitelistChangedLocked() { - for (LocationProviderManager p : mProviders) { - applyRequirementsLocked(p); + for (LocationProviderManager manager : mProviderManagers) { + applyRequirementsLocked(manager); } } @@ -623,22 +633,11 @@ public class LocationManagerService extends ILocationManager.Stub { @GuardedBy("mLock") private void initializeProvidersLocked() { - // create a passive location provider, which is always enabled - LocationProviderManager passiveProviderManager = new LocationProviderManager( - PASSIVE_PROVIDER); - addProviderLocked(passiveProviderManager); - mPassiveProvider = new PassiveProvider(mContext, passiveProviderManager); - passiveProviderManager.attachLocked(mPassiveProvider); - if (GnssManagerService.isGnssSupported()) { - // Create a gps location provider manager - LocationProviderManager gnssProviderManager = new LocationProviderManager(GPS_PROVIDER); - mRealProviders.add(gnssProviderManager); - addProviderLocked(gnssProviderManager); - - mGnssManagerService = new GnssManagerService(this, mContext, gnssProviderManager, - mLocationUsageLogger); - gnssProviderManager.attachLocked(mGnssManagerService.getGnssLocationProvider()); + mGnssManagerService = new GnssManagerService(this, mContext, mLocationUsageLogger); + LocationProviderManager gnssManager = new LocationProviderManager(GPS_PROVIDER); + mProviderManagers.add(gnssManager); + gnssManager.setRealProvider(mGnssManagerService.getGnssLocationProvider()); } /* @@ -662,37 +661,31 @@ public class LocationManagerService extends ILocationManager.Stub { ensureFallbackFusedProviderPresentLocked(pkgs); - // bind to network provider - LocationProviderManager networkProviderManager = new LocationProviderManager( - NETWORK_PROVIDER); LocationProviderProxy networkProvider = LocationProviderProxy.createAndBind( mContext, - networkProviderManager, NETWORK_LOCATION_SERVICE_ACTION, com.android.internal.R.bool.config_enableNetworkLocationOverlay, com.android.internal.R.string.config_networkLocationProviderPackageName, com.android.internal.R.array.config_locationProviderPackageNames); if (networkProvider != null) { - mRealProviders.add(networkProviderManager); - addProviderLocked(networkProviderManager); - networkProviderManager.attachLocked(networkProvider); + LocationProviderManager networkManager = new LocationProviderManager(NETWORK_PROVIDER); + mProviderManagers.add(networkManager); + networkManager.setRealProvider(networkProvider); } else { Slog.w(TAG, "no network location provider found"); } // bind to fused provider - LocationProviderManager fusedProviderManager = new LocationProviderManager(FUSED_PROVIDER); LocationProviderProxy fusedProvider = LocationProviderProxy.createAndBind( mContext, - fusedProviderManager, FUSED_LOCATION_SERVICE_ACTION, com.android.internal.R.bool.config_enableFusedLocationOverlay, com.android.internal.R.string.config_fusedLocationProviderPackageName, com.android.internal.R.array.config_locationProviderPackageNames); if (fusedProvider != null) { - mRealProviders.add(fusedProviderManager); - addProviderLocked(fusedProviderManager); - fusedProviderManager.attachLocked(fusedProvider); + LocationProviderManager fusedManager = new LocationProviderManager(FUSED_PROVIDER); + mProviderManagers.add(fusedManager); + fusedManager.setRealProvider(fusedProvider); } else { Slog.e(TAG, "no fused location provider found", new IllegalStateException("Location service needs a fused location provider")); @@ -754,10 +747,7 @@ public class LocationManagerService extends ILocationManager.Stub { Boolean.parseBoolean(fragments[7]) /* supportsBearing */, Integer.parseInt(fragments[8]) /* powerRequirement */, Integer.parseInt(fragments[9]) /* accuracy */); - LocationProviderManager testProviderManager = new LocationProviderManager(name); - addProviderLocked(testProviderManager); - testProviderManager.attachLocked( - new MockProvider(mContext, testProviderManager, properties)); + addTestProvider(name, properties, mContext.getOpPackageName()); } } @@ -771,231 +761,202 @@ public class LocationManagerService extends ILocationManager.Stub { Log.d(TAG, "foreground user is changing to " + userId); } - int oldUserId = userId; + int oldUserId = mCurrentUserId; mCurrentUserId = userId; onUserProfilesChangedLocked(); // let providers know the current user has changed - for (LocationProviderManager p : mProviders) { - p.onUseableChangedLocked(oldUserId); - p.onUseableChangedLocked(mCurrentUserId); + for (LocationProviderManager manager : mProviderManagers) { + // update LOCATION_PROVIDERS_ALLOWED for best effort backwards compatibility + mSettingsStore.setLocationProviderAllowed(manager.getName(), + manager.isUseable(mCurrentUserId), mCurrentUserId); + + manager.onUseableChangedLocked(oldUserId); + manager.onUseableChangedLocked(mCurrentUserId); } } /** * Location provider manager, manages a LocationProvider. */ - class LocationProviderManager implements AbstractLocationProvider.LocationProviderManager { + class LocationProviderManager implements MockableLocationProvider.Listener { private final String mName; - // remember to clear binder identity before invoking any provider operation - @GuardedBy("mLock") - @Nullable - protected AbstractLocationProvider mProvider; + // acquiring mLock makes operations on mProvider atomic, but is otherwise unnecessary + protected final MockableLocationProvider mProvider; @GuardedBy("mLock") - private SparseArray<Boolean> mUseable; // combined state for each user id - @GuardedBy("mLock") - private boolean mEnabled; // state of provider - - @GuardedBy("mLock") - @Nullable - private ProviderProperties mProperties; + private final SparseArray<Boolean> mUseable; // combined state for each user id private LocationProviderManager(String name) { mName = name; - - mProvider = null; mUseable = new SparseArray<>(1); - mEnabled = false; - mProperties = null; - - // update LOCATION_PROVIDERS_ALLOWED for best effort backwards compatibility - Settings.Secure.putStringForUser( - mContext.getContentResolver(), - Settings.Secure.LOCATION_PROVIDERS_ALLOWED, - "-" + mName, - mCurrentUserId); - } - - @GuardedBy("mLock") - public void attachLocked(AbstractLocationProvider provider) { - Objects.requireNonNull(provider); - checkState(mProvider == null); - if (D) { - Log.d(TAG, mName + " provider attached"); - } - - mProvider = provider; - - // it would be more correct to call this for all users, but we know this can only - // affect the current user since providers are disabled for non-current users - onUseableChangedLocked(mCurrentUserId); + // initialize last since this lets our reference escape + mProvider = new MockableLocationProvider(mContext, mLock, this); } public String getName() { return mName; } - @GuardedBy("mLock") - public List<String> getPackagesLocked() { - if (mProvider == null) { - return Collections.emptyList(); - } else { - // safe to not clear binder context since this doesn't call into the real provider - return mProvider.getProviderPackages(); - } + public boolean hasProvider() { + return mProvider.getProvider() != null; } - public boolean isMock() { - return false; + public void setRealProvider(AbstractLocationProvider provider) { + mProvider.setRealProvider(provider); } - @GuardedBy("mLock") - public boolean isPassiveLocked() { - return mProvider == mPassiveProvider; + public void setMockProvider(@Nullable MockProvider provider) { + mProvider.setMockProvider(provider); + } + + public Set<String> getPackages() { + return mProvider.getState().providerPackageNames; } - @GuardedBy("mLock") @Nullable - public ProviderProperties getPropertiesLocked() { - return mProperties; + public ProviderProperties getProperties() { + return mProvider.getState().properties; } - public void setRequest(ProviderRequest request, WorkSource workSource) { - // move calls going to providers onto a different thread to avoid deadlock - mHandler.post(() -> { - synchronized (mLock) { - if (mProvider != null) { - mProvider.onSetRequest(request, workSource); - } + public void setMockProviderEnabled(boolean enabled) { + synchronized (mLock) { + if (!mProvider.isMock()) { + throw new IllegalArgumentException(mName + " provider is not a test provider"); } - }); + + mProvider.setMockProviderEnabled(enabled); + } } - public void sendExtraCommand(String command, Bundle extras) { - int uid = Binder.getCallingUid(); - int pid = Binder.getCallingPid(); + public void setMockProviderLocation(Location location) { + synchronized (mLock) { + if (!mProvider.isMock()) { + throw new IllegalArgumentException(mName + " provider is not a test provider"); + } - // move calls going to providers onto a different thread to avoid deadlock - mHandler.post(() -> { - synchronized (mLock) { - if (mProvider != null) { - mProvider.onSendExtraCommand(uid, pid, command, extras); - } + String locationProvider = location.getProvider(); + if (!TextUtils.isEmpty(locationProvider) && !mName.equals(locationProvider)) { + // The location has an explicit provider that is different from the mock + // provider name. The caller may be trying to fool us via b/33091107. + EventLog.writeEvent(0x534e4554, "33091107", Binder.getCallingUid(), + mName + "!=" + locationProvider); } - }); - } - @GuardedBy("mLock") - public void dumpLocked(FileDescriptor fd, IndentingPrintWriter pw, String[] args) { - pw.print(mName + " provider"); - if (isMock()) { - pw.print(" [mock]"); + mProvider.setMockProviderLocation(location); } - pw.println(":"); + } - pw.increaseIndent(); + public List<LocationRequest> getMockProviderRequests() { + synchronized (mLock) { + if (!mProvider.isMock()) { + throw new IllegalArgumentException(mName + " provider is not a test provider"); + } - pw.println("useable=" + isUseableLocked(mCurrentUserId)); - if (!isUseableLocked(mCurrentUserId)) { - pw.println("attached=" + (mProvider != null)); - pw.println("enabled=" + mEnabled); + return mProvider.getCurrentRequest().locationRequests; } + } - pw.println("properties=" + mProperties); + public void setRequest(ProviderRequest request) { + mProvider.setRequest(request); + } - if (mProvider != null) { - // in order to be consistent with other provider APIs, this should be run on the - // location thread... but this likely isn't worth it just for dumping info. - long identity = Binder.clearCallingIdentity(); - try { - mProvider.dump(fd, pw, args); - } finally { - Binder.restoreCallingIdentity(identity); + public void sendExtraCommand(int uid, int pid, String command, Bundle extras) { + mProvider.sendExtraCommand(uid, pid, command, extras); + } + + public void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) { + synchronized (mLock) { + pw.print(mName + " provider"); + if (mProvider.isMock()) { + pw.print(" [mock]"); + } + pw.println(":"); + + pw.increaseIndent(); + + pw.println("useable=" + isUseable(mCurrentUserId)); + if (!isUseable(mCurrentUserId)) { + pw.println("enabled=" + mProvider.getState().enabled); } + + pw.println("properties=" + mProvider.getState().properties); } + mProvider.dump(fd, pw, args); + pw.decreaseIndent(); } + @GuardedBy("mLock") @Override public void onReportLocation(Location location) { - // likelihood of a 0,0 bug is far greater than this being a valid location - if (!isMock() && location.getLatitude() == 0 && location.getLongitude() == 0) { - Slog.w(TAG, "blocking 0,0 location from " + mName + " provider"); - return; + // don't validate mock locations + if (!location.isFromMockProvider()) { + if (location.getLatitude() == 0 && location.getLongitude() == 0) { + Slog.w(TAG, "blocking 0,0 location from " + mName + " provider"); + return; + } } - synchronized (mLock) { - handleLocationChangedLocked(location, this); - } + handleLocationChangedLocked(location, this); } + @GuardedBy("mLock") @Override public void onReportLocation(List<Location> locations) { if (mGnssManagerService == null) { return; } - synchronized (mLock) { - LocationProviderManager gpsProvider = getLocationProviderLocked(GPS_PROVIDER); - if (gpsProvider == null || !gpsProvider.isUseableLocked()) { - Slog.w(TAG, "reportLocationBatch() called without user permission"); - return; - } - mGnssManagerService.onReportLocation(locations); + if (!GPS_PROVIDER.equals(mName) || !isUseable()) { + Slog.w(TAG, "reportLocationBatch() called without user permission"); + return; } - } - @Override - public void onSetEnabled(boolean enabled) { - synchronized (mLock) { - if (enabled == mEnabled) { - return; - } - - if (D) { - Log.d(TAG, mName + " provider enabled is now " + mEnabled); - } - - mEnabled = enabled; - - // it would be more correct to call this for all users, but we know this can only - // affect the current user since providers are disabled for non-current users - onUseableChangedLocked(mCurrentUserId); - } + mGnssManagerService.onReportLocation(locations); } + @GuardedBy("mLock") @Override - public void onSetProperties(ProviderProperties properties) { - synchronized (mLock) { - mProperties = properties; + public void onStateChanged(State oldState, State newState) { + if (oldState.enabled != newState.enabled) { + // it would be more correct to call this for all users, but we know this can + // only affect the current user since providers are disabled for non-current + // users + onUseableChangedLocked(mCurrentUserId); } } @GuardedBy("mLock") - public boolean isUseableLocked() { - return isUseableLocked(mCurrentUserId); + public boolean isUseable() { + return isUseable(mCurrentUserId); } @GuardedBy("mLock") - public boolean isUseableLocked(int userId) { - return mUseable.get(userId, Boolean.FALSE); + public boolean isUseable(int userId) { + synchronized (mLock) { + return mUseable.get(userId, Boolean.FALSE); + } } @GuardedBy("mLock") public void onUseableChangedLocked(int userId) { + if (userId == UserHandle.USER_NULL) { + // only used during initialization - we don't care about the null user + return; + } + // if any property that contributes to "useability" here changes state, it MUST result // in a direct or indrect call to onUseableChangedLocked. this allows the provider to // guarantee that it will always eventually reach the correct state. - boolean useable = mProvider != null && mProviders.contains(this) - && isCurrentProfileLocked(userId) && isLocationEnabledForUser(userId) - && mEnabled; + boolean useable = isCurrentProfileLocked(userId) + && mSettingsStore.isLocationEnabled(userId) && mProvider.getState().enabled; - if (useable == isUseableLocked(userId)) { + if (useable == isUseable(userId)) { return; } mUseable.put(userId, useable); @@ -1007,11 +968,7 @@ public class LocationManagerService extends ILocationManager.Stub { // fused and passive provider never get public updates for legacy reasons if (!FUSED_PROVIDER.equals(mName) && !PASSIVE_PROVIDER.equals(mName)) { // update LOCATION_PROVIDERS_ALLOWED for best effort backwards compatibility - Settings.Secure.putStringForUser( - mContext.getContentResolver(), - Settings.Secure.LOCATION_PROVIDERS_ALLOWED, - (useable ? "+" : "-") + mName, - userId); + mSettingsStore.setLocationProviderAllowed(mName, useable, userId); Intent intent = new Intent(LocationManager.PROVIDERS_CHANGED_ACTION); intent.putExtra(LocationManager.EXTRA_PROVIDER_NAME, mName); @@ -1031,53 +988,38 @@ public class LocationManagerService extends ILocationManager.Stub { } } - private class MockLocationProvider extends LocationProviderManager { + class PassiveLocationProviderManager extends LocationProviderManager { - private ProviderRequest mCurrentRequest; - - private MockLocationProvider(String name) { - super(name); + private PassiveLocationProviderManager() { + super(PASSIVE_PROVIDER); } @Override - public void attachLocked(AbstractLocationProvider provider) { - checkState(provider instanceof MockProvider); - super.attachLocked(provider); - } - - public boolean isMock() { - return true; + public void setRealProvider(AbstractLocationProvider provider) { + Preconditions.checkArgument(provider instanceof PassiveProvider); + super.setRealProvider(provider); } - @GuardedBy("mLock") - public void setEnabledLocked(boolean enabled) { - if (mProvider != null) { - long identity = Binder.clearCallingIdentity(); - try { - ((MockProvider) mProvider).setEnabled(enabled); - } finally { - Binder.restoreCallingIdentity(identity); - } + @Override + public void setMockProvider(@Nullable MockProvider provider) { + if (provider != null) { + throw new IllegalArgumentException("Cannot mock the passive provider"); } } - @GuardedBy("mLock") - public void setLocationLocked(Location location) { - if (mProvider != null) { + public void updateLocation(Location location) { + synchronized (mLock) { + PassiveProvider passiveProvider = (PassiveProvider) mProvider.getProvider(); + Preconditions.checkState(passiveProvider != null); + long identity = Binder.clearCallingIdentity(); try { - ((MockProvider) mProvider).setLocation(location); + passiveProvider.updateLocation(location); } finally { Binder.restoreCallingIdentity(identity); } } } - - @Override - public void setRequest(ProviderRequest request, WorkSource workSource) { - super.setRequest(request, workSource); - mCurrentRequest = request; - } } /** @@ -1181,17 +1123,17 @@ public class LocationManagerService extends ILocationManager.Stub { // See if receiver has any enabled update records. Also note if any update records // are high power (has a high power provider with an interval under a threshold). for (UpdateRecord updateRecord : mUpdateRecords.values()) { - LocationProviderManager provider = getLocationProviderLocked( + LocationProviderManager manager = getLocationProviderManager( updateRecord.mProvider); - if (provider == null) { + if (manager == null) { continue; } - if (!provider.isUseableLocked() && !isSettingsExemptLocked(updateRecord)) { + if (!manager.isUseable() && !isSettingsExemptLocked(updateRecord)) { continue; } requestingLocation = true; - ProviderProperties properties = provider.getPropertiesLocked(); + ProviderProperties properties = manager.getProperties(); if (properties != null && properties.mPowerRequirement == Criteria.POWER_HIGH && updateRecord.mRequest.getInterval() < HIGH_POWER_INTERVAL_MS) { @@ -1432,7 +1374,7 @@ public class LocationManagerService extends ILocationManager.Stub { String featureId, String listenerIdentifier) { Objects.requireNonNull(listenerIdentifier); - return mGnssManagerService == null ? false : mGnssManagerService.addGnssBatchingCallback( + return mGnssManagerService != null && mGnssManagerService.addGnssBatchingCallback( callback, packageName, featureId, listenerIdentifier); } @@ -1443,7 +1385,7 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public boolean startGnssBatch(long periodNanos, boolean wakeOnFifoFull, String packageName) { - return mGnssManagerService == null ? false : mGnssManagerService.startGnssBatch(periodNanos, + return mGnssManagerService != null && mGnssManagerService.startGnssBatch(periodNanos, wakeOnFifoFull, packageName); } @@ -1454,35 +1396,14 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public boolean stopGnssBatch() { - return mGnssManagerService == null ? false : mGnssManagerService.stopGnssBatch(); + return mGnssManagerService != null && mGnssManagerService.stopGnssBatch(); } - @GuardedBy("mLock") - private void addProviderLocked(LocationProviderManager provider) { - Preconditions.checkState(getLocationProviderLocked(provider.getName()) == null); - - mProviders.add(provider); - - // it would be more correct to call this for all users, but we know this can only - // affect the current user since providers are disabled for non-current users - provider.onUseableChangedLocked(mCurrentUserId); - } - - @GuardedBy("mLock") - private void removeProviderLocked(LocationProviderManager provider) { - if (mProviders.remove(provider)) { - // it would be more correct to call this for all users, but we know this can only - // affect the current user since providers are disabled for non-current users - provider.onUseableChangedLocked(mCurrentUserId); - } - } - - @GuardedBy("mLock") @Nullable - private LocationProviderManager getLocationProviderLocked(String providerName) { - for (LocationProviderManager provider : mProviders) { - if (providerName.equals(provider.getName())) { - return provider; + private LocationProviderManager getLocationProviderManager(String providerName) { + for (LocationProviderManager manager : mProviderManagers) { + if (providerName.equals(manager.getName())) { + return manager; } } @@ -1531,12 +1452,12 @@ public class LocationManagerService extends ILocationManager.Stub { // network and fused providers are ok with COARSE or FINE return RESOLUTION_LEVEL_COARSE; } else { - for (LocationProviderManager lp : mProviders) { + for (LocationProviderManager lp : mProviderManagers) { if (!lp.getName().equals(provider)) { continue; } - ProviderProperties properties = lp.getPropertiesLocked(); + ProviderProperties properties = lp.getProperties(); if (properties != null) { if (properties.mRequiresSatellite) { // provider requiring satellites require FINE permission @@ -1587,11 +1508,9 @@ public class LocationManagerService extends ILocationManager.Stub { case RESOLUTION_LEVEL_COARSE: return AppOpsManager.OPSTR_COARSE_LOCATION; case RESOLUTION_LEVEL_FINE: - return AppOpsManager.OPSTR_FINE_LOCATION; + // fall through case RESOLUTION_LEVEL_NONE: - // The client is not allowed to get any location, so both FINE and COARSE ops will - // be denied. Pick the most restrictive one to be safe. - return AppOpsManager.OPSTR_FINE_LOCATION; + // fall through default: // Use the most restrictive ops if not sure. return AppOpsManager.OPSTR_FINE_LOCATION; @@ -1629,17 +1548,14 @@ public class LocationManagerService extends ILocationManager.Stub { */ @Override public List<String> getAllProviders() { - synchronized (mLock) { - ArrayList<String> providers = new ArrayList<>(mProviders.size()); - for (LocationProviderManager provider : mProviders) { - String name = provider.getName(); - if (FUSED_PROVIDER.equals(name)) { - continue; - } - providers.add(name); + ArrayList<String> providers = new ArrayList<>(mProviderManagers.size()); + for (LocationProviderManager manager : mProviderManagers) { + if (FUSED_PROVIDER.equals(manager.getName())) { + continue; } - return providers; + providers.add(manager.getName()); } + return providers; } /** @@ -1651,21 +1567,21 @@ public class LocationManagerService extends ILocationManager.Stub { public List<String> getProviders(Criteria criteria, boolean enabledOnly) { int allowedResolutionLevel = getCallerAllowedResolutionLevel(); synchronized (mLock) { - ArrayList<String> providers = new ArrayList<>(mProviders.size()); - for (LocationProviderManager provider : mProviders) { - String name = provider.getName(); + ArrayList<String> providers = new ArrayList<>(mProviderManagers.size()); + for (LocationProviderManager manager : mProviderManagers) { + String name = manager.getName(); if (FUSED_PROVIDER.equals(name)) { continue; } if (allowedResolutionLevel < getMinimumResolutionLevelForProviderUseLocked(name)) { continue; } - if (enabledOnly && !provider.isUseableLocked()) { + if (enabledOnly && !manager.isUseable()) { continue; } if (criteria != null && !android.location.LocationProvider.propertiesMeetCriteria( - name, provider.getPropertiesLocked(), criteria)) { + name, manager.getProperties(), criteria)) { continue; } providers.add(name); @@ -1702,12 +1618,12 @@ public class LocationManagerService extends ILocationManager.Stub { } @GuardedBy("mLock") - private void updateProviderUseableLocked(LocationProviderManager provider) { - boolean useable = provider.isUseableLocked(); + private void updateProviderUseableLocked(LocationProviderManager manager) { + boolean useable = manager.isUseable(); ArrayList<Receiver> deadReceivers = null; - ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider.getName()); + ArrayList<UpdateRecord> records = mRecordsByProvider.get(manager.getName()); if (records != null) { for (UpdateRecord record : records) { if (!isCurrentProfileLocked( @@ -1721,7 +1637,7 @@ public class LocationManagerService extends ILocationManager.Stub { } // Sends a notification message to the receiver - if (!record.mReceiver.callProviderEnabledLocked(provider.getName(), useable)) { + if (!record.mReceiver.callProviderEnabledLocked(manager.getName(), useable)) { if (deadReceivers == null) { deadReceivers = new ArrayList<>(); } @@ -1736,26 +1652,25 @@ public class LocationManagerService extends ILocationManager.Stub { } } - applyRequirementsLocked(provider); + applyRequirementsLocked(manager); } @GuardedBy("mLock") private void applyRequirementsLocked(String providerName) { - LocationProviderManager provider = getLocationProviderLocked(providerName); - if (provider != null) { - applyRequirementsLocked(provider); + LocationProviderManager manager = getLocationProviderManager(providerName); + if (manager != null) { + applyRequirementsLocked(manager); } } @GuardedBy("mLock") - private void applyRequirementsLocked(LocationProviderManager provider) { - ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider.getName()); - WorkSource worksource = new WorkSource(); - ProviderRequest providerRequest = new ProviderRequest(); + private void applyRequirementsLocked(LocationProviderManager manager) { + ArrayList<UpdateRecord> records = mRecordsByProvider.get(manager.getName()); + ProviderRequest.Builder providerRequest = new ProviderRequest.Builder(); // if provider is not active, it should not respond to requests - if (mProviders.contains(provider) && records != null && !records.isEmpty()) { + if (mProviderManagers.contains(manager) && records != null && !records.isEmpty()) { long backgroundThrottleInterval; long identity = Binder.clearCallingIdentity(); @@ -1765,6 +1680,8 @@ public class LocationManagerService extends ILocationManager.Stub { Binder.restoreCallingIdentity(identity); } + ArrayList<LocationRequest> requests = new ArrayList<>(records.size()); + final boolean isForegroundOnlyMode = mBatterySaverMode == PowerManager.LOCATION_MODE_FOREGROUND_ONLY; final boolean shouldThrottleRequests = @@ -1772,7 +1689,7 @@ public class LocationManagerService extends ILocationManager.Stub { == PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF && !mPowerManager.isInteractive(); // initialize the low power mode to true and set to false if any of the records requires - providerRequest.lowPowerMode = true; + providerRequest.setLowPowerMode(true); for (UpdateRecord record : records) { if (!isCurrentProfileLocked( UserHandle.getUserId(record.mReceiver.mCallerIdentity.mUid))) { @@ -1787,10 +1704,10 @@ public class LocationManagerService extends ILocationManager.Stub { } final boolean isBatterySaverDisablingLocation = shouldThrottleRequests || (isForegroundOnlyMode && !record.mIsForegroundUid); - if (!provider.isUseableLocked() || isBatterySaverDisablingLocation) { + if (!manager.isUseable() || isBatterySaverDisablingLocation) { if (isSettingsExemptLocked(record)) { - providerRequest.locationSettingsIgnored = true; - providerRequest.lowPowerMode = false; + providerRequest.setLocationSettingsIgnored(true); + providerRequest.setLowPowerMode(false); } else { continue; } @@ -1801,7 +1718,7 @@ public class LocationManagerService extends ILocationManager.Stub { // if we're forcing location, don't apply any throttling - if (!providerRequest.locationSettingsIgnored && !isThrottlingExemptLocked( + if (!providerRequest.isLocationSettingsIgnored() && !isThrottlingExemptLocked( record.mReceiver.mCallerIdentity)) { if (!record.mIsForegroundUid) { interval = Math.max(interval, backgroundThrottleInterval); @@ -1813,23 +1730,25 @@ public class LocationManagerService extends ILocationManager.Stub { } record.mRequest = locationRequest; - providerRequest.locationRequests.add(locationRequest); + requests.add(locationRequest); if (!locationRequest.isLowPowerMode()) { - providerRequest.lowPowerMode = false; + providerRequest.setLowPowerMode(false); } - if (interval < providerRequest.interval) { - providerRequest.reportLocation = true; - providerRequest.interval = interval; + if (interval < providerRequest.getInterval()) { + providerRequest.setInterval(interval); } } - if (providerRequest.reportLocation) { + providerRequest.setLocationRequests(requests); + + if (providerRequest.getInterval() < Long.MAX_VALUE) { // calculate who to blame for power // This is somewhat arbitrary. We pick a threshold interval // that is slightly higher that the minimum interval, and // spread the blame across all applications with a request // under that threshold. - long thresholdInterval = (providerRequest.interval + 1000) * 3 / 2; + // TODO: overflow + long thresholdInterval = (providerRequest.getInterval() + 1000) * 3 / 2; for (UpdateRecord record : records) { if (isCurrentProfileLocked( UserHandle.getUserId(record.mReceiver.mCallerIdentity.mUid))) { @@ -1837,18 +1756,18 @@ public class LocationManagerService extends ILocationManager.Stub { // Don't assign battery blame for update records whose // client has no permission to receive location data. - if (!providerRequest.locationRequests.contains(locationRequest)) { + if (!providerRequest.getLocationRequests().contains(locationRequest)) { continue; } if (locationRequest.getInterval() <= thresholdInterval) { if (record.mReceiver.mWorkSource != null && isValidWorkSource(record.mReceiver.mWorkSource)) { - worksource.add(record.mReceiver.mWorkSource); + providerRequest.getWorkSource().add(record.mReceiver.mWorkSource); } else { // Assign blame to caller if there's no WorkSource associated with // the request or if it's invalid. - worksource.add( + providerRequest.getWorkSource().add( record.mReceiver.mCallerIdentity.mUid, record.mReceiver.mCallerIdentity.mPackageName); } @@ -1858,7 +1777,7 @@ public class LocationManagerService extends ILocationManager.Stub { } } - provider.setRequest(providerRequest, worksource); + manager.setRequest(providerRequest.build()); } /** @@ -2198,8 +2117,8 @@ public class LocationManagerService extends ILocationManager.Stub { throw new IllegalArgumentException("provider name must not be null"); } - LocationProviderManager provider = getLocationProviderLocked(name); - if (provider == null) { + LocationProviderManager manager = getLocationProviderManager(name); + if (manager == null) { throw new IllegalArgumentException("provider doesn't exist: " + name); } @@ -2217,7 +2136,7 @@ public class LocationManagerService extends ILocationManager.Stub { oldRecord.disposeLocked(false); } - if (!provider.isUseableLocked() && !isSettingsExemptLocked(record)) { + if (!manager.isUseable() && !isSettingsExemptLocked(record)) { // Notify the listener that updates are currently disabled - but only if the request // does not ignore location settings receiver.callProviderEnabledLocked(name, false); @@ -2320,8 +2239,8 @@ public class LocationManagerService extends ILocationManager.Stub { // or use the fused provider String name = request.getProvider(); if (name == null) name = LocationManager.FUSED_PROVIDER; - LocationProviderManager provider = getLocationProviderLocked(name); - if (provider == null) return null; + LocationProviderManager manager = getLocationProviderManager(name); + if (manager == null) return null; // only the current user or location providers may get location this way if (!isCurrentProfileLocked(UserHandle.getUserId(uid)) && !isProviderPackage( @@ -2329,7 +2248,7 @@ public class LocationManagerService extends ILocationManager.Stub { return null; } - if (!provider.isUseableLocked()) { + if (!manager.isUseable()) { return null; } @@ -2450,19 +2369,19 @@ public class LocationManagerService extends ILocationManager.Stub { "Access Fine Location permission not granted to inject Location"); synchronized (mLock) { - LocationProviderManager provider = getLocationProviderLocked(location.getProvider()); - if (provider == null || !provider.isUseableLocked()) { + LocationProviderManager manager = getLocationProviderManager(location.getProvider()); + if (manager == null || !manager.isUseable()) { return false; } // NOTE: If last location is already available, location is not injected. If // provider's normal source (like a GPS chipset) have already provided an output // there is no need to inject this location. - if (mLastLocation.get(provider.getName()) != null) { + if (mLastLocation.get(manager.getName()) != null) { return false; } - updateLastLocationLocked(location, provider.getName()); + updateLastLocationLocked(location, manager.getName()); return true; } } @@ -2511,7 +2430,7 @@ public class LocationManagerService extends ILocationManager.Stub { packageName, request, /* hasListener= */ false, - intent != null, + true, geofence, mActivityManager.getPackageImportance(packageName)); } @@ -2542,7 +2461,7 @@ public class LocationManagerService extends ILocationManager.Stub { packageName, /* LocationRequest= */ null, /* hasListener= */ false, - intent != null, + true, geofence, mActivityManager.getPackageImportance(packageName)); } @@ -2555,7 +2474,7 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public boolean registerGnssStatusCallback(IGnssStatusListener listener, String packageName, String featureId) { - return mGnssManagerService == null ? false : mGnssManagerService.registerGnssStatusCallback( + return mGnssManagerService != null && mGnssManagerService.registerGnssStatusCallback( listener, packageName, featureId); } @@ -2569,9 +2488,8 @@ public class LocationManagerService extends ILocationManager.Stub { String packageName, String featureId, String listenerIdentifier) { Objects.requireNonNull(listenerIdentifier); - return mGnssManagerService == null ? false - : mGnssManagerService.addGnssMeasurementsListener(listener, packageName, featureId, - listenerIdentifier); + return mGnssManagerService != null && mGnssManagerService.addGnssMeasurementsListener( + listener, packageName, featureId, listenerIdentifier); } @Override @@ -2586,8 +2504,8 @@ public class LocationManagerService extends ILocationManager.Stub { public void injectGnssMeasurementCorrections( GnssMeasurementCorrections measurementCorrections, String packageName) { if (mGnssManagerService != null) { - mGnssManagerService.injectGnssMeasurementCorrections( - measurementCorrections, packageName); + mGnssManagerService.injectGnssMeasurementCorrections(measurementCorrections, + packageName); } } @@ -2602,9 +2520,8 @@ public class LocationManagerService extends ILocationManager.Stub { String packageName, String featureId, String listenerIdentifier) { Objects.requireNonNull(listenerIdentifier); - return mGnssManagerService == null ? false - : mGnssManagerService.addGnssNavigationMessageListener(listener, packageName, - featureId, listenerIdentifier); + return mGnssManagerService != null && mGnssManagerService.addGnssNavigationMessageListener( + listener, packageName, featureId, listenerIdentifier); } @Override @@ -2634,9 +2551,10 @@ public class LocationManagerService extends ILocationManager.Stub { LocationStatsEnums.API_SEND_EXTRA_COMMAND, providerName); - LocationProviderManager provider = getLocationProviderLocked(providerName); - if (provider != null) { - provider.sendExtraCommand(command, extras); + LocationProviderManager manager = getLocationProviderManager(providerName); + if (manager != null) { + manager.sendExtraCommand(Binder.getCallingUid(), Binder.getCallingPid(), command, + extras); } mLocationUsageLogger.logLocationApiUsage( @@ -2650,43 +2568,37 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public boolean sendNiResponse(int notifId, int userResponse) { - return mGnssManagerService == null ? false : mGnssManagerService.sendNiResponse(notifId, + return mGnssManagerService != null && mGnssManagerService.sendNiResponse(notifId, userResponse); } @Override public ProviderProperties getProviderProperties(String providerName) { - synchronized (mLock) { - LocationProviderManager provider = getLocationProviderLocked(providerName); - if (provider == null) { - return null; - } - return provider.getPropertiesLocked(); + LocationProviderManager manager = getLocationProviderManager(providerName); + if (manager == null) { + return null; } + return manager.getProperties(); } @Override public boolean isProviderPackage(String packageName) { mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_DEVICE_CONFIG, Manifest.permission.READ_DEVICE_CONFIG + " permission required"); - synchronized (mLock) { - for (LocationProviderManager provider : mProviders) { - if (provider.getPackagesLocked().contains(packageName)) { - return true; - } + for (LocationProviderManager manager : mProviderManagers) { + if (manager.getPackages().contains(packageName)) { + return true; } - return false; } + return false; } @Override public List<String> getProviderPackages(String providerName) { mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_DEVICE_CONFIG, Manifest.permission.READ_DEVICE_CONFIG + " permission required"); - synchronized (mLock) { - LocationProviderManager provider = getLocationProviderLocked(providerName); - return provider == null ? Collections.emptyList() : provider.getPackagesLocked(); - } + LocationProviderManager manager = getLocationProviderManager(providerName); + return manager == null ? Collections.emptyList() : new ArrayList<>(manager.getPackages()); } @Override @@ -2753,8 +2665,8 @@ public class LocationManagerService extends ILocationManager.Stub { if (FUSED_PROVIDER.equals(providerName)) return false; synchronized (mLock) { - LocationProviderManager provider = getLocationProviderLocked(providerName); - return provider != null && provider.isUseableLocked(userId); + LocationProviderManager manager = getLocationProviderManager(providerName); + return manager != null && manager.isUseable(userId); } } @@ -2792,37 +2704,39 @@ public class LocationManagerService extends ILocationManager.Stub { } @GuardedBy("mLock") - private void handleLocationChangedLocked(Location location, LocationProviderManager provider) { - if (!mProviders.contains(provider)) { + private void handleLocationChangedLocked(Location location, LocationProviderManager manager) { + if (!mProviderManagers.contains(manager)) { + Log.w(TAG, "received location from unknown provider: " + manager.getName()); return; } if (!location.isComplete()) { - Log.w(TAG, "Dropping incomplete location: " + location); + Log.w(TAG, "dropping incomplete location from " + manager.getName() + " provider: " + + location); return; } - // only notify passive provider and update last location for locations that come from - // useable providers - if (provider.isUseableLocked()) { - if (!provider.isPassiveLocked()) { - mPassiveProvider.updateLocation(location); - } + // notify passive provider + if (manager != mPassiveManager) { + mPassiveManager.updateLocation(new Location(location)); } if (D) Log.d(TAG, "incoming location: " + location); long now = SystemClock.elapsedRealtime(); - if (provider.isUseableLocked()) { - updateLastLocationLocked(location, provider.getName()); + + + // only update last location for locations that come from useable providers + if (manager.isUseable()) { + updateLastLocationLocked(location, manager.getName()); } // Update last known coarse interval location if enough time has passed. Location lastLocationCoarseInterval = mLastLocationCoarseInterval.get( - provider.getName()); + manager.getName()); if (lastLocationCoarseInterval == null) { lastLocationCoarseInterval = new Location(location); - if (provider.isUseableLocked()) { - mLastLocationCoarseInterval.put(provider.getName(), lastLocationCoarseInterval); + if (manager.isUseable()) { + mLastLocationCoarseInterval.put(manager.getName(), lastLocationCoarseInterval); } } long timeDeltaMs = TimeUnit.NANOSECONDS.toMillis(location.getElapsedRealtimeNanos() @@ -2837,7 +2751,7 @@ public class LocationManagerService extends ILocationManager.Stub { lastLocationCoarseInterval.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION); // Skip if there are no UpdateRecords for this provider. - ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider.getName()); + ArrayList<UpdateRecord> records = mRecordsByProvider.get(manager.getName()); if (records == null || records.size() == 0) return; // Fetch coarse location @@ -2854,7 +2768,7 @@ public class LocationManagerService extends ILocationManager.Stub { Receiver receiver = r.mReceiver; boolean receiverDead = false; - if (!provider.isUseableLocked() && !isSettingsExemptLocked(r)) { + if (!manager.isUseable() && !isSettingsExemptLocked(r)) { continue; } @@ -2949,7 +2863,7 @@ public class LocationManagerService extends ILocationManager.Stub { for (UpdateRecord r : deadUpdateRecords) { r.disposeLocked(true); } - applyRequirementsLocked(provider); + applyRequirementsLocked(manager); } } @@ -3006,143 +2920,99 @@ public class LocationManagerService extends ILocationManager.Stub { // Mock Providers - private boolean canCallerAccessMockLocation(String opPackageName) { - return mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), - opPackageName) == AppOpsManager.MODE_ALLOWED; - } - @Override - public void addTestProvider(String name, ProviderProperties properties, String opPackageName) { - if (!canCallerAccessMockLocation(opPackageName)) { + public void addTestProvider(String provider, ProviderProperties properties, + String packageName) { + if (mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), packageName) + != AppOpsManager.MODE_ALLOWED) { return; } - if (PASSIVE_PROVIDER.equals(name)) { - throw new IllegalArgumentException("Cannot mock the passive location provider"); - } - synchronized (mLock) { - long identity = Binder.clearCallingIdentity(); - try { - LocationProviderManager oldProvider = getLocationProviderLocked(name); - if (oldProvider != null) { - removeProviderLocked(oldProvider); - } - - MockLocationProvider mockProviderManager = new MockLocationProvider(name); - addProviderLocked(mockProviderManager); - mockProviderManager.attachLocked( - new MockProvider(mContext, mockProviderManager, properties)); - } finally { - Binder.restoreCallingIdentity(identity); + LocationProviderManager manager = getLocationProviderManager(provider); + if (manager == null) { + manager = new LocationProviderManager(provider); + mProviderManagers.add(manager); } + + manager.setMockProvider(new MockProvider(mContext, properties)); } } @Override - public void removeTestProvider(String name, String opPackageName) { - if (!canCallerAccessMockLocation(opPackageName)) { + public void removeTestProvider(String provider, String packageName) { + if (mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), packageName) + != AppOpsManager.MODE_ALLOWED) { return; } synchronized (mLock) { - long identity = Binder.clearCallingIdentity(); - try { - LocationProviderManager testProvider = getLocationProviderLocked(name); - if (testProvider == null || !testProvider.isMock()) { - return; - } - - removeProviderLocked(testProvider); - - // reinstate real provider if available - LocationProviderManager realProvider = null; - for (LocationProviderManager provider : mRealProviders) { - if (name.equals(provider.getName())) { - realProvider = provider; - break; - } - } + LocationProviderManager manager = getLocationProviderManager(provider); + if (manager == null) { + return; + } - if (realProvider != null) { - addProviderLocked(realProvider); - } - } finally { - Binder.restoreCallingIdentity(identity); + manager.setMockProvider(null); + if (!manager.hasProvider()) { + mProviderManagers.remove(manager); + mLastLocation.remove(manager.getName()); + mLastLocationCoarseInterval.remove(manager.getName()); } } } @Override - public void setTestProviderLocation(String providerName, Location location, - String opPackageName) { - if (!canCallerAccessMockLocation(opPackageName)) { + public void setTestProviderLocation(String provider, Location location, String packageName) { + if (mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), packageName) + != AppOpsManager.MODE_ALLOWED) { return; } - synchronized (mLock) { - LocationProviderManager testProvider = getLocationProviderLocked(providerName); - if (testProvider == null || !testProvider.isMock()) { - throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown"); - } - - String locationProvider = location.getProvider(); - if (!TextUtils.isEmpty(locationProvider) && !providerName.equals(locationProvider)) { - // The location has an explicit provider that is different from the mock - // provider name. The caller may be trying to fool us via b/33091107. - EventLog.writeEvent(0x534e4554, "33091107", Binder.getCallingUid(), - providerName + "!=" + location.getProvider()); - } - - ((MockLocationProvider) testProvider).setLocationLocked(location); + LocationProviderManager manager = getLocationProviderManager(provider); + if (manager == null) { + throw new IllegalArgumentException("provider doesn't exist: " + provider); } + + manager.setMockProviderLocation(location); } @Override - public void setTestProviderEnabled(String providerName, boolean enabled, String opPackageName) { - if (!canCallerAccessMockLocation(opPackageName)) { + public void setTestProviderEnabled(String provider, boolean enabled, String packageName) { + if (mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), packageName) + != AppOpsManager.MODE_ALLOWED) { return; } - synchronized (mLock) { - LocationProviderManager testProvider = getLocationProviderLocked(providerName); - if (testProvider == null || !testProvider.isMock()) { - throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown"); - } - - ((MockLocationProvider) testProvider).setEnabledLocked(enabled); + LocationProviderManager manager = getLocationProviderManager(provider); + if (manager == null) { + throw new IllegalArgumentException("provider doesn't exist: " + provider); } + + manager.setMockProviderEnabled(enabled); } @Override @NonNull - public List<LocationRequest> getTestProviderCurrentRequests(String providerName, - String opPackageName) { - if (!canCallerAccessMockLocation(opPackageName)) { + public List<LocationRequest> getTestProviderCurrentRequests(String provider, + String packageName) { + if (mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), packageName) + != AppOpsManager.MODE_ALLOWED) { return Collections.emptyList(); } - synchronized (mLock) { - LocationProviderManager testProvider = getLocationProviderLocked(providerName); - if (testProvider == null || !testProvider.isMock()) { - throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown"); - } - - MockLocationProvider provider = (MockLocationProvider) testProvider; - if (provider.mCurrentRequest == null) { - return Collections.emptyList(); - } - List<LocationRequest> requests = new ArrayList<>(); - for (LocationRequest request : provider.mCurrentRequest.locationRequests) { - requests.add(new LocationRequest(request)); - } - return requests; + LocationProviderManager manager = getLocationProviderManager(provider); + if (manager == null) { + throw new IllegalArgumentException("provider doesn't exist: " + provider); } + + return manager.getMockProviderRequests(); } @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; + if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) { + return; + } IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); @@ -3192,6 +3062,8 @@ public class LocationManagerService extends ILocationManager.Stub { } ipw.decreaseIndent(); + mRequestStatistics.history.dump(ipw); + ipw.println("Last Known Locations:"); ipw.increaseIndent(); for (Map.Entry<String, Location> entry : mLastLocation.entrySet()) { @@ -3224,25 +3096,27 @@ public class LocationManagerService extends ILocationManager.Stub { mLocationFudger.dump(fd, ipw, args); ipw.decreaseIndent(); } + } - ipw.println("Location Settings:"); - ipw.increaseIndent(); - mSettingsStore.dump(fd, ipw, args); - ipw.decreaseIndent(); + ipw.println("Location Settings:"); + ipw.increaseIndent(); + mSettingsStore.dump(fd, ipw, args); + ipw.decreaseIndent(); - ipw.println("Location Providers:"); - ipw.increaseIndent(); - for (LocationProviderManager provider : mProviders) { - provider.dumpLocked(fd, ipw, args); - } - ipw.decreaseIndent(); + ipw.println("Location Providers:"); + ipw.increaseIndent(); + for (LocationProviderManager manager : mProviderManagers) { + manager.dump(fd, ipw, args); } + ipw.decreaseIndent(); - if (mGnssManagerService != null) { - ipw.println("GNSS:"); - ipw.increaseIndent(); - mGnssManagerService.dump(fd, ipw, args); - ipw.decreaseIndent(); + synchronized (mLock) { + if (mGnssManagerService != null) { + ipw.println("GNSS:"); + ipw.increaseIndent(); + mGnssManagerService.dump(fd, ipw, args); + ipw.decreaseIndent(); + } } } } diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java index 1f736502ae42..05d83606b2d0 100644 --- a/services/core/java/com/android/server/NetworkManagementService.java +++ b/services/core/java/com/android/server/NetworkManagementService.java @@ -58,10 +58,12 @@ import android.net.NetworkStack; import android.net.NetworkStats; import android.net.NetworkUtils; import android.net.RouteInfo; -import android.net.TetherConfigParcel; import android.net.TetherStatsParcel; import android.net.UidRange; import android.net.UidRangeParcel; +import android.net.shared.NetdUtils; +import android.net.shared.RouteUtils; +import android.net.shared.RouteUtils.ModifyOperation; import android.net.util.NetdService; import android.os.BatteryStats; import android.os.Binder; @@ -898,48 +900,14 @@ public class NetworkManagementService extends INetworkManagementService.Stub { @Override public void addRoute(int netId, RouteInfo route) { - modifyRoute(MODIFY_OPERATION_ADD, netId, route); + NetworkStack.checkNetworkStackPermission(mContext); + RouteUtils.modifyRoute(mNetdService, ModifyOperation.ADD, netId, route); } @Override public void removeRoute(int netId, RouteInfo route) { - modifyRoute(MODIFY_OPERATION_REMOVE, netId, route); - } - - private void modifyRoute(boolean add, int netId, RouteInfo route) { NetworkStack.checkNetworkStackPermission(mContext); - - final String ifName = route.getInterface(); - final String dst = route.getDestination().toString(); - final String nextHop; - - switch (route.getType()) { - case RouteInfo.RTN_UNICAST: - if (route.hasGateway()) { - nextHop = route.getGateway().getHostAddress(); - } else { - nextHop = INetd.NEXTHOP_NONE; - } - break; - case RouteInfo.RTN_UNREACHABLE: - nextHop = INetd.NEXTHOP_UNREACHABLE; - break; - case RouteInfo.RTN_THROW: - nextHop = INetd.NEXTHOP_THROW; - break; - default: - nextHop = INetd.NEXTHOP_NONE; - break; - } - try { - if (add) { - mNetdService.networkAddRoute(netId, ifName, dst, nextHop); - } else { - mNetdService.networkRemoveRoute(netId, ifName, dst, nextHop); - } - } catch (RemoteException | ServiceSpecificException e) { - throw new IllegalStateException(e); - } + RouteUtils.modifyRoute(mNetdService, ModifyOperation.REMOVE, netId, route); } private ArrayList<String> readRouteList(String filename) { @@ -1023,12 +991,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub { @Override public void startTetheringWithConfiguration(boolean usingLegacyDnsProxy, String[] dhcpRange) { NetworkStack.checkNetworkStackPermission(mContext); - // an odd number of addrs will fail try { - final TetherConfigParcel config = new TetherConfigParcel(); - config.usingLegacyDnsProxy = usingLegacyDnsProxy; - config.dhcpRanges = dhcpRange; - mNetdService.tetherStartWithConfiguration(config); + NetdUtils.tetherStart(mNetdService, usingLegacyDnsProxy, dhcpRange); } catch (RemoteException | ServiceSpecificException e) { throw new IllegalStateException(e); } @@ -1060,26 +1024,21 @@ public class NetworkManagementService extends INetworkManagementService.Stub { public void tetherInterface(String iface) { NetworkStack.checkNetworkStackPermission(mContext); try { - mNetdService.tetherInterfaceAdd(iface); + final LinkAddress addr = getInterfaceConfig(iface).getLinkAddress(); + final IpPrefix dest = new IpPrefix(addr.getAddress(), addr.getPrefixLength()); + NetdUtils.tetherInterface(mNetdService, iface, dest); } catch (RemoteException | ServiceSpecificException e) { throw new IllegalStateException(e); } - List<RouteInfo> routes = new ArrayList<>(); - // The RouteInfo constructor truncates the LinkAddress to a network prefix, thus making it - // suitable to use as a route destination. - routes.add(new RouteInfo(getInterfaceConfig(iface).getLinkAddress(), null, iface)); - addInterfaceToLocalNetwork(iface, routes); } @Override public void untetherInterface(String iface) { NetworkStack.checkNetworkStackPermission(mContext); try { - mNetdService.tetherInterfaceRemove(iface); + NetdUtils.untetherInterface(mNetdService, iface); } catch (RemoteException | ServiceSpecificException e) { throw new IllegalStateException(e); - } finally { - removeInterfaceFromLocalNetwork(iface); } } @@ -2122,16 +2081,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub { @Override public void addInterfaceToLocalNetwork(String iface, List<RouteInfo> routes) { modifyInterfaceInNetwork(MODIFY_OPERATION_ADD, INetd.LOCAL_NET_ID, iface); - - for (RouteInfo route : routes) { - if (!route.isDefaultRoute()) { - modifyRoute(MODIFY_OPERATION_ADD, INetd.LOCAL_NET_ID, route); - } - } - - // IPv6 link local should be activated always. - modifyRoute(MODIFY_OPERATION_ADD, INetd.LOCAL_NET_ID, - new RouteInfo(new IpPrefix("fe80::/64"), null, iface)); + // modifyInterfaceInNetwork already check calling permission. + RouteUtils.addRoutesToLocalNetwork(mNetdService, iface, routes); } @Override @@ -2141,17 +2092,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub { @Override public int removeRoutesFromLocalNetwork(List<RouteInfo> routes) { - int failures = 0; - - for (RouteInfo route : routes) { - try { - modifyRoute(MODIFY_OPERATION_REMOVE, INetd.LOCAL_NET_ID, route); - } catch (IllegalStateException e) { - failures++; - } - } - - return failures; + NetworkStack.checkNetworkStackPermission(mContext); + return RouteUtils.removeRoutesFromLocalNetwork(mNetdService, routes); } @Override diff --git a/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java b/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java index cfe56052118f..d20936c2d217 100644 --- a/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java +++ b/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java @@ -36,11 +36,11 @@ import android.os.Looper; import android.os.Message; import android.os.PowerManager; import android.os.SystemClock; +import android.os.TimestampedValue; import android.provider.Settings; import android.util.Log; import android.util.NtpTrustedTime; import android.util.TimeUtils; -import android.util.TimestampedValue; import com.android.internal.util.DumpUtils; diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java index deff440aa0a6..7b4fd37f01c9 100644 --- a/services/core/java/com/android/server/PackageWatchdog.java +++ b/services/core/java/com/android/server/PackageWatchdog.java @@ -199,13 +199,14 @@ public class PackageWatchdog { mSystemClock = clock; mNumberOfNativeCrashPollsRemaining = NUMBER_OF_NATIVE_CRASH_POLLS; loadFromFile(); + sPackageWatchdog = this; } /** Creates or gets singleton instance of PackageWatchdog. */ public static PackageWatchdog getInstance(Context context) { synchronized (PackageWatchdog.class) { if (sPackageWatchdog == null) { - sPackageWatchdog = new PackageWatchdog(context); + new PackageWatchdog(context); } return sPackageWatchdog; } diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java index fb1a962c0ef3..3dafc64391fd 100644 --- a/services/core/java/com/android/server/RescueParty.java +++ b/services/core/java/com/android/server/RescueParty.java @@ -18,8 +18,12 @@ package com.android.server; import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo; +import android.annotation.Nullable; import android.content.ContentResolver; import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.VersionedPackage; import android.os.Build; import android.os.Environment; import android.os.FileUtils; @@ -36,8 +40,12 @@ import android.util.Slog; import android.util.SparseArray; import android.util.StatsLog; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; +import com.android.server.PackageWatchdog.FailureReasons; +import com.android.server.PackageWatchdog.PackageHealthObserver; +import com.android.server.PackageWatchdog.PackageHealthObserverImpact; import com.android.server.am.SettingsToPropertiesMapper; import com.android.server.utils.FlagNamespaceUtils; @@ -79,19 +87,30 @@ public class RescueParty { @VisibleForTesting static final long BOOT_TRIGGER_WINDOW_MILLIS = 600 * DateUtils.SECOND_IN_MILLIS; @VisibleForTesting - static final long PERSISTENT_APP_CRASH_TRIGGER_WINDOW_MILLIS = 30 * DateUtils.SECOND_IN_MILLIS; - @VisibleForTesting static final String TAG = "RescueParty"; + private static final String NAME = "rescue-party-observer"; + + private static final String PROP_DISABLE_RESCUE = "persist.sys.disable_rescue"; private static final String PROP_RESCUE_BOOT_START = "sys.rescue_boot_start"; private static final String PROP_VIRTUAL_DEVICE = "ro.hardware.virtual_device"; + private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT + | ApplicationInfo.FLAG_SYSTEM; + + /** Threshold for boot loops */ private static final Threshold sBoot = new BootThreshold(); /** Threshold for app crash loops */ private static SparseArray<Threshold> sApps = new SparseArray<>(); + /** Register the Rescue Party observer as a Package Watchdog health observer */ + public static void registerHealthObserver(Context context) { + PackageWatchdog.getInstance(context).registerHealthObserver( + RescuePartyObserver.getInstance(context)); + } + private static boolean isDisabled() { // Check if we're explicitly enabled for testing if (SystemProperties.getBoolean(PROP_ENABLE_RESCUE, false)) { @@ -135,24 +154,6 @@ public class RescueParty { } /** - * Take note of a persistent app or apex module crash. If we notice too many of these - * events happening in rapid succession, we'll send out a rescue party. - */ - public static void noteAppCrash(Context context, int uid) { - if (isDisabled()) return; - Threshold t = sApps.get(uid); - if (t == null) { - t = new AppThreshold(uid); - sApps.put(uid, t); - } - if (t.incrementAndTest()) { - t.reset(); - incrementRescueLevel(t.uid); - executeRescueLevel(context); - } - } - - /** * Check if we're currently attempting to reboot for a factory reset. */ public static boolean isAttemptingFactoryReset() { @@ -171,11 +172,6 @@ public class RescueParty { @VisibleForTesting static void resetAllThresholds() { sBoot.reset(); - - for (int i = 0; i < sApps.size(); i++) { - Threshold appThreshold = sApps.get(sApps.keyAt(i)); - appThreshold.reset(); - } } @VisibleForTesting @@ -243,6 +239,28 @@ public class RescueParty { FlagNamespaceUtils.NAMESPACE_NO_PACKAGE); } + private static int mapRescueLevelToUserImpact(int rescueLevel) { + switch(rescueLevel) { + case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS: + case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES: + return PackageHealthObserverImpact.USER_IMPACT_LOW; + case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS: + case LEVEL_FACTORY_RESET: + return PackageHealthObserverImpact.USER_IMPACT_HIGH; + default: + return PackageHealthObserverImpact.USER_IMPACT_NONE; + } + } + + private static int getPackageUid(Context context, String packageName) { + try { + return context.getPackageManager().getPackageUid(packageName, 0); + } catch (PackageManager.NameNotFoundException e) { + // Since UIDs are always >= 0, this value means the UID could not be determined. + return -1; + } + } + private static void resetAllSettings(Context context, int mode) throws Exception { // Try our best to reset all settings possible, and once finished // rethrow any exception that we encountered @@ -271,6 +289,89 @@ public class RescueParty { } /** + * Handle mitigation action for package failures. This observer will be register to Package + * Watchdog and will receive calls about package failures. This observer is persistent so it + * may choose to mitigate failures for packages it has not explicitly asked to observe. + */ + public static class RescuePartyObserver implements PackageHealthObserver { + + private Context mContext; + + @GuardedBy("RescuePartyObserver.class") + static RescuePartyObserver sRescuePartyObserver; + + private RescuePartyObserver(Context context) { + mContext = context; + } + + /** Creates or gets singleton instance of RescueParty. */ + public static RescuePartyObserver getInstance(Context context) { + synchronized (RescuePartyObserver.class) { + if (sRescuePartyObserver == null) { + sRescuePartyObserver = new RescuePartyObserver(context); + } + return sRescuePartyObserver; + } + } + + @Override + public int onHealthCheckFailed(@Nullable VersionedPackage failedPackage, + @FailureReasons int failureReason) { + if (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH + || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING) { + int rescueLevel = MathUtils.constrain( + SystemProperties.getInt(PROP_RESCUE_LEVEL, LEVEL_NONE) + 1, + LEVEL_NONE, LEVEL_FACTORY_RESET); + return mapRescueLevelToUserImpact(rescueLevel); + } else { + return PackageHealthObserverImpact.USER_IMPACT_NONE; + } + } + + @Override + public boolean execute(@Nullable VersionedPackage failedPackage, + @FailureReasons int failureReason) { + if (isDisabled()) { + return false; + } + if (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH + || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING) { + int triggerUid = getPackageUid(mContext, failedPackage.getPackageName()); + incrementRescueLevel(triggerUid); + executeRescueLevel(mContext); + return true; + } else { + return false; + } + } + + @Override + public boolean isPersistent() { + return true; + } + + @Override + public boolean mayObservePackage(String packageName) { + PackageManager pm = mContext.getPackageManager(); + try { + // A package is a Mainline module if this is non-null + if (pm.getModuleInfo(packageName, 0) != null) { + return true; + } + ApplicationInfo info = pm.getApplicationInfo(packageName, 0); + return (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK; + } catch (PackageManager.NameNotFoundException e) { + return false; + } + } + + @Override + public String getName() { + return NAME; + } + } + + /** * Threshold that can be triggered if a number of events occur within a * window of time. */ @@ -349,27 +450,6 @@ public class RescueParty { } } - /** - * Specialization of {@link Threshold} for monitoring app crashes. It stores - * counters in memory. - */ - private static class AppThreshold extends Threshold { - private int count; - private long start; - - public AppThreshold(int uid) { - // We're interested in TRIGGER_COUNT events in any - // PERSISTENT_APP_CRASH_TRIGGER_WINDOW_MILLIS second period; apps crash pretty quickly - // so we can keep a tight leash on them. - super(uid, TRIGGER_COUNT, PERSISTENT_APP_CRASH_TRIGGER_WINDOW_MILLIS); - } - - @Override public int getCount() { return count; } - @Override public void setCount(int count) { this.count = count; } - @Override public long getStart() { return start; } - @Override public void setStart(long start) { this.start = start; } - } - private static int[] getAllUserIds() { int[] userIds = { UserHandle.USER_SYSTEM }; try { diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 3d455ee1cf19..db542145a750 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -16,6 +16,7 @@ package com.android.server; +import static android.Manifest.permission.ACCESS_MTP; import static android.Manifest.permission.INSTALL_PACKAGES; import static android.Manifest.permission.READ_EXTERNAL_STORAGE; import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; @@ -111,6 +112,7 @@ import android.os.storage.StorageVolume; import android.os.storage.VolumeInfo; import android.os.storage.VolumeRecord; import android.provider.DeviceConfig; +import android.provider.Downloads; import android.provider.MediaStore; import android.provider.Settings; import android.sysprop.VoldProperties; @@ -367,6 +369,8 @@ class StorageManagerService extends IStorageManager.Stub private volatile int mMediaStoreAuthorityAppId = -1; + private volatile int mDownloadsAuthorityAppId = -1; + private volatile int mCurrentUserId = UserHandle.USER_SYSTEM; private final Installer mInstaller; @@ -1788,6 +1792,15 @@ class StorageManagerService extends IStorageManager.Stub mMediaStoreAuthorityAppId = UserHandle.getAppId(provider.applicationInfo.uid); } + provider = mPmInternal.resolveContentProvider( + Downloads.Impl.AUTHORITY, PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, + UserHandle.getUserId(UserHandle.USER_SYSTEM)); + + if (provider != null) { + mDownloadsAuthorityAppId = UserHandle.getAppId(provider.applicationInfo.uid); + } + try { mIAppOpsService.startWatchingMode(OP_REQUEST_INSTALL_PACKAGES, null, mAppOpsCallback); mIAppOpsService.startWatchingMode(OP_LEGACY_STORAGE, null, mAppOpsCallback); @@ -3881,6 +3894,19 @@ class StorageManagerService extends IStorageManager.Stub return Zygote.MOUNT_EXTERNAL_PASS_THROUGH; } + if (mIsFuseEnabled && mDownloadsAuthorityAppId == UserHandle.getAppId(uid)) { + // DownloadManager can write in app-private directories on behalf of apps; + // give it write access to Android/ + return Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE; + } + + final boolean hasMtp = mIPackageManager.checkUidPermission(ACCESS_MTP, uid) == + PERMISSION_GRANTED; + if (mIsFuseEnabled && hasMtp) { + // The process hosting the MTP server should be able to write in Android/ + return Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE; + } + // Determine if caller is holding runtime permission final boolean hasRead = StorageManager.checkPermissionAndCheckOp(mContext, false, 0, uid, packageName, READ_EXTERNAL_STORAGE, OP_READ_EXTERNAL_STORAGE); diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index 25bad641adf9..0be21c5f15b6 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -42,6 +42,7 @@ import android.os.Message; import android.os.RemoteException; import android.os.UserHandle; import android.telephony.Annotation; +import android.telephony.Annotation.ApnType; import android.telephony.Annotation.DataFailureCause; import android.telephony.Annotation.RadioPowerState; import android.telephony.Annotation.SrvccState; @@ -82,7 +83,6 @@ import com.android.internal.app.IBatteryStats; import com.android.internal.telephony.IOnSubscriptionsChangedListener; import com.android.internal.telephony.IPhoneStateListener; import com.android.internal.telephony.ITelephonyRegistry; -import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.TelephonyIntents; import com.android.internal.telephony.TelephonyPermissions; import com.android.internal.util.ArrayUtils; @@ -269,8 +269,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { private final LocalLog mListenLog = new LocalLog(100); // Per-phoneMap of APN Type to DataConnectionState - private List<Map<String, PreciseDataConnectionState>> mPreciseDataConnectionStates = - new ArrayList<Map<String, PreciseDataConnectionState>>(); + private List<Map<Integer, PreciseDataConnectionState>> mPreciseDataConnectionStates = + new ArrayList<Map<Integer, PreciseDataConnectionState>>(); // Nothing here yet, but putting it here in case we want to add more in the future. static final int ENFORCE_COARSE_LOCATION_PERMISSION_MASK = 0; @@ -467,7 +467,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mRingingCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE; mForegroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE; mBackgroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE; - mPreciseDataConnectionStates.add(new HashMap<String, PreciseDataConnectionState>()); + mPreciseDataConnectionStates.add(new HashMap<Integer, PreciseDataConnectionState>()); } } @@ -551,7 +551,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mRingingCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE; mForegroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE; mBackgroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE; - mPreciseDataConnectionStates.add(new HashMap<String, PreciseDataConnectionState>()); + mPreciseDataConnectionStates.add(new HashMap<Integer, PreciseDataConnectionState>()); } mAppOps = mContext.getSystemService(AppOpsManager.class); @@ -1496,11 +1496,12 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { * * @param phoneId the phoneId carrying the data connection * @param subId the subscriptionId for the data connection - * @param apnType the APN type that triggered a change in the data connection + * @param apnType the apn type bitmask, defined with {@code ApnSetting#TYPE_*} flags. * @param preciseState a PreciseDataConnectionState that has info about the data connection */ + @Override public void notifyDataConnectionForSubscriber( - int phoneId, int subId, String apnType, PreciseDataConnectionState preciseState) { + int phoneId, int subId, @ApnType int apnType, PreciseDataConnectionState preciseState) { if (!checkNotifyPermission("notifyDataConnection()" )) { return; } @@ -1526,7 +1527,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { synchronized (mRecords) { if (validatePhoneId(phoneId)) { // We only call the callback when the change is for default APN type. - if (PhoneConstants.APN_TYPE_DEFAULT.equals(apnType) + if ((ApnSetting.TYPE_DEFAULT & apnType) != 0 && (mDataConnectionState[phoneId] != state || mDataConnectionNetworkType[phoneId] != networkType)) { String str = "onDataConnectionStateChanged(" @@ -1595,7 +1596,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { loge("This function should not be invoked"); } - private void notifyDataConnectionFailedForSubscriber(int phoneId, int subId, String apnType) { + private void notifyDataConnectionFailedForSubscriber(int phoneId, int subId, int apnType) { if (!checkNotifyPermission("notifyDataConnectionFailed()")) { return; } @@ -1610,7 +1611,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { new PreciseDataConnectionState( TelephonyManager.DATA_UNKNOWN, TelephonyManager.NETWORK_TYPE_UNKNOWN, - ApnSetting.getApnTypesBitmaskFromString(apnType), null, null, + apnType, null, null, DataFailCause.NONE, null)); for (Record r : mRecords) { if (r.matchPhoneStateListenerEvent( @@ -1779,7 +1780,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } } - public void notifyPreciseDataConnectionFailed(int phoneId, int subId, String apnType, + @Override + public void notifyPreciseDataConnectionFailed(int phoneId, int subId, @ApnType int apnType, String apn, @DataFailureCause int failCause) { if (!checkNotifyPermission("notifyPreciseDataConnectionFailed()")) { return; @@ -1795,7 +1797,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { new PreciseDataConnectionState( TelephonyManager.DATA_UNKNOWN, TelephonyManager.NETWORK_TYPE_UNKNOWN, - ApnSetting.getApnTypesBitmaskFromString(apnType), null, null, + apnType, null, null, failCause, null)); for (Record r : mRecords) { if (r.matchPhoneStateListenerEvent( @@ -2177,12 +2179,32 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { /** Fired when a subscription's phone state changes. */ private static final String ACTION_SUBSCRIPTION_PHONE_STATE_CHANGED = "android.intent.action.SUBSCRIPTION_PHONE_STATE"; + /** + * Broadcast Action: The data connection state has changed for any one of the + * phone's mobile data connections (eg, default, MMS or GPS specific connection). + */ + private static final String ACTION_ANY_DATA_CONNECTION_STATE_CHANGED = + "android.intent.action.ANY_DATA_STATE"; // Legacy intent extra keys, copied from PhoneConstants. // Used in legacy intents sent here, for backward compatibility. + private static final String PHONE_CONSTANTS_DATA_APN_TYPE_KEY = "apnType"; + private static final String PHONE_CONSTANTS_DATA_APN_KEY = "apn"; private static final String PHONE_CONSTANTS_SLOT_KEY = "slot"; + private static final String PHONE_CONSTANTS_STATE_KEY = "state"; private static final String PHONE_CONSTANTS_SUBSCRIPTION_KEY = "subscription"; + /** + * Broadcast Action: The phone's signal strength has changed. The intent will have the + * following extra values: + * phoneName - A string version of the phone name. + * asu - A numeric value for the signal strength. + * An ASU is 0-31 or -1 if unknown (for GSM, dBm = -113 - 2 * asu). + * The following special values are defined: + * 0 means "-113 dBm or less".31 means "-51 dBm or greater". + */ + public static final String ACTION_SIGNAL_STRENGTH_CHANGED = "android.intent.action.SIG_STR"; + private void broadcastServiceStateChanged(ServiceState state, int phoneId, int subId) { long ident = Binder.clearCallingIdentity(); try { @@ -2217,7 +2239,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { Binder.restoreCallingIdentity(ident); } - Intent intent = new Intent(TelephonyIntents.ACTION_SIGNAL_STRENGTH_CHANGED); + Intent intent = new Intent(ACTION_SIGNAL_STRENGTH_CHANGED); Bundle data = new Bundle(); fillInSignalStrengthNotifierBundle(signalStrength, data); intent.putExtras(data); @@ -2318,19 +2340,50 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } private void broadcastDataConnectionStateChanged(int state, String apn, - String apnType, int subId) { + int apnType, int subId) { // Note: not reporting to the battery stats service here, because the // status bar takes care of that after taking into account all of the // required info. - Intent intent = new Intent(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED); - intent.putExtra(TelephonyManager.EXTRA_STATE, dataStateToString(state)); - - intent.putExtra(PhoneConstants.DATA_APN_KEY, apn); - intent.putExtra(PhoneConstants.DATA_APN_TYPE_KEY, apnType); + Intent intent = new Intent(ACTION_ANY_DATA_CONNECTION_STATE_CHANGED); + intent.putExtra(PHONE_CONSTANTS_STATE_KEY, dataStateToString(state)); + intent.putExtra(PHONE_CONSTANTS_DATA_APN_KEY, apn); + intent.putExtra(PHONE_CONSTANTS_DATA_APN_TYPE_KEY, getApnTypesStringFromBitmask(apnType)); intent.putExtra(PHONE_CONSTANTS_SUBSCRIPTION_KEY, subId); mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); } + private static final Map<Integer, String> APN_TYPE_INT_MAP; + static { + APN_TYPE_INT_MAP = new android.util.ArrayMap<Integer, String>(); + APN_TYPE_INT_MAP.put(ApnSetting.TYPE_DEFAULT, "default"); + APN_TYPE_INT_MAP.put(ApnSetting.TYPE_MMS, "mms"); + APN_TYPE_INT_MAP.put(ApnSetting.TYPE_SUPL, "supl"); + APN_TYPE_INT_MAP.put(ApnSetting.TYPE_DUN, "dun"); + APN_TYPE_INT_MAP.put(ApnSetting.TYPE_HIPRI, "hipri"); + APN_TYPE_INT_MAP.put(ApnSetting.TYPE_FOTA, "fota"); + APN_TYPE_INT_MAP.put(ApnSetting.TYPE_IMS, "ims"); + APN_TYPE_INT_MAP.put(ApnSetting.TYPE_CBS, "cbs"); + APN_TYPE_INT_MAP.put(ApnSetting.TYPE_IA, "ia"); + APN_TYPE_INT_MAP.put(ApnSetting.TYPE_EMERGENCY, "emergency"); + APN_TYPE_INT_MAP.put(ApnSetting.TYPE_MCX, "mcx"); + APN_TYPE_INT_MAP.put(ApnSetting.TYPE_XCAP, "xcap"); + } + + /** + * Copy of ApnSetting#getApnTypesStringFromBitmask for legacy broadcast. + * @param apnTypeBitmask bitmask of APN types. + * @return comma delimited list of APN types. + */ + private static String getApnTypesStringFromBitmask(int apnTypeBitmask) { + List<String> types = new ArrayList<>(); + for (Integer type : APN_TYPE_INT_MAP.keySet()) { + if ((apnTypeBitmask & type) == type) { + types.add(APN_TYPE_INT_MAP.get(type)); + } + } + return android.text.TextUtils.join(",", types); + } + private void enforceNotifyPermissionOrCarrierPrivilege(String method) { if (checkNotifyPermission()) { return; diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java index 2091c2a0c694..9ffe89c61a44 100644 --- a/services/core/java/com/android/server/UiModeManagerService.java +++ b/services/core/java/com/android/server/UiModeManagerService.java @@ -1059,8 +1059,8 @@ final class UiModeManagerService extends SystemService { if (Sandman.shouldStartDockApp(getContext(), homeIntent)) { try { int result = ActivityTaskManager.getService().startActivityWithConfig( - null, null, homeIntent, null, null, null, 0, 0, - mConfiguration, null, UserHandle.USER_CURRENT); + null, getContext().getBasePackageName(), homeIntent, null, null, null, + 0, 0, mConfiguration, null, UserHandle.USER_CURRENT); if (ActivityManager.isStartResultSuccessful(result)) { dockAppStarted = true; } else if (result != ActivityManager.START_INTENT_NOT_RESOLVED) { diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java index 454941ccdb03..a60b09fda2e8 100644 --- a/services/core/java/com/android/server/Watchdog.java +++ b/services/core/java/com/android/server/Watchdog.java @@ -39,8 +39,10 @@ import android.system.StructRlimit; import android.util.EventLog; import android.util.Log; import android.util.Slog; +import android.util.SparseArray; import android.util.StatsLog; +import com.android.internal.os.ProcessCpuTracker; import com.android.internal.os.ZygoteConnectionConstants; import com.android.server.am.ActivityManagerService; import com.android.server.wm.SurfaceAnimationThread; @@ -606,13 +608,18 @@ public class Watchdog extends Thread { pids.add(Process.myPid()); if (mPhonePid > 0) pids.add(mPhonePid); + long anrTime = SystemClock.uptimeMillis(); + ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(false); final File stack = ActivityManagerService.dumpStackTraces( - pids, null, null, getInterestingNativePids()); + pids, processCpuTracker, new SparseArray<>(), getInterestingNativePids()); // Give some extra time to make sure the stack traces get written. // The system's been hanging for a minute, another second or two won't hurt much. SystemClock.sleep(5000); + processCpuTracker.update(); + String cpuInfo = processCpuTracker.printCurrentState(anrTime); + // Trigger the kernel to dump all blocked threads, and backtraces on all CPUs to the kernel log doSysRq('w'); doSysRq('l'); @@ -627,7 +634,7 @@ public class Watchdog extends Thread { if (mActivity != null) { mActivity.addErrorToDropBox( "watchdog", null, "system_server", null, null, null, - subject, null, stack, null); + subject, cpuInfo, stack, null); } StatsLog.write(StatsLog.SYSTEM_SERVER_WATCHDOG_OCCURRED, subject); } diff --git a/services/core/java/com/android/server/adb/AdbDebuggingManager.java b/services/core/java/com/android/server/adb/AdbDebuggingManager.java index 4b48ef917744..143474bd5c94 100644 --- a/services/core/java/com/android/server/adb/AdbDebuggingManager.java +++ b/services/core/java/com/android/server/adb/AdbDebuggingManager.java @@ -413,6 +413,11 @@ public class AdbDebuggingManager { case MESSAGE_ADB_CLEAR: { Slog.d(TAG, "Received a request to clear the adb authorizations"); mConnectedKeys.clear(); + // If the key store has not yet been instantiated then do so now; this avoids + // the unnecessary creation of the key store when adb is not enabled. + if (mAdbKeyStore == null) { + mAdbKeyStore = new AdbKeyStore(); + } mAdbKeyStore.deleteKeyStore(); cancelJobToUpdateAdbKeyStore(); break; diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index a58bd9bf32ff..e2a036aec172 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -2068,7 +2068,7 @@ public final class ActiveServices { + " type=" + resolvedType + " callingUid=" + callingUid); userId = mAm.mUserController.handleIncomingUser(callingPid, callingUid, userId, false, - ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE, "service", + ActivityManagerInternal.ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE, "service", callingPackage); ServiceMap smap = getServiceMapLocked(userId); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 4bc02978da91..c21adb08270e 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -18673,6 +18673,11 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override + public void monitor() { + ActivityManagerService.this.monitor(); + } + + @Override public long inputDispatchingTimedOut(int pid, boolean aboveSystem, String reason) { synchronized (ActivityManagerService.this) { return ActivityManagerService.this.inputDispatchingTimedOut( diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index a4c44fabfba6..d7ad1c252401 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -2934,33 +2934,37 @@ final class ActivityManagerShellCommand extends ShellCommand { } ArraySet<Long> enabled = new ArraySet<>(); ArraySet<Long> disabled = new ArraySet<>(); - switch (toggleValue) { - case "enable": - enabled.add(changeId); - pw.println("Enabled change " + changeId + " for " + packageName + "."); - CompatibilityChangeConfig overrides = - new CompatibilityChangeConfig( - new Compatibility.ChangeConfig(enabled, disabled)); - platformCompat.setOverrides(overrides, packageName); - return 0; - case "disable": - disabled.add(changeId); - pw.println("Disabled change " + changeId + " for " + packageName + "."); - overrides = - new CompatibilityChangeConfig( - new Compatibility.ChangeConfig(enabled, disabled)); - platformCompat.setOverrides(overrides, packageName); - return 0; - case "reset": - if (platformCompat.clearOverride(changeId, packageName)) { - pw.println("Reset change " + changeId + " for " + packageName - + " to default value."); - } else { - pw.println("No override exists for changeId " + changeId + "."); - } - return 0; - default: - pw.println("Invalid toggle value: '" + toggleValue + "'."); + try { + switch (toggleValue) { + case "enable": + enabled.add(changeId); + CompatibilityChangeConfig overrides = + new CompatibilityChangeConfig( + new Compatibility.ChangeConfig(enabled, disabled)); + platformCompat.setOverrides(overrides, packageName); + pw.println("Enabled change " + changeId + " for " + packageName + "."); + return 0; + case "disable": + disabled.add(changeId); + overrides = + new CompatibilityChangeConfig( + new Compatibility.ChangeConfig(enabled, disabled)); + platformCompat.setOverrides(overrides, packageName); + pw.println("Disabled change " + changeId + " for " + packageName + "."); + return 0; + case "reset": + if (platformCompat.clearOverride(changeId, packageName)) { + pw.println("Reset change " + changeId + " for " + packageName + + " to default value."); + } else { + pw.println("No override exists for changeId " + changeId + "."); + } + return 0; + default: + pw.println("Invalid toggle value: '" + toggleValue + "'."); + } + } catch (SecurityException e) { + pw.println(e.getMessage()); } return -1; } diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java index 5e48dcf91676..8071f52037e6 100644 --- a/services/core/java/com/android/server/am/AppErrors.java +++ b/services/core/java/com/android/server/am/AppErrors.java @@ -33,8 +33,6 @@ import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; -import android.content.pm.ModuleInfo; -import android.content.pm.PackageManager; import android.content.pm.VersionedPackage; import android.net.Uri; import android.os.Binder; @@ -56,7 +54,6 @@ import com.android.internal.app.ProcessMap; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; import com.android.server.PackageWatchdog; -import com.android.server.RescueParty; import com.android.server.wm.WindowProcessController; import java.io.FileDescriptor; @@ -423,28 +420,6 @@ class AppErrors { } if (r != null) { - boolean isApexModule = false; - try { - for (String androidPackage : r.getPackageList()) { - ModuleInfo moduleInfo = mContext.getPackageManager().getModuleInfo( - androidPackage, /*flags=*/ 0); - if (moduleInfo != null) { - isApexModule = true; - break; - } - } - } catch (IllegalStateException | PackageManager.NameNotFoundException e) { - // Call to PackageManager#getModuleInfo() can result in NameNotFoundException or - // IllegalStateException. In case they are thrown, there isn't much we can do - // other than proceed with app crash handling. - } - - if (r.isPersistent() || isApexModule) { - // If a persistent app or apex module is stuck in a crash loop, the device isn't - // very usable, so we want to consider sending out a rescue party. - RescueParty.noteAppCrash(mContext, r.uid); - } - mPackageWatchdog.onPackageFailure(r.getPackageListWithVersionCode(), PackageWatchdog.FAILURE_REASON_APP_CRASH); } diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index a1e1f29016c2..b9f2e76c6ecb 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -103,6 +103,7 @@ import com.android.internal.util.ArrayUtils; import com.android.internal.util.MemInfoReader; import com.android.server.LocalServices; import com.android.server.ServiceThread; +import com.android.server.SystemConfig; import com.android.server.Watchdog; import com.android.server.compat.PlatformCompat; import com.android.server.pm.dex.DexManager; @@ -357,6 +358,8 @@ public final class ProcessList { private boolean mAppDataIsolationEnabled = false; + private ArrayList<String> mAppDataIsolationWhitelistedApps; + /** * Temporary to avoid allocations. Protected by main lock. */ @@ -645,6 +648,9 @@ public final class ProcessList { // want some apps enabled while some apps disabled mAppDataIsolationEnabled = SystemProperties.getBoolean(ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY, false); + mAppDataIsolationWhitelistedApps = new ArrayList<>( + SystemConfig.getInstance().getAppDataIsolationWhitelistedApps()); + if (sKillHandler == null) { sKillThread = new ServiceThread(TAG + ":kill", @@ -1555,20 +1561,27 @@ public final class ProcessList { } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } - + int numGids = 3; + if (mountExternal == Zygote.MOUNT_EXTERNAL_INSTALLER + || mountExternal == Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE) { + numGids++; + } /* * Add shared application and profile GIDs so applications can share some * resources like shared libraries and access user-wide resources */ if (ArrayUtils.isEmpty(permGids)) { - gids = new int[3]; + gids = new int[numGids]; } else { - gids = new int[permGids.length + 3]; - System.arraycopy(permGids, 0, gids, 3, permGids.length); + gids = new int[permGids.length + numGids]; + System.arraycopy(permGids, 0, gids, numGids, permGids.length); } gids[0] = UserHandle.getSharedAppGid(UserHandle.getAppId(uid)); gids[1] = UserHandle.getCacheAppGid(UserHandle.getAppId(uid)); gids[2] = UserHandle.getUserGid(UserHandle.getUserId(uid)); + if (numGids > 3) { + gids[3] = Process.SDCARD_RW_GID; + } // Replace any invalid GIDs if (gids[0] == UserHandle.ERR_GID) gids[0] = gids[2]; @@ -1905,6 +1918,16 @@ public final class ProcessList { result.put(packageName, Pair.create(volumeUuid, inode)); } } + if (mAppDataIsolationWhitelistedApps != null) { + for (String packageName : mAppDataIsolationWhitelistedApps) { + String volumeUuid = pmInt.getPackage(packageName).getVolumeUuid(); + long inode = pmInt.getCeDataInode(packageName, userId); + if (inode != 0) { + result.put(packageName, Pair.create(volumeUuid, inode)); + } + } + } + return result; } diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index eb1ab3863624..2b207827a5ef 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -16,12 +16,14 @@ 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.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; @@ -1641,9 +1643,10 @@ class UserController implements Handler.Callback { if (callingUid != 0 && callingUid != SYSTEM_UID) { final boolean allow; + final boolean isSameProfileGroup = isSameProfileGroup(callingUserId, targetUserId); if (mInjector.isCallerRecents(callingUid) && callingUserId == getCurrentUserId() - && isSameProfileGroup(callingUserId, targetUserId)) { + && isSameProfileGroup) { // If the caller is Recents and it is running in the current user, we then allow it // to access its profiles. allow = true; @@ -1654,6 +1657,9 @@ class UserController implements Handler.Callback { } else if (allowMode == ALLOW_FULL_ONLY) { // We require full access, sucks to be you. allow = false; + } else if (canInteractWithAcrossProfilesPermission( + allowMode, isSameProfileGroup, callingPid, callingUid)) { + allow = true; } else if (mInjector.checkComponentPermission(INTERACT_ACROSS_USERS, callingPid, callingUid, -1, true) != PackageManager.PERMISSION_GRANTED) { // If the caller does not have either permission, they are always doomed. @@ -1661,10 +1667,11 @@ class UserController implements Handler.Callback { } else if (allowMode == ALLOW_NON_FULL) { // We are blanket allowing non-full access, you lucky caller! allow = true; - } else if (allowMode == ALLOW_NON_FULL_IN_PROFILE) { + } else if (allowMode == ALLOW_NON_FULL_IN_PROFILE + || allowMode == ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE) { // We may or may not allow this depending on whether the two users are // in the same profile. - allow = isSameProfileGroup(callingUserId, targetUserId); + allow = isSameProfileGroup; } else { throw new IllegalArgumentException("Unknown mode: " + allowMode); } @@ -1690,6 +1697,11 @@ class UserController implements Handler.Callback { if (allowMode != ALLOW_FULL_ONLY) { builder.append(" or "); builder.append(INTERACT_ACROSS_USERS); + if (isSameProfileGroup + && allowMode == ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE) { + builder.append(" or "); + builder.append(INTERACT_ACROSS_PROFILES); + } } String msg = builder.toString(); Slog.w(TAG, msg); @@ -1710,6 +1722,19 @@ class UserController implements Handler.Callback { return targetUserId; } + private boolean canInteractWithAcrossProfilesPermission( + int allowMode, boolean isSameProfileGroup, int callingPid, int callingUid) { + if (allowMode != ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE) { + return false; + } + if (!isSameProfileGroup) { + return false; + } + return mInjector.checkComponentPermission( + INTERACT_ACROSS_PROFILES, callingPid, callingUid, /*owningUid= */-1, + /*exported= */true) == PackageManager.PERMISSION_GRANTED; + } + int unsafeConvertIncomingUser(@UserIdInt int userId) { return (userId == UserHandle.USER_CURRENT || userId == UserHandle.USER_CURRENT_OR_SELF) ? getCurrentUserId(): userId; diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 5bbb517f6071..a7593c7a43ec 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -17,6 +17,11 @@ package com.android.server.appop; import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION; +import static android.app.AppOpsManager.FILTER_BY_FEATURE_ID; +import static android.app.AppOpsManager.FILTER_BY_OP_NAMES; +import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME; +import static android.app.AppOpsManager.FILTER_BY_UID; +import static android.app.AppOpsManager.HistoricalOpsRequestFilter; import static android.app.AppOpsManager.NoteOpEvent; import static android.app.AppOpsManager.OP_CAMERA; import static android.app.AppOpsManager.OP_COARSE_LOCATION; @@ -53,7 +58,6 @@ import android.app.ActivityThread; import android.app.AppGlobals; import android.app.AppOpsManager; import android.app.AppOpsManager.HistoricalOps; -import android.app.AppOpsManager.HistoricalOpsRequest; import android.app.AppOpsManager.Mode; import android.app.AppOpsManager.OpEntry; import android.app.AppOpsManager.OpFeatureEntry; @@ -632,6 +636,7 @@ public class AppOpsService extends IAppOpsService.Stub { } private final class FeatureOp { + public final @Nullable String featureId; public final @NonNull Op parent; /** @@ -658,7 +663,8 @@ public class AppOpsService extends IAppOpsService.Stub { @GuardedBy("AppOpsService.this") private @Nullable ArrayMap<IBinder, InProgressStartOpEvent> mInProgressEvents; - FeatureOp(@NonNull Op parent) { + FeatureOp(@Nullable String featureId, @NonNull Op parent) { + this.featureId = featureId; this.parent = parent; } @@ -676,6 +682,9 @@ public class AppOpsService extends IAppOpsService.Stub { @OpFlags int flags) { accessed(System.currentTimeMillis(), -1, proxyUid, proxyPackageName, proxyFeatureId, uidState, flags); + + mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, parent.packageName, + featureId, uidState, flags); } /** @@ -720,6 +729,9 @@ public class AppOpsService extends IAppOpsService.Stub { */ public void rejected(@AppOpsManager.UidState int uidState, @OpFlags int flags) { rejected(System.currentTimeMillis(), uidState, flags); + + mHistoricalRegistry.incrementOpRejected(parent.op, parent.uid, parent.packageName, + featureId, uidState, flags); } /** @@ -780,7 +792,7 @@ public class AppOpsService extends IAppOpsService.Stub { // startOp events don't support proxy, hence use flags==SELF mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, parent.packageName, - uidState, OP_FLAG_SELF); + featureId, uidState, OP_FLAG_SELF); } /** @@ -820,8 +832,8 @@ public class AppOpsService extends IAppOpsService.Stub { mAccessEvents.put(makeKey(event.getUidState(), OP_FLAG_SELF), finishedEvent); mHistoricalRegistry.increaseOpAccessDuration(parent.op, parent.uid, - parent.packageName, event.getUidState(), AppOpsManager.OP_FLAG_SELF, - finishedEvent.getDuration()); + parent.packageName, featureId, event.getUidState(), + AppOpsManager.OP_FLAG_SELF, finishedEvent.getDuration()); mInProgressStartOpEventPool.release(event); @@ -1031,7 +1043,7 @@ public class AppOpsService extends IAppOpsService.Stub { featureOp = mFeatures.get(featureId); if (featureOp == null) { - featureOp = new FeatureOp(parent); + featureOp = new FeatureOp(featureId, parent); mFeatures.put(featureId, featureOp); } @@ -1697,18 +1709,47 @@ public class AppOpsService extends IAppOpsService.Stub { } } + /** + * Verify that historical appop request arguments are valid. + */ + private void ensureHistoricalOpRequestIsValid(int uid, String packageName, String featureId, + List<String> opNames, int filter, long beginTimeMillis, long endTimeMillis, + int flags) { + if ((filter & FILTER_BY_UID) != 0) { + Preconditions.checkArgument(uid != Process.INVALID_UID); + } else { + Preconditions.checkArgument(uid == Process.INVALID_UID); + } + + if ((filter & FILTER_BY_PACKAGE_NAME) != 0) { + Objects.requireNonNull(packageName); + } else { + Preconditions.checkArgument(packageName == null); + } + + if ((filter & FILTER_BY_FEATURE_ID) == 0) { + Preconditions.checkArgument(featureId == null); + } + + if ((filter & FILTER_BY_OP_NAMES) != 0) { + Objects.requireNonNull(opNames); + } else { + Preconditions.checkArgument(opNames == null); + } + + Preconditions.checkFlagsArgument(filter, + FILTER_BY_UID | FILTER_BY_PACKAGE_NAME | FILTER_BY_FEATURE_ID | FILTER_BY_OP_NAMES); + Preconditions.checkArgumentNonnegative(beginTimeMillis); + Preconditions.checkArgument(endTimeMillis > beginTimeMillis); + Preconditions.checkFlagsArgument(flags, OP_FLAGS_ALL); + } + @Override - public void getHistoricalOps(int uid, @NonNull String packageName, - @Nullable List<String> opNames, long beginTimeMillis, long endTimeMillis, - @OpFlags int flags, @NonNull RemoteCallback callback) { - // Use the builder to validate arguments. - new HistoricalOpsRequest.Builder( - beginTimeMillis, endTimeMillis) - .setUid(uid) - .setPackageName(packageName) - .setOpNames(opNames) - .setFlags(flags) - .build(); + public void getHistoricalOps(int uid, String packageName, String featureId, + List<String> opNames, int filter, long beginTimeMillis, long endTimeMillis, + int flags, RemoteCallback callback) { + ensureHistoricalOpRequestIsValid(uid, packageName, featureId, opNames, filter, + beginTimeMillis, endTimeMillis, flags); Objects.requireNonNull(callback, "callback cannot be null"); mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS, @@ -1718,22 +1759,16 @@ public class AppOpsService extends IAppOpsService.Stub { ? opNames.toArray(new String[opNames.size()]) : null; // Must not hold the appops lock - mHistoricalRegistry.getHistoricalOps(uid, packageName, opNamesArray, + mHistoricalRegistry.getHistoricalOps(uid, packageName, featureId, opNamesArray, filter, beginTimeMillis, endTimeMillis, flags, callback); } @Override - public void getHistoricalOpsFromDiskRaw(int uid, @NonNull String packageName, - @Nullable List<String> opNames, long beginTimeMillis, long endTimeMillis, - @OpFlags int flags, @NonNull RemoteCallback callback) { - // Use the builder to validate arguments. - new HistoricalOpsRequest.Builder( - beginTimeMillis, endTimeMillis) - .setUid(uid) - .setPackageName(packageName) - .setOpNames(opNames) - .setFlags(flags) - .build(); + public void getHistoricalOpsFromDiskRaw(int uid, String packageName, String featureId, + List<String> opNames, int filter, long beginTimeMillis, long endTimeMillis, + int flags, RemoteCallback callback) { + ensureHistoricalOpRequestIsValid(uid, packageName, featureId, opNames, filter, + beginTimeMillis, endTimeMillis, flags); Objects.requireNonNull(callback, "callback cannot be null"); mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS, @@ -1743,8 +1778,8 @@ public class AppOpsService extends IAppOpsService.Stub { ? opNames.toArray(new String[opNames.size()]) : null; // Must not hold the appops lock - mHistoricalRegistry.getHistoricalOpsFromDiskRaw(uid, packageName, opNamesArray, - beginTimeMillis, endTimeMillis, flags, callback); + mHistoricalRegistry.getHistoricalOpsFromDiskRaw(uid, packageName, featureId, opNamesArray, + filter, beginTimeMillis, endTimeMillis, flags, callback); } @Override @@ -2631,8 +2666,6 @@ public class AppOpsService extends IAppOpsService.Stub { + switchCode + " (" + code + ") uid " + uid + " package " + packageName); featureOp.rejected(uidState.state, flags); - mHistoricalRegistry.incrementOpRejected(code, uid, packageName, - uidState.state, flags); scheduleOpNotedIfNeededLocked(code, uid, packageName, uidMode); return uidMode; } @@ -2645,8 +2678,6 @@ public class AppOpsService extends IAppOpsService.Stub { + switchCode + " (" + code + ") uid " + uid + " package " + packageName); featureOp.rejected(uidState.state, flags); - mHistoricalRegistry.incrementOpRejected(code, uid, packageName, - uidState.state, flags); scheduleOpNotedIfNeededLocked(code, uid, packageName, mode); return mode; } @@ -2654,9 +2685,6 @@ public class AppOpsService extends IAppOpsService.Stub { if (DEBUG) Slog.d(TAG, "noteOperation: allowing code " + code + " uid " + uid + " package " + packageName + (featureId == null ? "" : "." + featureId)); featureOp.accessed(proxyUid, proxyPackageName, proxyFeatureId, uidState.state, flags); - // TODO moltmann: Add features to historical app-ops - mHistoricalRegistry.incrementOpAccessedCount(op.op, uid, packageName, - uidState.state, flags); scheduleOpNotedIfNeededLocked(code, uid, packageName, AppOpsManager.MODE_ALLOWED); @@ -2938,8 +2966,6 @@ public class AppOpsService extends IAppOpsService.Stub { + switchCode + " (" + code + ") uid " + uid + " package " + resolvedPackageName); featureOp.rejected(uidState.state, AppOpsManager.OP_FLAG_SELF); - mHistoricalRegistry.incrementOpRejected(opCode, uid, packageName, - uidState.state, AppOpsManager.OP_FLAG_SELF); return uidMode; } } else { @@ -2952,8 +2978,6 @@ public class AppOpsService extends IAppOpsService.Stub { + switchCode + " (" + code + ") uid " + uid + " package " + resolvedPackageName); featureOp.rejected(uidState.state, AppOpsManager.OP_FLAG_SELF); - mHistoricalRegistry.incrementOpRejected(opCode, uid, packageName, - uidState.state, AppOpsManager.OP_FLAG_SELF); return mode; } } @@ -4437,16 +4461,24 @@ public class AppOpsService extends IAppOpsService.Stub { pw.println(" Limit output to data associated with the given app op mode."); pw.println(" --package [PACKAGE]"); pw.println(" Limit output to data associated with the given package name."); + pw.println(" --featureId [featureId]"); + pw.println(" Limit output to data associated with the given feature id."); pw.println(" --watchers"); pw.println(" Only output the watcher sections."); pw.println(" --history"); pw.println(" Output the historical data."); } - private void dumpStatesLocked(@NonNull PrintWriter pw, long nowElapsed, @NonNull Op op, - long now, @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix) { + private void dumpStatesLocked(@NonNull PrintWriter pw, @Nullable String filterFeatureId, + @HistoricalOpsRequestFilter int filter, long nowElapsed, @NonNull Op op, long now, + @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix) { final int numFeatures = op.mFeatures.size(); for (int i = 0; i < numFeatures; i++) { + if ((filter & FILTER_BY_FEATURE_ID) != 0 && !Objects.equals(op.mFeatures.keyAt(i), + filterFeatureId)) { + continue; + } + pw.print(prefix + op.mFeatures.keyAt(i) + "=[\n"); dumpStatesLocked(pw, nowElapsed, op, op.mFeatures.keyAt(i), now, sdf, date, prefix + " "); @@ -4563,10 +4595,12 @@ public class AppOpsService extends IAppOpsService.Stub { int dumpOp = OP_NONE; String dumpPackage = null; + String dumpFeatureId = null; int dumpUid = Process.INVALID_UID; int dumpMode = -1; boolean dumpWatchers = false; boolean dumpHistory = false; + @HistoricalOpsRequestFilter int dumpFilter = 0; if (args != null) { for (int i=0; i<args.length; i++) { @@ -4583,6 +4617,7 @@ public class AppOpsService extends IAppOpsService.Stub { return; } dumpOp = Shell.strOpToOp(args[i], pw); + dumpFilter |= FILTER_BY_OP_NAMES; if (dumpOp < 0) { return; } @@ -4593,6 +4628,7 @@ public class AppOpsService extends IAppOpsService.Stub { return; } dumpPackage = args[i]; + dumpFilter |= FILTER_BY_PACKAGE_NAME; try { dumpUid = AppGlobals.getPackageManager().getPackageUid(dumpPackage, PackageManager.MATCH_KNOWN_PACKAGES | PackageManager.MATCH_INSTANT, @@ -4604,6 +4640,15 @@ public class AppOpsService extends IAppOpsService.Stub { return; } dumpUid = UserHandle.getAppId(dumpUid); + dumpFilter |= FILTER_BY_UID; + } else if ("--featureId".equals(arg)) { + i++; + if (i >= args.length) { + pw.println("No argument for --featureId option"); + return; + } + dumpFeatureId = args[i]; + dumpFilter |= FILTER_BY_FEATURE_ID; } else if ("--mode".equals(arg)) { i++; if (i >= args.length) { @@ -4640,8 +4685,8 @@ public class AppOpsService extends IAppOpsService.Stub { final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); final Date date = new Date(); boolean needSep = false; - if (dumpOp < 0 && dumpMode < 0 && dumpPackage == null && mProfileOwners != null - && !dumpWatchers && !dumpHistory) { + if (dumpFilter == 0 && dumpMode < 0 && mProfileOwners != null && !dumpWatchers + && !dumpHistory) { pw.println(" Profile owners:"); for (int poi = 0; poi < mProfileOwners.size(); poi++) { pw.print(" User #"); @@ -4944,7 +4989,8 @@ public class AppOpsService extends IAppOpsService.Stub { pw.print("="); pw.print(AppOpsManager.modeToName(mode)); } pw.println("): "); - dumpStatesLocked(pw, nowElapsed, op, now, sdf, date, " "); + dumpStatesLocked(pw, dumpFeatureId, dumpFilter, nowElapsed, op, now, sdf, + date, " "); } } } @@ -5043,7 +5089,8 @@ public class AppOpsService extends IAppOpsService.Stub { // Must not hold the appops lock if (dumpHistory && !dumpWatchers) { - mHistoricalRegistry.dump(" ", pw, dumpUid, dumpPackage, dumpOp); + mHistoricalRegistry.dump(" ", pw, dumpUid, dumpPackage, dumpFeatureId, dumpOp, + dumpFilter); } } diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java index 2175ca0362b0..cd450d421b0a 100644 --- a/services/core/java/com/android/server/appop/HistoricalRegistry.java +++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java @@ -15,12 +15,19 @@ */ package com.android.server.appop; +import static android.app.AppOpsManager.FILTER_BY_FEATURE_ID; +import static android.app.AppOpsManager.FILTER_BY_OP_NAMES; +import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME; +import static android.app.AppOpsManager.FILTER_BY_UID; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AppOpsManager; +import android.app.AppOpsManager.HistoricalFeatureOps; import android.app.AppOpsManager.HistoricalMode; import android.app.AppOpsManager.HistoricalOp; import android.app.AppOpsManager.HistoricalOps; +import android.app.AppOpsManager.HistoricalOpsRequestFilter; import android.app.AppOpsManager.HistoricalPackageOps; import android.app.AppOpsManager.HistoricalUidOps; import android.app.AppOpsManager.OpFlags; @@ -72,6 +79,7 @@ import java.util.Collections; import java.util.Date; import java.util.LinkedList; import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -273,8 +281,9 @@ final class HistoricalRegistry { + "=" + setting + " resetting!"); } - void dump(String prefix, PrintWriter pw, int filterUid, - String filterPackage, int filterOp) { + void dump(String prefix, PrintWriter pw, int filterUid, @Nullable String filterPackage, + @Nullable String filterFeatureId, int filterOp, + @HistoricalOpsRequestFilter int filter) { if (!isApiEnabled()) { return; } @@ -289,7 +298,7 @@ final class HistoricalRegistry { pw.println(AppOpsManager.historicalModeToString(mMode)); final StringDumpVisitor visitor = new StringDumpVisitor(prefix + " ", - pw, filterUid, filterPackage, filterOp); + pw, filterUid, filterPackage, filterFeatureId, filterOp, filter); final long nowMillis = System.currentTimeMillis(); // Dump in memory state first @@ -329,7 +338,8 @@ final class HistoricalRegistry { } void getHistoricalOpsFromDiskRaw(int uid, @NonNull String packageName, - @Nullable String[] opNames, long beginTimeMillis, long endTimeMillis, + @Nullable String featureId, @Nullable String[] opNames, + @HistoricalOpsRequestFilter int filter, long beginTimeMillis, long endTimeMillis, @OpFlags int flags, @NonNull RemoteCallback callback) { if (!isApiEnabled()) { callback.sendResult(new Bundle()); @@ -344,8 +354,8 @@ final class HistoricalRegistry { return; } final HistoricalOps result = new HistoricalOps(beginTimeMillis, endTimeMillis); - mPersistence.collectHistoricalOpsDLocked(result, uid, packageName, opNames, - beginTimeMillis, endTimeMillis, flags); + mPersistence.collectHistoricalOpsDLocked(result, uid, packageName, featureId, + opNames, filter, beginTimeMillis, endTimeMillis, flags); final Bundle payload = new Bundle(); payload.putParcelable(AppOpsManager.KEY_HISTORICAL_OPS, result); callback.sendResult(payload); @@ -353,9 +363,10 @@ final class HistoricalRegistry { } } - void getHistoricalOps(int uid, @NonNull String packageName, - @Nullable String[] opNames, long beginTimeMillis, long endTimeMillis, - @OpFlags int flags, @NonNull RemoteCallback callback) { + void getHistoricalOps(int uid, @NonNull String packageName, @Nullable String featureId, + @Nullable String[] opNames, @HistoricalOpsRequestFilter int filter, + long beginTimeMillis, long endTimeMillis, @OpFlags int flags, + @NonNull RemoteCallback callback) { if (!isApiEnabled()) { callback.sendResult(new Bundle()); return; @@ -390,8 +401,8 @@ final class HistoricalRegistry { || inMemoryAdjEndTimeMillis <= currentOps.getBeginTimeMillis())) { // Some of the current batch falls into the query, so extract that. final HistoricalOps currentOpsCopy = new HistoricalOps(currentOps); - currentOpsCopy.filter(uid, packageName, opNames, inMemoryAdjBeginTimeMillis, - inMemoryAdjEndTimeMillis); + currentOpsCopy.filter(uid, packageName, featureId, opNames, filter, + inMemoryAdjBeginTimeMillis, inMemoryAdjEndTimeMillis); result.merge(currentOpsCopy); } pendingWrites = new ArrayList<>(mPendingWrites); @@ -410,8 +421,8 @@ final class HistoricalRegistry { - onDiskAndInMemoryOffsetMillis, 0); final long onDiskAdjEndTimeMillis = Math.max(inMemoryAdjEndTimeMillis - onDiskAndInMemoryOffsetMillis, 0); - mPersistence.collectHistoricalOpsDLocked(result, uid, packageName, opNames, - onDiskAdjBeginTimeMillis, onDiskAdjEndTimeMillis, flags); + mPersistence.collectHistoricalOpsDLocked(result, uid, packageName, featureId, + opNames, filter, onDiskAdjBeginTimeMillis, onDiskAdjEndTimeMillis, flags); } // Rebase the result time to be since epoch. @@ -425,43 +436,47 @@ final class HistoricalRegistry { } void incrementOpAccessedCount(int op, int uid, @NonNull String packageName, - @UidState int uidState, @OpFlags int flags) { + @Nullable String featureId, @UidState int uidState, @OpFlags int flags) { synchronized (mInMemoryLock) { if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) { if (!isPersistenceInitializedMLocked()) { Slog.e(LOG_TAG, "Interaction before persistence initialized"); return; } - getUpdatedPendingHistoricalOpsMLocked(System.currentTimeMillis()) - .increaseAccessCount(op, uid, packageName, uidState, flags, 1); + getUpdatedPendingHistoricalOpsMLocked( + System.currentTimeMillis()).increaseAccessCount(op, uid, packageName, + featureId, uidState, flags, 1); } } } void incrementOpRejected(int op, int uid, @NonNull String packageName, - @UidState int uidState, @OpFlags int flags) { + @Nullable String featureId, @UidState int uidState, @OpFlags int flags) { synchronized (mInMemoryLock) { if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) { if (!isPersistenceInitializedMLocked()) { Slog.e(LOG_TAG, "Interaction before persistence initialized"); return; } - getUpdatedPendingHistoricalOpsMLocked(System.currentTimeMillis()) - .increaseRejectCount(op, uid, packageName, uidState, flags, 1); + getUpdatedPendingHistoricalOpsMLocked( + System.currentTimeMillis()).increaseRejectCount(op, uid, packageName, + featureId, uidState, flags, 1); } } } void increaseOpAccessDuration(int op, int uid, @NonNull String packageName, - @UidState int uidState, @OpFlags int flags, long increment) { + @Nullable String featureId, @UidState int uidState, @OpFlags int flags, + long increment) { synchronized (mInMemoryLock) { if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) { if (!isPersistenceInitializedMLocked()) { Slog.e(LOG_TAG, "Interaction before persistence initialized"); return; } - getUpdatedPendingHistoricalOpsMLocked(System.currentTimeMillis()) - .increaseAccessDuration(op, uid, packageName, uidState, flags, increment); + getUpdatedPendingHistoricalOpsMLocked( + System.currentTimeMillis()).increaseAccessDuration(op, uid, packageName, + featureId, uidState, flags, increment); } } } @@ -713,6 +728,7 @@ final class HistoricalRegistry { private static final String TAG_OPS = "ops"; private static final String TAG_UID = "uid"; private static final String TAG_PACKAGE = "pkg"; + private static final String TAG_FEATURE = "ftr"; private static final String TAG_OP = "op"; private static final String TAG_STATE = "st"; @@ -791,8 +807,8 @@ final class HistoricalRegistry { @Nullable List<HistoricalOps> readHistoryRawDLocked() { return collectHistoricalOpsBaseDLocked(Process.INVALID_UID /*filterUid*/, - null /*filterPackageName*/, null /*filterOpNames*/, - 0 /*filterBeginTimeMills*/, Long.MAX_VALUE /*filterEndTimeMills*/, + null /*filterPackageName*/, null /*filterFeatureId*/, null /*filterOpNames*/, + 0 /*filter*/, 0 /*filterBeginTimeMills*/, Long.MAX_VALUE /*filterEndTimeMills*/, AppOpsManager.OP_FLAGS_ALL); } @@ -846,11 +862,12 @@ final class HistoricalRegistry { } private void collectHistoricalOpsDLocked(@NonNull HistoricalOps currentOps, - int filterUid, @NonNull String filterPackageName, @Nullable String[] filterOpNames, + int filterUid, @Nullable String filterPackageName, @Nullable String filterFeatureId, + @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter, long filterBeingMillis, long filterEndMillis, @OpFlags int filterFlags) { final List<HistoricalOps> readOps = collectHistoricalOpsBaseDLocked(filterUid, - filterPackageName, filterOpNames, filterBeingMillis, filterEndMillis, - filterFlags); + filterPackageName, filterFeatureId, filterOpNames, filter, filterBeingMillis, + filterEndMillis, filterFlags); if (readOps != null) { final int readCount = readOps.size(); for (int i = 0; i < readCount; i++) { @@ -861,7 +878,8 @@ final class HistoricalRegistry { } private @Nullable LinkedList<HistoricalOps> collectHistoricalOpsBaseDLocked( - int filterUid, @NonNull String filterPackageName, @Nullable String[] filterOpNames, + int filterUid, @Nullable String filterPackageName, @Nullable String filterFeatureId, + @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter, long filterBeginTimeMillis, long filterEndTimeMillis, @OpFlags int filterFlags) { File baseDir = null; try { @@ -874,9 +892,9 @@ final class HistoricalRegistry { final Set<String> historyFiles = getHistoricalFileNames(baseDir); final long[] globalContentOffsetMillis = {0}; final LinkedList<HistoricalOps> ops = collectHistoricalOpsRecursiveDLocked( - baseDir, filterUid, filterPackageName, filterOpNames, filterBeginTimeMillis, - filterEndTimeMillis, filterFlags, globalContentOffsetMillis, - null /*outOps*/, 0 /*depth*/, historyFiles); + baseDir, filterUid, filterPackageName, filterFeatureId, filterOpNames, + filter, filterBeginTimeMillis, filterEndTimeMillis, filterFlags, + globalContentOffsetMillis, null /*outOps*/, 0 /*depth*/, historyFiles); if (DEBUG) { filesInvariant.stopTracking(baseDir); } @@ -890,8 +908,9 @@ final class HistoricalRegistry { } private @Nullable LinkedList<HistoricalOps> collectHistoricalOpsRecursiveDLocked( - @NonNull File baseDir, int filterUid, @NonNull String filterPackageName, - @Nullable String[] filterOpNames, long filterBeginTimeMillis, + @NonNull File baseDir, int filterUid, @Nullable String filterPackageName, + @Nullable String filterFeatureId, @Nullable String[] filterOpNames, + @HistoricalOpsRequestFilter int filter, long filterBeginTimeMillis, long filterEndTimeMillis, @OpFlags int filterFlags, @NonNull long[] globalContentOffsetMillis, @Nullable LinkedList<HistoricalOps> outOps, int depth, @@ -908,17 +927,17 @@ final class HistoricalRegistry { // Read historical data at this level final List<HistoricalOps> readOps = readHistoricalOpsLocked(baseDir, previousIntervalEndMillis, currentIntervalEndMillis, filterUid, - filterPackageName, filterOpNames, filterBeginTimeMillis, filterEndTimeMillis, - filterFlags, globalContentOffsetMillis, depth, historyFiles); - + filterPackageName, filterFeatureId, filterOpNames, filter, + filterBeginTimeMillis, filterEndTimeMillis, filterFlags, + globalContentOffsetMillis, depth, historyFiles); // Empty is a special signal to stop diving if (readOps != null && readOps.isEmpty()) { return outOps; } // Collect older historical data from subsequent levels - outOps = collectHistoricalOpsRecursiveDLocked( - baseDir, filterUid, filterPackageName, filterOpNames, filterBeginTimeMillis, + outOps = collectHistoricalOpsRecursiveDLocked(baseDir, filterUid, filterPackageName, + filterFeatureId, filterOpNames, filter, filterBeginTimeMillis, filterEndTimeMillis, filterFlags, globalContentOffsetMillis, outOps, depth + 1, historyFiles); @@ -987,10 +1006,10 @@ final class HistoricalRegistry { final List<HistoricalOps> existingOps = readHistoricalOpsLocked(oldBaseDir, previousIntervalEndMillis, currentIntervalEndMillis, Process.INVALID_UID /*filterUid*/, null /*filterPackageName*/, - null /*filterOpNames*/, Long.MIN_VALUE /*filterBeginTimeMillis*/, - Long.MAX_VALUE /*filterEndTimeMillis*/, AppOpsManager.OP_FLAGS_ALL, - null, depth, null /*historyFiles*/); - + null /*filterFeatureId*/, null /*filterOpNames*/, 0 /*filter*/, + Long.MIN_VALUE /*filterBeginTimeMillis*/, + Long.MAX_VALUE /*filterEndTimeMillis*/, AppOpsManager.OP_FLAGS_ALL, null, depth, + null /*historyFiles*/); if (DEBUG) { enforceOpsWellFormed(existingOps); } @@ -1100,8 +1119,9 @@ final class HistoricalRegistry { } private @Nullable List<HistoricalOps> readHistoricalOpsLocked(File baseDir, - long intervalBeginMillis, long intervalEndMillis, - int filterUid, @Nullable String filterPackageName, @Nullable String[] filterOpNames, + long intervalBeginMillis, long intervalEndMillis, int filterUid, + @Nullable String filterPackageName, @Nullable String filterFeatureId, + @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter, long filterBeginTimeMillis, long filterEndTimeMillis, @OpFlags int filterFlags, @Nullable long[] cumulativeOverflowMillis, int depth, @NonNull Set<String> historyFiles) @@ -1127,13 +1147,14 @@ final class HistoricalRegistry { return null; } } - return readHistoricalOpsLocked(file, filterUid, filterPackageName, filterOpNames, - filterBeginTimeMillis, filterEndTimeMillis, filterFlags, + return readHistoricalOpsLocked(file, filterUid, filterPackageName, filterFeatureId, + filterOpNames, filter, filterBeginTimeMillis, filterEndTimeMillis, filterFlags, cumulativeOverflowMillis); } private @Nullable List<HistoricalOps> readHistoricalOpsLocked(@NonNull File file, - int filterUid, @Nullable String filterPackageName, @Nullable String[] filterOpNames, + int filterUid, @Nullable String filterPackageName, @Nullable String filterFeatureId, + @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter, long filterBeginTimeMillis, long filterEndTimeMillis, @OpFlags int filterFlags, @Nullable long[] cumulativeOverflowMillis) throws IOException, XmlPullParserException { @@ -1158,9 +1179,10 @@ final class HistoricalRegistry { final int depth = parser.getDepth(); while (XmlUtils.nextElementWithin(parser, depth)) { if (TAG_OPS.equals(parser.getName())) { - final HistoricalOps ops = readeHistoricalOpsDLocked(parser, - filterUid, filterPackageName, filterOpNames, filterBeginTimeMillis, - filterEndTimeMillis, filterFlags, cumulativeOverflowMillis); + final HistoricalOps ops = readeHistoricalOpsDLocked(parser, filterUid, + filterPackageName, filterFeatureId, filterOpNames, filter, + filterBeginTimeMillis, filterEndTimeMillis, filterFlags, + cumulativeOverflowMillis); if (ops == null) { continue; } @@ -1193,7 +1215,8 @@ final class HistoricalRegistry { private @Nullable HistoricalOps readeHistoricalOpsDLocked( @NonNull XmlPullParser parser, int filterUid, @Nullable String filterPackageName, - @Nullable String[] filterOpNames, long filterBeginTimeMillis, + @Nullable String filterFeatureId, @Nullable String[] filterOpNames, + @HistoricalOpsRequestFilter int filter, long filterBeginTimeMillis, long filterEndTimeMillis, @OpFlags int filterFlags, @Nullable long[] cumulativeOverflowMillis) throws IOException, XmlPullParserException { @@ -1222,7 +1245,8 @@ final class HistoricalRegistry { while (XmlUtils.nextElementWithin(parser, depth)) { if (TAG_UID.equals(parser.getName())) { final HistoricalOps returnedOps = readHistoricalUidOpsDLocked(ops, parser, - filterUid, filterPackageName, filterOpNames, filterFlags, filterScale); + filterUid, filterPackageName, filterFeatureId, filterOpNames, filter, + filterFlags, filterScale); if (ops == null) { ops = returnedOps; } @@ -1236,11 +1260,12 @@ final class HistoricalRegistry { private @Nullable HistoricalOps readHistoricalUidOpsDLocked( @Nullable HistoricalOps ops, @NonNull XmlPullParser parser, int filterUid, - @Nullable String filterPackageName, @Nullable String[] filterOpNames, + @Nullable String filterPackageName, @Nullable String filterFeatureId, + @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter, @OpFlags int filterFlags, double filterScale) throws IOException, XmlPullParserException { final int uid = XmlUtils.readIntAttribute(parser, ATTR_NAME); - if (filterUid != Process.INVALID_UID && filterUid != uid) { + if ((filter & FILTER_BY_UID) != 0 && filterUid != uid) { XmlUtils.skipCurrentTag(parser); return null; } @@ -1248,8 +1273,8 @@ final class HistoricalRegistry { while (XmlUtils.nextElementWithin(parser, depth)) { if (TAG_PACKAGE.equals(parser.getName())) { final HistoricalOps returnedOps = readHistoricalPackageOpsDLocked(ops, - uid, parser, filterPackageName, filterOpNames, filterFlags, - filterScale); + uid, parser, filterPackageName, filterFeatureId, filterOpNames, filter, + filterFlags, filterScale); if (ops == null) { ops = returnedOps; } @@ -1260,19 +1285,46 @@ final class HistoricalRegistry { private @Nullable HistoricalOps readHistoricalPackageOpsDLocked( @Nullable HistoricalOps ops, int uid, @NonNull XmlPullParser parser, - @Nullable String filterPackageName, @Nullable String[] filterOpNames, + @Nullable String filterPackageName, @Nullable String filterFeatureId, + @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter, @OpFlags int filterFlags, double filterScale) throws IOException, XmlPullParserException { final String packageName = XmlUtils.readStringAttribute(parser, ATTR_NAME); - if (filterPackageName != null && !filterPackageName.equals(packageName)) { + if ((filter & FILTER_BY_PACKAGE_NAME) != 0 && !filterPackageName.equals(packageName)) { + XmlUtils.skipCurrentTag(parser); + return null; + } + final int depth = parser.getDepth(); + while (XmlUtils.nextElementWithin(parser, depth)) { + if (TAG_FEATURE.equals(parser.getName())) { + final HistoricalOps returnedOps = readHistoricalFeatureOpsDLocked(ops, uid, + packageName, parser, filterFeatureId, filterOpNames, filter, + filterFlags, filterScale); + if (ops == null) { + ops = returnedOps; + } + } + } + return ops; + } + + private @Nullable HistoricalOps readHistoricalFeatureOpsDLocked(@Nullable HistoricalOps ops, + int uid, String packageName, @NonNull XmlPullParser parser, + @Nullable String filterFeatureId, @Nullable String[] filterOpNames, + @HistoricalOpsRequestFilter int filter, @OpFlags int filterFlags, + double filterScale) + throws IOException, XmlPullParserException { + final String featureId = XmlUtils.readStringAttribute(parser, ATTR_NAME); + if ((filter & FILTER_BY_FEATURE_ID) != 0 && !Objects.equals(filterFeatureId, + featureId)) { XmlUtils.skipCurrentTag(parser); return null; } final int depth = parser.getDepth(); while (XmlUtils.nextElementWithin(parser, depth)) { if (TAG_OP.equals(parser.getName())) { - final HistoricalOps returnedOps = readHistoricalOpDLocked(ops, uid, - packageName, parser, filterOpNames, filterFlags, filterScale); + final HistoricalOps returnedOps = readHistoricalOpDLocked(ops, uid, packageName, + featureId, parser, filterOpNames, filter, filterFlags, filterScale); if (ops == null) { ops = returnedOps; } @@ -1282,11 +1334,13 @@ final class HistoricalRegistry { } private @Nullable HistoricalOps readHistoricalOpDLocked(@Nullable HistoricalOps ops, - int uid, String packageName, @NonNull XmlPullParser parser, - @Nullable String[] filterOpNames, @OpFlags int filterFlags, double filterScale) + int uid, @NonNull String packageName, @Nullable String featureId, + @NonNull XmlPullParser parser, @Nullable String[] filterOpNames, + @HistoricalOpsRequestFilter int filter, @OpFlags int filterFlags, + double filterScale) throws IOException, XmlPullParserException { final int op = XmlUtils.readIntAttribute(parser, ATTR_NAME); - if (filterOpNames != null && !ArrayUtils.contains(filterOpNames, + if ((filter & FILTER_BY_OP_NAMES) != 0 && !ArrayUtils.contains(filterOpNames, AppOpsManager.opToPublicName(op))) { XmlUtils.skipCurrentTag(parser); return null; @@ -1295,7 +1349,7 @@ final class HistoricalRegistry { while (XmlUtils.nextElementWithin(parser, depth)) { if (TAG_STATE.equals(parser.getName())) { final HistoricalOps returnedOps = readStateDLocked(ops, uid, - packageName, op, parser, filterFlags, filterScale); + packageName, featureId, op, parser, filterFlags, filterScale); if (ops == null) { ops = returnedOps; } @@ -1305,8 +1359,9 @@ final class HistoricalRegistry { } private @Nullable HistoricalOps readStateDLocked(@Nullable HistoricalOps ops, - int uid, String packageName, int op, @NonNull XmlPullParser parser, - @OpFlags int filterFlags, double filterScale) throws IOException { + int uid, @NonNull String packageName, @Nullable String featureId, int op, + @NonNull XmlPullParser parser, @OpFlags int filterFlags, double filterScale) + throws IOException { final long key = XmlUtils.readLongAttribute(parser, ATTR_NAME); final int flags = AppOpsManager.extractFlagsFromKey(key) & filterFlags; if (flags == 0) { @@ -1322,7 +1377,8 @@ final class HistoricalRegistry { if (ops == null) { ops = new HistoricalOps(0, 0); } - ops.increaseAccessCount(op, uid, packageName, uidState, flags, accessCount); + ops.increaseAccessCount(op, uid, packageName, featureId, uidState, flags, + accessCount); } long rejectCount = XmlUtils.readLongAttribute(parser, ATTR_REJECT_COUNT, 0); if (rejectCount > 0) { @@ -1333,7 +1389,8 @@ final class HistoricalRegistry { if (ops == null) { ops = new HistoricalOps(0, 0); } - ops.increaseRejectCount(op, uid, packageName, uidState, flags, rejectCount); + ops.increaseRejectCount(op, uid, packageName, featureId, uidState, flags, + rejectCount); } long accessDuration = XmlUtils.readLongAttribute(parser, ATTR_ACCESS_DURATION, 0); if (accessDuration > 0) { @@ -1344,7 +1401,8 @@ final class HistoricalRegistry { if (ops == null) { ops = new HistoricalOps(0, 0); } - ops.increaseAccessDuration(op, uid, packageName, uidState, flags, accessDuration); + ops.increaseAccessDuration(op, uid, packageName, featureId, uidState, flags, + accessDuration); } return ops; } @@ -1409,12 +1467,24 @@ final class HistoricalRegistry { @NonNull XmlSerializer serializer) throws IOException { serializer.startTag(null, TAG_PACKAGE); serializer.attribute(null, ATTR_NAME, packageOps.getPackageName()); - final int opCount = packageOps.getOpCount(); + final int numFeatures = packageOps.getFeatureCount(); + for (int i = 0; i < numFeatures; i++) { + final HistoricalFeatureOps op = packageOps.getFeatureOpsAt(i); + writeHistoricalFeatureOpsDLocked(op, serializer); + } + serializer.endTag(null, TAG_PACKAGE); + } + + private void writeHistoricalFeatureOpsDLocked(@NonNull HistoricalFeatureOps featureOps, + @NonNull XmlSerializer serializer) throws IOException { + serializer.startTag(null, TAG_FEATURE); + XmlUtils.writeStringAttribute(serializer, ATTR_NAME, featureOps.getFeatureId()); + final int opCount = featureOps.getOpCount(); for (int i = 0; i < opCount; i++) { - final HistoricalOp op = packageOps.getOpAt(i); + final HistoricalOp op = featureOps.getOpAt(i); writeHistoricalOpDLocked(op, serializer); } - serializer.endTag(null, TAG_PACKAGE); + serializer.endTag(null, TAG_FEATURE); } private void writeHistoricalOpDLocked(@NonNull HistoricalOp op, @@ -1648,24 +1718,31 @@ final class HistoricalRegistry { private final @NonNull String mOpsPrefix; private final @NonNull String mUidPrefix; private final @NonNull String mPackagePrefix; + private final @NonNull String mFeaturePrefix; private final @NonNull String mEntryPrefix; private final @NonNull String mUidStatePrefix; private final @NonNull PrintWriter mWriter; private final int mFilterUid; private final String mFilterPackage; + private final String mFilterFeatureId; private final int mFilterOp; + private final @HistoricalOpsRequestFilter int mFilter; - StringDumpVisitor(@NonNull String prefix, @NonNull PrintWriter writer, - int filterUid, String filterPackage, int filterOp) { + StringDumpVisitor(@NonNull String prefix, @NonNull PrintWriter writer, int filterUid, + @Nullable String filterPackage, @Nullable String filterFeatureId, int filterOp, + @HistoricalOpsRequestFilter int filter) { mOpsPrefix = prefix + " "; mUidPrefix = mOpsPrefix + " "; mPackagePrefix = mUidPrefix + " "; - mEntryPrefix = mPackagePrefix + " "; + mFeaturePrefix = mPackagePrefix + " "; + mEntryPrefix = mFeaturePrefix + " "; mUidStatePrefix = mEntryPrefix + " "; mWriter = writer; mFilterUid = filterUid; mFilterPackage = filterPackage; + mFilterFeatureId = filterFeatureId; mFilterOp = filterOp; + mFilter = filter; } @Override @@ -1691,7 +1768,7 @@ final class HistoricalRegistry { @Override public void visitHistoricalUidOps(HistoricalUidOps ops) { - if (mFilterUid != Process.INVALID_UID && mFilterUid != ops.getUid()) { + if ((mFilter & FILTER_BY_UID) != 0 && mFilterUid != ops.getUid()) { return; } mWriter.println(); @@ -1703,7 +1780,8 @@ final class HistoricalRegistry { @Override public void visitHistoricalPackageOps(HistoricalPackageOps ops) { - if (mFilterPackage != null && !mFilterPackage.equals(ops.getPackageName())) { + if ((mFilter & FILTER_BY_PACKAGE_NAME) != 0 && !mFilterPackage.equals( + ops.getPackageName())) { return; } mWriter.print(mPackagePrefix); @@ -1713,8 +1791,20 @@ final class HistoricalRegistry { } @Override + public void visitHistoricalFeatureOps(HistoricalFeatureOps ops) { + if ((mFilter & FILTER_BY_FEATURE_ID) != 0 && !Objects.equals(mFilterPackage, + ops.getFeatureId())) { + return; + } + mWriter.print(mFeaturePrefix); + mWriter.print("Feature "); + mWriter.print(ops.getFeatureId()); + mWriter.println(":"); + } + + @Override public void visitHistoricalOp(HistoricalOp ops) { - if (mFilterOp != AppOpsManager.OP_NONE && mFilterOp != ops.getOpCode()) { + if ((mFilter & FILTER_BY_OP_NAMES) != 0 && mFilterOp != ops.getOpCode()) { return; } mWriter.print(mEntryPrefix); diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java index cf83dd630a29..f15d999e1006 100644 --- a/services/core/java/com/android/server/compat/CompatConfig.java +++ b/services/core/java/com/android/server/compat/CompatConfig.java @@ -17,8 +17,10 @@ package com.android.server.compat; import android.compat.Compatibility.ChangeConfig; +import android.content.Context; import android.content.pm.ApplicationInfo; import android.os.Environment; +import android.os.RemoteException; import android.text.TextUtils; import android.util.LongArray; import android.util.LongSparseArray; @@ -26,8 +28,11 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.compat.AndroidBuildClassifier; import com.android.internal.compat.CompatibilityChangeConfig; import com.android.internal.compat.CompatibilityChangeInfo; +import com.android.internal.compat.IOverrideValidator; +import com.android.internal.compat.OverrideAllowedState; import com.android.server.compat.config.Change; import com.android.server.compat.config.XmlParser; @@ -54,22 +59,14 @@ final class CompatConfig { private static final String TAG = "CompatConfig"; - private static final CompatConfig sInstance = new CompatConfig().initConfigFromLib( - Environment.buildPath( - Environment.getRootDirectory(), "etc", "compatconfig")); - @GuardedBy("mChanges") private final LongSparseArray<CompatChange> mChanges = new LongSparseArray<>(); - @VisibleForTesting - CompatConfig() { - } + private IOverrideValidator mOverrideValidator; - /** - * @return The static instance of this class to be used within the system server. - */ - static CompatConfig get() { - return sInstance; + @VisibleForTesting + CompatConfig(AndroidBuildClassifier androidBuildClassifier, Context context) { + mOverrideValidator = new OverrideValidatorImpl(androidBuildClassifier, context, this); } /** @@ -159,8 +156,12 @@ final class CompatConfig { * @param enabled If the change should be enabled or disabled. * @return {@code true} if the change existed before adding the override. */ - boolean addOverride(long changeId, String packageName, boolean enabled) { + boolean addOverride(long changeId, String packageName, boolean enabled) + throws RemoteException, SecurityException { boolean alreadyKnown = true; + OverrideAllowedState allowedState = + mOverrideValidator.getOverrideAllowedState(changeId, packageName); + allowedState.enforce(changeId, packageName); synchronized (mChanges) { CompatChange c = mChanges.get(changeId); if (c == null) { @@ -186,6 +187,20 @@ final class CompatConfig { } /** + * Returns the minimum sdk version for which this change should be enabled (or 0 if it is not + * target sdk gated). + */ + int minTargetSdkForChangeId(long changeId) { + synchronized (mChanges) { + CompatChange c = mChanges.get(changeId); + if (c == null) { + return 0; + } + return c.getEnableAfterTargetSdk(); + } + } + + /** * Removes an override previously added via {@link #addOverride(long, String, boolean)}. This * restores the default behaviour for the given change and app, once any app processes have been * restarted. @@ -194,34 +209,44 @@ final class CompatConfig { * @param packageName The app package name that was overridden. * @return {@code true} if an override existed; */ - boolean removeOverride(long changeId, String packageName) { + boolean removeOverride(long changeId, String packageName) + throws RemoteException, SecurityException { boolean overrideExists = false; synchronized (mChanges) { CompatChange c = mChanges.get(changeId); - if (c != null) { - overrideExists = true; - c.removePackageOverride(packageName); + try { + if (c != null) { + OverrideAllowedState allowedState = + mOverrideValidator.getOverrideAllowedState(changeId, packageName); + allowedState.enforce(changeId, packageName); + overrideExists = true; + c.removePackageOverride(packageName); + } + } catch (RemoteException e) { + // Should never occur, since validator is in the same process. + throw new RuntimeException("Unable to call override validator!", e); } } return overrideExists; } /** - * Overrides the enabled state for a given change and app. This method is intended to be used - * *only* for debugging purposes. + * Overrides the enabled state for a given change and app. * * <p>Note, package overrides are not persistent and will be lost on system or runtime restart. * * @param overrides list of overrides to default changes config. * @param packageName app for which the overrides will be applied. */ - void addOverrides(CompatibilityChangeConfig overrides, String packageName) { + void addOverrides(CompatibilityChangeConfig overrides, String packageName) + throws RemoteException, SecurityException { synchronized (mChanges) { for (Long changeId : overrides.enabledChanges()) { addOverride(changeId, packageName, true); } for (Long changeId : overrides.disabledChanges()) { addOverride(changeId, packageName, false); + } } } @@ -235,10 +260,22 @@ final class CompatConfig { * * @param packageName The package for which the overrides should be purged. */ - void removePackageOverrides(String packageName) { + void removePackageOverrides(String packageName) throws RemoteException, SecurityException { synchronized (mChanges) { for (int i = 0; i < mChanges.size(); ++i) { - mChanges.valueAt(i).removePackageOverride(packageName); + try { + CompatChange change = mChanges.valueAt(i); + OverrideAllowedState allowedState = + mOverrideValidator.getOverrideAllowedState(change.getId(), + packageName); + allowedState.enforce(change.getId(), packageName); + if (change != null) { + mChanges.valueAt(i).removePackageOverride(packageName); + } + } catch (RemoteException e) { + // Should never occur, since validator is in the same process. + throw new RuntimeException("Unable to call override validator!", e); + } } } } @@ -326,17 +363,23 @@ final class CompatConfig { } } - CompatConfig initConfigFromLib(File libraryDir) { + static CompatConfig create(AndroidBuildClassifier androidBuildClassifier, Context context) { + CompatConfig config = new CompatConfig(androidBuildClassifier, context); + config.initConfigFromLib(Environment.buildPath( + Environment.getRootDirectory(), "etc", "compatconfig")); + return config; + } + + void initConfigFromLib(File libraryDir) { if (!libraryDir.exists() || !libraryDir.isDirectory()) { Slog.e(TAG, "No directory " + libraryDir + ", skipping"); - return this; + return; } for (File f : libraryDir.listFiles()) { Slog.d(TAG, "Found a config file: " + f.getPath()); //TODO(b/138222363): Handle duplicate ids across config files. readConfig(f); } - return this; } private void readConfig(File configFile) { @@ -350,4 +393,7 @@ final class CompatConfig { } } + IOverrideValidator getOverrideValidator() { + return mOverrideValidator; + } } diff --git a/services/core/java/com/android/server/compat/OverrideValidatorImpl.java b/services/core/java/com/android/server/compat/OverrideValidatorImpl.java new file mode 100644 index 000000000000..dfc00806992b --- /dev/null +++ b/services/core/java/com/android/server/compat/OverrideValidatorImpl.java @@ -0,0 +1,94 @@ +/* + * 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.compat; + +import static com.android.internal.compat.OverrideAllowedState.ALLOWED; +import static com.android.internal.compat.OverrideAllowedState.DISABLED_NON_TARGET_SDK; +import static com.android.internal.compat.OverrideAllowedState.DISABLED_NOT_DEBUGGABLE; +import static com.android.internal.compat.OverrideAllowedState.DISABLED_TARGET_SDK_TOO_HIGH; +import static com.android.internal.compat.OverrideAllowedState.PACKAGE_DOES_NOT_EXIST; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.compat.AndroidBuildClassifier; +import com.android.internal.compat.IOverrideValidator; +import com.android.internal.compat.OverrideAllowedState; + +/** + * Implementation of the policy for allowing compat change overrides. + */ +public class OverrideValidatorImpl extends IOverrideValidator.Stub { + + private AndroidBuildClassifier mAndroidBuildClassifier; + private Context mContext; + private CompatConfig mCompatConfig; + + @VisibleForTesting + OverrideValidatorImpl(AndroidBuildClassifier androidBuildClassifier, + Context context, CompatConfig config) { + mAndroidBuildClassifier = androidBuildClassifier; + mContext = context; + mCompatConfig = config; + } + + @Override + public OverrideAllowedState getOverrideAllowedState(long changeId, String packageName) { + boolean debuggableBuild = false; + boolean finalBuild = false; + + debuggableBuild = mAndroidBuildClassifier.isDebuggableBuild(); + finalBuild = mAndroidBuildClassifier.isFinalBuild(); + + // Allow any override for userdebug or eng builds. + if (debuggableBuild) { + return new OverrideAllowedState(ALLOWED, -1, -1); + } + PackageManager packageManager = mContext.getPackageManager(); + if (packageManager == null) { + throw new IllegalStateException("No PackageManager!"); + } + ApplicationInfo applicationInfo; + try { + applicationInfo = packageManager.getApplicationInfo(packageName, 0); + } catch (NameNotFoundException e) { + return new OverrideAllowedState(PACKAGE_DOES_NOT_EXIST, -1, -1); + } + int appTargetSdk = applicationInfo.targetSdkVersion; + // Only allow overriding debuggable apps. + if ((applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) == 0) { + return new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1); + } + int minTargetSdk = mCompatConfig.minTargetSdkForChangeId(changeId); + // Do not allow overriding non-target sdk gated changes on user builds + if (minTargetSdk == -1) { + return new OverrideAllowedState(DISABLED_NON_TARGET_SDK, appTargetSdk, minTargetSdk); + } + // Allow overriding any change for debuggable apps on non-final builds. + if (!finalBuild) { + return new OverrideAllowedState(ALLOWED, appTargetSdk, minTargetSdk); + } + // Only allow to opt-in for a targetSdk gated change. + if (applicationInfo.targetSdkVersion < minTargetSdk) { + return new OverrideAllowedState(ALLOWED, appTargetSdk, minTargetSdk); + } + return new OverrideAllowedState(DISABLED_TARGET_SDK_TOO_HIGH, appTargetSdk, minTargetSdk); + } +} diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java index 6ec4b9fbdb93..bb8b12e86e16 100644 --- a/services/core/java/com/android/server/compat/PlatformCompat.java +++ b/services/core/java/com/android/server/compat/PlatformCompat.java @@ -27,9 +27,12 @@ import android.os.UserHandle; import android.util.Slog; import android.util.StatsLog; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.compat.AndroidBuildClassifier; import com.android.internal.compat.ChangeReporter; import com.android.internal.compat.CompatibilityChangeConfig; import com.android.internal.compat.CompatibilityChangeInfo; +import com.android.internal.compat.IOverrideValidator; import com.android.internal.compat.IPlatformCompat; import com.android.internal.util.DumpUtils; import com.android.server.LocalServices; @@ -46,11 +49,21 @@ public class PlatformCompat extends IPlatformCompat.Stub { private final Context mContext; private final ChangeReporter mChangeReporter; + private final CompatConfig mCompatConfig; public PlatformCompat(Context context) { mContext = context; mChangeReporter = new ChangeReporter( StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__SOURCE__SYSTEM_SERVER); + mCompatConfig = CompatConfig.create(new AndroidBuildClassifier(), mContext); + } + + @VisibleForTesting + PlatformCompat(Context context, CompatConfig compatConfig) { + mContext = context; + mChangeReporter = new ChangeReporter( + StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__SOURCE__SYSTEM_SERVER); + mCompatConfig = compatConfig; } @Override @@ -75,7 +88,7 @@ public class PlatformCompat extends IPlatformCompat.Stub { @Override public boolean isChangeEnabled(long changeId, ApplicationInfo appInfo) { - if (CompatConfig.get().isChangeEnabled(changeId, appInfo)) { + if (mCompatConfig.isChangeEnabled(changeId, appInfo)) { reportChange(changeId, appInfo.uid, StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__ENABLED); return true; @@ -122,57 +135,59 @@ public class PlatformCompat extends IPlatformCompat.Stub { * otherwise. */ public boolean registerListener(long changeId, CompatChange.ChangeListener listener) { - return CompatConfig.get().registerListener(changeId, listener); + return mCompatConfig.registerListener(changeId, listener); } @Override - public void setOverrides(CompatibilityChangeConfig overrides, String packageName) { - CompatConfig.get().addOverrides(overrides, packageName); + public void setOverrides(CompatibilityChangeConfig overrides, String packageName) + throws RemoteException, SecurityException { + mCompatConfig.addOverrides(overrides, packageName); killPackage(packageName); } @Override - public void setOverridesForTest(CompatibilityChangeConfig overrides, String packageName) { - CompatConfig.get().addOverrides(overrides, packageName); + public void setOverridesForTest(CompatibilityChangeConfig overrides, String packageName) + throws RemoteException, SecurityException { + mCompatConfig.addOverrides(overrides, packageName); } @Override - public void clearOverrides(String packageName) { - CompatConfig config = CompatConfig.get(); - config.removePackageOverrides(packageName); + public void clearOverrides(String packageName) throws RemoteException, SecurityException { + mCompatConfig.removePackageOverrides(packageName); killPackage(packageName); } @Override - public void clearOverridesForTest(String packageName) { - CompatConfig config = CompatConfig.get(); - config.removePackageOverrides(packageName); + public void clearOverridesForTest(String packageName) + throws RemoteException, SecurityException { + mCompatConfig.removePackageOverrides(packageName); } @Override - public boolean clearOverride(long changeId, String packageName) { - boolean existed = CompatConfig.get().removeOverride(changeId, packageName); + public boolean clearOverride(long changeId, String packageName) + throws RemoteException, SecurityException { + boolean existed = mCompatConfig.removeOverride(changeId, packageName); killPackage(packageName); return existed; } @Override public CompatibilityChangeConfig getAppConfig(ApplicationInfo appInfo) { - return CompatConfig.get().getAppConfig(appInfo); + return mCompatConfig.getAppConfig(appInfo); } @Override public CompatibilityChangeInfo[] listAllChanges() { - return CompatConfig.get().dumpChanges(); + return mCompatConfig.dumpChanges(); } /** * Check whether the change is known to the compat config. - * @param changeId + * * @return {@code true} if the change is known. */ public boolean isKnownChangeId(long changeId) { - return CompatConfig.get().isKnownChangeId(changeId); + return mCompatConfig.isKnownChangeId(changeId); } @@ -182,11 +197,11 @@ public class PlatformCompat extends IPlatformCompat.Stub { * * @param appInfo The app in question * @return A sorted long array of change IDs. We use a primitive array to minimize memory - * footprint: Every app process will store this array statically so we aim to reduce - * overhead as much as possible. + * footprint: Every app process will store this array statically so we aim to reduce + * overhead as much as possible. */ public long[] getDisabledChanges(ApplicationInfo appInfo) { - return CompatConfig.get().getDisabledChanges(appInfo); + return mCompatConfig.getDisabledChanges(appInfo); } /** @@ -196,18 +211,24 @@ public class PlatformCompat extends IPlatformCompat.Stub { * @return The change ID, or {@code -1} if no change with that name exists. */ public long lookupChangeId(String name) { - return CompatConfig.get().lookupChangeId(name); + return mCompatConfig.lookupChangeId(name); } @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, "platform_compat", pw)) return; - CompatConfig.get().dumpConfig(pw); + mCompatConfig.dumpConfig(pw); + } + + @Override + public IOverrideValidator getOverrideValidator() { + return mCompatConfig.getOverrideValidator(); } /** * Clears information stored about events reported on behalf of an app. * To be called once upon app start or end. A second call would be a no-op. + * * @param appInfo the app to reset */ public void resetReporting(ApplicationInfo appInfo) { diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index e39d6d5389e3..d7ae5b5c91bd 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -1305,13 +1305,21 @@ public final class DisplayManagerService extends SystemService { } private SurfaceControl.ScreenshotGraphicBuffer screenshotInternal(int displayId) { - final IBinder token = getDisplayToken(displayId); - if (token == null) { - return null; + synchronized (mSyncRoot) { + final IBinder token = getDisplayToken(displayId); + if (token == null) { + return null; + } + final LogicalDisplay logicalDisplay = mLogicalDisplays.get(displayId); + if (logicalDisplay == null) { + return null; + } + + final DisplayInfo displayInfo = logicalDisplay.getDisplayInfoLocked(); + return SurfaceControl.screenshotToBufferWithSecureLayersUnsafe(token, new Rect(), + displayInfo.getNaturalWidth(), displayInfo.getNaturalHeight(), + false /* useIdentityTransform */, 0 /* rotation */); } - return SurfaceControl.screenshotToBufferWithSecureLayersUnsafe( - token, new Rect(), 0 /* width */, 0 /* height */, - false /* useIdentityTransform */, 0 /* rotation */); } @VisibleForTesting diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java index ad728c18dd59..8498dcb32eb1 100644 --- a/services/core/java/com/android/server/display/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/DisplayModeDirector.java @@ -237,15 +237,15 @@ public class DisplayModeDirector { lowestConsideredPriority++; } - int defaultModeId = defaultMode.getModeId(); + int baseModeId = defaultMode.getModeId(); if (availableModes.length > 0) { - defaultModeId = availableModes[0]; + baseModeId = availableModes[0]; } // filterModes function is going to filter the modes based on the voting system. If // the application requests a given mode with preferredModeId function, it will be - // stored as defaultModeId. + // stored as baseModeId. return new DesiredDisplayModeSpecs( - defaultModeId, new RefreshRateRange(minRefreshRate, maxRefreshRate)); + baseModeId, new RefreshRateRange(minRefreshRate, maxRefreshRate)); } } @@ -526,7 +526,7 @@ public class DisplayModeDirector { } /** - * Information about the desired display mode to be set by the system. Includes the default + * Information about the desired display mode to be set by the system. Includes the base * mode ID and refresh rate range. * * We have this class in addition to SurfaceControl.DesiredDisplayConfigSpecs to make clear the @@ -535,10 +535,10 @@ public class DisplayModeDirector { */ public static final class DesiredDisplayModeSpecs { /** - * Default mode ID. This is what system defaults to for all other settings, or + * Base mode ID. This is what system defaults to for all other settings, or * if the refresh rate range is not available. */ - public int defaultModeId; + public int baseModeId; /** * The refresh rate range. */ @@ -548,9 +548,8 @@ public class DisplayModeDirector { refreshRateRange = new RefreshRateRange(); } - public DesiredDisplayModeSpecs( - int defaultModeId, @NonNull RefreshRateRange refreshRateRange) { - this.defaultModeId = defaultModeId; + public DesiredDisplayModeSpecs(int baseModeId, @NonNull RefreshRateRange refreshRateRange) { + this.baseModeId = baseModeId; this.refreshRateRange = refreshRateRange; } @@ -559,7 +558,7 @@ public class DisplayModeDirector { */ @Override public String toString() { - return String.format("defaultModeId=%d min=%.0f max=%.0f", defaultModeId, + return String.format("baseModeId=%d min=%.0f max=%.0f", baseModeId, refreshRateRange.min, refreshRateRange.max); } /** @@ -577,7 +576,7 @@ public class DisplayModeDirector { DesiredDisplayModeSpecs desiredDisplayModeSpecs = (DesiredDisplayModeSpecs) other; - if (defaultModeId != desiredDisplayModeSpecs.defaultModeId) { + if (baseModeId != desiredDisplayModeSpecs.baseModeId) { return false; } if (!refreshRateRange.equals(desiredDisplayModeSpecs.refreshRateRange)) { @@ -588,14 +587,14 @@ public class DisplayModeDirector { @Override public int hashCode() { - return Objects.hash(defaultModeId, refreshRateRange); + return Objects.hash(baseModeId, refreshRateRange); } /** * Copy values from the other object. */ public void copyFrom(DesiredDisplayModeSpecs other) { - defaultModeId = other.defaultModeId; + baseModeId = other.baseModeId; refreshRateRange.min = other.refreshRateRange.min; refreshRateRange.max = other.refreshRateRange.max; } diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index cf94d4695497..fb8a4193286b 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -272,14 +272,14 @@ final class LocalDisplayAdapter extends DisplayAdapter { // Check whether surface flinger spontaneously changed display config specs out from // under us. If so, schedule a traversal to reapply our display config specs. - if (mDisplayModeSpecs.defaultModeId != 0) { - int activeDefaultMode = + if (mDisplayModeSpecs.baseModeId != 0) { + int activeBaseMode = findMatchingModeIdLocked(physicalDisplayConfigSpecs.defaultConfig); // If we can't map the defaultConfig index to a mode, then the physical display // configs must have changed, and the code below for handling changes to the // list of available modes will take care of updating display config specs. - if (activeDefaultMode != 0) { - if (mDisplayModeSpecs.defaultModeId != activeDefaultMode + if (activeBaseMode != 0) { + if (mDisplayModeSpecs.baseModeId != activeBaseMode || mDisplayModeSpecs.refreshRateRange.min != physicalDisplayConfigSpecs.minRefreshRate || mDisplayModeSpecs.refreshRateRange.max @@ -312,14 +312,14 @@ final class LocalDisplayAdapter extends DisplayAdapter { mDefaultModeId = activeRecord.mMode.getModeId(); } - // Determine whether the display mode specs' default mode is still there. - if (mSupportedModes.indexOfKey(mDisplayModeSpecs.defaultModeId) < 0) { - if (mDisplayModeSpecs.defaultModeId != 0) { + // Determine whether the display mode specs' base mode is still there. + if (mSupportedModes.indexOfKey(mDisplayModeSpecs.baseModeId) < 0) { + if (mDisplayModeSpecs.baseModeId != 0) { Slog.w(TAG, - "DisplayModeSpecs default mode no longer available, using currently" - + " active mode as default."); + "DisplayModeSpecs base mode no longer available, using currently" + + " active mode."); } - mDisplayModeSpecs.defaultModeId = activeRecord.mMode.getModeId(); + mDisplayModeSpecs.baseModeId = activeRecord.mMode.getModeId(); mDisplayModeSpecsInvalid = true; } @@ -648,13 +648,13 @@ final class LocalDisplayAdapter extends DisplayAdapter { @Override public void setDesiredDisplayModeSpecsLocked( DisplayModeDirector.DesiredDisplayModeSpecs displayModeSpecs) { - if (displayModeSpecs.defaultModeId == 0) { + if (displayModeSpecs.baseModeId == 0) { // Bail if the caller is requesting a null mode. We'll get called again shortly with // a valid mode. return; } - int defaultPhysIndex = findDisplayInfoIndexLocked(displayModeSpecs.defaultModeId); - if (defaultPhysIndex < 0) { + int basePhysIndex = findDisplayInfoIndexLocked(displayModeSpecs.baseModeId); + if (basePhysIndex < 0) { // When a display is hotplugged, it's possible for a mode to be removed that was // previously valid. Because of the way display changes are propagated through the // framework, and the caching of the display mode specs in LogicalDisplay, it's @@ -662,8 +662,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { // mode. This should only happen in extremely rare cases. A followup call will // contain a valid mode id. Slog.w(TAG, - "Ignoring request for invalid default mode id " - + displayModeSpecs.defaultModeId); + "Ignoring request for invalid base mode id " + displayModeSpecs.baseModeId); updateDeviceInfoLocked(); return; } @@ -673,7 +672,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { getHandler().sendMessage(PooledLambda.obtainMessage( LocalDisplayDevice::setDesiredDisplayModeSpecsAsync, this, getDisplayTokenLocked(), - new SurfaceControl.DesiredDisplayConfigSpecs(defaultPhysIndex, + new SurfaceControl.DesiredDisplayConfigSpecs(basePhysIndex, mDisplayModeSpecs.refreshRateRange.min, mDisplayModeSpecs.refreshRateRange.max))); } diff --git a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java index b6255d15795e..c8e5f6c8f53b 100644 --- a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java +++ b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java @@ -317,7 +317,7 @@ final class OverlayDisplayAdapter extends DisplayAdapter { @Override public void setDesiredDisplayModeSpecsLocked( DisplayModeDirector.DesiredDisplayModeSpecs displayModeSpecs) { - final int id = displayModeSpecs.defaultModeId; + final int id = displayModeSpecs.baseModeId; int index = -1; if (id == 0) { // Use the default. diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index ae6d63adc7e7..90358ca1f133 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -83,11 +83,11 @@ import android.view.ViewConfiguration; import android.widget.Toast; import com.android.internal.R; +import com.android.internal.annotations.GuardedBy; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.os.SomeArgs; import com.android.internal.util.DumpUtils; -import com.android.internal.util.Preconditions; import com.android.internal.util.XmlUtils; import com.android.server.DisplayThread; import com.android.server.LocalServices; @@ -186,6 +186,9 @@ public class InputManagerService extends IInputManager.Stub // The associations of input devices to displays by port. Maps from input device port (String) // to display id (int). Currently only accessed by InputReader. private final Map<String, Integer> mStaticAssociations; + private final Object mAssociationsLock = new Object(); + @GuardedBy("mAssociationLock") + private final Map<String, Integer> mRuntimeAssociations = new HashMap<String, Integer>(); private static native long nativeInit(InputManagerService service, Context context, MessageQueue messageQueue); @@ -240,6 +243,7 @@ public class InputManagerService extends IInputManager.Stub private static native void nativeSetCustomPointerIcon(long ptr, PointerIcon icon); private static native void nativeSetPointerCapture(long ptr, boolean detached); private static native boolean nativeCanDispatchToDisplay(long ptr, int deviceId, int displayId); + private static native void nativeNotifyPortAssociationsChanged(long ptr); // Input event injection constants defined in InputDispatcher.h. private static final int INPUT_EVENT_INJECTION_SUCCEEDED = 0; @@ -1723,6 +1727,49 @@ public class InputManagerService extends IInputManager.Stub nativeSetCustomPointerIcon(mPtr, icon); } + /** + * Add a runtime association between the input port and the display port. This overrides any + * static associations. + * @param inputPort The port of the input device. + * @param displayPort The physical port of the associated display. + */ + @Override // Binder call + public void addPortAssociation(@NonNull String inputPort, int displayPort) { + if (!checkCallingPermission( + android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY_BY_PORT, + "addPortAssociation()")) { + throw new SecurityException( + "Requires ASSOCIATE_INPUT_DEVICE_TO_DISPLAY_BY_PORT permission"); + } + + Objects.requireNonNull(inputPort); + synchronized (mAssociationsLock) { + mRuntimeAssociations.put(inputPort, displayPort); + } + nativeNotifyPortAssociationsChanged(mPtr); + } + + /** + * Remove the runtime association between the input port and the display port. Any existing + * static association for the cleared input port will be restored. + * @param inputPort The port of the input device to be cleared. + */ + @Override // Binder call + public void removePortAssociation(@NonNull String inputPort) { + if (!checkCallingPermission( + android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY_BY_PORT, + "clearPortAssociations()")) { + throw new SecurityException( + "Requires ASSOCIATE_INPUT_DEVICE_TO_DISPLAY_BY_PORT permission"); + } + + Objects.requireNonNull(inputPort); + synchronized (mAssociationsLock) { + mRuntimeAssociations.remove(inputPort); + } + nativeNotifyPortAssociationsChanged(mPtr); + } + @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; @@ -1743,6 +1790,16 @@ public class InputManagerService extends IInputManager.Stub pw.println(" display: " + v); }); } + + synchronized (mAssociationsLock) { + if (!mRuntimeAssociations.isEmpty()) { + pw.println("Runtime Associations:"); + mRuntimeAssociations.forEach((k, v) -> { + pw.print(" port: " + k); + pw.println(" display: " + v); + }); + } + } } private boolean checkCallingPermission(String permission, String func) { @@ -1766,6 +1823,7 @@ public class InputManagerService extends IInputManager.Stub @Override public void monitor() { synchronized (mInputFilterLock) { } + synchronized (mAssociationsLock) { /* Test if blocked by associations lock. */} nativeMonitor(mPtr); } @@ -1930,7 +1988,7 @@ public class InputManagerService extends IInputManager.Stub * @return Flattened list */ private static List<String> flatten(@NonNull Map<String, Integer> map) { - List<String> list = new ArrayList<>(map.size() * 2); + final List<String> list = new ArrayList<>(map.size() * 2); map.forEach((k, v)-> { list.add(k); list.add(v.toString()); @@ -1943,11 +2001,11 @@ public class InputManagerService extends IInputManager.Stub * directory. */ private static Map<String, Integer> loadStaticInputPortAssociations() { - File baseDir = Environment.getVendorDirectory(); - File confFile = new File(baseDir, PORT_ASSOCIATIONS_PATH); + final File baseDir = Environment.getVendorDirectory(); + final File confFile = new File(baseDir, PORT_ASSOCIATIONS_PATH); try { - InputStream stream = new FileInputStream(confFile); + final InputStream stream = new FileInputStream(confFile); return ConfigurationProcessor.processInputPortAssociations(stream); } catch (FileNotFoundException e) { // Most of the time, file will not exist, which is expected. @@ -1960,7 +2018,14 @@ public class InputManagerService extends IInputManager.Stub // Native callback private String[] getInputPortAssociations() { - List<String> associationList = flatten(mStaticAssociations); + final Map<String, Integer> associations = new HashMap<>(mStaticAssociations); + + // merge the runtime associations. + synchronized (mAssociationsLock) { + associations.putAll(mRuntimeAssociations); + } + + final List<String> associationList = flatten(associations); return associationList.toArray(new String[0]); } diff --git a/services/core/java/com/android/server/integrity/IntegrityFileManager.java b/services/core/java/com/android/server/integrity/IntegrityFileManager.java index 46daf12b101d..17a4b9c6c170 100644 --- a/services/core/java/com/android/server/integrity/IntegrityFileManager.java +++ b/services/core/java/com/android/server/integrity/IntegrityFileManager.java @@ -25,6 +25,8 @@ import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.server.integrity.model.RuleMetadata; import com.android.server.integrity.parser.RuleBinaryParser; +import com.android.server.integrity.parser.RuleIndexRange; +import com.android.server.integrity.parser.RuleIndexingController; import com.android.server.integrity.parser.RuleMetadataParser; import com.android.server.integrity.parser.RuleParseException; import com.android.server.integrity.parser.RuleParser; @@ -37,6 +39,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.util.Collections; import java.util.List; import java.util.Optional; @@ -61,7 +64,10 @@ public class IntegrityFileManager { // update rules atomically. private final File mStagingDir; - @Nullable private RuleMetadata mRuleMetadataCache; + @Nullable + private RuleMetadata mRuleMetadataCache; + @Nullable + private RuleIndexingController mRuleIndexingController; /** Get the singleton instance of this class. */ public static synchronized IntegrityFileManager getInstance() { @@ -100,6 +106,8 @@ public class IntegrityFileManager { Slog.e(TAG, "Error reading metadata file.", e); } } + + updateRuleIndexingController(); } /** @@ -109,7 +117,8 @@ public class IntegrityFileManager { */ public boolean initialized() { return new File(mRulesDir, RULES_FILE).exists() - && new File(mRulesDir, METADATA_FILE).exists(); + && new File(mRulesDir, METADATA_FILE).exists() + && new File(mRulesDir, INDEXING_FILE).exists(); } /** Write rules to persistent storage. */ @@ -131,6 +140,9 @@ public class IntegrityFileManager { } switchStagingRulesDir(); + + // Update object holding the indexing information. + updateRuleIndexingController(); } /** @@ -140,11 +152,21 @@ public class IntegrityFileManager { */ public List<Rule> readRules(AppInstallMetadata appInstallMetadata) throws IOException, RuleParseException { - // TODO: select rules by index synchronized (RULES_LOCK) { + // Try to identify indexes from the index file. + List<RuleIndexRange> ruleReadingIndexes; + try { + ruleReadingIndexes = + mRuleIndexingController.identifyRulesToEvaluate(appInstallMetadata); + } catch (Exception e) { + Slog.w(TAG, "Error identifying the rule indexes. Trying unindexed.", e); + ruleReadingIndexes = Collections.emptyList(); + } + + // Read the rules based on the index information when available. try (FileInputStream inputStream = new FileInputStream(new File(mRulesDir, RULES_FILE))) { - List<Rule> rules = mRuleParser.parse(inputStream); + List<Rule> rules = mRuleParser.parse(inputStream, ruleReadingIndexes); return rules; } } @@ -168,6 +190,17 @@ public class IntegrityFileManager { } } + private void updateRuleIndexingController() { + File ruleIndexingFile = new File(mRulesDir, INDEXING_FILE); + if (ruleIndexingFile.exists()) { + try (FileInputStream inputStream = new FileInputStream(ruleIndexingFile)) { + mRuleIndexingController = new RuleIndexingController(inputStream); + } catch (Exception e) { + Slog.e(TAG, "Error parsing the rule indexing file.", e); + } + } + } + private void writeMetadata(File directory, String ruleProvider, String version) throws IOException { mRuleMetadataCache = new RuleMetadata(ruleProvider, version); diff --git a/services/core/java/com/android/server/integrity/model/BitTrackedInputStream.java b/services/core/java/com/android/server/integrity/model/BitTrackedInputStream.java new file mode 100644 index 000000000000..e555e3e5746e --- /dev/null +++ b/services/core/java/com/android/server/integrity/model/BitTrackedInputStream.java @@ -0,0 +1,69 @@ +/* + * 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.integrity.model; + +import java.io.IOException; +import java.io.InputStream; + +/** + * An input stream that tracks the total number read bytes since construction and allows moving + * fast forward to a certain byte any time during the execution. + * + * This class is used for efficient reading of rules based on the rule indexing. + */ +public class BitTrackedInputStream extends BitInputStream { + + private static int sReadBitsCount; + + /** Constructor with byte array. */ + public BitTrackedInputStream(byte[] inputStream) { + super(inputStream); + sReadBitsCount = 0; + } + + /** Constructor with input stream. */ + public BitTrackedInputStream(InputStream inputStream) { + super(inputStream); + sReadBitsCount = 0; + } + + /** Obtains an integer value of the next {@code numOfBits}. */ + @Override + public int getNext(int numOfBits) throws IOException { + sReadBitsCount += numOfBits; + return super.getNext(numOfBits); + } + + /** Returns the current cursor position showing the number of bits that are read. */ + public int getReadBitsCount() { + return sReadBitsCount; + } + + /** + * Sets the cursor to the specified byte location. + * + * Note that the integer parameter specifies the location in bytes -- not bits. + */ + public void setCursorToByteLocation(int byteLocation) throws IOException { + int bitCountToRead = byteLocation * 8 - sReadBitsCount; + if (bitCountToRead < 0) { + throw new IllegalStateException("The byte position is already read."); + } + super.getNext(bitCountToRead); + sReadBitsCount = byteLocation * 8; + } +} diff --git a/services/core/java/com/android/server/integrity/serializer/ByteTrackedOutputStream.java b/services/core/java/com/android/server/integrity/model/ByteTrackedOutputStream.java index 62815a9806ee..f575599e1c49 100644 --- a/services/core/java/com/android/server/integrity/serializer/ByteTrackedOutputStream.java +++ b/services/core/java/com/android/server/integrity/model/ByteTrackedOutputStream.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.integrity.serializer; +package com.android.server.integrity.model; import java.io.IOException; import java.io.OutputStream; diff --git a/services/core/java/com/android/server/integrity/model/IndexingFileConstants.java b/services/core/java/com/android/server/integrity/model/IndexingFileConstants.java new file mode 100644 index 000000000000..52df898706d6 --- /dev/null +++ b/services/core/java/com/android/server/integrity/model/IndexingFileConstants.java @@ -0,0 +1,27 @@ +/* + * 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.integrity.model; + +/** A helper class containing special indexing file constants. */ +public final class IndexingFileConstants { + // The parsing time seems acceptable for this block size based on the tests in + // go/ic-rule-file-format. + public static final int INDEXING_BLOCK_SIZE = 100; + + public static final String START_INDEXING_KEY = "START_KEY"; + public static final String END_INDEXING_KEY = "END_KEY"; +} diff --git a/services/core/java/com/android/server/integrity/parser/BinaryFileOperations.java b/services/core/java/com/android/server/integrity/parser/BinaryFileOperations.java new file mode 100644 index 000000000000..2c5b7d3c122c --- /dev/null +++ b/services/core/java/com/android/server/integrity/parser/BinaryFileOperations.java @@ -0,0 +1,77 @@ +/* + * 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.integrity.parser; + +import static com.android.server.integrity.model.ComponentBitSize.IS_HASHED_BITS; +import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS; + +import com.android.server.integrity.IntegrityUtils; +import com.android.server.integrity.model.BitInputStream; + +import java.io.IOException; +import java.nio.ByteBuffer; + +/** + * Helper methods for reading standard data structures from {@link BitInputStream}. + */ +public class BinaryFileOperations { + + /** + * Read an string value with the given size and hash status from a {@code BitInputStream}. + * + * If the value is hashed, get the hex-encoding of the value. Serialized values are in raw form. + * All hashed values are hex-encoded. + */ + public static String getStringValue(BitInputStream bitInputStream) throws IOException { + boolean isHashedValue = bitInputStream.getNext(IS_HASHED_BITS) == 1; + int valueSize = bitInputStream.getNext(VALUE_SIZE_BITS); + return getStringValue(bitInputStream, valueSize, isHashedValue); + } + + /** + * Read an string value with the given size and hash status from a {@code BitInputStream}. + * + * If the value is hashed, get the hex-encoding of the value. Serialized values are in raw form. + * All hashed values are hex-encoded. + */ + public static String getStringValue( + BitInputStream bitInputStream, int valueSize, boolean isHashedValue) + throws IOException { + if (!isHashedValue) { + StringBuilder value = new StringBuilder(); + while (valueSize-- > 0) { + value.append((char) bitInputStream.getNext(/* numOfBits= */ 8)); + } + return value.toString(); + } + ByteBuffer byteBuffer = ByteBuffer.allocate(valueSize); + while (valueSize-- > 0) { + byteBuffer.put((byte) (bitInputStream.getNext(/* numOfBits= */ 8) & 0xFF)); + } + return IntegrityUtils.getHexDigest(byteBuffer.array()); + } + + /** Read an integer value from a {@code BitInputStream}. */ + public static int getIntValue(BitInputStream bitInputStream) throws IOException { + return bitInputStream.getNext(/* numOfBits= */ 32); + } + + /** Read an boolean value from a {@code BitInputStream}. */ + public static boolean getBooleanValue(BitInputStream bitInputStream) throws IOException { + return bitInputStream.getNext(/* numOfBits= */ 1) == 1; + } +} diff --git a/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java b/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java index 8f84abc88752..e744326c49db 100644 --- a/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java +++ b/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java @@ -28,79 +28,112 @@ import static com.android.server.integrity.model.ComponentBitSize.OPERATOR_BITS; import static com.android.server.integrity.model.ComponentBitSize.SEPARATOR_BITS; import static com.android.server.integrity.model.ComponentBitSize.SIGNAL_BIT; import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS; +import static com.android.server.integrity.parser.BinaryFileOperations.getBooleanValue; +import static com.android.server.integrity.parser.BinaryFileOperations.getIntValue; +import static com.android.server.integrity.parser.BinaryFileOperations.getStringValue; import android.content.integrity.AtomicFormula; import android.content.integrity.CompoundFormula; import android.content.integrity.Formula; import android.content.integrity.Rule; -import com.android.server.integrity.IntegrityUtils; -import com.android.server.integrity.model.BitInputStream; +import com.android.server.integrity.model.BitTrackedInputStream; import java.io.IOException; import java.io.InputStream; -import java.nio.ByteBuffer; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** A helper class to parse rules into the {@link Rule} model from Binary representation. */ public class RuleBinaryParser implements RuleParser { - private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); - @Override public List<Rule> parse(byte[] ruleBytes) throws RuleParseException { try { - BitInputStream bitInputStream = new BitInputStream(ruleBytes); - return parseRules(bitInputStream); + BitTrackedInputStream bitTrackedInputStream = new BitTrackedInputStream(ruleBytes); + return parseRules(bitTrackedInputStream, /* indexRanges= */ Collections.emptyList()); } catch (Exception e) { throw new RuleParseException(e.getMessage(), e); } } @Override - public List<Rule> parse(InputStream inputStream) throws RuleParseException { + public List<Rule> parse(InputStream inputStream, List<RuleIndexRange> indexRanges) + throws RuleParseException { try { - BitInputStream bitInputStream = new BitInputStream(inputStream); - return parseRules(bitInputStream); + BitTrackedInputStream bitTrackedInputStream = new BitTrackedInputStream(inputStream); + return parseRules(bitTrackedInputStream, indexRanges); } catch (Exception e) { throw new RuleParseException(e.getMessage(), e); } } - private List<Rule> parseRules(BitInputStream bitInputStream) throws IOException { - List<Rule> parsedRules = new ArrayList<>(); + private List<Rule> parseRules( + BitTrackedInputStream bitTrackedInputStream, + List<RuleIndexRange> indexRanges) + throws IOException { // Read the rule binary file format version. - bitInputStream.getNext(FORMAT_VERSION_BITS); + bitTrackedInputStream.getNext(FORMAT_VERSION_BITS); + + return indexRanges.isEmpty() + ? parseAllRules(bitTrackedInputStream) + : parseIndexedRules(bitTrackedInputStream, indexRanges); + } + + private List<Rule> parseAllRules(BitTrackedInputStream bitTrackedInputStream) + throws IOException { + List<Rule> parsedRules = new ArrayList<>(); + + while (bitTrackedInputStream.hasNext()) { + if (bitTrackedInputStream.getNext(SIGNAL_BIT) == 1) { + parsedRules.add(parseRule(bitTrackedInputStream)); + } + } + + return parsedRules; + } + + private List<Rule> parseIndexedRules( + BitTrackedInputStream bitTrackedInputStream, List<RuleIndexRange> indexRanges) + throws IOException { + List<Rule> parsedRules = new ArrayList<>(); - while (bitInputStream.hasNext()) { - if (bitInputStream.getNext(SIGNAL_BIT) == 1) { - parsedRules.add(parseRule(bitInputStream)); + for (RuleIndexRange range : indexRanges) { + // Skip the rules that are not in the range. + bitTrackedInputStream.setCursorToByteLocation(range.getStartIndex()); + + // Read the rules until we reach the end index. + while (bitTrackedInputStream.hasNext() + && bitTrackedInputStream.getReadBitsCount() < range.getEndIndex()) { + if (bitTrackedInputStream.getNext(SIGNAL_BIT) == 1) { + parsedRules.add(parseRule(bitTrackedInputStream)); + } } } return parsedRules; } - private Rule parseRule(BitInputStream bitInputStream) throws IOException { - Formula formula = parseFormula(bitInputStream); - int effect = bitInputStream.getNext(EFFECT_BITS); + private Rule parseRule(BitTrackedInputStream bitTrackedInputStream) throws IOException { + Formula formula = parseFormula(bitTrackedInputStream); + int effect = bitTrackedInputStream.getNext(EFFECT_BITS); - if (bitInputStream.getNext(SIGNAL_BIT) != 1) { + if (bitTrackedInputStream.getNext(SIGNAL_BIT) != 1) { throw new IllegalArgumentException("A rule must end with a '1' bit."); } return new Rule(formula, effect); } - private Formula parseFormula(BitInputStream bitInputStream) throws IOException { - int separator = bitInputStream.getNext(SEPARATOR_BITS); + private Formula parseFormula(BitTrackedInputStream bitTrackedInputStream) throws IOException { + int separator = bitTrackedInputStream.getNext(SEPARATOR_BITS); switch (separator) { case ATOMIC_FORMULA_START: - return parseAtomicFormula(bitInputStream); + return parseAtomicFormula(bitTrackedInputStream); case COMPOUND_FORMULA_START: - return parseCompoundFormula(bitInputStream); + return parseCompoundFormula(bitTrackedInputStream); case COMPOUND_FORMULA_END: return null; default: @@ -109,69 +142,43 @@ public class RuleBinaryParser implements RuleParser { } } - private CompoundFormula parseCompoundFormula(BitInputStream bitInputStream) throws IOException { - int connector = bitInputStream.getNext(CONNECTOR_BITS); + private CompoundFormula parseCompoundFormula(BitTrackedInputStream bitTrackedInputStream) + throws IOException { + int connector = bitTrackedInputStream.getNext(CONNECTOR_BITS); List<Formula> formulas = new ArrayList<>(); - Formula parsedFormula = parseFormula(bitInputStream); + Formula parsedFormula = parseFormula(bitTrackedInputStream); while (parsedFormula != null) { formulas.add(parsedFormula); - parsedFormula = parseFormula(bitInputStream); + parsedFormula = parseFormula(bitTrackedInputStream); } return new CompoundFormula(connector, formulas); } - private AtomicFormula parseAtomicFormula(BitInputStream bitInputStream) throws IOException { - int key = bitInputStream.getNext(KEY_BITS); - int operator = bitInputStream.getNext(OPERATOR_BITS); + private AtomicFormula parseAtomicFormula(BitTrackedInputStream bitTrackedInputStream) + throws IOException { + int key = bitTrackedInputStream.getNext(KEY_BITS); + int operator = bitTrackedInputStream.getNext(OPERATOR_BITS); switch (key) { case AtomicFormula.PACKAGE_NAME: case AtomicFormula.APP_CERTIFICATE: case AtomicFormula.INSTALLER_NAME: case AtomicFormula.INSTALLER_CERTIFICATE: - boolean isHashedValue = bitInputStream.getNext(IS_HASHED_BITS) == 1; - int valueSize = bitInputStream.getNext(VALUE_SIZE_BITS); - String stringValue = getStringValue(bitInputStream, valueSize, isHashedValue); + boolean isHashedValue = bitTrackedInputStream.getNext(IS_HASHED_BITS) == 1; + int valueSize = bitTrackedInputStream.getNext(VALUE_SIZE_BITS); + String stringValue = getStringValue(bitTrackedInputStream, valueSize, + isHashedValue); return new AtomicFormula.StringAtomicFormula(key, stringValue, isHashedValue); case AtomicFormula.VERSION_CODE: - int intValue = getIntValue(bitInputStream); + int intValue = getIntValue(bitTrackedInputStream); return new AtomicFormula.IntAtomicFormula(key, operator, intValue); case AtomicFormula.PRE_INSTALLED: - boolean booleanValue = getBooleanValue(bitInputStream); + boolean booleanValue = getBooleanValue(bitTrackedInputStream); return new AtomicFormula.BooleanAtomicFormula(key, booleanValue); default: throw new IllegalArgumentException(String.format("Unknown key: %d", key)); } } - - // Get value string from stream. - // If the value is not hashed, get its raw form directly. - // If the value is hashed, get the hex-encoding of the value. Serialized values are in raw form. - // All hashed values are hex-encoded. - private static String getStringValue( - BitInputStream bitInputStream, int valueSize, boolean isHashedValue) - throws IOException { - if (!isHashedValue) { - StringBuilder value = new StringBuilder(); - while (valueSize-- > 0) { - value.append((char) bitInputStream.getNext(/* numOfBits= */ 8)); - } - return value.toString(); - } - ByteBuffer byteBuffer = ByteBuffer.allocate(valueSize); - while (valueSize-- > 0) { - byteBuffer.put((byte) (bitInputStream.getNext(/* numOfBits= */ 8) & 0xFF)); - } - return IntegrityUtils.getHexDigest(byteBuffer.array()); - } - - private static int getIntValue(BitInputStream bitInputStream) throws IOException { - return bitInputStream.getNext(/* numOfBits= */ 32); - } - - private static boolean getBooleanValue(BitInputStream bitInputStream) throws IOException { - return bitInputStream.getNext(/* numOfBits= */ 1) == 1; - } } diff --git a/services/core/java/com/android/server/integrity/parser/RuleIndexRange.java b/services/core/java/com/android/server/integrity/parser/RuleIndexRange.java new file mode 100644 index 000000000000..8c8450e5fdc4 --- /dev/null +++ b/services/core/java/com/android/server/integrity/parser/RuleIndexRange.java @@ -0,0 +1,50 @@ +/* + * 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.integrity.parser; + +import android.annotation.Nullable; + +/** + * A wrapper class to represent an indexing range that is identified by the {@link + * RuleIndexingController}. + */ +public class RuleIndexRange { + private static int sStartIndex; + private static int sEndIndex; + + /** Constructor with start and end indexes. */ + public RuleIndexRange(int startIndex, int endIndex) { + this.sStartIndex = startIndex; + this.sEndIndex = endIndex; + } + + /** Returns the startIndex. */ + public int getStartIndex() { + return sStartIndex; + } + + /** Returns the end index. */ + public int getEndIndex() { + return sEndIndex; + } + + @Override + public boolean equals(@Nullable Object object) { + return sStartIndex == ((RuleIndexRange) object).getStartIndex() + && sEndIndex == ((RuleIndexRange) object).getEndIndex(); + } +} diff --git a/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java b/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java new file mode 100644 index 000000000000..c9713220d6e8 --- /dev/null +++ b/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java @@ -0,0 +1,111 @@ +/* + * 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.integrity.parser; + +import static com.android.server.integrity.model.IndexingFileConstants.END_INDEXING_KEY; +import static com.android.server.integrity.model.IndexingFileConstants.START_INDEXING_KEY; +import static com.android.server.integrity.parser.BinaryFileOperations.getIntValue; +import static com.android.server.integrity.parser.BinaryFileOperations.getStringValue; + +import android.content.integrity.AppInstallMetadata; + +import com.android.server.integrity.model.BitInputStream; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.TreeSet; +import java.util.stream.Collectors; + +/** Helper class to identify the necessary indexes that needs to be read. */ +public class RuleIndexingController { + + private static LinkedHashMap<String, Integer> sPackageNameBasedIndexes; + private static LinkedHashMap<String, Integer> sAppCertificateBasedIndexes; + private static LinkedHashMap<String, Integer> sUnindexedRuleIndexes; + + /** + * Provide the indexing file to read and the object will be constructed by reading and + * identifying the indexes. + */ + public RuleIndexingController(InputStream inputStream) throws IOException { + BitInputStream bitInputStream = new BitInputStream(inputStream); + sPackageNameBasedIndexes = getNextIndexGroup(bitInputStream); + sAppCertificateBasedIndexes = getNextIndexGroup(bitInputStream); + sUnindexedRuleIndexes = getNextIndexGroup(bitInputStream); + } + + /** + * Returns a list of integers with the starting and ending bytes of the rules that needs to be + * read and evaluated. + */ + public List<RuleIndexRange> identifyRulesToEvaluate(AppInstallMetadata appInstallMetadata) { + ArrayList<RuleIndexRange> indexRanges = new ArrayList(); + + // Add the range for package name indexes rules. + indexRanges.add( + searchIndexingKeysRangeContainingKey( + sPackageNameBasedIndexes, appInstallMetadata.getPackageName())); + + // Add the range for app certificate indexes rules. + indexRanges.add( + searchIndexingKeysRangeContainingKey( + sAppCertificateBasedIndexes, appInstallMetadata.getAppCertificate())); + + // Add the range for unindexed rules. + indexRanges.add( + new RuleIndexRange( + sUnindexedRuleIndexes.get(START_INDEXING_KEY), + sUnindexedRuleIndexes.get(END_INDEXING_KEY))); + + return indexRanges; + } + + private LinkedHashMap<String, Integer> getNextIndexGroup(BitInputStream bitInputStream) + throws IOException { + LinkedHashMap<String, Integer> keyToIndexMap = new LinkedHashMap<>(); + while (bitInputStream.hasNext()) { + String key = getStringValue(bitInputStream); + int value = getIntValue(bitInputStream); + + keyToIndexMap.put(key, value); + + if (key.matches(END_INDEXING_KEY)) { + break; + } + } + return keyToIndexMap; + } + + private RuleIndexRange searchIndexingKeysRangeContainingKey( + LinkedHashMap<String, Integer> indexMap, String searchedKey) { + TreeSet<String> keyTreeSet = + indexMap.keySet().stream() + .filter(key -> !key.matches(START_INDEXING_KEY) && !key.matches( + END_INDEXING_KEY)) + .collect(Collectors.toCollection(TreeSet::new)); + + String minIndex = keyTreeSet.floor(searchedKey); + String maxIndex = keyTreeSet.ceiling(searchedKey); + + return new RuleIndexRange( + indexMap.get(minIndex == null ? START_INDEXING_KEY : minIndex), + indexMap.get(maxIndex == null ? END_INDEXING_KEY : maxIndex)); + } +} diff --git a/services/core/java/com/android/server/integrity/parser/RuleParser.java b/services/core/java/com/android/server/integrity/parser/RuleParser.java index 81783d5c7324..a8e9f6134759 100644 --- a/services/core/java/com/android/server/integrity/parser/RuleParser.java +++ b/services/core/java/com/android/server/integrity/parser/RuleParser.java @@ -28,5 +28,6 @@ public interface RuleParser { List<Rule> parse(byte[] ruleBytes) throws RuleParseException; /** Parse rules from an input stream. */ - List<Rule> parse(InputStream inputStream) throws RuleParseException; + List<Rule> parse(InputStream inputStream, List<RuleIndexRange> ruleIndexRanges) + throws RuleParseException; } diff --git a/services/core/java/com/android/server/integrity/parser/RuleXmlParser.java b/services/core/java/com/android/server/integrity/parser/RuleXmlParser.java index d405583442bd..497be8424286 100644 --- a/services/core/java/com/android/server/integrity/parser/RuleXmlParser.java +++ b/services/core/java/com/android/server/integrity/parser/RuleXmlParser.java @@ -62,7 +62,8 @@ public final class RuleXmlParser implements RuleParser { } @Override - public List<Rule> parse(InputStream inputStream) throws RuleParseException { + public List<Rule> parse(InputStream inputStream, List<RuleIndexRange> indexRanges) + throws RuleParseException { try { XmlPullParser xmlPullParser = Xml.newPullParser(); xmlPullParser.setInput(inputStream, StandardCharsets.UTF_8.name()); diff --git a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java index 35e673f4405f..f964d4cf2724 100644 --- a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java +++ b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java @@ -27,6 +27,9 @@ import static com.android.server.integrity.model.ComponentBitSize.KEY_BITS; import static com.android.server.integrity.model.ComponentBitSize.OPERATOR_BITS; import static com.android.server.integrity.model.ComponentBitSize.SEPARATOR_BITS; import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS; +import static com.android.server.integrity.model.IndexingFileConstants.END_INDEXING_KEY; +import static com.android.server.integrity.model.IndexingFileConstants.INDEXING_BLOCK_SIZE; +import static com.android.server.integrity.model.IndexingFileConstants.START_INDEXING_KEY; import static com.android.server.integrity.serializer.RuleIndexingDetails.APP_CERTIFICATE_INDEXED; import static com.android.server.integrity.serializer.RuleIndexingDetails.NOT_INDEXED; import static com.android.server.integrity.serializer.RuleIndexingDetails.PACKAGE_NAME_INDEXED; @@ -39,6 +42,7 @@ import android.content.integrity.Rule; import com.android.internal.util.Preconditions; import com.android.server.integrity.IntegrityUtils; import com.android.server.integrity.model.BitOutputStream; +import com.android.server.integrity.model.ByteTrackedOutputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -52,13 +56,6 @@ import java.util.TreeMap; /** A helper class to serialize rules from the {@link Rule} model to Binary representation. */ public class RuleBinarySerializer implements RuleSerializer { - // The parsing time seems acceptable for this block size based on the tests in - // go/ic-rule-file-format. - public static final int INDEXING_BLOCK_SIZE = 100; - - public static final String START_INDEXING_KEY = "START_KEY"; - public static final String END_INDEXING_KEY = "END_KEY"; - // Get the byte representation for a list of rules. @Override public byte[] serialize(List<Rule> rules, Optional<Integer> formatVersion) @@ -113,7 +110,8 @@ public class RuleBinarySerializer implements RuleSerializer { } private void serializeRuleFileMetadata(Optional<Integer> formatVersion, - ByteTrackedOutputStream outputStream) throws IOException { + ByteTrackedOutputStream outputStream) + throws IOException { int formatVersionValue = formatVersion.orElse(DEFAULT_FORMAT_VERSION); BitOutputStream bitOutputStream = new BitOutputStream(); diff --git a/services/core/java/com/android/server/location/AbstractLocationProvider.java b/services/core/java/com/android/server/location/AbstractLocationProvider.java index ccfc98e2291b..ed6a759409d4 100644 --- a/services/core/java/com/android/server/location/AbstractLocationProvider.java +++ b/services/core/java/com/android/server/location/AbstractLocationProvider.java @@ -16,11 +16,11 @@ package com.android.server.location; +import android.annotation.Nullable; import android.content.Context; import android.location.Location; import android.os.Binder; import android.os.Bundle; -import android.os.WorkSource; import com.android.internal.location.ProviderProperties; import com.android.internal.location.ProviderRequest; @@ -29,127 +29,336 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Collections; import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.UnaryOperator; /** - * Location Manager's interface for location providers. Always starts as disabled. + * Base class for all location providers. * * @hide */ public abstract class AbstractLocationProvider { /** - * Interface for communicating from a location provider back to the location service. + * Interface for listening to location providers. */ - public interface LocationProviderManager { + public interface Listener { /** - * May be called to inform the location service of a change in this location provider's - * enabled/disabled state. + * Called when a provider's state changes. May be invoked from any thread. Will be + * invoked with a cleared binder identity. */ - void onSetEnabled(boolean enabled); + void onStateChanged(State oldState, State newState); /** - * May be called to inform the location service of a change in this location provider's - * properties. + * Called when a provider has a new location available. May be invoked from any thread. Will + * be invoked with a cleared binder identity. */ - void onSetProperties(ProviderProperties properties); + void onReportLocation(Location location); /** - * May be called to inform the location service that this provider has a new location - * available. + * Called when a provider has a new location available. May be invoked from any thread. Will + * be invoked with a cleared binder identity. */ - void onReportLocation(Location location); + void onReportLocation(List<Location> locations); + } + + /** + * Holds a representation of the public state of a provider. + */ + public static final class State { /** - * May be called to inform the location service that this provider has a new location - * available. + * Default state value for a location provider that is disabled with no properties and an + * empty provider package list. */ - void onReportLocation(List<Location> locations); + public static final State EMPTY_STATE = new State(false, null, + Collections.emptySet()); + + /** + * The provider's enabled state. + */ + public final boolean enabled; + + /** + * The provider's properties. + */ + @Nullable public final ProviderProperties properties; + + /** + * The provider's package name list - provider packages may be afforded special privileges. + */ + public final Set<String> providerPackageNames; + + private State(boolean enabled, ProviderProperties properties, + Set<String> providerPackageNames) { + this.enabled = enabled; + this.properties = properties; + this.providerPackageNames = Objects.requireNonNull(providerPackageNames); + } + + private State withEnabled(boolean enabled) { + if (enabled == this.enabled) { + return this; + } else { + return new State(enabled, properties, providerPackageNames); + } + } + + private State withProperties(ProviderProperties properties) { + if (properties.equals(this.properties)) { + return this; + } else { + return new State(enabled, properties, providerPackageNames); + } + } + + private State withProviderPackageNames(Set<String> providerPackageNames) { + if (providerPackageNames.equals(this.providerPackageNames)) { + return this; + } else { + return new State(enabled, properties, providerPackageNames); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof State)) { + return false; + } + State state = (State) o; + return enabled == state.enabled && properties == state.properties + && providerPackageNames.equals(state.providerPackageNames); + } + + @Override + public int hashCode() { + return Objects.hash(enabled, properties, providerPackageNames); + } + } + + // combines listener and state information so that they can be updated atomically with respect + // to each other and an ordering established. + private static class InternalState { + @Nullable public final Listener listener; + public final State state; + + private InternalState(@Nullable Listener listener, State state) { + this.listener = listener; + this.state = state; + } + + private InternalState withListener(Listener listener) { + if (listener == this.listener) { + return this; + } else { + return new InternalState(listener, state); + } + } + + private InternalState withState(State state) { + if (state.equals(this.state)) { + return this; + } else { + return new InternalState(listener, state); + } + } + + private InternalState withState(UnaryOperator<State> operator) { + return withState(operator.apply(state)); + } } protected final Context mContext; - private final LocationProviderManager mLocationProviderManager; + protected final Executor mExecutor; + + // we use a lock-free implementation to update state to ensure atomicity between updating the + // provider state and setting the listener, so that the state updates a listener sees are + // consistent with when the listener was set (a listener should not see any updates that occur + // before it was set, and should not miss any updates that occur after it was set). + private final AtomicReference<InternalState> mInternalState; - protected AbstractLocationProvider( - Context context, LocationProviderManager locationProviderManager) { + protected AbstractLocationProvider(Context context, Executor executor) { + this(context, executor, Collections.singleton(context.getPackageName())); + } + + protected AbstractLocationProvider(Context context, Executor executor, + Set<String> packageNames) { mContext = context; - mLocationProviderManager = locationProviderManager; + mExecutor = executor; + mInternalState = new AtomicReference<>( + new InternalState(null, State.EMPTY_STATE.withProviderPackageNames(packageNames))); } /** - * Call this method to report a change in provider enabled/disabled status. May be called from - * any thread. + * Sets the listener and returns the state at the moment the listener was set. The listener can + * expect to receive all state updates from after this point. */ - protected void setEnabled(boolean enabled) { - long identity = Binder.clearCallingIdentity(); - try { - mLocationProviderManager.onSetEnabled(enabled); - } finally { - Binder.restoreCallingIdentity(identity); + State setListener(@Nullable Listener listener) { + return mInternalState.updateAndGet( + internalState -> internalState.withListener(listener)).state; + } + + /** + * Retrieves the state of the provider. + */ + State getState() { + return mInternalState.get().state; + } + + /** + * Sets the state of the provider to the new state. + */ + void setState(State newState) { + InternalState oldInternalState = mInternalState.getAndUpdate( + internalState -> internalState.withState(newState)); + if (newState.equals(oldInternalState.state)) { + return; + } + + // we know that we only updated the state, so the listener for the old state is the same as + // the listener for the new state. + if (oldInternalState.listener != null) { + long identity = Binder.clearCallingIdentity(); + try { + oldInternalState.listener.onStateChanged(oldInternalState.state, newState); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } + + private void setState(UnaryOperator<State> operator) { + InternalState oldInternalState = mInternalState.getAndUpdate( + internalState -> internalState.withState(operator)); + + // recreate the new state from our knowledge of the old state - unfortunately may result in + // an extra allocation, but oh well... + State newState = operator.apply(oldInternalState.state); + + if (newState.equals(oldInternalState.state)) { + return; + } + + // we know that we only updated the state, so the listener for the old state is the same as + // the listener for the new state. + if (oldInternalState.listener != null) { + long identity = Binder.clearCallingIdentity(); + try { + oldInternalState.listener.onStateChanged(oldInternalState.state, newState); + } finally { + Binder.restoreCallingIdentity(identity); + } } } /** - * Call this method to report a change in provider properties. May be called from - * any thread. + * The current enabled state of this provider. + */ + protected boolean isEnabled() { + return mInternalState.get().state.enabled; + } + + /** + * The current provider properties of this provider. + */ + @Nullable + protected ProviderProperties getProperties() { + return mInternalState.get().state.properties; + } + + /** + * The current package set of this provider. + */ + protected Set<String> getProviderPackages() { + return mInternalState.get().state.providerPackageNames; + } + + /** + * Call this method to report a change in provider enabled/disabled status. + */ + protected void setEnabled(boolean enabled) { + setState(state -> state.withEnabled(enabled)); + } + + /** + * Call this method to report a change in provider properties. */ protected void setProperties(ProviderProperties properties) { - long identity = Binder.clearCallingIdentity(); - try { - mLocationProviderManager.onSetProperties(properties); - } finally { - Binder.restoreCallingIdentity(identity); - } + setState(state -> state.withProperties(properties)); + } + + /** + * Call this method to report a change in provider packages. + */ + protected void setPackageNames(Set<String> packageNames) { + setState(state -> state.withProviderPackageNames(packageNames)); } /** - * Call this method to report a new location. May be called from any thread. + * Call this method to report a new location. */ protected void reportLocation(Location location) { - long identity = Binder.clearCallingIdentity(); - try { - mLocationProviderManager.onReportLocation(location); - } finally { - Binder.restoreCallingIdentity(identity); + Listener listener = mInternalState.get().listener; + if (listener != null) { + long identity = Binder.clearCallingIdentity(); + try { + listener.onReportLocation(location); + } finally { + Binder.restoreCallingIdentity(identity); + } } } /** - * Call this method to report a new location. May be called from any thread. + * Call this method to report a new location. */ protected void reportLocation(List<Location> locations) { - long identity = Binder.clearCallingIdentity(); - try { - mLocationProviderManager.onReportLocation(locations); - } finally { - Binder.restoreCallingIdentity(identity); + Listener listener = mInternalState.get().listener; + if (listener != null) { + long identity = Binder.clearCallingIdentity(); + try { + listener.onReportLocation(locations); + } finally { + Binder.restoreCallingIdentity(identity); + } } } /** - * Invoked by the location service to return a list of packages currently associated with this - * provider. May be called from any thread. + * Sets a new request and worksource for the provider. */ - public List<String> getProviderPackages() { - return Collections.singletonList(mContext.getPackageName()); + public final void setRequest(ProviderRequest request) { + // all calls into the provider must be moved onto the provider thread to prevent deadlock + mExecutor.execute(() -> onSetRequest(request)); } /** - * Invoked by the location service to deliver a new request for fulfillment to the provider. - * Replaces any previous requests completely. Will always be invoked from the location service - * thread with a cleared binder identity. + * Always invoked on the provider executor. */ - public abstract void onSetRequest(ProviderRequest request, WorkSource source); + protected abstract void onSetRequest(ProviderRequest request); + + /** + * Sends an extra command to the provider for it to interpret as it likes. + */ + public final void sendExtraCommand(int uid, int pid, String command, Bundle extras) { + // all calls into the provider must be moved onto the provider thread to prevent deadlock + mExecutor.execute(() -> onExtraCommand(uid, pid, command, extras)); + } /** - * Invoked by the location service to deliver a custom command to this provider. Will always be - * invoked from the location service thread with a cleared binder identity. + * Always invoked on the provider executor. */ - public void onSendExtraCommand(int uid, int pid, String command, Bundle extras) {} + protected void onExtraCommand(int uid, int pid, String command, Bundle extras) {} /** - * Invoked by the location service to dump debug or log information. May be invoked from any - * thread. + * Dumps debug or log information. May be invoked from any thread. */ public abstract void dump(FileDescriptor fd, PrintWriter pw, String[] args); } diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java index d8561b697caa..15cf190952d1 100644 --- a/services/core/java/com/android/server/location/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/GnssLocationProvider.java @@ -43,6 +43,7 @@ import android.os.BatteryStats; import android.os.Binder; import android.os.Bundle; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.Looper; import android.os.Message; import android.os.PersistableBundle; @@ -113,8 +114,15 @@ public class GnssLocationProvider extends AbstractLocationProvider implements private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); private static final ProviderProperties PROPERTIES = new ProviderProperties( - true, true, false, false, true, true, true, - Criteria.POWER_HIGH, Criteria.ACCURACY_FINE); + /* requiresNetwork = */false, + /* requiresSatellite = */true, + /* requiresCell = */false, + /* hasMonetaryCost = */false, + /* supportAltitude = */true, + /* supportsSpeed = */true, + /* supportsBearing = */true, + Criteria.POWER_HIGH, + Criteria.ACCURACY_FINE); // these need to match GnssPositionMode enum in IGnss.hal private static final int GPS_POSITION_MODE_STANDALONE = 0; @@ -616,13 +624,12 @@ public class GnssLocationProvider extends AbstractLocationProvider implements } } - public GnssLocationProvider(Context context, LocationProviderManager locationProviderManager, - Looper looper) { - super(context, locationProviderManager); + public GnssLocationProvider(Context context, Handler handler) { + super(context, new HandlerExecutor(handler)); ensureInitialized(); - mLooper = looper; + mLooper = handler.getLooper(); // Create a wake lock mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); @@ -639,7 +646,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements mTimeoutIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(ALARM_TIMEOUT), 0); mNetworkConnectivityHandler = new GnssNetworkConnectivityHandler(context, - GnssLocationProvider.this::onNetworkAvailable, looper); + GnssLocationProvider.this::onNetworkAvailable, mLooper); // App ops service to keep track of who is accessing the GPS mAppOps = mContext.getSystemService(AppOpsManager.class); @@ -649,7 +656,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements BatteryStats.SERVICE_NAME)); // Construct internal handler - mHandler = new ProviderHandler(looper); + mHandler = new ProviderHandler(mLooper); // Load GPS configuration and register listeners in the background: // some operations, such as opening files and registering broadcast receivers, can take a @@ -693,10 +700,10 @@ public class GnssLocationProvider extends AbstractLocationProvider implements }; mGnssMetrics = new GnssMetrics(mBatteryStats); - mNtpTimeHelper = new NtpTimeHelper(mContext, looper, this); + mNtpTimeHelper = new NtpTimeHelper(mContext, mLooper, this); GnssSatelliteBlacklistHelper gnssSatelliteBlacklistHelper = new GnssSatelliteBlacklistHelper(mContext, - looper, this); + mLooper, this); mHandler.post(gnssSatelliteBlacklistHelper::updateSatelliteBlacklist); mGnssBatchingProvider = new GnssBatchingProvider(); mGnssGeofenceProvider = new GnssGeofenceProvider(); @@ -1047,8 +1054,8 @@ public class GnssLocationProvider extends AbstractLocationProvider implements } @Override - public void onSetRequest(ProviderRequest request, WorkSource source) { - sendMessage(SET_REQUEST, 0, new GpsRequest(request, source)); + public void onSetRequest(ProviderRequest request) { + sendMessage(SET_REQUEST, 0, new GpsRequest(request, request.workSource)); } private void handleSetRequest(ProviderRequest request, WorkSource source) { @@ -1185,7 +1192,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements } @Override - public void onSendExtraCommand(int uid, int pid, String command, Bundle extras) { + public void onExtraCommand(int uid, int pid, String command, Bundle extras) { long identity = Binder.clearCallingIdentity(); try { @@ -2064,10 +2071,8 @@ public class GnssLocationProvider extends AbstractLocationProvider implements } /** - * This method is bound to {@link #GnssLocationProvider(Context, LocationProviderManager, - * Looper)}. - * It is in charge of loading properties and registering for events that will be posted to - * this handler. + * This method is bound to the constructor. It is in charge of loading properties and + * registering for events that will be posted to this handler. */ private void handleInitialize() { // class_init_native() already initializes the GNSS service handle during class loading. diff --git a/services/core/java/com/android/server/location/LocationProviderProxy.java b/services/core/java/com/android/server/location/LocationProviderProxy.java index 694f14904668..8a149afa6238 100644 --- a/services/core/java/com/android/server/location/LocationProviderProxy.java +++ b/services/core/java/com/android/server/location/LocationProviderProxy.java @@ -23,12 +23,13 @@ import android.content.Context; import android.content.pm.PackageManager; import android.location.Location; import android.os.Bundle; +import android.os.Handler; +import android.os.HandlerExecutor; import android.os.IBinder; import android.os.RemoteException; -import android.os.WorkSource; +import android.util.ArraySet; import android.util.Log; -import com.android.internal.annotations.GuardedBy; import com.android.internal.location.ILocationProvider; import com.android.internal.location.ILocationProviderManager; import com.android.internal.location.ProviderProperties; @@ -39,10 +40,8 @@ import com.android.server.ServiceWatcher; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; /** * Proxy for ILocationProvider implementations. @@ -52,59 +51,64 @@ public class LocationProviderProxy extends AbstractLocationProvider { private static final String TAG = "LocationProviderProxy"; private static final boolean D = LocationManagerService.D; - // used to ensure that updates to mProviderPackages are atomic - private final Object mProviderPackagesLock = new Object(); - - // used to ensure that updates to mRequest and mWorkSource are atomic - private final Object mRequestLock = new Object(); + private static final int MAX_ADDITIONAL_PACKAGES = 2; private final ILocationProviderManager.Stub mManager = new ILocationProviderManager.Stub() { // executed on binder thread @Override public void onSetAdditionalProviderPackages(List<String> packageNames) { - LocationProviderProxy.this.onSetAdditionalProviderPackages(packageNames); + int maxCount = Math.min(MAX_ADDITIONAL_PACKAGES, packageNames.size()) + 1; + ArraySet<String> allPackages = new ArraySet<>(maxCount); + allPackages.add(mServiceWatcher.getCurrentPackageName()); + for (String packageName : packageNames) { + if (packageNames.size() >= maxCount) { + return; + } + + try { + mContext.getPackageManager().getPackageInfo(packageName, MATCH_SYSTEM_ONLY); + allPackages.add(packageName); + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, mServiceWatcher + " specified unknown additional provider package: " + + packageName); + } + } + + setPackageNames(allPackages); } // executed on binder thread @Override public void onSetEnabled(boolean enabled) { - LocationProviderProxy.this.setEnabled(enabled); + setEnabled(enabled); } // executed on binder thread @Override public void onSetProperties(ProviderProperties properties) { - LocationProviderProxy.this.setProperties(properties); + setProperties(properties); } // executed on binder thread @Override public void onReportLocation(Location location) { - LocationProviderProxy.this.reportLocation(location); + reportLocation(location); } }; private final ServiceWatcher mServiceWatcher; - @GuardedBy("mProviderPackagesLock") - private final CopyOnWriteArrayList<String> mProviderPackages = new CopyOnWriteArrayList<>(); - - @GuardedBy("mRequestLock") - @Nullable - private ProviderRequest mRequest; - @GuardedBy("mRequestLock") - private WorkSource mWorkSource; + @Nullable private ProviderRequest mRequest; /** * Creates a new LocationProviderProxy and immediately begins binding to the best applicable * service. */ @Nullable - public static LocationProviderProxy createAndBind( - Context context, LocationProviderManager locationProviderManager, String action, + public static LocationProviderProxy createAndBind(Context context, String action, int overlaySwitchResId, int defaultServicePackageNameResId, int initialPackageNamesResId) { - LocationProviderProxy proxy = new LocationProviderProxy(context, locationProviderManager, + LocationProviderProxy proxy = new LocationProviderProxy(context, FgThread.getHandler(), action, overlaySwitchResId, defaultServicePackageNameResId, initialPackageNamesResId); if (proxy.bind()) { @@ -114,14 +118,13 @@ public class LocationProviderProxy extends AbstractLocationProvider { } } - private LocationProviderProxy(Context context, LocationProviderManager locationProviderManager, - String action, int overlaySwitchResId, int defaultServicePackageNameResId, + private LocationProviderProxy(Context context, Handler handler, String action, + int overlaySwitchResId, int defaultServicePackageNameResId, int initialPackageNamesResId) { - super(context, locationProviderManager); + super(context, new HandlerExecutor(handler), Collections.emptySet()); mServiceWatcher = new ServiceWatcher(context, TAG, action, overlaySwitchResId, - defaultServicePackageNameResId, initialPackageNamesResId, - FgThread.getHandler()) { + defaultServicePackageNameResId, initialPackageNamesResId, handler) { @Override protected void onBind() { @@ -130,14 +133,11 @@ public class LocationProviderProxy extends AbstractLocationProvider { @Override protected void onUnbind() { - resetProviderPackages(Collections.emptyList()); - setEnabled(false); - setProperties(null); + setState(State.EMPTY_STATE); } }; mRequest = null; - mWorkSource = new WorkSource(); } private boolean bind() { @@ -148,77 +148,34 @@ public class LocationProviderProxy extends AbstractLocationProvider { ILocationProvider service = ILocationProvider.Stub.asInterface(binder); if (D) Log.d(TAG, "applying state to connected service " + mServiceWatcher); - resetProviderPackages(Collections.emptyList()); + setPackageNames(Collections.singleton(mServiceWatcher.getCurrentPackageName())); service.setLocationProviderManager(mManager); - synchronized (mRequestLock) { - if (mRequest != null) { - service.setRequest(mRequest, mWorkSource); - } - } - } - - @Override - public List<String> getProviderPackages() { - synchronized (mProviderPackagesLock) { - return mProviderPackages; + if (mRequest != null) { + service.setRequest(mRequest, mRequest.workSource); } } @Override - public void onSetRequest(ProviderRequest request, WorkSource source) { - synchronized (mRequestLock) { - mRequest = request; - mWorkSource = source; - } + public void onSetRequest(ProviderRequest request) { mServiceWatcher.runOnBinder(binder -> { + mRequest = request; ILocationProvider service = ILocationProvider.Stub.asInterface(binder); - service.setRequest(request, source); + service.setRequest(request, request.workSource); }); } @Override - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - pw.println("service=" + mServiceWatcher); - synchronized (mProviderPackagesLock) { - if (mProviderPackages.size() > 1) { - pw.println("additional packages=" + mProviderPackages); - } - } - } - - @Override - public void onSendExtraCommand(int uid, int pid, String command, Bundle extras) { + public void onExtraCommand(int uid, int pid, String command, Bundle extras) { mServiceWatcher.runOnBinder(binder -> { ILocationProvider service = ILocationProvider.Stub.asInterface(binder); service.sendExtraCommand(command, extras); }); } - private void onSetAdditionalProviderPackages(List<String> packageNames) { - resetProviderPackages(packageNames); - } - - private void resetProviderPackages(List<String> additionalPackageNames) { - ArrayList<String> permittedPackages = new ArrayList<>(additionalPackageNames.size()); - for (String packageName : additionalPackageNames) { - try { - mContext.getPackageManager().getPackageInfo(packageName, MATCH_SYSTEM_ONLY); - permittedPackages.add(packageName); - } catch (PackageManager.NameNotFoundException e) { - Log.w(TAG, mServiceWatcher + " specified unknown additional provider package: " - + packageName); - } - } - - synchronized (mProviderPackagesLock) { - mProviderPackages.clear(); - String myPackage = mServiceWatcher.getCurrentPackageName(); - if (myPackage != null) { - mProviderPackages.add(myPackage); - mProviderPackages.addAll(permittedPackages); - } - } + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("service=" + mServiceWatcher); } } diff --git a/services/core/java/com/android/server/location/LocationRequestStatistics.java b/services/core/java/com/android/server/location/LocationRequestStatistics.java index b7ccb26da64b..45c833498ac7 100644 --- a/services/core/java/com/android/server/location/LocationRequestStatistics.java +++ b/services/core/java/com/android/server/location/LocationRequestStatistics.java @@ -1,8 +1,29 @@ +/* + * 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.location; import android.os.SystemClock; import android.util.Log; +import android.util.TimeUtils; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.IndentingPrintWriter; +import java.util.ArrayList; import java.util.HashMap; /** @@ -17,6 +38,8 @@ public class LocationRequestStatistics { public final HashMap<PackageProviderKey, PackageStatistics> statistics = new HashMap<PackageProviderKey, PackageStatistics>(); + public final RequestSummaryLimitedHistory history = new RequestSummaryLimitedHistory(); + /** * Signals that a package has started requesting locations. * @@ -34,6 +57,7 @@ public class LocationRequestStatistics { } stats.startRequesting(intervalMs); stats.updateForeground(isForeground); + history.addRequest(packageName, providerName, intervalMs); } /** @@ -48,6 +72,7 @@ public class LocationRequestStatistics { if (stats != null) { stats.stopRequesting(); } + history.removeRequest(packageName, providerName); } /** @@ -77,7 +102,7 @@ public class LocationRequestStatistics { */ public final String providerName; - public PackageProviderKey(String packageName, String providerName) { + PackageProviderKey(String packageName, String providerName) { this.packageName = packageName; this.providerName = providerName; } @@ -100,6 +125,104 @@ public class LocationRequestStatistics { } /** + * A data structure to hold past requests + */ + public static class RequestSummaryLimitedHistory { + @VisibleForTesting + static final int MAX_SIZE = 100; + + final ArrayList<RequestSummary> mList = new ArrayList<>(MAX_SIZE); + + /** + * Append an added location request to the history + */ + @VisibleForTesting + void addRequest(String packageName, String providerName, long intervalMs) { + addRequestSummary(new RequestSummary(packageName, providerName, intervalMs)); + } + + /** + * Append a removed location request to the history + */ + @VisibleForTesting + void removeRequest(String packageName, String providerName) { + addRequestSummary(new RequestSummary( + packageName, providerName, RequestSummary.REQUEST_ENDED_INTERVAL)); + } + + private void addRequestSummary(RequestSummary summary) { + while (mList.size() >= MAX_SIZE) { + mList.remove(0); + } + mList.add(summary); + } + + /** + * Dump history to a printwriter (for dumpsys location) + */ + public void dump(IndentingPrintWriter ipw) { + long systemElapsedOffsetMillis = System.currentTimeMillis() + - SystemClock.elapsedRealtime(); + + ipw.println("Last Several Location Requests:"); + ipw.increaseIndent(); + + for (RequestSummary requestSummary : mList) { + requestSummary.dump(ipw, systemElapsedOffsetMillis); + } + + ipw.decreaseIndent(); + } + } + + /** + * A data structure to hold a single request + */ + static class RequestSummary { + /** + * Name of package requesting location. + */ + private final String mPackageName; + /** + * Name of provider being requested (e.g. "gps"). + */ + private final String mProviderName; + /** + * Interval Requested, or REQUEST_ENDED_INTERVAL indicating request has ended + */ + private final long mIntervalMillis; + /** + * Elapsed time of request + */ + private final long mElapsedRealtimeMillis; + + /** + * Placeholder for requested ending (other values indicate request started/changed) + */ + static final long REQUEST_ENDED_INTERVAL = -1; + + RequestSummary(String packageName, String providerName, long intervalMillis) { + this.mPackageName = packageName; + this.mProviderName = providerName; + this.mIntervalMillis = intervalMillis; + this.mElapsedRealtimeMillis = SystemClock.elapsedRealtime(); + } + + void dump(IndentingPrintWriter ipw, long systemElapsedOffsetMillis) { + StringBuilder s = new StringBuilder(); + long systemTimeMillis = systemElapsedOffsetMillis + mElapsedRealtimeMillis; + s.append("At ").append(TimeUtils.formatForLogging(systemTimeMillis)).append(": ") + .append(mIntervalMillis == REQUEST_ENDED_INTERVAL ? "- " : "+ ") + .append(String.format("%7s", mProviderName)).append(" request from ") + .append(mPackageName); + if (mIntervalMillis != REQUEST_ENDED_INTERVAL) { + s.append(" at interval ").append(mIntervalMillis / 1000).append(" seconds"); + } + ipw.println(s); + } + } + + /** * Usage statistics for a package/provider pair. */ public static class PackageStatistics { diff --git a/services/core/java/com/android/server/location/LocationSettingsStore.java b/services/core/java/com/android/server/location/LocationSettingsStore.java index f625452975c0..0e8720ebb08f 100644 --- a/services/core/java/com/android/server/location/LocationSettingsStore.java +++ b/services/core/java/com/android/server/location/LocationSettingsStore.java @@ -16,6 +16,8 @@ package com.android.server.location; +import static android.location.LocationManager.FUSED_PROVIDER; +import static android.location.LocationManager.PASSIVE_PROVIDER; import static android.provider.Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS; import static android.provider.Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST; import static android.provider.Settings.Global.LOCATION_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS; @@ -28,6 +30,7 @@ import android.app.ActivityManager; import android.content.Context; import android.database.ContentObserver; import android.net.Uri; +import android.os.Binder; import android.os.Handler; import android.os.UserHandle; import android.provider.Settings; @@ -248,6 +251,9 @@ public class LocationSettingsStore { DEFAULT_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS); } + /** + * Retrieve maximum age of the last location. + */ public long getMaxLastLocationAgeMs() { return Settings.Global.getLong( mContext.getContentResolver(), @@ -256,6 +262,29 @@ public class LocationSettingsStore { } /** + * Set a value for the deprecated LOCATION_PROVIDERS_ALLOWED setting. This is used purely for + * backwards compatibility for old clients, and may be removed in the future. + */ + public void setLocationProviderAllowed(String provider, boolean enabled, int userId) { + // fused and passive provider never get public updates for legacy reasons + if (FUSED_PROVIDER.equals(provider) || PASSIVE_PROVIDER.equals(provider)) { + return; + } + + long identity = Binder.clearCallingIdentity(); + try { + // update LOCATION_PROVIDERS_ALLOWED for best effort backwards compatibility + Settings.Secure.putStringForUser( + mContext.getContentResolver(), + Settings.Secure.LOCATION_PROVIDERS_ALLOWED, + (enabled ? "+" : "-") + provider, + userId); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + /** * Dump info for debugging. */ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { diff --git a/services/core/java/com/android/server/location/MockProvider.java b/services/core/java/com/android/server/location/MockProvider.java index 472876bfd86a..60c9fc12c201 100644 --- a/services/core/java/com/android/server/location/MockProvider.java +++ b/services/core/java/com/android/server/location/MockProvider.java @@ -19,7 +19,6 @@ package com.android.server.location; import android.annotation.Nullable; import android.content.Context; import android.location.Location; -import android.os.WorkSource; import com.android.internal.location.ProviderProperties; import com.android.internal.location.ProviderRequest; @@ -34,41 +33,33 @@ import java.io.PrintWriter; */ public class MockProvider extends AbstractLocationProvider { - private boolean mEnabled; @Nullable private Location mLocation; - public MockProvider(Context context, - LocationProviderManager locationProviderManager, ProviderProperties properties) { - super(context, locationProviderManager); - - mEnabled = true; - mLocation = null; - + public MockProvider(Context context, ProviderProperties properties) { + // using a direct executor is only acceptable because this class is so simple it is trivial + // to verify that it does not acquire any locks or re-enter LMS from callbacks + super(context, Runnable::run); setProperties(properties); } /** Sets the enabled state of this mock provider. */ - public void setEnabled(boolean enabled) { - mEnabled = enabled; - super.setEnabled(enabled); + public void setProviderEnabled(boolean enabled) { + setEnabled(enabled); } /** Sets the location to report for this mock provider. */ - public void setLocation(Location l) { - mLocation = new Location(l); - if (!mLocation.isFromMockProvider()) { - mLocation.setIsFromMockProvider(true); - } - if (mEnabled) { - reportLocation(mLocation); - } + public void setProviderLocation(Location l) { + Location location = new Location(l); + location.setIsFromMockProvider(true); + mLocation = location; + reportLocation(location); } @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - pw.println("last location=" + mLocation); + pw.println("last mock location=" + mLocation); } @Override - public void onSetRequest(ProviderRequest request, WorkSource source) {} + public void onSetRequest(ProviderRequest request) {} } diff --git a/services/core/java/com/android/server/location/MockableLocationProvider.java b/services/core/java/com/android/server/location/MockableLocationProvider.java new file mode 100644 index 000000000000..f50dfe7edbb7 --- /dev/null +++ b/services/core/java/com/android/server/location/MockableLocationProvider.java @@ -0,0 +1,289 @@ +/* + * 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.location; + +import android.annotation.Nullable; +import android.content.Context; +import android.location.Location; +import android.os.Bundle; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.location.ProviderRequest; +import com.android.internal.util.Preconditions; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.Collections; +import java.util.List; + +/** + * Represents a location provider that may switch between a mock implementation and a real + * implementation. Requires owners to provide a lock object that will be used internally and held + * for the duration of all listener callbacks. Owners are reponsible for ensuring this cannot lead + * to deadlock. + * + * In order to ensure deadlock does not occur, the owner must validate that the ONLY lock which can + * be held BOTH when calling into this class AND when receiving a callback from this class is the + * lock given to this class via the constructor. Holding any other lock is ok as long as there is no + * possibility that it can be obtained within both codepaths. + * + * Holding the given lock guarantees atomicity of any operations on this class for the duration. + * + * @hide + */ +public class MockableLocationProvider extends AbstractLocationProvider { + + private final Object mOwnerLock; + + @GuardedBy("mOwnerLock") + @Nullable private AbstractLocationProvider mProvider; + @GuardedBy("mOwnerLock") + @Nullable private AbstractLocationProvider mRealProvider; + @GuardedBy("mOwnerLock") + @Nullable private MockProvider mMockProvider; + + @GuardedBy("mOwnerLock") + private ProviderRequest mRequest; + + /** + * The given lock object will be held any time the listener is invoked, and may also be acquired + * and released during the course of invoking any public methods. Holding the given lock ensures + * that provider state cannot change except as result of an explicit call by the owner of the + * lock into this class. The client is reponsible for ensuring this cannot cause deadlock. + * + * The client should expect that it may being to receive callbacks as soon as this constructor + * is invoked. + */ + public MockableLocationProvider(Context context, Object ownerLock, Listener listener) { + // using a direct executor is acceptable because all inbound calls are delegated to the + // actual provider implementations which will use their own executors + super(context, Runnable::run, Collections.emptySet()); + mOwnerLock = ownerLock; + mRequest = ProviderRequest.EMPTY_REQUEST; + + setListener(listener); + } + + /** + * Returns the current provider implementation. May be null if there is no current + * implementation. + */ + @Nullable + public AbstractLocationProvider getProvider() { + synchronized (mOwnerLock) { + return mProvider; + } + } + + /** + * Sets the real provider implementation, replacing any previous real provider implementation. + * May cause an inline invocation of {@link Listener#onStateChanged(State, State)} if this + * results in a state change. + */ + public void setRealProvider(@Nullable AbstractLocationProvider provider) { + synchronized (mOwnerLock) { + if (mRealProvider == provider) { + return; + } + + mRealProvider = provider; + if (!isMock()) { + setProviderLocked(mRealProvider); + } + } + } + + /** + * Sets the mock provider implementation, replacing any previous mock provider implementation. + * Mock implementations are always used instead of real implementations if set. May cause an + * inline invocation of {@link Listener#onStateChanged(State, State)} if this results in a + * state change. + */ + public void setMockProvider(@Nullable MockProvider provider) { + synchronized (mOwnerLock) { + if (mMockProvider == provider) { + return; + } + + mMockProvider = provider; + if (mMockProvider != null) { + setProviderLocked(mMockProvider); + } else { + setProviderLocked(mRealProvider); + } + } + } + + @GuardedBy("mOwnerLock") + private void setProviderLocked(@Nullable AbstractLocationProvider provider) { + if (mProvider == provider) { + return; + } + + AbstractLocationProvider oldProvider = mProvider; + mProvider = provider; + + if (oldProvider != null) { + // do this after switching the provider - so even if the old provider is using a direct + // executor, if it re-enters this class within setRequest(), it will be ignored + oldProvider.setListener(null); + oldProvider.setRequest(ProviderRequest.EMPTY_REQUEST); + } + + State newState; + if (mProvider != null) { + newState = mProvider.setListener(new ListenerWrapper(mProvider)); + } else { + newState = State.EMPTY_STATE; + } + + ProviderRequest oldRequest = mRequest; + setState(newState); + + if (mProvider != null && oldRequest == mRequest) { + mProvider.setRequest(mRequest); + } + } + + /** + * Returns true if the current active provider implementation is the mock implementation, and + * false otherwise. + */ + public boolean isMock() { + synchronized (mOwnerLock) { + return mMockProvider != null && mProvider == mMockProvider; + } + } + + /** + * Sets the mock provider implementation's enabled state. Will throw an exception if the mock + * provider is not currently the active implementation. + */ + public void setMockProviderEnabled(boolean enabled) { + synchronized (mOwnerLock) { + Preconditions.checkState(isMock()); + mMockProvider.setProviderEnabled(enabled); + } + } + /** + * Sets the mock provider implementation's location. Will throw an exception if the mock + * provider is not currently the active implementation. + */ + public void setMockProviderLocation(Location location) { + synchronized (mOwnerLock) { + Preconditions.checkState(isMock()); + mMockProvider.setProviderLocation(location); + } + } + + @Override + public State getState() { + return super.getState(); + } + + /** + * Returns the current location request. + */ + public ProviderRequest getCurrentRequest() { + synchronized (mOwnerLock) { + return mRequest; + } + } + + protected void onSetRequest(ProviderRequest request) { + synchronized (mOwnerLock) { + if (request == mRequest) { + return; + } + + mRequest = request; + + if (mProvider != null) { + mProvider.setRequest(request); + } + } + } + + protected void onExtraCommand(int uid, int pid, String command, Bundle extras) { + synchronized (mOwnerLock) { + if (mProvider != null) { + mProvider.sendExtraCommand(uid, pid, command, extras); + } + } + } + + /** + * Dumps the current provider implementation. + */ + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + AbstractLocationProvider provider; + synchronized (mOwnerLock) { + provider = mProvider; + pw.println("request=" + mRequest); + } + + if (provider != null) { + // dump outside the lock in case the provider wants to acquire its own locks, and since + // the default provider dump behavior does not move things onto the provider thread... + provider.dump(fd, pw, args); + } + } + + // ensures that callbacks from the incorrect provider are never visible to clients - this + // requires holding the owner's lock for the duration of the callback + private class ListenerWrapper implements Listener { + + private final AbstractLocationProvider mListenerProvider; + + private ListenerWrapper(AbstractLocationProvider listenerProvider) { + mListenerProvider = listenerProvider; + } + + @Override + public final void onStateChanged(State oldState, State newState) { + synchronized (mOwnerLock) { + if (mListenerProvider != mProvider) { + return; + } + + setState(newState); + } + } + + @Override + public final void onReportLocation(Location location) { + synchronized (mOwnerLock) { + if (mListenerProvider != mProvider) { + return; + } + + reportLocation(location); + } + } + + @Override + public final void onReportLocation(List<Location> locations) { + synchronized (mOwnerLock) { + if (mListenerProvider != mProvider) { + return; + } + + reportLocation(locations); + } + } + } +} diff --git a/services/core/java/com/android/server/location/PassiveProvider.java b/services/core/java/com/android/server/location/PassiveProvider.java index 639b1eb1ed5e..b33877069d70 100644 --- a/services/core/java/com/android/server/location/PassiveProvider.java +++ b/services/core/java/com/android/server/location/PassiveProvider.java @@ -19,7 +19,6 @@ package com.android.server.location; import android.content.Context; import android.location.Criteria; import android.location.Location; -import android.os.WorkSource; import com.android.internal.location.ProviderProperties; import com.android.internal.location.ProviderRequest; @@ -37,13 +36,22 @@ import java.io.PrintWriter; public class PassiveProvider extends AbstractLocationProvider { private static final ProviderProperties PROPERTIES = new ProviderProperties( - false, false, false, false, false, false, false, - Criteria.POWER_LOW, Criteria.ACCURACY_COARSE); + /* requiresNetwork = */false, + /* requiresSatellite = */false, + /* requiresCell = */false, + /* hasMonetaryCost = */false, + /* supportsAltitude = */false, + /* supportsSpeed = */false, + /* supportsBearing = */false, + Criteria.POWER_LOW, + Criteria.ACCURACY_COARSE); - private boolean mReportLocation; + private volatile boolean mReportLocation; - public PassiveProvider(Context context, LocationProviderManager locationProviderManager) { - super(context, locationProviderManager); + public PassiveProvider(Context context) { + // using a direct executor is only acceptable because this class is so simple it is trivial + // to verify that it does not acquire any locks or re-enter LMS from callbacks + super(context, Runnable::run); mReportLocation = false; @@ -52,7 +60,7 @@ public class PassiveProvider extends AbstractLocationProvider { } @Override - public void onSetRequest(ProviderRequest request, WorkSource source) { + public void onSetRequest(ProviderRequest request) { mReportLocation = request.reportLocation; } @@ -63,7 +71,5 @@ public class PassiveProvider extends AbstractLocationProvider { } @Override - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - pw.println("report location=" + mReportLocation); - } + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {} } diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 4546ea4b9ebc..7233f3465d5b 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -20,6 +20,7 @@ import static android.Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE; import static android.Manifest.permission.READ_CONTACTS; import static android.content.Context.KEYGUARD_SERVICE; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.os.UserHandle.USER_ALL; import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE; import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD; @@ -29,6 +30,7 @@ import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN; import static com.android.internal.widget.LockPatternUtils.EscrowTokenStateChangeCallback; import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_HANDLE_KEY; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT; +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE; import static com.android.internal.widget.LockPatternUtils.USER_FRP; import static com.android.internal.widget.LockPatternUtils.frpCredentialEnabled; import static com.android.internal.widget.LockPatternUtils.userOwnsFrpCredential; @@ -118,6 +120,7 @@ import com.android.internal.widget.ILockSettings; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockSettingsInternal; import com.android.internal.widget.LockscreenCredential; +import com.android.internal.widget.RebootEscrowListener; import com.android.internal.widget.VerifyCredentialResponse; import com.android.server.LocalServices; import com.android.server.ServiceThread; @@ -222,7 +225,10 @@ public class LockSettingsService extends ILockSettings.Stub { private final RecoverableKeyStoreManager mRecoverableKeyStoreManager; + private final RebootEscrowManager mRebootEscrowManager; + private boolean mFirstCallToVold; + // Current password metric for all users on the device. Updated when user unlocks // the device or changes password. Removed when user is stopped. @GuardedBy("this") @@ -483,6 +489,11 @@ public class LockSettingsService extends ILockSettings.Stub { new PasswordSlotManager()); } + public RebootEscrowManager getRebootEscrowManager(RebootEscrowManager.Callbacks callbacks, + LockSettingsStorage storage) { + return new RebootEscrowManager(mContext, callbacks, storage); + } + public boolean hasEnrolledBiometrics(int userId) { BiometricManager bm = mContext.getSystemService(BiometricManager.class); return bm.hasEnrolledBiometrics(userId); @@ -550,6 +561,9 @@ public class LockSettingsService extends ILockSettings.Stub { mSpManager = injector.getSyntheticPasswordManager(mStorage); + mRebootEscrowManager = injector.getRebootEscrowManager(new RebootEscrowCallbacks(), + mStorage); + LocalServices.addService(LockSettingsInternal.class, new LocalService()); } @@ -782,7 +796,14 @@ public class LockSettingsService extends ILockSettings.Stub { migrateOldData(); getGateKeeperService(); mSpManager.initWeaverService(); - // Find the AuthSecret HAL + getAuthSecretHal(); + mDeviceProvisionedObserver.onSystemReady(); + mRebootEscrowManager.loadRebootEscrowDataIfAvailable(); + // TODO: maybe skip this for split system user mode. + mStorage.prefetchUser(UserHandle.USER_SYSTEM); + } + + private void getAuthSecretHal() { try { mAuthSecretService = IAuthSecret.getService(); } catch (NoSuchElementException e) { @@ -790,9 +811,6 @@ public class LockSettingsService extends ILockSettings.Stub { } catch (RemoteException e) { Slog.w(TAG, "Failed to get AuthSecret HAL", e); } - mDeviceProvisionedObserver.onSystemReady(); - // TODO: maybe skip this for split system user mode. - mStorage.prefetchUser(UserHandle.USER_SYSTEM); } private void migrateOldData() { @@ -2466,10 +2484,18 @@ public class LockSettingsService extends ILockSettings.Stub { private void onAuthTokenKnownForUser(@UserIdInt int userId, AuthenticationToken auth) { if (mInjector.isGsiRunning()) { - Slog.w(TAG, "AuthSecret disabled in GSI"); + Slog.w(TAG, "Running in GSI; skipping calls to AuthSecret and RebootEscrow"); return; } + mRebootEscrowManager.callToRebootEscrowIfNeeded(userId, auth.getVersion(), + auth.getSyntheticPassword()); + + callToAuthSecretIfNeeded(userId, auth); + } + + private void callToAuthSecretIfNeeded(@UserIdInt int userId, + AuthenticationToken auth) { // Pass the primary user's auth secret to the HAL if (mAuthSecretService != null && mUserManager.getUserInfo(userId).isPrimary()) { try { @@ -3288,5 +3314,46 @@ public class LockSettingsService extends ILockSettings.Stub { return LockSettingsService.this.getUserPasswordMetrics(userHandle); } + @Override + public void prepareRebootEscrow() { + if (!mRebootEscrowManager.prepareRebootEscrow()) { + return; + } + mStrongAuth.requireStrongAuth(STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE, USER_ALL); + } + + @Override + public void setRebootEscrowListener(RebootEscrowListener listener) { + mRebootEscrowManager.setRebootEscrowListener(listener); + } + + @Override + public void clearRebootEscrow() { + if (!mRebootEscrowManager.clearRebootEscrow()) { + return; + } + mStrongAuth.noLongerRequireStrongAuth(STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE, + USER_ALL); + } + + @Override + public boolean armRebootEscrow() { + return mRebootEscrowManager.armRebootEscrowIfNeeded(); + } + } + + private class RebootEscrowCallbacks implements RebootEscrowManager.Callbacks { + @Override + public boolean isUserSecure(int userId) { + return LockSettingsService.this.isUserSecure(userId); + } + + @Override + public void onRebootEscrowRestored(byte spVersion, byte[] syntheticPassword, int userId) { + SyntheticPasswordManager.AuthenticationToken + authToken = new SyntheticPasswordManager.AuthenticationToken(spVersion); + authToken.recreateDirectly(syntheticPassword); + onCredentialVerified(authToken, CHALLENGE_NONE, 0, null, userId); + } } } diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java index 3dab3ce7116c..fec0189377c8 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java @@ -81,6 +81,8 @@ class LockSettingsStorage { private static final String LOCK_PASSWORD_FILE = "gatekeeper.password.key"; private static final String CHILD_PROFILE_LOCK_FILE = "gatekeeper.profile.key"; + private static final String REBOOT_ESCROW_FILE = "reboot.escrow.key"; + private static final String SYNTHETIC_PASSWORD_DIRECTORY = "spblob/"; private static final Object DEFAULT = new Object(); @@ -261,6 +263,22 @@ class LockSettingsStorage { return hasFile(getChildProfileLockFile(userId)); } + public void writeRebootEscrow(int userId, byte[] rebootEscrow) { + writeFile(getRebootEscrowFile(userId), rebootEscrow); + } + + public byte[] readRebootEscrow(int userId) { + return readFile(getRebootEscrowFile(userId)); + } + + public boolean hasRebootEscrow(int userId) { + return hasFile(getRebootEscrowFile(userId)); + } + + public void removeRebootEscrow(int userId) { + deleteFile(getRebootEscrowFile(userId)); + } + public boolean hasPassword(int userId) { return hasFile(getLockPasswordFilename(userId)); } @@ -384,6 +402,11 @@ class LockSettingsStorage { return getLockCredentialFilePathForUser(userId, CHILD_PROFILE_LOCK_FILE); } + @VisibleForTesting + String getRebootEscrowFile(int userId) { + return getLockCredentialFilePathForUser(userId, REBOOT_ESCROW_FILE); + } + private String getLockCredentialFilePathForUser(int userId, String basename) { String dataSystemDirectory = Environment.getDataDirectory().getAbsolutePath() + SYSTEM_DIRECTORY; @@ -479,18 +502,10 @@ class LockSettingsStorage { if (parentInfo == null) { // This user owns its lock settings files - safe to delete them synchronized (mFileWriteLock) { - String name = getLockPasswordFilename(userId); - File file = new File(name); - if (file.exists()) { - file.delete(); - mCache.putFile(name, null); - } - name = getLockPatternFilename(userId); - file = new File(name); - if (file.exists()) { - file.delete(); - mCache.putFile(name, null); - } + deleteFilesAndRemoveCache( + getLockPasswordFilename(userId), + getLockPatternFilename(userId), + getRebootEscrowFile(userId)); } } else { // Managed profile @@ -512,6 +527,16 @@ class LockSettingsStorage { } } + private void deleteFilesAndRemoveCache(String... names) { + for (String name : names) { + File file = new File(name); + if (file.exists()) { + file.delete(); + mCache.putFile(name, null); + } + } + } + @VisibleForTesting void closeDatabase() { mOpenHelper.close(); diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java b/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java index a84306c97dc0..91cf53ee9a1f 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java @@ -16,10 +16,8 @@ package com.android.server.locksettings; -import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker - .STRONG_AUTH_NOT_REQUIRED; -import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker - .STRONG_AUTH_REQUIRED_AFTER_TIMEOUT; +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED; +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT; import android.app.AlarmManager; import android.app.AlarmManager.OnAlarmListener; @@ -50,6 +48,7 @@ public class LockSettingsStrongAuth { private static final int MSG_UNREGISTER_TRACKER = 3; private static final int MSG_REMOVE_USER = 4; private static final int MSG_SCHEDULE_STRONG_AUTH_TIMEOUT = 5; + private static final int MSG_NO_LONGER_REQUIRE_STRONG_AUTH = 6; private static final String STRONG_AUTH_TIMEOUT_ALARM_TAG = "LockSettingsStrongAuth.timeoutForUser"; @@ -109,6 +108,26 @@ public class LockSettingsStrongAuth { } } + private void handleNoLongerRequireStrongAuth(int strongAuthReason, int userId) { + if (userId == UserHandle.USER_ALL) { + for (int i = 0; i < mStrongAuthForUser.size(); i++) { + int key = mStrongAuthForUser.keyAt(i); + handleNoLongerRequireStrongAuthOneUser(strongAuthReason, key); + } + } else { + handleNoLongerRequireStrongAuthOneUser(strongAuthReason, userId); + } + } + + private void handleNoLongerRequireStrongAuthOneUser(int strongAuthReason, int userId) { + int oldValue = mStrongAuthForUser.get(userId, mDefaultStrongAuthFlags); + int newValue = oldValue & ~strongAuthReason; + if (oldValue != newValue) { + mStrongAuthForUser.put(userId, newValue); + notifyStrongAuthTrackers(newValue, userId); + } + } + private void handleRemoveUser(int userId) { int index = mStrongAuthForUser.indexOfKey(userId); if (index >= 0) { @@ -174,6 +193,16 @@ public class LockSettingsStrongAuth { } } + void noLongerRequireStrongAuth(int strongAuthReason, int userId) { + if (userId == UserHandle.USER_ALL || userId >= UserHandle.USER_SYSTEM) { + mHandler.obtainMessage(MSG_NO_LONGER_REQUIRE_STRONG_AUTH, strongAuthReason, + userId).sendToTarget(); + } else { + throw new IllegalArgumentException( + "userId must be an explicit user id or USER_ALL"); + } + } + public void reportUnlock(int userId) { requireStrongAuth(STRONG_AUTH_NOT_REQUIRED, userId); } @@ -216,6 +245,9 @@ public class LockSettingsStrongAuth { case MSG_SCHEDULE_STRONG_AUTH_TIMEOUT: handleScheduleStrongAuthTimeout(msg.arg1); break; + case MSG_NO_LONGER_REQUIRE_STRONG_AUTH: + handleNoLongerRequireStrongAuth(msg.arg1, msg.arg2); + break; } } }; diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowData.java b/services/core/java/com/android/server/locksettings/RebootEscrowData.java new file mode 100644 index 000000000000..aee608e8a544 --- /dev/null +++ b/services/core/java/com/android/server/locksettings/RebootEscrowData.java @@ -0,0 +1,178 @@ +/* + * 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.locksettings; + +import com.android.internal.util.Preconditions; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.KeyGenerator; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +/** + * Holds the data necessary to complete a reboot escrow of the Synthetic Password. + */ +class RebootEscrowData { + /** + * This is the current version of the escrow data format. This should be incremented if the + * format on disk is changed. + */ + private static final int CURRENT_VERSION = 1; + + /** The secret key will be of this format. */ + private static final String KEY_ALGO = "AES"; + + /** The key size used for encrypting the reboot escrow data. */ + private static final int KEY_SIZE_BITS = 256; + + /** The algorithm used for the encryption of the key blob. */ + private static final String CIPHER_ALGO = "AES/GCM/NoPadding"; + + private RebootEscrowData(byte spVersion, byte[] iv, byte[] syntheticPassword, byte[] blob, + byte[] key) { + mSpVersion = spVersion; + mIv = iv; + mSyntheticPassword = syntheticPassword; + mBlob = blob; + mKey = key; + } + + private final byte mSpVersion; + private final byte[] mIv; + private final byte[] mSyntheticPassword; + private final byte[] mBlob; + private final byte[] mKey; + + public byte getSpVersion() { + return mSpVersion; + } + + public byte[] getIv() { + return mIv; + } + + public byte[] getSyntheticPassword() { + return mSyntheticPassword; + } + + public byte[] getBlob() { + return mBlob; + } + + public byte[] getKey() { + return mKey; + } + + static SecretKeySpec fromKeyBytes(byte[] keyBytes) { + return new SecretKeySpec(keyBytes, KEY_ALGO); + } + + static RebootEscrowData fromEncryptedData(SecretKeySpec keySpec, byte[] blob) + throws IOException { + Preconditions.checkNotNull(keySpec); + Preconditions.checkNotNull(blob); + + DataInputStream dis = new DataInputStream(new ByteArrayInputStream(blob)); + int version = dis.readInt(); + if (version != CURRENT_VERSION) { + throw new IOException("Unsupported version " + version); + } + + byte spVersion = dis.readByte(); + + int ivSize = dis.readInt(); + if (ivSize < 0 || ivSize > 32) { + throw new IOException("IV out of range: " + ivSize); + } + byte[] iv = new byte[ivSize]; + dis.readFully(iv); + + int cipherTextSize = dis.readInt(); + if (cipherTextSize < 0) { + throw new IOException("Invalid cipher text size: " + cipherTextSize); + } + + byte[] cipherText = new byte[cipherTextSize]; + dis.readFully(cipherText); + + final byte[] syntheticPassword; + try { + Cipher c = Cipher.getInstance(CIPHER_ALGO); + c.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(iv)); + syntheticPassword = c.doFinal(cipherText); + } catch (NoSuchAlgorithmException | InvalidKeyException | BadPaddingException + | IllegalBlockSizeException | NoSuchPaddingException + | InvalidAlgorithmParameterException e) { + throw new IOException("Could not decrypt ciphertext", e); + } + + return new RebootEscrowData(spVersion, iv, syntheticPassword, blob, keySpec.getEncoded()); + } + + static RebootEscrowData fromSyntheticPassword(byte spVersion, byte[] syntheticPassword) + throws IOException { + Preconditions.checkNotNull(syntheticPassword); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(bos); + + final SecretKey secretKey; + try { + KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGO); + keyGenerator.init(KEY_SIZE_BITS, new SecureRandom()); + secretKey = keyGenerator.generateKey(); + } catch (NoSuchAlgorithmException e) { + throw new IOException("Could not generate new secret key", e); + } + + final byte[] cipherText; + final byte[] iv; + try { + Cipher cipher = Cipher.getInstance(CIPHER_ALGO); + cipher.init(Cipher.ENCRYPT_MODE, secretKey); + cipherText = cipher.doFinal(syntheticPassword); + iv = cipher.getIV(); + } catch (NoSuchAlgorithmException | BadPaddingException | IllegalBlockSizeException + | NoSuchPaddingException | InvalidKeyException e) { + throw new IOException("Could not encrypt reboot escrow data", e); + } + + dos.writeInt(CURRENT_VERSION); + dos.writeByte(spVersion); + dos.writeInt(iv.length); + dos.write(iv); + dos.writeInt(cipherText.length); + dos.write(cipherText); + + return new RebootEscrowData(spVersion, iv, syntheticPassword, bos.toByteArray(), + secretKey.getEncoded()); + } +} diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java new file mode 100644 index 000000000000..46ea9d11d1dc --- /dev/null +++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java @@ -0,0 +1,302 @@ +/* + * 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.locksettings; + +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.content.Context; +import android.content.pm.UserInfo; +import android.hardware.rebootescrow.IRebootEscrow; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.UserManager; +import android.util.Slog; +import android.util.StatsLog; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.widget.RebootEscrowListener; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.crypto.spec.SecretKeySpec; + +class RebootEscrowManager { + private static final String TAG = "RebootEscrowManager"; + + /** + * Used to track when the reboot escrow is wanted. Set to false when mRebootEscrowReady is + * true. + */ + private final AtomicBoolean mRebootEscrowWanted = new AtomicBoolean(false); + + /** Used to track when reboot escrow is ready. */ + private boolean mRebootEscrowReady; + + /** Notified when mRebootEscrowReady changes. */ + private RebootEscrowListener mRebootEscrowListener; + + /** + * Stores the reboot escrow data between when it's supplied and when + * {@link #armRebootEscrowIfNeeded()} is called. + */ + private RebootEscrowData mPendingRebootEscrowData; + + private final UserManager mUserManager; + + private final Injector mInjector; + + private final LockSettingsStorage mStorage; + + private final Callbacks mCallbacks; + + interface Callbacks { + boolean isUserSecure(int userId); + void onRebootEscrowRestored(byte spVersion, byte[] syntheticPassword, int userId); + } + + static class Injector { + protected Context mContext; + + Injector(Context context) { + mContext = context; + } + + public Context getContext() { + return mContext; + } + public UserManager getUserManager() { + return (UserManager) mContext.getSystemService(Context.USER_SERVICE); + } + + public @Nullable IRebootEscrow getRebootEscrow() { + try { + return IRebootEscrow.Stub.asInterface(ServiceManager.getService( + "android.hardware.rebootescrow.IRebootEscrow/default")); + } catch (NoSuchElementException e) { + Slog.i(TAG, "Device doesn't implement RebootEscrow HAL"); + } + return null; + } + } + + RebootEscrowManager(Context context, Callbacks callbacks, LockSettingsStorage storage) { + this(new Injector(context), callbacks, storage); + } + + @VisibleForTesting + RebootEscrowManager(Injector injector, Callbacks callbacks, + LockSettingsStorage storage) { + mInjector = injector; + mCallbacks = callbacks; + mStorage = storage; + mUserManager = injector.getUserManager(); + } + + void loadRebootEscrowDataIfAvailable() { + List<UserInfo> users = mUserManager.getUsers(); + List<UserInfo> rebootEscrowUsers = new ArrayList<>(); + for (UserInfo user : users) { + if (mCallbacks.isUserSecure(user.id) && mStorage.hasRebootEscrow(user.id)) { + rebootEscrowUsers.add(user); + } + } + + if (rebootEscrowUsers.isEmpty()) { + return; + } + + SecretKeySpec escrowKey = getAndClearRebootEscrowKey(); + if (escrowKey == null) { + Slog.w(TAG, "Had reboot escrow data for users, but no key; removing escrow storage."); + for (UserInfo user : users) { + mStorage.removeRebootEscrow(user.id); + } + StatsLog.write(StatsLog.REBOOT_ESCROW_RECOVERY_REPORTED, false); + return; + } + + boolean allUsersUnlocked = true; + for (UserInfo user : rebootEscrowUsers) { + allUsersUnlocked &= restoreRebootEscrowForUser(user.id, escrowKey); + } + StatsLog.write(StatsLog.REBOOT_ESCROW_RECOVERY_REPORTED, allUsersUnlocked); + } + + private SecretKeySpec getAndClearRebootEscrowKey() { + IRebootEscrow rebootEscrow = mInjector.getRebootEscrow(); + if (rebootEscrow == null) { + return null; + } + + try { + byte[] escrowKeyBytes = rebootEscrow.retrieveKey(); + if (escrowKeyBytes == null) { + Slog.w(TAG, "Had reboot escrow data for users, but could not retrieve key"); + return null; + } else if (escrowKeyBytes.length != 32) { + Slog.e(TAG, "IRebootEscrow returned key of incorrect size " + + escrowKeyBytes.length); + return null; + } + + // Make sure we didn't get the null key. + int zero = 0; + for (int i = 0; i < escrowKeyBytes.length; i++) { + zero |= escrowKeyBytes[i]; + } + if (zero == 0) { + Slog.w(TAG, "IRebootEscrow returned an all-zeroes key"); + return null; + } + + // Overwrite the existing key with the null key + rebootEscrow.storeKey(new byte[32]); + + return RebootEscrowData.fromKeyBytes(escrowKeyBytes); + } catch (RemoteException e) { + Slog.w(TAG, "Could not retrieve escrow data"); + return null; + } + } + + private boolean restoreRebootEscrowForUser(@UserIdInt int userId, SecretKeySpec escrowKey) { + if (!mStorage.hasRebootEscrow(userId)) { + return false; + } + + try { + byte[] blob = mStorage.readRebootEscrow(userId); + mStorage.removeRebootEscrow(userId); + + RebootEscrowData escrowData = RebootEscrowData.fromEncryptedData(escrowKey, blob); + + mCallbacks.onRebootEscrowRestored(escrowData.getSpVersion(), + escrowData.getSyntheticPassword(), userId); + return true; + } catch (IOException e) { + Slog.w(TAG, "Could not load reboot escrow data for user " + userId, e); + } + return false; + } + + void callToRebootEscrowIfNeeded(@UserIdInt int userId, byte spVersion, + byte[] syntheticPassword) { + if (!mRebootEscrowWanted.compareAndSet(true, false)) { + return; + } + + IRebootEscrow rebootEscrow = mInjector.getRebootEscrow(); + if (rebootEscrow == null) { + setRebootEscrowReady(false); + return; + } + + final RebootEscrowData escrowData; + try { + escrowData = RebootEscrowData.fromSyntheticPassword(spVersion, syntheticPassword); + } catch (IOException e) { + setRebootEscrowReady(false); + Slog.w(TAG, "Could not escrow reboot data", e); + return; + } + + mPendingRebootEscrowData = escrowData; + mStorage.writeRebootEscrow(userId, escrowData.getBlob()); + + setRebootEscrowReady(true); + } + + private void clearRebootEscrowIfNeeded() { + mRebootEscrowWanted.set(false); + setRebootEscrowReady(false); + + IRebootEscrow rebootEscrow = mInjector.getRebootEscrow(); + if (rebootEscrow == null) { + return; + } + + try { + rebootEscrow.storeKey(new byte[32]); + } catch (RemoteException e) { + Slog.w(TAG, "Could not call RebootEscrow HAL to shred key"); + } + + List<UserInfo> users = mUserManager.getUsers(); + for (UserInfo user : users) { + mStorage.removeRebootEscrow(user.id); + } + } + + boolean armRebootEscrowIfNeeded() { + if (!mRebootEscrowReady) { + return false; + } + + IRebootEscrow rebootEscrow = mInjector.getRebootEscrow(); + if (rebootEscrow == null) { + return false; + } + + RebootEscrowData escrowData = mPendingRebootEscrowData; + if (escrowData == null) { + return false; + } + + boolean armedRebootEscrow = false; + try { + rebootEscrow.storeKey(escrowData.getKey()); + armedRebootEscrow = true; + } catch (RemoteException e) { + Slog.w(TAG, "Failed escrow secret to RebootEscrow HAL", e); + } + return armedRebootEscrow; + } + + private void setRebootEscrowReady(boolean ready) { + if (mRebootEscrowReady != ready) { + mRebootEscrowListener.onPreparedForReboot(ready); + } + mRebootEscrowReady = ready; + } + + boolean prepareRebootEscrow() { + if (mInjector.getRebootEscrow() == null) { + return false; + } + + clearRebootEscrowIfNeeded(); + mRebootEscrowWanted.set(true); + return true; + } + + boolean clearRebootEscrow() { + if (mInjector.getRebootEscrow() == null) { + return false; + } + + clearRebootEscrowIfNeeded(); + return true; + } + + void setRebootEscrowListener(RebootEscrowListener listener) { + mRebootEscrowListener = listener; + } +} diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java index 0fe16be2188e..b726e571cf0c 100644 --- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java +++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java @@ -38,7 +38,6 @@ import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; -import com.android.internal.util.Preconditions; import com.android.internal.widget.ICheckCredentialProgressCallback; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockscreenCredential; @@ -285,6 +284,14 @@ public class SyntheticPasswordManager { public byte[] getSyntheticPassword() { return mSyntheticPassword; } + + /** + * Returns the version of this AuthenticationToken for use with reconstructing + * this with a synthetic password version. + */ + public byte getVersion() { + return mVersion; + } } static class PasswordData { diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java index 29338ba06dc2..52750f392d5b 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java @@ -20,6 +20,7 @@ import static android.security.keystore.recovery.KeyChainProtectionParams.TYPE_L import android.content.Context; import android.os.RemoteException; +import android.os.UserHandle; import android.security.Scrypt; import android.security.keystore.recovery.KeyChainProtectionParams; import android.security.keystore.recovery.KeyChainSnapshot; @@ -163,16 +164,28 @@ public class KeySyncTask implements Runnable { } private void syncKeys() throws RemoteException { + int generation = mPlatformKeyManager.getGenerationId(mUserId); if (mCredentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) { // Application keys for the user will not be available for sync. Log.w(TAG, "Credentials are not set for user " + mUserId); - int generation = mPlatformKeyManager.getGenerationId(mUserId); - mPlatformKeyManager.invalidatePlatformKey(mUserId, generation); + if (generation < PlatformKeyManager.MIN_GENERATION_ID_FOR_UNLOCKED_DEVICE_REQUIRED + || mUserId != UserHandle.USER_SYSTEM) { + // Only invalidate keys with legacy protection param. + mPlatformKeyManager.invalidatePlatformKey(mUserId, generation); + } return; } if (isCustomLockScreen()) { - Log.w(TAG, "Unsupported credential type " + mCredentialType + "for user " + mUserId); - mRecoverableKeyStoreDb.invalidateKeysForUserIdOnCustomScreenLock(mUserId); + Log.w(TAG, "Unsupported credential type " + mCredentialType + " for user " + mUserId); + // Keys will be synced when user starts using non custom screen lock. + if (generation < PlatformKeyManager.MIN_GENERATION_ID_FOR_UNLOCKED_DEVICE_REQUIRED + || mUserId != UserHandle.USER_SYSTEM) { + mRecoverableKeyStoreDb.invalidateKeysForUserIdOnCustomScreenLock(mUserId); + } + return; + } + if (mPlatformKeyManager.isDeviceLocked(mUserId) && mUserId == UserHandle.USER_SYSTEM) { + Log.w(TAG, "Can't sync keys for locked user " + mUserId); return; } diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java index 0ad6c2a69556..0761cde825b6 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java @@ -67,8 +67,9 @@ import javax.crypto.spec.GCMParameterSpec; * @hide */ public class PlatformKeyManager { - private static final String TAG = "PlatformKeyManager"; + static final int MIN_GENERATION_ID_FOR_UNLOCKED_DEVICE_REQUIRED = 1000000; + private static final String TAG = "PlatformKeyManager"; private static final String KEY_ALGORITHM = "AES"; private static final int KEY_SIZE_BITS = 256; private static final String KEY_ALIAS_PREFIX = @@ -131,14 +132,14 @@ public class PlatformKeyManager { /** * Returns {@code true} if the platform key is available. A platform key won't be available if - * the user has not set up a lock screen. + * device is locked. * * @param userId The ID of the user to whose lock screen the platform key must be bound. * * @hide */ - public boolean isAvailable(int userId) { - return mContext.getSystemService(KeyguardManager.class).isDeviceSecure(userId); + public boolean isDeviceLocked(int userId) { + return mContext.getSystemService(KeyguardManager.class).isDeviceLocked(userId); } /** @@ -169,7 +170,6 @@ public class PlatformKeyManager { * @param userId The ID of the user to whose lock screen the platform key must be bound. * @throws NoSuchAlgorithmException if AES is unavailable - should never happen. * @throws KeyStoreException if there is an error in AndroidKeyStore. - * @throws InsecureUserException if the user does not have a lock screen set. * @throws IOException if there was an issue with local database update. * @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}. * @@ -177,13 +177,8 @@ public class PlatformKeyManager { */ @VisibleForTesting void regenerate(int userId) - throws NoSuchAlgorithmException, KeyStoreException, InsecureUserException, IOException, + throws NoSuchAlgorithmException, KeyStoreException, IOException, RemoteException { - if (!isAvailable(userId)) { - throw new InsecureUserException(String.format( - Locale.US, "%d does not have a lock screen set.", userId)); - } - int generationId = getGenerationId(userId); int nextId; if (generationId == -1) { @@ -192,6 +187,7 @@ public class PlatformKeyManager { invalidatePlatformKey(userId, generationId); nextId = generationId + 1; } + generationId = Math.max(generationId, MIN_GENERATION_ID_FOR_UNLOCKED_DEVICE_REQUIRED); generateAndLoadKey(userId, nextId); } @@ -203,7 +199,6 @@ public class PlatformKeyManager { * @throws KeyStoreException if there was an AndroidKeyStore error. * @throws UnrecoverableKeyException if the key could not be recovered. * @throws NoSuchAlgorithmException if AES is unavailable - should never occur. - * @throws InsecureUserException if the user does not have a lock screen set. * @throws IOException if there was an issue with local database update. * @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}. * @@ -211,7 +206,7 @@ public class PlatformKeyManager { */ public PlatformEncryptionKey getEncryptKey(int userId) throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, - InsecureUserException, IOException, RemoteException { + IOException, RemoteException { init(userId); try { // Try to see if the decryption key is still accessible before using the encryption key. @@ -234,12 +229,11 @@ public class PlatformKeyManager { * @throws KeyStoreException if there was an AndroidKeyStore error. * @throws UnrecoverableKeyException if the key could not be recovered. * @throws NoSuchAlgorithmException if AES is unavailable - should never occur. - * @throws InsecureUserException if the user does not have a lock screen set. * * @hide */ private PlatformEncryptionKey getEncryptKeyInternal(int userId) throws KeyStoreException, - UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException { + UnrecoverableKeyException, NoSuchAlgorithmException { int generationId = getGenerationId(userId); String alias = getEncryptAlias(userId, generationId); if (!isKeyLoaded(userId, generationId)) { @@ -258,7 +252,6 @@ public class PlatformKeyManager { * @throws KeyStoreException if there was an AndroidKeyStore error. * @throws UnrecoverableKeyException if the key could not be recovered. * @throws NoSuchAlgorithmException if AES is unavailable - should never occur. - * @throws InsecureUserException if the user does not have a lock screen set. * @throws IOException if there was an issue with local database update. * @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}. * @@ -266,7 +259,7 @@ public class PlatformKeyManager { */ public PlatformDecryptionKey getDecryptKey(int userId) throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, - InsecureUserException, IOException, RemoteException { + IOException, RemoteException { init(userId); try { PlatformDecryptionKey decryptionKey = getDecryptKeyInternal(userId); @@ -288,12 +281,11 @@ public class PlatformKeyManager { * @throws KeyStoreException if there was an AndroidKeyStore error. * @throws UnrecoverableKeyException if the key could not be recovered. * @throws NoSuchAlgorithmException if AES is unavailable - should never occur. - * @throws InsecureUserException if the user does not have a lock screen set. * * @hide */ private PlatformDecryptionKey getDecryptKeyInternal(int userId) throws KeyStoreException, - UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException { + UnrecoverableKeyException, NoSuchAlgorithmException { int generationId = getGenerationId(userId); String alias = getDecryptAlias(userId, generationId); if (!isKeyLoaded(userId, generationId)) { @@ -340,13 +332,8 @@ public class PlatformKeyManager { * @hide */ void init(int userId) - throws KeyStoreException, NoSuchAlgorithmException, InsecureUserException, IOException, + throws KeyStoreException, NoSuchAlgorithmException, IOException, RemoteException { - if (!isAvailable(userId)) { - throw new InsecureUserException(String.format( - Locale.US, "%d does not have a lock screen set.", userId)); - } - int generationId = getGenerationId(userId); if (isKeyLoaded(userId, generationId)) { Log.i(TAG, String.format( @@ -363,6 +350,7 @@ public class PlatformKeyManager { generationId++; } + generationId = Math.max(generationId, MIN_GENERATION_ID_FOR_UNLOCKED_DEVICE_REQUIRED); generateAndLoadKey(userId, generationId); } @@ -440,12 +428,16 @@ public class PlatformKeyManager { KeyProtection.Builder decryptionKeyProtection = new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT) - .setUserAuthenticationRequired(true) - .setUserAuthenticationValidityDurationSeconds( - USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS) .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE); - if (userId != UserHandle.USER_SYSTEM) { + // Skip UserAuthenticationRequired for main user + if (userId == UserHandle.USER_SYSTEM) { + decryptionKeyProtection.setUnlockedDeviceRequired(true); + } else { + // With setUnlockedDeviceRequired, KeyStore thinks that device is locked . + decryptionKeyProtection.setUserAuthenticationRequired(true); + decryptionKeyProtection.setUserAuthenticationValidityDurationSeconds( + USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS); // Bind decryption key to secondary profile lock screen secret. long secureUserId = getGateKeeperService().getSecureUserId(userId); // TODO(b/124095438): Propagate this failure instead of silently failing. diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java index 383d5cf326c0..6d97ed7a69a7 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java @@ -19,7 +19,6 @@ package com.android.server.locksettings.recoverablekeystore; import static android.security.keystore.recovery.RecoveryController.ERROR_BAD_CERTIFICATE_FORMAT; import static android.security.keystore.recovery.RecoveryController.ERROR_DECRYPTION_FAILED; import static android.security.keystore.recovery.RecoveryController.ERROR_DOWNGRADE_CERTIFICATE; -import static android.security.keystore.recovery.RecoveryController.ERROR_INSECURE_USER; import static android.security.keystore.recovery.RecoveryController.ERROR_INVALID_CERTIFICATE; import static android.security.keystore.recovery.RecoveryController.ERROR_INVALID_KEY_FORMAT; import static android.security.keystore.recovery.RecoveryController.ERROR_NO_SNAPSHOT_PENDING; @@ -46,7 +45,6 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.HexDump; -import com.android.internal.util.Preconditions; import com.android.server.locksettings.recoverablekeystore.certificate.CertParsingException; import com.android.server.locksettings.recoverablekeystore.certificate.CertUtils; import com.android.server.locksettings.recoverablekeystore.certificate.CertValidationException; @@ -76,8 +74,9 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; -import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; import javax.crypto.AEADBadTagException; @@ -89,13 +88,14 @@ import javax.crypto.AEADBadTagException; */ public class RecoverableKeyStoreManager { private static final String TAG = "RecoverableKeyStoreMgr"; + private static final long SYNC_DELAY_MILLIS = 2000; private static RecoverableKeyStoreManager mInstance; private final Context mContext; private final RecoverableKeyStoreDb mDatabase; private final RecoverySessionStorage mRecoverySessionStorage; - private final ExecutorService mExecutorService; + private final ScheduledExecutorService mExecutorService; private final RecoverySnapshotListenersStorage mListenersStorage; private final RecoverableKeyGenerator mRecoverableKeyGenerator; private final RecoverySnapshotStorage mSnapshotStorage; @@ -136,7 +136,7 @@ public class RecoverableKeyStoreManager { context.getApplicationContext(), db, new RecoverySessionStorage(), - Executors.newSingleThreadExecutor(), + Executors.newScheduledThreadPool(1), snapshotStorage, new RecoverySnapshotListenersStorage(), platformKeyManager, @@ -152,7 +152,7 @@ public class RecoverableKeyStoreManager { Context context, RecoverableKeyStoreDb recoverableKeyStoreDb, RecoverySessionStorage recoverySessionStorage, - ExecutorService executorService, + ScheduledExecutorService executorService, RecoverySnapshotStorage snapshotStorage, RecoverySnapshotListenersStorage listenersStorage, PlatformKeyManager platformKeyManager, @@ -724,8 +724,6 @@ public class RecoverableKeyStoreManager { throw new RuntimeException(e); } catch (KeyStoreException | UnrecoverableKeyException | IOException e) { throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage()); - } catch (InsecureUserException e) { - throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage()); } try { @@ -793,8 +791,6 @@ public class RecoverableKeyStoreManager { throw new RuntimeException(e); } catch (KeyStoreException | UnrecoverableKeyException | IOException e) { throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage()); - } catch (InsecureUserException e) { - throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage()); } try { @@ -915,7 +911,7 @@ public class RecoverableKeyStoreManager { int storedHashType, @NonNull byte[] credential, int userId) { // So as not to block the critical path unlocking the phone, defer to another thread. try { - mExecutorService.execute(KeySyncTask.newInstance( + mExecutorService.schedule(KeySyncTask.newInstance( mContext, mDatabase, mSnapshotStorage, @@ -923,7 +919,10 @@ public class RecoverableKeyStoreManager { userId, storedHashType, credential, - /*credentialUpdated=*/ false)); + /*credentialUpdated=*/ false), + SYNC_DELAY_MILLIS, + TimeUnit.MILLISECONDS + ); } catch (NoSuchAlgorithmException e) { Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e); } catch (KeyStoreException e) { @@ -947,7 +946,7 @@ public class RecoverableKeyStoreManager { int userId) { // So as not to block the critical path unlocking the phone, defer to another thread. try { - mExecutorService.execute(KeySyncTask.newInstance( + mExecutorService.schedule(KeySyncTask.newInstance( mContext, mDatabase, mSnapshotStorage, @@ -955,7 +954,10 @@ public class RecoverableKeyStoreManager { userId, storedHashType, credential, - /*credentialUpdated=*/ true)); + /*credentialUpdated=*/ true), + SYNC_DELAY_MILLIS, + TimeUnit.MILLISECONDS + ); } catch (NoSuchAlgorithmException e) { Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e); } catch (KeyStoreException e) { diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java index 115155ce24b5..9c9a4121830f 100644 --- a/services/core/java/com/android/server/media/MediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java @@ -20,7 +20,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; import android.content.Intent; -import android.media.MediaRoute2Info; import android.media.MediaRoute2ProviderInfo; import android.media.RouteSessionInfo; @@ -47,16 +46,16 @@ abstract class MediaRoute2Provider { } public abstract void requestCreateSession(String packageName, String routeId, - String controlCategory, long requestId); - public abstract void releaseSession(int sessionId); + String routeType, long requestId); + public abstract void releaseSession(String sessionId); - public abstract void selectRoute(int sessionId, MediaRoute2Info route); - public abstract void deselectRoute(int sessionId, MediaRoute2Info route); - public abstract void transferToRoute(int sessionId, MediaRoute2Info route); + public abstract void selectRoute(String sessionId, String routeId); + public abstract void deselectRoute(String sessionId, String routeId); + public abstract void transferToRoute(String sessionId, String routeId); - public abstract void sendControlRequest(MediaRoute2Info route, Intent request); - public abstract void requestSetVolume(MediaRoute2Info route, int volume); - public abstract void requestUpdateVolume(MediaRoute2Info route, int delta); + public abstract void sendControlRequest(String routeId, Intent request); + public abstract void requestSetVolume(String routeId, int volume); + public abstract void requestUpdateVolume(String routeId, int delta); @NonNull public String getUniqueId() { diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java index f8d8f9fd5fbd..635983575226 100644 --- a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java +++ b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java @@ -24,7 +24,6 @@ import android.content.Intent; import android.content.ServiceConnection; import android.media.IMediaRoute2Provider; import android.media.IMediaRoute2ProviderClient; -import android.media.MediaRoute2Info; import android.media.MediaRoute2ProviderInfo; import android.media.MediaRoute2ProviderService; import android.media.RouteSessionInfo; @@ -77,17 +76,17 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv } @Override - public void requestCreateSession(String packageName, String routeId, String controlCategory, + public void requestCreateSession(String packageName, String routeId, String routeType, long requestId) { if (mConnectionReady) { - mActiveConnection.requestCreateSession(packageName, routeId, controlCategory, + mActiveConnection.requestCreateSession(packageName, routeId, routeType, requestId); updateBinding(); } } @Override - public void releaseSession(int sessionId) { + public void releaseSession(String sessionId) { if (mConnectionReady) { mActiveConnection.releaseSession(sessionId); updateBinding(); @@ -95,46 +94,46 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv } @Override - public void selectRoute(int sessionId, MediaRoute2Info route) { + public void selectRoute(String sessionId, String routeId) { if (mConnectionReady) { - mActiveConnection.selectRoute(sessionId, route.getId()); + mActiveConnection.selectRoute(sessionId, routeId); } } @Override - public void deselectRoute(int sessionId, MediaRoute2Info route) { + public void deselectRoute(String sessionId, String routeId) { if (mConnectionReady) { - mActiveConnection.deselectRoute(sessionId, route.getId()); + mActiveConnection.deselectRoute(sessionId, routeId); } } @Override - public void transferToRoute(int sessionId, MediaRoute2Info route) { + public void transferToRoute(String sessionId, String routeId) { if (mConnectionReady) { - mActiveConnection.transferToRoute(sessionId, route.getId()); + mActiveConnection.transferToRoute(sessionId, routeId); } } @Override - public void sendControlRequest(MediaRoute2Info route, Intent request) { + public void sendControlRequest(String routeId, Intent request) { if (mConnectionReady) { - mActiveConnection.sendControlRequest(route.getId(), request); + mActiveConnection.sendControlRequest(routeId, request); updateBinding(); } } @Override - public void requestSetVolume(MediaRoute2Info route, int volume) { + public void requestSetVolume(String routeId, int volume) { if (mConnectionReady) { - mActiveConnection.requestSetVolume(route.getId(), volume); + mActiveConnection.requestSetVolume(routeId, volume); updateBinding(); } } @Override - public void requestUpdateVolume(MediaRoute2Info route, int delta) { + public void requestUpdateVolume(String routeId, int delta) { if (mConnectionReady) { - mActiveConnection.requestUpdateVolume(route.getId(), delta); + mActiveConnection.requestUpdateVolume(routeId, delta); updateBinding(); } } @@ -303,6 +302,11 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv + mComponentName); return; } + + sessionInfo = new RouteSessionInfo.Builder(sessionInfo) + .setProviderId(getUniqueId()) + .build(); + mCallback.onSessionInfoChanged(this, sessionInfo); } @@ -346,17 +350,17 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv mClient.dispose(); } - public void requestCreateSession(String packageName, String routeId, String controlCategory, + public void requestCreateSession(String packageName, String routeId, String routeType, long requestId) { try { mProvider.requestCreateSession(packageName, routeId, - controlCategory, requestId); + routeType, requestId); } catch (RemoteException ex) { Slog.e(TAG, "Failed to deliver request to create a session.", ex); } } - public void releaseSession(int sessionId) { + public void releaseSession(String sessionId) { try { mProvider.releaseSession(sessionId); } catch (RemoteException ex) { @@ -364,7 +368,7 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv } } - public void selectRoute(int sessionId, String routeId) { + public void selectRoute(String sessionId, String routeId) { try { mProvider.selectRoute(sessionId, routeId); } catch (RemoteException ex) { @@ -372,7 +376,7 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv } } - public void deselectRoute(int sessionId, String routeId) { + public void deselectRoute(String sessionId, String routeId) { try { mProvider.deselectRoute(sessionId, routeId); } catch (RemoteException ex) { @@ -380,7 +384,7 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv } } - public void transferToRoute(int sessionId, String routeId) { + public void transferToRoute(String sessionId, String routeId) { try { mProvider.transferToRoute(sessionId, routeId); } catch (RemoteException ex) { diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index 22ba1bf1a851..487ab5201278 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -16,6 +16,9 @@ package com.android.server.media; +import static android.media.MediaRouter2Utils.getOriginalId; +import static android.media.MediaRouter2Utils.getProviderId; + import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; import android.annotation.NonNull; @@ -28,6 +31,7 @@ import android.media.IMediaRouter2Client; import android.media.IMediaRouter2Manager; import android.media.MediaRoute2Info; import android.media.MediaRoute2ProviderInfo; +import android.media.RouteDiscoveryRequest; import android.media.RouteSessionInfo; import android.os.Binder; import android.os.Bundle; @@ -79,7 +83,6 @@ class MediaRouter2ServiceImpl { @GuardedBy("mLock") private int mCurrentUserId = -1; - MediaRouter2ServiceImpl(Context context) { mContext = context; } @@ -89,17 +92,23 @@ class MediaRouter2ServiceImpl { final int uid = Binder.getCallingUid(); final int userId = UserHandle.getUserId(uid); - Collection<MediaRoute2Info> systemRoutes; - synchronized (mLock) { - UserRecord userRecord = mUserRecords.get(userId); - if (userRecord == null) { - userRecord = new UserRecord(userId); - mUserRecords.put(userId, userRecord); - initializeUserLocked(userRecord); + final long token = Binder.clearCallingIdentity(); + try { + Collection<MediaRoute2Info> systemRoutes; + synchronized (mLock) { + UserRecord userRecord = getOrCreateUserRecordLocked(userId); + MediaRoute2ProviderInfo providerInfo = + userRecord.mHandler.mSystemProvider.getProviderInfo(); + if (providerInfo != null) { + systemRoutes = providerInfo.getRoutes(); + } else { + systemRoutes = Collections.emptyList(); + } } - systemRoutes = userRecord.mHandler.mSystemProvider.getProviderInfo().getRoutes(); + return new ArrayList<>(systemRoutes); + } finally { + Binder.restoreCallingIdentity(token); } - return new ArrayList<>(systemRoutes); } public void registerClient(@NonNull IMediaRouter2Client client, @@ -169,18 +178,18 @@ class MediaRouter2ServiceImpl { } public void requestCreateSession(IMediaRouter2Client client, MediaRoute2Info route, - String controlCategory, int requestId) { + String routeType, int requestId) { Objects.requireNonNull(client, "client must not be null"); Objects.requireNonNull(route, "route must not be null"); - if (TextUtils.isEmpty(controlCategory)) { - throw new IllegalArgumentException("controlCategory must not be empty"); + if (TextUtils.isEmpty(routeType)) { + throw new IllegalArgumentException("routeType must not be empty"); } final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { - requestCreateSessionLocked(client, route, controlCategory, requestId); + requestCreateSessionLocked(client, route, routeType, requestId); } } finally { Binder.restoreCallingIdentity(token); @@ -191,6 +200,9 @@ class MediaRouter2ServiceImpl { MediaRoute2Info route) { Objects.requireNonNull(client, "client must not be null"); Objects.requireNonNull(route, "route must not be null"); + if (TextUtils.isEmpty(uniqueSessionId)) { + throw new IllegalArgumentException("uniqueSessionId must not be empty"); + } final long token = Binder.clearCallingIdentity(); try { @@ -207,6 +219,9 @@ class MediaRouter2ServiceImpl { MediaRoute2Info route) { Objects.requireNonNull(client, "client must not be null"); Objects.requireNonNull(route, "route must not be null"); + if (TextUtils.isEmpty(uniqueSessionId)) { + throw new IllegalArgumentException("uniqueSessionId must not be empty"); + } final long token = Binder.clearCallingIdentity(); try { @@ -222,6 +237,9 @@ class MediaRouter2ServiceImpl { MediaRoute2Info route) { Objects.requireNonNull(client, "client must not be null"); Objects.requireNonNull(route, "route must not be null"); + if (TextUtils.isEmpty(uniqueSessionId)) { + throw new IllegalArgumentException("uniqueSessionId must not be empty"); + } final long token = Binder.clearCallingIdentity(); try { @@ -235,6 +253,9 @@ class MediaRouter2ServiceImpl { public void releaseSession(IMediaRouter2Client client, String uniqueSessionId) { Objects.requireNonNull(client, "client must not be null"); + if (TextUtils.isEmpty(uniqueSessionId)) { + throw new IllegalArgumentException("uniqueSessionId must not be empty"); + } final long token = Binder.clearCallingIdentity(); try { @@ -262,16 +283,16 @@ class MediaRouter2ServiceImpl { } } - public void setControlCategories(@NonNull IMediaRouter2Client client, - @NonNull List<String> categories) { + public void setDiscoveryRequest2(@NonNull IMediaRouter2Client client, + @NonNull RouteDiscoveryRequest request) { Objects.requireNonNull(client, "client must not be null"); - Objects.requireNonNull(categories, "categories must not be null"); + Objects.requireNonNull(request, "request must not be null"); final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { Client2Record clientRecord = mAllClientRecords.get(client.asBinder()); - setControlCategoriesLocked(clientRecord, categories); + setDiscoveryRequestLocked(clientRecord, request); } } finally { Binder.restoreCallingIdentity(token); @@ -400,12 +421,7 @@ class MediaRouter2ServiceImpl { int uid, int pid, String packageName, int userId, boolean trusted) { final IBinder binder = client.asBinder(); if (mAllClientRecords.get(binder) == null) { - UserRecord userRecord = mUserRecords.get(userId); - if (userRecord == null) { - userRecord = new UserRecord(userId); - mUserRecords.put(userId, userRecord); - initializeUserLocked(userRecord); - } + UserRecord userRecord = getOrCreateUserRecordLocked(userId); Client2Record clientRecord = new Client2Record(userRecord, client, uid, pid, packageName, trusted); try { @@ -434,7 +450,7 @@ class MediaRouter2ServiceImpl { } private void requestCreateSessionLocked(@NonNull IMediaRouter2Client client, - @NonNull MediaRoute2Info route, @NonNull String controlCategory, long requestId) { + @NonNull MediaRoute2Info route, @NonNull String routeType, long requestId) { final IBinder binder = client.asBinder(); final Client2Record clientRecord = mAllClientRecords.get(binder); @@ -447,7 +463,7 @@ class MediaRouter2ServiceImpl { clientRecord.mUserRecord.mHandler.sendMessage( obtainMessage(UserHandler::requestCreateSessionOnHandler, clientRecord.mUserRecord.mHandler, - clientRecord, route, controlCategory, requestId)); + clientRecord, route, routeType, requestId)); } } @@ -502,13 +518,14 @@ class MediaRouter2ServiceImpl { } } - private void setControlCategoriesLocked(Client2Record clientRecord, List<String> categories) { + private void setDiscoveryRequestLocked(Client2Record clientRecord, + RouteDiscoveryRequest discoveryRequest) { if (clientRecord != null) { - if (clientRecord.mControlCategories.equals(categories)) { + if (clientRecord.mDiscoveryRequest.equals(discoveryRequest)) { return; } - clientRecord.mControlCategories = categories; + clientRecord.mDiscoveryRequest = discoveryRequest; clientRecord.mUserRecord.mHandler.sendMessage( obtainMessage(UserHandler::updateClientUsage, clientRecord.mUserRecord.mHandler, clientRecord)); @@ -556,12 +573,7 @@ class MediaRouter2ServiceImpl { final IBinder binder = manager.asBinder(); ManagerRecord managerRecord = mAllManagerRecords.get(binder); if (managerRecord == null) { - boolean newUser = false; - UserRecord userRecord = mUserRecords.get(userId); - if (userRecord == null) { - userRecord = new UserRecord(userId); - newUser = true; - } + UserRecord userRecord = getOrCreateUserRecordLocked(userId); managerRecord = new ManagerRecord(userRecord, manager, uid, pid, packageName, trusted); try { binder.linkToDeath(managerRecord, 0); @@ -569,11 +581,6 @@ class MediaRouter2ServiceImpl { throw new RuntimeException("Media router manager died prematurely.", ex); } - if (newUser) { - mUserRecords.put(userId, userRecord); - initializeUserLocked(userRecord); - } - userRecord.mManagerRecords.add(managerRecord); mAllManagerRecords.put(binder, managerRecord); @@ -615,9 +622,9 @@ class MediaRouter2ServiceImpl { } long uniqueRequestId = toUniqueRequestId(managerRecord.mClientId, requestId); if (clientRecord != null && managerRecord.mTrusted) { - //TODO: select category properly + //TODO: select route type properly requestCreateSessionLocked(clientRecord.mClient, route, - route.getSupportedCategories().get(0), uniqueRequestId); + route.getRouteTypes().get(0), uniqueRequestId); } } } @@ -661,14 +668,17 @@ class MediaRouter2ServiceImpl { return sessionInfos; } - private void initializeUserLocked(UserRecord userRecord) { - if (DEBUG) { - Slog.d(TAG, userRecord + ": Initialized"); - } - if (userRecord.mUserId == mCurrentUserId) { - userRecord.mHandler.sendMessage( - obtainMessage(UserHandler::start, userRecord.mHandler)); + private UserRecord getOrCreateUserRecordLocked(int userId) { + UserRecord userRecord = mUserRecords.get(userId); + if (userRecord == null) { + userRecord = new UserRecord(userId); + mUserRecords.put(userId, userRecord); + if (userId == mCurrentUserId) { + userRecord.mHandler.sendMessage( + obtainMessage(UserHandler::start, userRecord.mHandler)); + } } + return userRecord; } private void disposeUserIfNeededLocked(UserRecord userRecord) { @@ -732,7 +742,7 @@ class MediaRouter2ServiceImpl { public final boolean mTrusted; public final int mClientId; - public List<String> mControlCategories; + public RouteDiscoveryRequest mDiscoveryRequest; public boolean mIsManagerSelecting; public MediaRoute2Info mSelectingRoute; public MediaRoute2Info mSelectedRoute; @@ -742,7 +752,7 @@ class MediaRouter2ServiceImpl { mUserRecord = userRecord; mPackageName = packageName; mSelectRouteSequenceNumbers = new ArrayList<>(); - mControlCategories = Collections.emptyList(); + mDiscoveryRequest = RouteDiscoveryRequest.EMPTY; mClient = client; mUid = uid; mPid = pid; @@ -968,7 +978,7 @@ class MediaRouter2ServiceImpl { } private void requestCreateSessionOnHandler(Client2Record clientRecord, - MediaRoute2Info route, String controlCategory, long requestId) { + MediaRoute2Info route, String routeType, long requestId) { final MediaRoute2Provider provider = findProvider(route.getProviderId()); if (provider == null) { @@ -978,20 +988,20 @@ class MediaRouter2ServiceImpl { return; } - if (!route.getSupportedCategories().contains(controlCategory)) { + if (!route.getRouteTypes().contains(routeType)) { Slog.w(TAG, "Ignoring session creation request since the given route=" + route - + " doesn't support the given category=" + controlCategory); + + " doesn't support the given type=" + routeType); notifySessionCreationFailed(clientRecord, toClientRequestId(requestId)); return; } // TODO: Apply timeout for each request (How many seconds should we wait?) SessionCreationRequest request = new SessionCreationRequest( - clientRecord, route, controlCategory, requestId); + clientRecord, route, routeType, requestId); mSessionCreationRequests.add(request); - provider.requestCreateSession(clientRecord.mPackageName, route.getId(), - controlCategory, requestId); + provider.requestCreateSession(clientRecord.mPackageName, route.getOriginalId(), + routeType, requestId); } private void selectRouteOnHandler(@NonNull Client2Record clientRecord, @@ -1007,7 +1017,7 @@ class MediaRouter2ServiceImpl { if (provider == null) { return; } - provider.selectRoute(RouteSessionInfo.getSessionId(uniqueSessionId), route); + provider.selectRoute(getOriginalId(uniqueSessionId), route.getOriginalId()); } private void deselectRouteOnHandler(@NonNull Client2Record clientRecord, @@ -1023,7 +1033,7 @@ class MediaRouter2ServiceImpl { if (provider == null) { return; } - provider.deselectRoute(RouteSessionInfo.getSessionId(uniqueSessionId), route); + provider.deselectRoute(getOriginalId(uniqueSessionId), route.getOriginalId()); } private void transferToRouteOnHandler(@NonNull Client2Record clientRecord, @@ -1039,7 +1049,8 @@ class MediaRouter2ServiceImpl { if (provider == null) { return; } - provider.transferToRoute(RouteSessionInfo.getSessionId(uniqueSessionId), route); + provider.transferToRoute(getOriginalId(uniqueSessionId), + route.getOriginalId()); } private boolean checkArgumentsForSessionControl(@NonNull Client2Record clientRecord, @@ -1070,9 +1081,9 @@ class MediaRouter2ServiceImpl { return false; } - final Integer sessionId = RouteSessionInfo.getSessionId(uniqueSessionId); + final String sessionId = getOriginalId(uniqueSessionId); if (sessionId == null) { - Slog.w(TAG, "Failed to get int session id from unique session id. " + Slog.w(TAG, "Failed to get original session id from unique session id. " + "uniqueSessionId=" + uniqueSessionId); return false; } @@ -1095,14 +1106,14 @@ class MediaRouter2ServiceImpl { return; } - final String providerId = RouteSessionInfo.getProviderId(uniqueSessionId); + final String providerId = getProviderId(uniqueSessionId); if (providerId == null) { Slog.w(TAG, "Ignoring releasing session with invalid unique session ID. " + "uniqueSessionId=" + uniqueSessionId); return; } - final Integer sessionId = RouteSessionInfo.getSessionId(uniqueSessionId); + final String sessionId = getOriginalId(uniqueSessionId); if (sessionId == null) { Slog.w(TAG, "Ignoring releasing session with invalid unique session ID. " + "uniqueSessionId=" + uniqueSessionId + " providerId=" + providerId); @@ -1148,15 +1159,15 @@ class MediaRouter2ServiceImpl { } String originalRouteId = matchingRequest.mRoute.getId(); - String originalCategory = matchingRequest.mControlCategory; + String originalRouteType = matchingRequest.mRouteType; Client2Record client2Record = matchingRequest.mClientRecord; if (!sessionInfo.getSelectedRoutes().contains(originalRouteId) - || !TextUtils.equals(originalCategory, - sessionInfo.getControlCategory())) { + || !TextUtils.equals(originalRouteType, + sessionInfo.getRouteType())) { Slog.w(TAG, "Created session doesn't match the original request." + " originalRouteId=" + originalRouteId - + ", originalCategory=" + originalCategory + ", requestId=" + requestId + + ", originalRouteType=" + originalRouteType + ", requestId=" + requestId + ", sessionInfo=" + sessionInfo); notifySessionCreationFailed(matchingRequest.mClientRecord, toClientRequestId(requestId)); @@ -1166,41 +1177,34 @@ class MediaRouter2ServiceImpl { // Succeeded notifySessionCreated(matchingRequest.mClientRecord, sessionInfo, toClientRequestId(requestId)); - mSessionToClientMap.put(sessionInfo.getUniqueSessionId(), client2Record); + mSessionToClientMap.put(sessionInfo.getId(), client2Record); // TODO: Tell managers for the session creation } private void onSessionInfoChangedOnHandler(@NonNull MediaRoute2Provider provider, @NonNull RouteSessionInfo sessionInfo) { - RouteSessionInfo sessionInfoWithProviderId = new RouteSessionInfo.Builder(sessionInfo) - .setProviderId(provider.getUniqueId()) - .build(); Client2Record client2Record = mSessionToClientMap.get( - sessionInfoWithProviderId.getUniqueSessionId()); + sessionInfo.getId()); if (client2Record == null) { - Slog.w(TAG, "No matching client found for session=" + sessionInfoWithProviderId); + Slog.w(TAG, "No matching client found for session=" + sessionInfo); // TODO: Tell managers for the session update return; } - notifySessionInfoChanged(client2Record, sessionInfoWithProviderId); + notifySessionInfoChanged(client2Record, sessionInfo); // TODO: Tell managers for the session update } private void onSessionReleasedOnHandler(@NonNull MediaRoute2Provider provider, @NonNull RouteSessionInfo sessionInfo) { - RouteSessionInfo sessionInfoWithProviderId = new RouteSessionInfo.Builder(sessionInfo) - .setProviderId(provider.getUniqueId()) - .build(); - Client2Record client2Record = mSessionToClientMap.get( - sessionInfoWithProviderId.getUniqueSessionId()); + Client2Record client2Record = mSessionToClientMap.get(sessionInfo.getId()); if (client2Record == null) { - Slog.w(TAG, "No matching client found for session=" + sessionInfoWithProviderId); + Slog.w(TAG, "No matching client found for session=" + sessionInfo); // TODO: Tell managers for the session release return; } - notifySessionReleased(client2Record, sessionInfoWithProviderId); + notifySessionReleased(client2Record, sessionInfo); // TODO: Tell managers for the session release } @@ -1246,21 +1250,21 @@ class MediaRouter2ServiceImpl { private void sendControlRequest(MediaRoute2Info route, Intent request) { final MediaRoute2Provider provider = findProvider(route.getProviderId()); if (provider != null) { - provider.sendControlRequest(route, request); + provider.sendControlRequest(route.getOriginalId(), request); } } private void requestSetVolume(MediaRoute2Info route, int volume) { final MediaRoute2Provider provider = findProvider(route.getProviderId()); if (provider != null) { - provider.requestSetVolume(route, volume); + provider.requestSetVolume(route.getOriginalId(), volume); } } private void requestUpdateVolume(MediaRoute2Info route, int delta) { final MediaRoute2Provider provider = findProvider(route.getProviderId()); if (provider != null) { - provider.requestUpdateVolume(route, delta); + provider.requestUpdateVolume(route.getOriginalId(), delta); } } @@ -1408,8 +1412,8 @@ class MediaRouter2ServiceImpl { try { manager.notifyRouteSelected(clientRecord.mPackageName, clientRecord.mSelectedRoute); - manager.notifyControlCategoriesChanged(clientRecord.mPackageName, - clientRecord.mControlCategories); + manager.notifyRouteTypesChanged(clientRecord.mPackageName, + clientRecord.mDiscoveryRequest.getRouteTypes()); } catch (RemoteException ex) { Slog.w(TAG, "Failed to update client usage. Manager probably died.", ex); } @@ -1428,15 +1432,15 @@ class MediaRouter2ServiceImpl { final class SessionCreationRequest { public final Client2Record mClientRecord; public final MediaRoute2Info mRoute; - public final String mControlCategory; + public final String mRouteType; public final long mRequestId; SessionCreationRequest(@NonNull Client2Record clientRecord, @NonNull MediaRoute2Info route, - @NonNull String controlCategory, long requestId) { + @NonNull String routeType, long requestId) { mClientRecord = clientRecord; mRoute = route; - mControlCategory = controlCategory; + mRouteType = routeType; mRequestId = requestId; } } diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java index d77f43b4435d..c76555cf15cf 100644 --- a/services/core/java/com/android/server/media/MediaRouterService.java +++ b/services/core/java/com/android/server/media/MediaRouterService.java @@ -39,6 +39,7 @@ import android.media.MediaRouter; import android.media.MediaRouterClientState; import android.media.RemoteDisplayState; import android.media.RemoteDisplayState.RemoteDisplayInfo; +import android.media.RouteDiscoveryRequest; import android.media.RouteSessionInfo; import android.os.Binder; import android.os.Handler; @@ -459,8 +460,8 @@ public final class MediaRouterService extends IMediaRouterService.Stub // Binder call @Override public void requestCreateSession(IMediaRouter2Client client, MediaRoute2Info route, - String controlCategory, int requestId) { - mService2.requestCreateSession(client, route, controlCategory, requestId); + String routeType, int requestId) { + mService2.requestCreateSession(client, route, routeType, requestId); } // Binder call @@ -519,8 +520,8 @@ public final class MediaRouterService extends IMediaRouterService.Stub } // Binder call @Override - public void setControlCategories(IMediaRouter2Client client, List<String> categories) { - mService2.setControlCategories(client, categories); + public void setDiscoveryRequest2(IMediaRouter2Client client, RouteDiscoveryRequest request) { + mService2.setDiscoveryRequest2(client, request); } // Binder call diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java new file mode 100644 index 000000000000..f3241ee44569 --- /dev/null +++ b/services/core/java/com/android/server/media/MediaSession2Record.java @@ -0,0 +1,179 @@ +/* + * 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.media; + +import android.media.MediaController2; +import android.media.Session2CommandGroup; +import android.media.Session2Token; +import android.os.Handler; +import android.os.HandlerExecutor; +import android.os.Looper; +import android.os.ResultReceiver; +import android.os.UserHandle; +import android.util.Log; +import android.view.KeyEvent; + +import com.android.internal.annotations.GuardedBy; + +import java.io.PrintWriter; + +/** + * Keeps the record of {@link Session2Token} helps to send command to the corresponding session. + */ +// TODO(jaewan): Do not call service method directly -- introduce listener instead. +public class MediaSession2Record implements MediaSessionRecordImpl { + private static final String TAG = "MediaSession2Record"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + private final Object mLock = new Object(); + + @GuardedBy("mLock") + private final Session2Token mSessionToken; + @GuardedBy("mLock") + private final HandlerExecutor mHandlerExecutor; + @GuardedBy("mLock") + private final MediaController2 mController; + @GuardedBy("mLock") + private final MediaSessionService mService; + @GuardedBy("mLock") + private boolean mIsConnected; + + public MediaSession2Record(Session2Token sessionToken, MediaSessionService service, + Looper handlerLooper) { + mSessionToken = sessionToken; + mService = service; + mHandlerExecutor = new HandlerExecutor(new Handler(handlerLooper)); + mController = new MediaController2.Builder(service.getContext(), sessionToken) + .setControllerCallback(mHandlerExecutor, new Controller2Callback()) + .build(); + } + + @Override + public String getPackageName() { + return mSessionToken.getPackageName(); + } + + public Session2Token getSession2Token() { + return mSessionToken; + } + + @Override + public int getUid() { + return mSessionToken.getUid(); + } + + @Override + public int getUserId() { + return UserHandle.getUserId(mSessionToken.getUid()); + } + + @Override + public boolean isSystemPriority() { + // System priority session is currently only allowed for telephony, and it's OK to stick to + // the media1 API at this moment. + return false; + } + + @Override + public void adjustVolume(String packageName, String opPackageName, int pid, int uid, + boolean asSystemService, int direction, int flags, boolean useSuggested) { + // TODO(jaewan): Add API to adjust volume. + } + + @Override + public boolean isActive() { + synchronized (mLock) { + return mIsConnected; + } + } + + @Override + public boolean checkPlaybackActiveState(boolean expected) { + synchronized (mLock) { + return mIsConnected && mController.isPlaybackActive() == expected; + } + } + + @Override + public boolean isPlaybackTypeLocal() { + // TODO(jaewan): Implement -- need API to know whether the playback is remote or local. + return true; + } + + @Override + public void close() { + synchronized (mLock) { + // Call close regardless of the mIsAvailable. This may be called when it's not yet + // connected. + mController.close(); + } + } + + @Override + public boolean sendMediaButton(String packageName, int pid, int uid, boolean asSystemService, + KeyEvent ke, int sequenceId, ResultReceiver cb) { + // TODO(jaewan): Implement. + return false; + } + + @Override + public void dump(PrintWriter pw, String prefix) { + pw.println(prefix + "token=" + mSessionToken); + pw.println(prefix + "controller=" + mController); + + final String indent = prefix + " "; + pw.println(indent + "playbackActive=" + mController.isPlaybackActive()); + } + + @Override + public String toString() { + // TODO(jaewan): Also add getId(). + return getPackageName() + " (userId=" + getUserId() + ")"; + } + + private class Controller2Callback extends MediaController2.ControllerCallback { + @Override + public void onConnected(MediaController2 controller, Session2CommandGroup allowedCommands) { + if (DEBUG) { + Log.d(TAG, "connected to " + mSessionToken + ", allowed=" + allowedCommands); + } + synchronized (mLock) { + mIsConnected = true; + } + mService.onSessionActiveStateChanged(MediaSession2Record.this); + } + + @Override + public void onDisconnected(MediaController2 controller) { + if (DEBUG) { + Log.d(TAG, "disconnected from " + mSessionToken); + } + synchronized (mLock) { + mIsConnected = false; + } + mService.onSessionDied(MediaSession2Record.this); + } + + @Override + public void onPlaybackActiveChanged(MediaController2 controller, boolean playbackActive) { + if (DEBUG) { + Log.d(TAG, "playback active changed, " + mSessionToken + ", active=" + + playbackActive); + } + mService.onSessionPlaybackStateChanged(MediaSession2Record.this, playbackActive); + } + } +} diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index aa24ed26023a..df115d0f2773 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -56,13 +56,15 @@ import com.android.server.LocalServices; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; /** * This is the system implementation of a Session. Apps will interact with the * MediaSession wrapper class instead. */ -public class MediaSessionRecord implements IBinder.DeathRecipient, AutoCloseable { +// TODO(jaewan): Do not call service method directly -- introduce listener instead. +public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionRecordImpl { private static final String TAG = "MediaSessionRecord"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); @@ -72,6 +74,24 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, AutoCloseable */ private static final int OPTIMISTIC_VOLUME_TIMEOUT = 1000; + /** + * These are states that usually indicate the user took an action and should + * bump priority regardless of the old state. + */ + private static final List<Integer> ALWAYS_PRIORITY_STATES = Arrays.asList( + PlaybackState.STATE_FAST_FORWARDING, + PlaybackState.STATE_REWINDING, + PlaybackState.STATE_SKIPPING_TO_PREVIOUS, + PlaybackState.STATE_SKIPPING_TO_NEXT); + /** + * These are states that usually indicate the user took an action if they + * were entered from a non-priority state. + */ + private static final List<Integer> TRANSITION_PRIORITY_STATES = Arrays.asList( + PlaybackState.STATE_BUFFERING, + PlaybackState.STATE_CONNECTING, + PlaybackState.STATE_PLAYING); + private final MessageHandler mHandler; private final int mOwnerPid; @@ -170,6 +190,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, AutoCloseable * * @return Info that identifies this session. */ + @Override public String getPackageName() { return mPackageName; } @@ -188,6 +209,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, AutoCloseable * * @return The UID for this session. */ + @Override public int getUid() { return mOwnerUid; } @@ -197,6 +219,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, AutoCloseable * * @return The user id for this session. */ + @Override public int getUserId() { return mUserId; } @@ -207,6 +230,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, AutoCloseable * * @return True if this is a system priority session, false otherwise */ + @Override public boolean isSystemPriority() { return (mFlags & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0; } @@ -220,7 +244,6 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, AutoCloseable * @param opPackageName The op package that made the original volume request. * @param pid The pid that made the original volume request. * @param uid The uid that made the original volume request. - * @param caller caller binder. can be {@code null} if it's from the volume key. * @param asSystemService {@code true} if the event sent to the session as if it was come from * the system service instead of the app process. This helps sessions to distinguish * between the key injection by the app and key events from the hardware devices. @@ -318,9 +341,13 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, AutoCloseable /** * Check if this session has been set to active by the app. + * <p> + * It's not used to prioritize sessions for dispatching media keys since API 26, but still used + * to filter session list in MediaSessionManager#getActiveSessions(). * * @return True if the session is active, false otherwise. */ + @Override public boolean isActive() { return mIsActive && !mDestroyed; } @@ -333,6 +360,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, AutoCloseable * @param expected True if playback is expected to be active. false otherwise. * @return True if the session's playback matches with the expectation. false otherwise. */ + @Override public boolean checkPlaybackActiveState(boolean expected) { if (mPlaybackState == null) { return false; @@ -345,13 +373,14 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, AutoCloseable * * @return {@code true} if the playback is local. */ - public boolean isPlaybackLocal() { + @Override + public boolean isPlaybackTypeLocal() { return mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL; } @Override public void binderDied() { - mService.sessionDied(this); + mService.onSessionDied(this); } /** @@ -383,7 +412,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, AutoCloseable * @param sequenceId (optional) sequence id. Use this only when a wake lock is needed. * @param cb (optional) result receiver to receive callback. Use this only when a wake lock is * needed. - * @return {@code true} if the attempt to send media button was successfuly. + * @return {@code true} if the attempt to send media button was successfully. * {@code false} otherwise. */ public boolean sendMediaButton(String packageName, int pid, int uid, boolean asSystemService, @@ -392,6 +421,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, AutoCloseable cb); } + @Override public void dump(PrintWriter pw, String prefix) { pw.println(prefix + mTag + " " + this); @@ -712,7 +742,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, AutoCloseable public void destroySession() throws RemoteException { final long token = Binder.clearCallingIdentity(); try { - mService.destroySession(MediaSessionRecord.this); + mService.onSessionDied(MediaSessionRecord.this); } finally { Binder.restoreCallingIdentity(token); } @@ -734,7 +764,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, AutoCloseable mIsActive = active; final long token = Binder.clearCallingIdentity(); try { - mService.updateSession(MediaSessionRecord.this); + mService.onSessionActiveStateChanged(MediaSessionRecord.this); } finally { Binder.restoreCallingIdentity(token); } @@ -801,12 +831,16 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, AutoCloseable ? PlaybackState.STATE_NONE : mPlaybackState.getState(); int newState = state == null ? PlaybackState.STATE_NONE : state.getState(); + boolean shouldUpdatePriority = ALWAYS_PRIORITY_STATES.contains(newState) + || (!TRANSITION_PRIORITY_STATES.contains(oldState) + && TRANSITION_PRIORITY_STATES.contains(newState)); synchronized (mLock) { mPlaybackState = state; } final long token = Binder.clearCallingIdentity(); try { - mService.onSessionPlaystateChanged(MediaSessionRecord.this, oldState, newState); + mService.onSessionPlaybackStateChanged( + MediaSessionRecord.this, shouldUpdatePriority); } finally { Binder.restoreCallingIdentity(token); } diff --git a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java new file mode 100644 index 000000000000..2cde89a7a6f6 --- /dev/null +++ b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java @@ -0,0 +1,143 @@ +/* + * 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.media; + +import android.media.AudioManager; +import android.os.ResultReceiver; +import android.view.KeyEvent; + +import java.io.PrintWriter; + +/** + * Common interfaces between {@link MediaSessionRecord} and {@link MediaSession2Record}. + */ +public interface MediaSessionRecordImpl extends AutoCloseable { + + /** + * Get the info for this session. + * + * @return Info that identifies this session. + */ + String getPackageName(); + + /** + * Get the UID this session was created for. + * + * @return The UID for this session. + */ + int getUid(); + + /** + * Get the user id this session was created for. + * + * @return The user id for this session. + */ + int getUserId(); + + /** + * Check if this session has system priorty and should receive media buttons + * before any other sessions. + * + * @return True if this is a system priority session, false otherwise + */ + boolean isSystemPriority(); + + /** + * Send a volume adjustment to the session owner. Direction must be one of + * {@link AudioManager#ADJUST_LOWER}, {@link AudioManager#ADJUST_RAISE}, + * {@link AudioManager#ADJUST_SAME}. + * + * @param packageName The package that made the original volume request. + * @param opPackageName The op package that made the original volume request. + * @param pid The pid that made the original volume request. + * @param uid The uid that made the original volume request. + * @param asSystemService {@code true} if the event sent to the session as if it was come from + * the system service instead of the app process. This helps sessions to distinguish + * between the key injection by the app and key events from the hardware devices. + * Should be used only when the volume key events aren't handled by foreground + * activity. {@code false} otherwise to tell session about the real caller. + * @param direction The direction to adjust volume in. + * @param flags Any of the flags from {@link AudioManager}. + * @param useSuggested True to use adjustSuggestedStreamVolume instead of + */ + void adjustVolume(String packageName, String opPackageName, int pid, int uid, + boolean asSystemService, int direction, int flags, boolean useSuggested); + + /** + * Check if this session has been set to active by the app. (i.e. ready to receive command and + * getters are available). + * + * @return True if the session is active, false otherwise. + */ + // TODO(jaewan): Find better naming, or remove this from the MediaSessionRecordImpl. + boolean isActive(); + + /** + * Check if the session's playback active state matches with the expectation. This always return + * {@code false} if the playback state is unknown (e.g. {@code null}), where we cannot know the + * actual playback state associated with the session. + * + * @param expected True if playback is expected to be active. false otherwise. + * @return True if the session's playback matches with the expectation. false otherwise. + */ + boolean checkPlaybackActiveState(boolean expected); + + /** + * Check whether the playback type is local or remote. + * <p> + * <ul> + * <li>Local: volume changes the stream volume because playback happens on this device.</li> + * <li>Remote: volume is sent to the apps callback because playback happens on the remote + * device and we cannot know how to control the volume of it.</li> + * </ul> + * + * @return {@code true} if the playback is local. {@code false} if the playback is remote. + */ + boolean isPlaybackTypeLocal(); + + /** + * Sends media button. + * + * @param packageName caller package name + * @param pid caller pid + * @param uid caller uid + * @param asSystemService {@code true} if the event sent to the session as if it was come from + * the system service instead of the app process. + * @param ke key events + * @param sequenceId (optional) sequence id. Use this only when a wake lock is needed. + * @param cb (optional) result receiver to receive callback. Use this only when a wake lock is + * needed. + * @return {@code true} if the attempt to send media button was successfully. + * {@code false} otherwise. + */ + boolean sendMediaButton(String packageName, int pid, int uid, boolean asSystemService, + KeyEvent ke, int sequenceId, ResultReceiver cb); + + /** + * Dumps internal state + * + * @param pw print writer + * @param prefix prefix + */ + void dump(PrintWriter pw, String prefix); + + /** + * Override {@link AutoCloseable#close} to tell not to throw exception. + */ + @Override + void close(); +} diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index 0f059dba5274..f71fb582e3ed 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -40,10 +40,7 @@ import android.media.AudioManager; import android.media.AudioManagerInternal; import android.media.AudioPlaybackConfiguration; import android.media.AudioSystem; -import android.media.IAudioService; import android.media.IRemoteVolumeController; -import android.media.MediaController2; -import android.media.Session2CommandGroup; import android.media.Session2Token; import android.media.session.IActiveSessionsListener; import android.media.session.IOnMediaKeyEventDispatchedListener; @@ -61,7 +58,6 @@ import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.Handler; -import android.os.HandlerExecutor; import android.os.IBinder; import android.os.Message; import android.os.PowerManager; @@ -123,11 +119,6 @@ public class MediaSessionService extends SystemService implements Monitor { @GuardedBy("mLock") private final ArrayList<SessionsListenerRecord> mSessionsListeners = new ArrayList<SessionsListenerRecord>(); - // Map user id as index to list of Session2Tokens - // TODO: Keep session2 info in MediaSessionStack for prioritizing both session1 and session2 in - // one place. - @GuardedBy("mLock") - private final SparseArray<List<Session2Token>> mSession2TokensPerUser = new SparseArray<>(); @GuardedBy("mLock") private final List<Session2TokensListenerRecord> mSession2TokensListenerRecords = new ArrayList<>(); @@ -189,16 +180,11 @@ public class MediaSessionService extends SystemService implements Monitor { updateUser(); } - private IAudioService getAudioService() { - IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE); - return IAudioService.Stub.asInterface(b); - } - private boolean isGlobalPriorityActiveLocked() { return mGlobalPrioritySession != null && mGlobalPrioritySession.isActive(); } - void updateSession(MediaSessionRecord record) { + void onSessionActiveStateChanged(MediaSessionRecordImpl record) { synchronized (mLock) { FullUserRecord user = getFullUserRecordLocked(record.getUserId()); if (user == null) { @@ -215,12 +201,14 @@ public class MediaSessionService extends SystemService implements Monitor { Log.w(TAG, "Unknown session updated. Ignoring."); return; } - user.mPriorityStack.onSessionStateChange(record); + user.mPriorityStack.onSessionActiveStateChanged(record); } - mHandler.postSessionsChanged(record.getUserId()); + + mHandler.postSessionsChanged(record); } } + // Currently only media1 can become global priority session. void setGlobalPrioritySession(MediaSessionRecord record) { synchronized (mLock) { FullUserRecord user = getFullUserRecordLocked(record.getUserId()); @@ -266,11 +254,13 @@ public class MediaSessionService extends SystemService implements Monitor { List<Session2Token> getSession2TokensLocked(int userId) { List<Session2Token> list = new ArrayList<>(); if (userId == USER_ALL) { - for (int i = 0; i < mSession2TokensPerUser.size(); i++) { - list.addAll(mSession2TokensPerUser.valueAt(i)); + int size = mUserRecords.size(); + for (int i = 0; i < size; i++) { + list.addAll(mUserRecords.valueAt(i).mPriorityStack.getSession2Tokens(userId)); } } else { - list.addAll(mSession2TokensPerUser.get(userId)); + FullUserRecord user = getFullUserRecordLocked(userId); + list.addAll(user.mPriorityStack.getSession2Tokens(userId)); } return list; } @@ -297,14 +287,15 @@ public class MediaSessionService extends SystemService implements Monitor { } } - void onSessionPlaystateChanged(MediaSessionRecord record, int oldState, int newState) { + void onSessionPlaybackStateChanged(MediaSessionRecordImpl record, + boolean shouldUpdatePriority) { synchronized (mLock) { FullUserRecord user = getFullUserRecordLocked(record.getUserId()); if (user == null || !user.mPriorityStack.contains(record)) { Log.d(TAG, "Unknown session changed playback state. Ignoring."); return; } - user.mPriorityStack.onPlaystateChanged(record, oldState, newState); + user.mPriorityStack.onPlaybackStateChanged(record, shouldUpdatePriority); } } @@ -347,7 +338,6 @@ public class MediaSessionService extends SystemService implements Monitor { user.destroySessionsForUserLocked(userId); } } - mSession2TokensPerUser.remove(userId); updateUser(); } } @@ -366,13 +356,7 @@ public class MediaSessionService extends SystemService implements Monitor { } } - void sessionDied(MediaSessionRecord session) { - synchronized (mLock) { - destroySessionLocked(session); - } - } - - void destroySession(MediaSessionRecord session) { + void onSessionDied(MediaSessionRecordImpl session) { synchronized (mLock) { destroySessionLocked(session); } @@ -393,9 +377,6 @@ public class MediaSessionService extends SystemService implements Monitor { mUserRecords.put(userInfo.id, new FullUserRecord(userInfo.id)); } } - if (mSession2TokensPerUser.get(userInfo.id) == null) { - mSession2TokensPerUser.put(userInfo.id, new ArrayList<>()); - } } } // Ensure that the current full user exists. @@ -405,9 +386,6 @@ public class MediaSessionService extends SystemService implements Monitor { Log.w(TAG, "Cannot find FullUserInfo for the current user " + currentFullUserId); mCurrentFullUserRecord = new FullUserRecord(currentFullUserId); mUserRecords.put(currentFullUserId, mCurrentFullUserRecord); - if (mSession2TokensPerUser.get(currentFullUserId) == null) { - mSession2TokensPerUser.put(currentFullUserId, new ArrayList<>()); - } } mFullUserIds.put(currentFullUserId, currentFullUserId); } @@ -444,7 +422,7 @@ public class MediaSessionService extends SystemService implements Monitor { * 5. We need to unlink to death from the cb binder * 6. We need to tell the session to do any final cleanup (onDestroy) */ - private void destroySessionLocked(MediaSessionRecord session) { + private void destroySessionLocked(MediaSessionRecordImpl session) { if (DEBUG) { Log.d(TAG, "Destroying " + session); } @@ -461,7 +439,7 @@ public class MediaSessionService extends SystemService implements Monitor { } session.close(); - mHandler.postSessionsChanged(session.getUserId()); + mHandler.postSessionsChanged(session); } private void enforcePackageName(String packageName, int uid) { @@ -541,15 +519,6 @@ public class MediaSessionService extends SystemService implements Monitor { return false; } - private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId, - String callerPackageName, ISessionCallback cb, String tag, Bundle sessionInfo) - throws RemoteException { - synchronized (mLock) { - return createSessionLocked(callerPid, callerUid, userId, callerPackageName, cb, - tag, sessionInfo); - } - } - /* * When a session is created the following things need to happen. * 1. Its callback binder needs a link to death @@ -557,29 +526,31 @@ public class MediaSessionService extends SystemService implements Monitor { * 3. It needs to be added to the priority stack. * 4. It needs to be added to the relevant user record. */ - private MediaSessionRecord createSessionLocked(int callerPid, int callerUid, int userId, + private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId, String callerPackageName, ISessionCallback cb, String tag, Bundle sessionInfo) { - FullUserRecord user = getFullUserRecordLocked(userId); - if (user == null) { - Log.w(TAG, "Request from invalid user: " + userId + ", pkg=" + callerPackageName); - throw new RuntimeException("Session request from invalid user."); - } + synchronized (mLock) { + FullUserRecord user = getFullUserRecordLocked(userId); + if (user == null) { + Log.w(TAG, "Request from invalid user: " + userId + ", pkg=" + callerPackageName); + throw new RuntimeException("Session request from invalid user."); + } - final MediaSessionRecord session; - try { - session = new MediaSessionRecord(callerPid, callerUid, userId, - callerPackageName, cb, tag, sessionInfo, this, mHandler.getLooper()); - } catch (RemoteException e) { - throw new RuntimeException("Media Session owner died prematurely.", e); - } + final MediaSessionRecord session; + try { + session = new MediaSessionRecord(callerPid, callerUid, userId, + callerPackageName, cb, tag, sessionInfo, this, mHandler.getLooper()); + } catch (RemoteException e) { + throw new RuntimeException("Media Session owner died prematurely.", e); + } - user.mPriorityStack.addSession(session); - mHandler.postSessionsChanged(userId); + user.mPriorityStack.addSession(session); + mHandler.postSessionsChanged(session); - if (DEBUG) { - Log.d(TAG, "Created session for " + callerPackageName + " with tag " + tag); + if (DEBUG) { + Log.d(TAG, "Created session for " + callerPackageName + " with tag " + tag); + } + return session; } - return session; } private int findIndexOfSessionsListenerLocked(IActiveSessionsListener listener) { @@ -600,16 +571,16 @@ public class MediaSessionService extends SystemService implements Monitor { return -1; } - private void pushSessionsChanged(int userId) { + private void pushSession1Changed(int userId) { synchronized (mLock) { FullUserRecord user = getFullUserRecordLocked(userId); if (user == null) { - Log.w(TAG, "pushSessionsChanged failed. No user with id=" + userId); + Log.w(TAG, "pushSession1ChangedOnHandler failed. No user with id=" + userId); return; } List<MediaSessionRecord> records = getActiveSessionsLocked(userId); int size = records.size(); - ArrayList<MediaSession.Token> tokens = new ArrayList<MediaSession.Token>(); + ArrayList<MediaSession.Token> tokens = new ArrayList<>(); for (int i = 0; i < size; i++) { tokens.add(records.get(i).getSessionToken()); } @@ -629,6 +600,27 @@ public class MediaSessionService extends SystemService implements Monitor { } } + void pushSession2Changed(int userId) { + synchronized (mLock) { + List<Session2Token> allSession2Tokens = getSession2TokensLocked(USER_ALL); + List<Session2Token> session2Tokens = getSession2TokensLocked(userId); + + for (int i = mSession2TokensListenerRecords.size() - 1; i >= 0; i--) { + Session2TokensListenerRecord listenerRecord = mSession2TokensListenerRecords.get(i); + try { + if (listenerRecord.userId == USER_ALL) { + listenerRecord.listener.onSession2TokensChanged(allSession2Tokens); + } else if (listenerRecord.userId == userId) { + listenerRecord.listener.onSession2TokensChanged(session2Tokens); + } + } catch (RemoteException e) { + Log.w(TAG, "Failed to notify Session2Token change. Removing listener.", e); + mSession2TokensListenerRecords.remove(i); + } + } + } + } + private void pushRemoteVolumeUpdateLocked(int userId) { FullUserRecord user = getFullUserRecordLocked(userId); if (user == null) { @@ -638,8 +630,13 @@ public class MediaSessionService extends SystemService implements Monitor { synchronized (mLock) { int size = mRemoteVolumeControllers.beginBroadcast(); - MediaSessionRecord record = user.mPriorityStack.getDefaultRemoteSession(userId); - MediaSession.Token token = record == null ? null : record.getSessionToken(); + MediaSessionRecordImpl record = user.mPriorityStack.getDefaultRemoteSession(userId); + if (record instanceof MediaSession2Record) { + // TODO(jaewan): Implement + return; + } + MediaSession.Token token = record == null + ? null : ((MediaSessionRecord) record).getSessionToken(); for (int i = size - 1; i >= 0; i--) { try { @@ -653,34 +650,15 @@ public class MediaSessionService extends SystemService implements Monitor { } } - void pushSession2TokensChangedLocked(int userId) { - List<Session2Token> allSession2Tokens = getSession2TokensLocked(USER_ALL); - List<Session2Token> session2Tokens = getSession2TokensLocked(userId); - - for (int i = mSession2TokensListenerRecords.size() - 1; i >= 0; i--) { - Session2TokensListenerRecord listenerRecord = mSession2TokensListenerRecords.get(i); - try { - if (listenerRecord.userId == USER_ALL) { - listenerRecord.listener.onSession2TokensChanged(allSession2Tokens); - } else if (listenerRecord.userId == userId) { - listenerRecord.listener.onSession2TokensChanged(session2Tokens); - } - } catch (RemoteException e) { - Log.w(TAG, "Failed to notify Session2Token change. Removing listener.", e); - mSession2TokensListenerRecords.remove(i); - } - } - } - /** * Called when the media button receiver for the {@code record} is changed. * * @param record the media session whose media button receiver is updated. */ - public void onMediaButtonReceiverChanged(MediaSessionRecord record) { + public void onMediaButtonReceiverChanged(MediaSessionRecordImpl record) { synchronized (mLock) { FullUserRecord user = getFullUserRecordLocked(record.getUserId()); - MediaSessionRecord mediaButtonSession = + MediaSessionRecordImpl mediaButtonSession = user.mPriorityStack.getMediaButtonSession(); if (record == mediaButtonSession) { user.rememberMediaButtonReceiverLocked(mediaButtonSession); @@ -868,39 +846,34 @@ public class MediaSessionService extends SystemService implements Monitor { pw.println(indent + "Restored MediaButtonReceiverComponentType: " + mRestoredMediaButtonReceiverComponentType); mPriorityStack.dump(pw, indent); - pw.println(indent + "Session2Tokens:"); - for (int i = 0; i < mSession2TokensPerUser.size(); i++) { - List<Session2Token> list = mSession2TokensPerUser.valueAt(i); - if (list == null || list.size() == 0) { - continue; - } - for (Session2Token token : list) { - pw.println(indent + " " + token); - } - } } @Override - public void onMediaButtonSessionChanged(MediaSessionRecord oldMediaButtonSession, - MediaSessionRecord newMediaButtonSession) { + public void onMediaButtonSessionChanged(MediaSessionRecordImpl oldMediaButtonSession, + MediaSessionRecordImpl newMediaButtonSession) { if (DEBUG_KEY_EVENT) { Log.d(TAG, "Media button session is changed to " + newMediaButtonSession); } synchronized (mLock) { if (oldMediaButtonSession != null) { - mHandler.postSessionsChanged(oldMediaButtonSession.getUserId()); + mHandler.postSessionsChanged(oldMediaButtonSession); } if (newMediaButtonSession != null) { rememberMediaButtonReceiverLocked(newMediaButtonSession); - mHandler.postSessionsChanged(newMediaButtonSession.getUserId()); + mHandler.postSessionsChanged(newMediaButtonSession); } pushAddressedPlayerChangedLocked(); } } // Remember media button receiver and keep it in the persistent storage. - public void rememberMediaButtonReceiverLocked(MediaSessionRecord record) { - PendingIntent receiver = record.getMediaButtonReceiver(); + public void rememberMediaButtonReceiverLocked(MediaSessionRecordImpl record) { + if (record instanceof MediaSession2Record) { + // TODO(jaewan): Implement + return; + } + MediaSessionRecord sessionRecord = (MediaSessionRecord) record; + PendingIntent receiver = sessionRecord.getMediaButtonReceiver(); mLastMediaButtonReceiver = receiver; mRestoredMediaButtonReceiver = null; mRestoredMediaButtonReceiverComponentType = COMPONENT_TYPE_INVALID; @@ -925,10 +898,15 @@ public class MediaSessionService extends SystemService implements Monitor { private void pushAddressedPlayerChangedLocked( IOnMediaKeyEventSessionChangedListener callback) { try { - MediaSessionRecord mediaButtonSession = getMediaButtonSessionLocked(); + MediaSessionRecordImpl mediaButtonSession = getMediaButtonSessionLocked(); if (mediaButtonSession != null) { - callback.onMediaKeyEventSessionChanged(mediaButtonSession.getPackageName(), - mediaButtonSession.getSessionToken()); + if (mediaButtonSession instanceof MediaSessionRecord) { + MediaSessionRecord session1 = (MediaSessionRecord) mediaButtonSession; + callback.onMediaKeyEventSessionChanged(session1.getPackageName(), + session1.getSessionToken()); + } else { + // TODO(jaewan): Implement + } } else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null) { callback.onMediaKeyEventSessionChanged( mCurrentFullUserRecord.mLastMediaButtonReceiver @@ -951,7 +929,7 @@ public class MediaSessionService extends SystemService implements Monitor { } } - private MediaSessionRecord getMediaButtonSessionLocked() { + private MediaSessionRecordImpl getMediaButtonSessionLocked() { return isGlobalPriorityActiveLocked() ? mGlobalPrioritySession : mPriorityStack.getMediaButtonSession(); } @@ -1132,14 +1110,13 @@ public class MediaSessionService extends SystemService implements Monitor { throw new SecurityException("Unexpected Session2Token's UID, expected=" + uid + " but actually=" + sessionToken.getUid()); } - Controller2Callback callback = new Controller2Callback(sessionToken); - // Note: It's safe not to keep controller here because it wouldn't be GC'ed until - // it's closed. - // TODO: Keep controller as well for better readability - // because the GC behavior isn't straightforward. - MediaController2 controller = new MediaController2.Builder(mContext, sessionToken) - .setControllerCallback(new HandlerExecutor(mHandler), callback) - .build(); + MediaSession2Record record = new MediaSession2Record( + sessionToken, MediaSessionService.this, mHandler.getLooper()); + synchronized (mLock) { + FullUserRecord user = getFullUserRecordLocked(record.getUserId()); + user.mPriorityStack.addSession(record); + } + // Do not immediately notify changes -- do so when framework can dispatch command } finally { Binder.restoreCallingIdentity(token); } @@ -1180,7 +1157,8 @@ public class MediaSessionService extends SystemService implements Monitor { null /* optional packageName */); List<Session2Token> result; synchronized (mLock) { - result = getSession2TokensLocked(resolvedUserId); + FullUserRecord user = getFullUserRecordLocked(userId); + result = user.mPriorityStack.getSession2Tokens(resolvedUserId); } return new ParceledListSlice(result); } finally { @@ -2018,7 +1996,7 @@ public class MediaSessionService extends SystemService implements Monitor { private void dispatchAdjustVolumeLocked(String packageName, String opPackageName, int pid, int uid, boolean asSystemService, int suggestedStream, int direction, int flags) { - MediaSessionRecord session = isGlobalPriorityActiveLocked() ? mGlobalPrioritySession + MediaSessionRecordImpl session = isGlobalPriorityActiveLocked() ? mGlobalPrioritySession : mCurrentFullUserRecord.mPriorityStack.getDefaultVolumeSession(); boolean preferSuggestedStream = false; @@ -2109,7 +2087,13 @@ public class MediaSessionService extends SystemService implements Monitor { private void dispatchMediaKeyEventLocked(String packageName, int pid, int uid, boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock) { - MediaSessionRecord session = mCurrentFullUserRecord.getMediaButtonSessionLocked(); + if (mCurrentFullUserRecord.getMediaButtonSessionLocked() + instanceof MediaSession2Record) { + // TODO(jaewan): Implement + return; + } + MediaSessionRecord session = + (MediaSessionRecord) mCurrentFullUserRecord.getMediaButtonSessionLocked(); if (session != null) { if (DEBUG_KEY_EVENT) { Log.d(TAG, "Sending " + keyEvent + " to " + session); @@ -2389,15 +2373,19 @@ public class MediaSessionService extends SystemService implements Monitor { } final class MessageHandler extends Handler { - private static final int MSG_SESSIONS_CHANGED = 1; - private static final int MSG_VOLUME_INITIAL_DOWN = 2; + private static final int MSG_SESSIONS_1_CHANGED = 1; + private static final int MSG_SESSIONS_2_CHANGED = 2; + private static final int MSG_VOLUME_INITIAL_DOWN = 3; private final SparseArray<Integer> mIntegerCache = new SparseArray<>(); @Override public void handleMessage(Message msg) { switch (msg.what) { - case MSG_SESSIONS_CHANGED: - pushSessionsChanged((int) msg.obj); + case MSG_SESSIONS_1_CHANGED: + pushSession1Changed((int) msg.obj); + break; + case MSG_SESSIONS_2_CHANGED: + pushSession2Changed((int) msg.obj); break; case MSG_VOLUME_INITIAL_DOWN: synchronized (mLock) { @@ -2412,41 +2400,19 @@ public class MediaSessionService extends SystemService implements Monitor { } } - public void postSessionsChanged(int userId) { + public void postSessionsChanged(MediaSessionRecordImpl record) { // Use object instead of the arguments when posting message to remove pending requests. - Integer userIdInteger = mIntegerCache.get(userId); + Integer userIdInteger = mIntegerCache.get(record.getUserId()); if (userIdInteger == null) { - userIdInteger = Integer.valueOf(userId); - mIntegerCache.put(userId, userIdInteger); + userIdInteger = Integer.valueOf(record.getUserId()); + mIntegerCache.put(record.getUserId(), userIdInteger); } - removeMessages(MSG_SESSIONS_CHANGED, userIdInteger); - obtainMessage(MSG_SESSIONS_CHANGED, userIdInteger).sendToTarget(); - } - } - private class Controller2Callback extends MediaController2.ControllerCallback { - private final Session2Token mToken; - - Controller2Callback(Session2Token token) { - mToken = token; - } - - @Override - public void onConnected(MediaController2 controller, Session2CommandGroup allowedCommands) { - synchronized (mLock) { - int userId = UserHandle.getUserId(mToken.getUid()); - mSession2TokensPerUser.get(userId).add(mToken); - pushSession2TokensChangedLocked(userId); - } - } - - @Override - public void onDisconnected(MediaController2 controller) { - synchronized (mLock) { - int userId = UserHandle.getUserId(mToken.getUid()); - mSession2TokensPerUser.get(userId).remove(mToken); - pushSession2TokensChangedLocked(userId); - } + int msg = (record instanceof MediaSessionRecord) + ? MSG_SESSIONS_1_CHANGED : MSG_SESSIONS_2_CHANGED; + removeMessages(msg, userIdInteger); + obtainMessage(msg, userIdInteger).sendToTarget(); } } + } diff --git a/services/core/java/com/android/server/media/MediaSessionStack.java b/services/core/java/com/android/server/media/MediaSessionStack.java index 732563f6e05e..7bb7cf4b74ad 100644 --- a/services/core/java/com/android/server/media/MediaSessionStack.java +++ b/services/core/java/com/android/server/media/MediaSessionStack.java @@ -16,8 +16,8 @@ package com.android.server.media; +import android.media.Session2Token; import android.media.session.MediaSession; -import android.media.session.PlaybackState; import android.os.Debug; import android.os.UserHandle; import android.util.IntArray; @@ -45,51 +45,30 @@ class MediaSessionStack { /** * Called when the media button session is changed. */ - void onMediaButtonSessionChanged(MediaSessionRecord oldMediaButtonSession, - MediaSessionRecord newMediaButtonSession); + void onMediaButtonSessionChanged(MediaSessionRecordImpl oldMediaButtonSession, + MediaSessionRecordImpl newMediaButtonSession); } /** - * These are states that usually indicate the user took an action and should - * bump priority regardless of the old state. + * Sorted list of the media sessions */ - private static final int[] ALWAYS_PRIORITY_STATES = { - PlaybackState.STATE_FAST_FORWARDING, - PlaybackState.STATE_REWINDING, - PlaybackState.STATE_SKIPPING_TO_PREVIOUS, - PlaybackState.STATE_SKIPPING_TO_NEXT }; - /** - * These are states that usually indicate the user took an action if they - * were entered from a non-priority state. - */ - private static final int[] TRANSITION_PRIORITY_STATES = { - PlaybackState.STATE_BUFFERING, - PlaybackState.STATE_CONNECTING, - PlaybackState.STATE_PLAYING }; - - /** - * Sorted list of the media sessions. - * The session of which PlaybackState is changed to ALWAYS_PRIORITY_STATES or - * TRANSITION_PRIORITY_STATES comes first. - * @see #shouldUpdatePriority - */ - private final List<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>(); + private final List<MediaSessionRecordImpl> mSessions = new ArrayList<>(); private final AudioPlayerStateMonitor mAudioPlayerStateMonitor; private final OnMediaButtonSessionChangedListener mOnMediaButtonSessionChangedListener; /** * The media button session which receives media key events. - * It could be null if the previous media buttion session is released. + * It could be null if the previous media button session is released. */ - private MediaSessionRecord mMediaButtonSession; + private MediaSessionRecordImpl mMediaButtonSession; - private MediaSessionRecord mCachedVolumeDefault; + private MediaSessionRecordImpl mCachedVolumeDefault; /** * Cache the result of the {@link #getActiveSessions} per user. */ - private final SparseArray<ArrayList<MediaSessionRecord>> mCachedActiveLists = + private final SparseArray<List<MediaSessionRecord>> mCachedActiveLists = new SparseArray<>(); MediaSessionStack(AudioPlayerStateMonitor monitor, OnMediaButtonSessionChangedListener listener) { @@ -102,7 +81,7 @@ class MediaSessionStack { * * @param record The record to add. */ - public void addSession(MediaSessionRecord record) { + public void addSession(MediaSessionRecordImpl record) { mSessions.add(record); clearCache(record.getUserId()); @@ -117,7 +96,7 @@ class MediaSessionStack { * * @param record The record to remove. */ - public void removeSession(MediaSessionRecord record) { + public void removeSession(MediaSessionRecordImpl record) { mSessions.remove(record); if (mMediaButtonSession == record) { // When the media button session is removed, nullify the media button session and do not @@ -131,7 +110,7 @@ class MediaSessionStack { /** * Return if the record exists in the priority tracker. */ - public boolean contains(MediaSessionRecord record) { + public boolean contains(MediaSessionRecordImpl record) { return mSessions.contains(record); } @@ -142,9 +121,12 @@ class MediaSessionStack { * @return the MediaSessionRecord. Can be {@code null} if the session is gone meanwhile. */ public MediaSessionRecord getMediaSessionRecord(MediaSession.Token sessionToken) { - for (MediaSessionRecord record : mSessions) { - if (Objects.equals(record.getSessionToken(), sessionToken)) { - return record; + for (MediaSessionRecordImpl record : mSessions) { + if (record instanceof MediaSessionRecord) { + MediaSessionRecord session1 = (MediaSessionRecord) record; + if (Objects.equals(session1.getSessionToken(), sessionToken)) { + return session1; + } } } return null; @@ -154,15 +136,15 @@ class MediaSessionStack { * Notify the priority tracker that a session's playback state changed. * * @param record The record that changed. - * @param oldState Its old playback state. - * @param newState Its new playback state. + * @param shouldUpdatePriority {@code true} if the record needs to prioritized */ - public void onPlaystateChanged(MediaSessionRecord record, int oldState, int newState) { - if (shouldUpdatePriority(oldState, newState)) { + public void onPlaybackStateChanged( + MediaSessionRecordImpl record, boolean shouldUpdatePriority) { + if (shouldUpdatePriority) { mSessions.remove(record); mSessions.add(0, record); clearCache(record.getUserId()); - } else if (!MediaSession.isActiveState(newState)) { + } else if (record.checkPlaybackActiveState(false)) { // Just clear the volume cache when a state goes inactive mCachedVolumeDefault = null; } @@ -172,7 +154,7 @@ class MediaSessionStack { // In that case, we pick the media session whose PlaybackState matches // the audio playback configuration. if (mMediaButtonSession != null && mMediaButtonSession.getUid() == record.getUid()) { - MediaSessionRecord newMediaButtonSession = + MediaSessionRecordImpl newMediaButtonSession = findMediaButtonSession(mMediaButtonSession.getUid()); if (newMediaButtonSession != mMediaButtonSession) { updateMediaButtonSession(newMediaButtonSession); @@ -185,7 +167,7 @@ class MediaSessionStack { * * @param record The record that changed. */ - public void onSessionStateChange(MediaSessionRecord record) { + public void onSessionActiveStateChanged(MediaSessionRecordImpl record) { // For now just clear the cache. Eventually we'll selectively clear // depending on what changed. clearCache(record.getUserId()); @@ -203,7 +185,7 @@ class MediaSessionStack { } IntArray audioPlaybackUids = mAudioPlayerStateMonitor.getSortedAudioPlaybackClientUids(); for (int i = 0; i < audioPlaybackUids.size(); i++) { - MediaSessionRecord mediaButtonSession = + MediaSessionRecordImpl mediaButtonSession = findMediaButtonSession(audioPlaybackUids.get(i)); if (mediaButtonSession != null) { // Found the media button session. @@ -225,9 +207,9 @@ class MediaSessionStack { * @return The media button session. Returns {@code null} if the app doesn't have a media * session. */ - private MediaSessionRecord findMediaButtonSession(int uid) { - MediaSessionRecord mediaButtonSession = null; - for (MediaSessionRecord session : mSessions) { + private MediaSessionRecordImpl findMediaButtonSession(int uid) { + MediaSessionRecordImpl mediaButtonSession = null; + for (MediaSessionRecordImpl session : mSessions) { if (uid == session.getUid()) { if (session.checkPlaybackActiveState( mAudioPlayerStateMonitor.isPlaybackActive(session.getUid()))) { @@ -253,8 +235,8 @@ class MediaSessionStack { * for all users in this {@link MediaSessionStack}. * @return All the active sessions in priority order. */ - public ArrayList<MediaSessionRecord> getActiveSessions(int userId) { - ArrayList<MediaSessionRecord> cachedActiveList = mCachedActiveLists.get(userId); + public List<MediaSessionRecord> getActiveSessions(int userId) { + List<MediaSessionRecord> cachedActiveList = mCachedActiveLists.get(userId); if (cachedActiveList == null) { cachedActiveList = getPriorityList(true, userId); mCachedActiveLists.put(userId, cachedActiveList); @@ -263,26 +245,46 @@ class MediaSessionStack { } /** + * Gets the session2 tokens. + * + * @param userId The user to check. It can be {@link UserHandle#USER_ALL} to get all session2 + * tokens for all users in this {@link MediaSessionStack}. + * @return All session2 tokens. + */ + public List<Session2Token> getSession2Tokens(int userId) { + ArrayList<Session2Token> session2Records = new ArrayList<>(); + for (MediaSessionRecordImpl record : mSessions) { + if ((userId == UserHandle.USER_ALL || record.getUserId() == userId) + && record.isActive() + && record instanceof MediaSession2Record) { + MediaSession2Record session2 = (MediaSession2Record) record; + session2Records.add(session2.getSession2Token()); + } + } + return session2Records; + } + + /** * Get the media button session which receives the media button events. * * @return The media button session or null. */ - public MediaSessionRecord getMediaButtonSession() { + public MediaSessionRecordImpl getMediaButtonSession() { return mMediaButtonSession; } - private void updateMediaButtonSession(MediaSessionRecord newMediaButtonSession) { - MediaSessionRecord oldMediaButtonSession = mMediaButtonSession; + private void updateMediaButtonSession(MediaSessionRecordImpl newMediaButtonSession) { + MediaSessionRecordImpl oldMediaButtonSession = mMediaButtonSession; mMediaButtonSession = newMediaButtonSession; mOnMediaButtonSessionChangedListener.onMediaButtonSessionChanged( oldMediaButtonSession, newMediaButtonSession); } - public MediaSessionRecord getDefaultVolumeSession() { + public MediaSessionRecordImpl getDefaultVolumeSession() { if (mCachedVolumeDefault != null) { return mCachedVolumeDefault; } - ArrayList<MediaSessionRecord> records = getPriorityList(true, UserHandle.USER_ALL); + List<MediaSessionRecord> records = getPriorityList(true, UserHandle.USER_ALL); int size = records.size(); for (int i = 0; i < size; i++) { MediaSessionRecord record = records.get(i); @@ -294,13 +296,13 @@ class MediaSessionStack { return null; } - public MediaSessionRecord getDefaultRemoteSession(int userId) { - ArrayList<MediaSessionRecord> records = getPriorityList(true, userId); + public MediaSessionRecordImpl getDefaultRemoteSession(int userId) { + List<MediaSessionRecord> records = getPriorityList(true, userId); int size = records.size(); for (int i = 0; i < size; i++) { MediaSessionRecord record = records.get(i); - if (!record.isPlaybackLocal()) { + if (!record.isPlaybackTypeLocal()) { return record; } } @@ -308,16 +310,11 @@ class MediaSessionStack { } public void dump(PrintWriter pw, String prefix) { - ArrayList<MediaSessionRecord> sortedSessions = getPriorityList(false, - UserHandle.USER_ALL); - int count = sortedSessions.size(); pw.println(prefix + "Media button session is " + mMediaButtonSession); - pw.println(prefix + "Sessions Stack - have " + count + " sessions:"); + pw.println(prefix + "Sessions Stack - have " + mSessions.size() + " sessions:"); String indent = prefix + " "; - for (int i = 0; i < count; i++) { - MediaSessionRecord record = sortedSessions.get(i); + for (MediaSessionRecordImpl record : mSessions) { record.dump(pw, indent); - pw.println(); } } @@ -335,17 +332,19 @@ class MediaSessionStack { * will return sessions for all users. * @return The priority sorted list of sessions. */ - public ArrayList<MediaSessionRecord> getPriorityList(boolean activeOnly, int userId) { - ArrayList<MediaSessionRecord> result = new ArrayList<MediaSessionRecord>(); + public List<MediaSessionRecord> getPriorityList(boolean activeOnly, int userId) { + List<MediaSessionRecord> result = new ArrayList<MediaSessionRecord>(); int lastPlaybackActiveIndex = 0; int lastActiveIndex = 0; - int size = mSessions.size(); - for (int i = 0; i < size; i++) { - final MediaSessionRecord session = mSessions.get(i); + for (MediaSessionRecordImpl record : mSessions) { + if (!(record instanceof MediaSessionRecord)) { + continue; + } + final MediaSessionRecord session = (MediaSessionRecord) record; - if (userId != UserHandle.USER_ALL && userId != session.getUserId()) { - // Filter out sessions for the wrong user + if ((userId != UserHandle.USER_ALL && userId != session.getUserId())) { + // Filter out sessions for the wrong user or session2. continue; } @@ -369,26 +368,6 @@ class MediaSessionStack { return result; } - private boolean shouldUpdatePriority(int oldState, int newState) { - if (containsState(newState, ALWAYS_PRIORITY_STATES)) { - return true; - } - if (!containsState(oldState, TRANSITION_PRIORITY_STATES) - && containsState(newState, TRANSITION_PRIORITY_STATES)) { - return true; - } - return false; - } - - private boolean containsState(int state, int[] states) { - for (int i = 0; i < states.length; i++) { - if (states[i] == state) { - return true; - } - } - return false; - } - private void clearCache(int userId) { mCachedVolumeDefault = null; mCachedActiveLists.remove(userId); diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java index 8fdfcbfb2ad9..0ea4e63231d4 100644 --- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java @@ -48,8 +48,8 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { static final String BLUETOOTH_ROUTE_ID = "BLUETOOTH_ROUTE"; // TODO: Move these to a proper place - public static final String CATEGORY_LIVE_AUDIO = "android.media.intent.category.LIVE_AUDIO"; - public static final String CATEGORY_LIVE_VIDEO = "android.media.intent.category.LIVE_VIDEO"; + public static final String TYPE_LIVE_AUDIO = "android.media.intent.route.TYPE_LIVE_AUDIO"; + public static final String TYPE_LIVE_VIDEO = "android.media.intent.route.TYPE_LIVE_VIDEO"; private final AudioManager mAudioManager; private final IAudioService mAudioService; @@ -91,44 +91,44 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { } @Override - public void requestCreateSession(String packageName, String routeId, String controlCategory, + public void requestCreateSession(String packageName, String routeId, String routeType, long requestId) { // Do nothing } @Override - public void releaseSession(int sessionId) { + public void releaseSession(String sessionId) { // Do nothing } @Override - public void selectRoute(int sessionId, MediaRoute2Info route) { + public void selectRoute(String sessionId, String routeId) { //TODO: implement method } @Override - public void deselectRoute(int sessionId, MediaRoute2Info route) { + public void deselectRoute(String sessionId, String routeId) { //TODO: implement method } @Override - public void transferToRoute(int sessionId, MediaRoute2Info route) { + public void transferToRoute(String sessionId, String routeId) { //TODO: implement method } //TODO: implement method @Override - public void sendControlRequest(@NonNull MediaRoute2Info route, @NonNull Intent request) { + public void sendControlRequest(@NonNull String routeId, @NonNull Intent request) { } //TODO: implement method @Override - public void requestSetVolume(MediaRoute2Info route, int volume) { + public void requestSetVolume(String routeId, int volume) { } //TODO: implement method @Override - public void requestUpdateVolume(MediaRoute2Info route, int delta) { + public void requestUpdateVolume(String routeId, int delta) { } void initializeRoutes() { @@ -141,8 +141,8 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE) .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)) .setVolume(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC)) - .addSupportedCategory(CATEGORY_LIVE_AUDIO) - .addSupportedCategory(CATEGORY_LIVE_VIDEO) + .addRouteType(TYPE_LIVE_AUDIO) + .addRouteType(TYPE_LIVE_VIDEO) .build(); AudioRoutesInfo newAudioRoutes = null; @@ -181,8 +181,8 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE) .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)) .setVolume(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC)) - .addSupportedCategory(CATEGORY_LIVE_AUDIO) - .addSupportedCategory(CATEGORY_LIVE_VIDEO) + .addRouteType(TYPE_LIVE_AUDIO) + .addRouteType(TYPE_LIVE_VIDEO) .build(); if (!TextUtils.equals(newRoutes.bluetoothName, mCurAudioRoutesInfo.bluetoothName)) { @@ -193,7 +193,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { mCurAudioRoutesInfo.bluetoothName) .setDescription(mContext.getResources().getText( R.string.bluetooth_a2dp_audio_route_name).toString()) - .addSupportedCategory(CATEGORY_LIVE_AUDIO) + .addRouteType(TYPE_LIVE_AUDIO) .build(); } else { mBluetoothA2dpRoute = null; diff --git a/services/core/java/com/android/server/net/NetworkStatsFactory.java b/services/core/java/com/android/server/net/NetworkStatsFactory.java index 3ca1803262e7..22b01bee6c6a 100644 --- a/services/core/java/com/android/server/net/NetworkStatsFactory.java +++ b/services/core/java/com/android/server/net/NetworkStatsFactory.java @@ -229,7 +229,7 @@ public class NetworkStatsFactory { entry.txPackets += reader.nextLong(); } - stats.addValues(entry); + stats.addEntry(entry); reader.finishLine(); } } catch (NullPointerException|NumberFormatException e) { @@ -279,7 +279,7 @@ public class NetworkStatsFactory { entry.txBytes = reader.nextLong(); entry.txPackets = reader.nextLong(); - stats.addValues(entry); + stats.addEntry(entry); reader.finishLine(); } } catch (NullPointerException|NumberFormatException e) { @@ -439,7 +439,7 @@ public class NetworkStatsFactory { if ((limitIfaces == null || ArrayUtils.contains(limitIfaces, entry.iface)) && (limitUid == UID_ALL || limitUid == entry.uid) && (limitTag == TAG_ALL || limitTag == entry.tag)) { - stats.addValues(entry); + stats.addEntry(entry); } reader.finishLine(); diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java index ec8a8e7c4c1a..7a6f29764f09 100644 --- a/services/core/java/com/android/server/net/NetworkStatsService.java +++ b/services/core/java/com/android/server/net/NetworkStatsService.java @@ -27,6 +27,7 @@ import static android.net.ConnectivityManager.isNetworkTypeMobile; import static android.net.NetworkStack.checkNetworkStackPermission; import static android.net.NetworkStats.DEFAULT_NETWORK_ALL; import static android.net.NetworkStats.IFACE_ALL; +import static android.net.NetworkStats.IFACE_VT; import static android.net.NetworkStats.INTERFACES_ALL; import static android.net.NetworkStats.METERED_ALL; import static android.net.NetworkStats.ROAMING_ALL; @@ -211,7 +212,8 @@ public class NetworkStatsService extends INetworkStatsService.Stub { /** * Virtual network interface for video telephony. This is for VT data usage counting purpose. */ - public static final String VT_INTERFACE = "vt_data0"; + // TODO: Remove this after no one is using it. + public static final String VT_INTERFACE = NetworkStats.IFACE_VT; /** * Settings that can be changed externally. @@ -712,7 +714,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { final NetworkStatsHistory.Entry entry = history.getValues(start, end, now, null); final NetworkStats stats = new NetworkStats(end - start, 1); - stats.addValues(new NetworkStats.Entry(IFACE_ALL, UID_ALL, SET_ALL, TAG_NONE, + stats.addEntry(new NetworkStats.Entry(IFACE_ALL, UID_ALL, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, entry.rxBytes, entry.rxPackets, entry.txBytes, entry.txPackets, entry.operations)); return stats; @@ -1179,8 +1181,8 @@ public class NetworkStatsService extends INetworkStatsService.Stub { ident.getSubType(), ident.getSubscriberId(), ident.getNetworkId(), ident.getRoaming(), true /* metered */, true /* onDefaultNetwork */); - findOrCreateNetworkIdentitySet(mActiveIfaces, VT_INTERFACE).add(vtIdent); - findOrCreateNetworkIdentitySet(mActiveUidIfaces, VT_INTERFACE).add(vtIdent); + findOrCreateNetworkIdentitySet(mActiveIfaces, IFACE_VT).add(vtIdent); + findOrCreateNetworkIdentitySet(mActiveUidIfaces, IFACE_VT).add(vtIdent); } if (isMobile) { diff --git a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java index d66fd574ef4a..da2ca9b0cfde 100644 --- a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java +++ b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java @@ -43,7 +43,8 @@ public class NotificationChannelExtractor implements NotificationSignalExtractor } record.updateNotificationChannel(mConfig.getNotificationChannel(record.sbn.getPackageName(), - record.sbn.getUid(), record.getChannel().getId(), false)); + record.sbn.getUid(), record.getChannel().getId(), + record.getNotification().getShortcutId(), false)); return null; } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index f42f4f7c5c61..0e08033e0f68 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -25,6 +25,7 @@ import static android.app.Notification.FLAG_INSISTENT; import static android.app.Notification.FLAG_NO_CLEAR; import static android.app.Notification.FLAG_ONGOING_EVENT; import static android.app.Notification.FLAG_ONLY_ALERT_ONCE; +import static android.app.NotificationChannel.CONVERSATION_CHANNEL_ID_FORMAT; import static android.app.NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED; import static android.app.NotificationManager.ACTION_AUTOMATIC_ZEN_RULE_STATUS_CHANGED; import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED; @@ -1009,12 +1010,14 @@ public class NotificationManagerService extends SystemService { if (clearEffects) { clearEffects(); } + mAssistants.onPanelRevealed(items); } @Override public void onPanelHidden() { MetricsLogger.hidden(getContext(), MetricsEvent.NOTIFICATION_PANEL); EventLogTags.writeNotificationPanelHidden(); + mAssistants.onPanelHidden(); } @Override @@ -1061,6 +1064,7 @@ public class NotificationManagerService extends SystemService { reportSeen(r); } r.setVisibility(true, nv.rank, nv.count); + mAssistants.notifyAssistantVisibilityChangedLocked(r.sbn, true); boolean isHun = (nv.location == NotificationVisibility.NotificationLocation.LOCATION_FIRST_HEADS_UP); // hasBeenVisiblyExpanded must be called after updating the expansion state of @@ -1079,6 +1083,7 @@ public class NotificationManagerService extends SystemService { NotificationRecord r = mNotificationsByKey.get(nv.key); if (r == null) continue; r.setVisibility(false, nv.rank, nv.count); + mAssistants.notifyAssistantVisibilityChangedLocked(r.sbn, false); nv.recycle(); } } @@ -1182,21 +1187,14 @@ public class NotificationManagerService extends SystemService { @Override public void onNotificationBubbleChanged(String key, boolean isBubble) { - String pkg; - synchronized (mNotificationLock) { - NotificationRecord r = mNotificationsByKey.get(key); - pkg = r != null && r.sbn != null ? r.sbn.getPackageName() : null; - } - boolean isAppForeground = pkg != null - && mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND; synchronized (mNotificationLock) { NotificationRecord r = mNotificationsByKey.get(key); if (r != null) { final StatusBarNotification n = r.sbn; final int callingUid = n.getUid(); - pkg = n.getPackageName(); + final String pkg = n.getPackageName(); if (isBubble && isNotificationAppropriateToBubble(r, pkg, callingUid, - null /* oldEntry */, isAppForeground)) { + null /* oldEntry */)) { r.getNotification().flags |= FLAG_BUBBLE; } else { r.getNotification().flags &= ~FLAG_BUBBLE; @@ -2227,8 +2225,8 @@ public class NotificationManagerService extends SystemService { maybeNotifyChannelOwner(pkg, uid, preUpdate, channel); if (!fromListener) { - final NotificationChannel modifiedChannel = - mPreferencesHelper.getNotificationChannel(pkg, uid, channel.getId(), false); + final NotificationChannel modifiedChannel = mPreferencesHelper.getNotificationChannel( + pkg, uid, channel.getId(), false); mListeners.notifyNotificationChannelChanged( pkg, UserHandle.getUserHandleForUid(uid), modifiedChannel, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED); @@ -2489,7 +2487,7 @@ public class NotificationManagerService extends SystemService { .setUid(r.sbn.getUid()) .setChannelId(r.getChannel().getId()) .setChannelName(r.getChannel().getName().toString()) - .setPostedTimeMs(r.sbn.getPostTime()) + .setPostedTimeMs(System.currentTimeMillis()) .setTitle(getHistoryTitle(r.getNotification())) .setText(getHistoryText( r.sbn.getPackageContext(getContext()), r.getNotification())) @@ -3025,21 +3023,43 @@ public class NotificationManagerService extends SystemService { @Override public void createNotificationChannels(String pkg, - ParceledListSlice channelsList) throws RemoteException { + ParceledListSlice channelsList) { checkCallerIsSystemOrSameApp(pkg); createNotificationChannelsImpl(pkg, Binder.getCallingUid(), channelsList); } @Override public void createNotificationChannelsForPackage(String pkg, int uid, - ParceledListSlice channelsList) throws RemoteException { - checkCallerIsSystem(); + ParceledListSlice channelsList) { + enforceSystemOrSystemUI("only system can call this"); createNotificationChannelsImpl(pkg, uid, channelsList); } @Override + public void createConversationNotificationChannelForPackage(String pkg, int uid, + NotificationChannel parentChannel, String conversationId) { + enforceSystemOrSystemUI("only system can call this"); + Preconditions.checkNotNull(parentChannel); + Preconditions.checkNotNull(conversationId); + String parentId = parentChannel.getId(); + NotificationChannel conversationChannel = parentChannel; + conversationChannel.setId(String.format( + CONVERSATION_CHANNEL_ID_FORMAT, parentId, conversationId)); + conversationChannel.setConversationId(parentId, conversationId); + createNotificationChannelsImpl( + pkg, uid, new ParceledListSlice(Arrays.asList(conversationChannel))); + } + + @Override public NotificationChannel getNotificationChannel(String callingPkg, int userId, String targetPkg, String channelId) { + return getConversationNotificationChannel( + callingPkg, userId, targetPkg, channelId, null); + } + + @Override + public NotificationChannel getConversationNotificationChannel(String callingPkg, int userId, + String targetPkg, String channelId, String conversationId) { if (canNotifyAsPackage(callingPkg, targetPkg, userId) || isCallingUidSystem()) { int targetUid = -1; @@ -3049,7 +3069,8 @@ public class NotificationManagerService extends SystemService { /* ignore */ } return mPreferencesHelper.getNotificationChannel( - targetPkg, targetUid, channelId, false /* includeDeleted */); + targetPkg, targetUid, channelId, conversationId, + false /* includeDeleted */); } throw new SecurityException("Pkg " + callingPkg + " cannot read channels for " + targetPkg + " in " + userId); @@ -3080,6 +3101,30 @@ public class NotificationManagerService extends SystemService { } @Override + public void deleteConversationNotificationChannels(String pkg, int uid, + String conversationId) { + checkCallerIsSystem(); + final int callingUid = Binder.getCallingUid(); + List<NotificationChannel> channels = + mPreferencesHelper.getNotificationChannelsByConversationId( + pkg, uid, conversationId); + if (!channels.isEmpty()) { + for (NotificationChannel nc : channels) { + cancelAllNotificationsInt(MY_UID, MY_PID, pkg, nc.getId(), 0, 0, true, + UserHandle.getUserId(callingUid), REASON_CHANNEL_BANNED, null); + mPreferencesHelper.deleteNotificationChannel(pkg, callingUid, nc.getId()); + mListeners.notifyNotificationChannelChanged(pkg, + UserHandle.getUserHandleForUid(callingUid), + mPreferencesHelper.getNotificationChannel( + pkg, callingUid, nc.getId(), true), + NOTIFICATION_CHANNEL_OR_GROUP_DELETED); + } + handleSavePolicyFile(); + } + } + + + @Override public NotificationChannelGroup getNotificationChannelGroup(String pkg, String groupId) { checkCallerIsSystemOrSameApp(pkg); return mPreferencesHelper.getNotificationChannelGroupWithChannels( @@ -5211,7 +5256,8 @@ public class NotificationManagerService extends SystemService { channelId = (new Notification.TvExtender(notification)).getChannelId(); } final NotificationChannel channel = mPreferencesHelper.getNotificationChannel(pkg, - notificationUid, channelId, false /* includeDeleted */); + notificationUid, channelId, notification.getShortcutId(), + false /* includeDeleted */); if (channel == null) { final String noChannelStr = "No Channel found for " + "pkg=" + pkg @@ -5386,7 +5432,7 @@ public class NotificationManagerService extends SystemService { private void flagNotificationForBubbles(NotificationRecord r, String pkg, int userId, NotificationRecord oldRecord, boolean isAppForeground) { Notification notification = r.getNotification(); - if (isNotificationAppropriateToBubble(r, pkg, userId, oldRecord, isAppForeground)) { + if (isNotificationAppropriateToBubble(r, pkg, userId, oldRecord)) { notification.flags |= FLAG_BUBBLE; } else { notification.flags &= ~FLAG_BUBBLE; @@ -5406,7 +5452,7 @@ public class NotificationManagerService extends SystemService { * accounting for user choice & policy. */ private boolean isNotificationAppropriateToBubble(NotificationRecord r, String pkg, int userId, - NotificationRecord oldRecord, boolean isAppForeground) { + NotificationRecord oldRecord) { Notification notification = r.getNotification(); if (!canBubble(r, pkg, userId)) { // no log: canBubble has its own @@ -5418,11 +5464,6 @@ public class NotificationManagerService extends SystemService { return false; } - if (isAppForeground) { - // If the app is foreground it always gets to bubble - return true; - } - if (oldRecord != null && (oldRecord.getNotification().flags & FLAG_BUBBLE) != 0) { // This is an update to an active bubble return true; @@ -5438,7 +5479,7 @@ public class NotificationManagerService extends SystemService { boolean isMessageStyle = Notification.MessagingStyle.class.equals( notification.getNotificationStyle()); if (!isMessageStyle && (peopleList == null || peopleList.isEmpty())) { - logBubbleError(r.getKey(), "if not foreground, must have a person and be " + logBubbleError(r.getKey(), "Must have a person and be " + "Notification.MessageStyle or Notification.CATEGORY_CALL"); return false; } @@ -5446,6 +5487,11 @@ public class NotificationManagerService extends SystemService { // Communication is a message or a call boolean isCall = CATEGORY_CALL.equals(notification.category); boolean hasForegroundService = (notification.flags & FLAG_FOREGROUND_SERVICE) != 0; + if (hasForegroundService && !isCall) { + logBubbleError(r.getKey(), + "foreground services must be Notification.CATEGORY_CALL to bubble"); + return false; + } if (isMessageStyle) { if (hasValidRemoteInput(notification)) { return true; @@ -5459,7 +5505,7 @@ public class NotificationManagerService extends SystemService { logBubbleError(r.getKey(), "calls require foreground service"); return false; } - logBubbleError(r.getKey(), "if not foreground, must be " + logBubbleError(r.getKey(), "Must be " + "Notification.MessageStyle or Notification.CATEGORY_CALL"); return false; } @@ -8304,6 +8350,32 @@ public class NotificationManagerService extends SystemService { } } + protected void onPanelRevealed(int items) { + for (final ManagedServiceInfo info : NotificationAssistants.this.getServices()) { + mHandler.post(() -> { + final INotificationListener assistant = (INotificationListener) info.service; + try { + assistant.onPanelRevealed(items); + } catch (RemoteException ex) { + Slog.e(TAG, "unable to notify assistant (panel revealed): " + info, ex); + } + }); + } + } + + protected void onPanelHidden() { + for (final ManagedServiceInfo info : NotificationAssistants.this.getServices()) { + mHandler.post(() -> { + final INotificationListener assistant = (INotificationListener) info.service; + try { + assistant.onPanelHidden(); + } catch (RemoteException ex) { + Slog.e(TAG, "unable to notify assistant (panel hidden): " + info, ex); + } + }); + } + } + boolean hasUserSet(int userId) { synchronized (mLock) { return mUserSetMap.getOrDefault(userId, false); @@ -8371,6 +8443,24 @@ public class NotificationManagerService extends SystemService { } @GuardedBy("mNotificationLock") + void notifyAssistantVisibilityChangedLocked( + final StatusBarNotification sbn, + final boolean isVisible) { + final String key = sbn.getKey(); + Slog.d(TAG, "notifyAssistantVisibilityChangedLocked: " + key); + notifyAssistantLocked( + sbn, + false /* sameUserOnly */, + (assistant, sbnHolder) -> { + try { + assistant.onNotificationVisibilityChanged(key, isVisible); + } catch (RemoteException ex) { + Slog.e(TAG, "unable to notify assistant (visible): " + assistant, ex); + } + }); + } + + @GuardedBy("mNotificationLock") void notifyAssistantExpansionChangedLocked( final StatusBarNotification sbn, final boolean isUserAction, diff --git a/services/core/java/com/android/server/notification/OWNERS b/services/core/java/com/android/server/notification/OWNERS new file mode 100644 index 000000000000..5a19656b36a6 --- /dev/null +++ b/services/core/java/com/android/server/notification/OWNERS @@ -0,0 +1,4 @@ +dsandler@android.com +juliacr@google.com +beverlyt@google.com +pixel@google.com diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index cdb0a17ada0a..92fcb7f99f9c 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -770,6 +770,13 @@ public class PreferencesHelper implements RankingConfig { channel.setShowBadge(false); } channel.setOriginalImportance(channel.getImportance()); + + // validate parent + if (channel.getParentChannelId() != null) { + Preconditions.checkArgument(r.channels.containsKey(channel.getParentChannelId()), + "Tried to create a conversation channel without a preexisting parent"); + } + r.channels.put(channel.getId(), channel); if (channel.canBypassDnd() != mAreChannelsBypassingDnd) { updateChannelsBypassingDnd(mContext.getUserId()); @@ -851,6 +858,13 @@ public class PreferencesHelper implements RankingConfig { public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId, boolean includeDeleted) { Objects.requireNonNull(pkg); + return getNotificationChannel(pkg, uid, channelId, null, includeDeleted); + } + + @Override + public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId, + String conversationId, boolean includeDeleted) { + Preconditions.checkNotNull(pkg); synchronized (mPackagePreferences) { PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid); if (r == null) { @@ -859,14 +873,51 @@ public class PreferencesHelper implements RankingConfig { if (channelId == null) { channelId = NotificationChannel.DEFAULT_CHANNEL_ID; } - final NotificationChannel nc = r.channels.get(channelId); - if (nc != null && (includeDeleted || !nc.isDeleted())) { - return nc; + if (conversationId == null) { + final NotificationChannel nc = r.channels.get(channelId); + if (nc != null && (includeDeleted || !nc.isDeleted())) { + return nc; + } + } else { + // look for an automatically created conversation specific channel + return findConversationChannel(r, channelId, conversationId, includeDeleted); } return null; } } + private NotificationChannel findConversationChannel(PackagePreferences p, String parentId, + String conversationId, boolean includeDeleted) { + for (NotificationChannel nc : p.channels.values()) { + if (conversationId.equals(nc.getConversationId()) + && parentId.equals(nc.getParentChannelId()) + && (includeDeleted || !nc.isDeleted())) { + return nc; + } + } + return null; + } + + public List<NotificationChannel> getNotificationChannelsByConversationId(String pkg, int uid, + String conversationId) { + Preconditions.checkNotNull(pkg); + Preconditions.checkNotNull(conversationId); + List<NotificationChannel> channels = new ArrayList<>(); + synchronized (mPackagePreferences) { + PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid); + if (r == null) { + return channels; + } + for (NotificationChannel nc : r.channels.values()) { + if (conversationId.equals(nc.getConversationId()) + && !nc.isDeleted()) { + channels.add(nc); + } + } + return channels; + } + } + @Override public void deleteNotificationChannel(String pkg, int uid, String channelId) { synchronized (mPackagePreferences) { diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java index 7816f3619023..4b044c13ecc6 100644 --- a/services/core/java/com/android/server/notification/RankingConfig.java +++ b/services/core/java/com/android/server/notification/RankingConfig.java @@ -41,10 +41,15 @@ public interface RankingConfig { int uid, boolean includeDeleted, boolean includeNonGrouped, boolean includeEmpty); boolean createNotificationChannel(String pkg, int uid, NotificationChannel channel, boolean fromTargetApp, boolean hasDndAccess); - void updateNotificationChannel(String pkg, int uid, NotificationChannel channel, boolean fromUser); - NotificationChannel getNotificationChannel(String pkg, int uid, String channelId, boolean includeDeleted); + void updateNotificationChannel(String pkg, int uid, NotificationChannel channel, + boolean fromUser); + NotificationChannel getNotificationChannel(String pkg, int uid, String channelId, + boolean includeDeleted); + NotificationChannel getNotificationChannel(String pkg, int uid, String channelId, + String conversationId, boolean includeDeleted); void deleteNotificationChannel(String pkg, int uid, String channelId); void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId); void permanentlyDeleteNotificationChannels(String pkg, int uid); - ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid, boolean includeDeleted); + ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid, + boolean includeDeleted); } diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java index f1947ac15645..b782ca96ae88 100644 --- a/services/core/java/com/android/server/om/OverlayManagerService.java +++ b/services/core/java/com/android/server/om/OverlayManagerService.java @@ -1154,6 +1154,10 @@ public final class OverlayManagerService extends SystemService { throws RemoteException, IOException { PackageInfo packageInfo = mPackageManager.getPackageInfo(targetPackageName, 0, userId); + if (packageInfo == null) { + throw new IOException("Unable to get target package"); + } + String baseCodePath = packageInfo.applicationInfo.getBaseCodePath(); ApkAssets apkAssets = null; diff --git a/services/core/java/com/android/server/people/PeopleServiceInternal.java b/services/core/java/com/android/server/people/PeopleServiceInternal.java new file mode 100644 index 000000000000..31d303621116 --- /dev/null +++ b/services/core/java/com/android/server/people/PeopleServiceInternal.java @@ -0,0 +1,24 @@ +/* + * 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.people; + +import android.service.appprediction.IPredictionService; + +/** + * @hide Only for use within the system server. + */ +public abstract class PeopleServiceInternal extends IPredictionService.Stub {} diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java index 307a07bb09a2..a009183f7ecb 100644 --- a/services/core/java/com/android/server/pm/ApexManager.java +++ b/services/core/java/com/android/server/pm/ApexManager.java @@ -32,10 +32,13 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.pm.PackageParser; +import android.content.pm.parsing.AndroidPackage; import android.os.Environment; import android.os.RemoteException; import android.os.ServiceManager; import android.sysprop.ApexProperties; +import android.util.ArrayMap; +import android.util.ArraySet; import android.util.Singleton; import android.util.Slog; @@ -44,15 +47,20 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.BackgroundThread; import com.android.internal.util.IndentingPrintWriter; +import com.google.android.collect.Lists; + import java.io.File; 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.Collections; +import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; /** @@ -97,12 +105,27 @@ abstract class ApexManager { * Minimal information about APEX mount points and the original APEX package they refer to. */ static class ActiveApexInfo { + @Nullable public final String apexModuleName; public final File apexDirectory; - public final File preinstalledApexPath; + public final File preInstalledApexPath; + + private ActiveApexInfo(File apexDirectory, File preInstalledApexPath) { + this(null, apexDirectory, preInstalledApexPath); + } - private ActiveApexInfo(File apexDirectory, File preinstalledApexPath) { + private ActiveApexInfo(@Nullable String apexModuleName, File apexDirectory, + File preInstalledApexPath) { + this.apexModuleName = apexModuleName; this.apexDirectory = apexDirectory; - this.preinstalledApexPath = preinstalledApexPath; + this.preInstalledApexPath = preInstalledApexPath; + } + + private ActiveApexInfo(ApexInfo apexInfo) { + this( + apexInfo.moduleName, + new File(Environment.getApexDirectory() + File.separator + + apexInfo.moduleName), + new File(apexInfo.preinstalledModulePath)); } } @@ -232,6 +255,17 @@ abstract class ApexManager { abstract boolean uninstallApex(String apexPackagePath); /** + * Registers an APK package as an embedded apk of apex. + */ + abstract void registerApkInApex(AndroidPackage pkg); + + /** + * Returns list of {@code packageName} of apks inside the given apex. + * @param apexPackageName Package name of the apk container of apex + */ + abstract List<String> getApksInApex(String apexPackageName); + + /** * Dumps various state information to the provided {@link PrintWriter} object. * * @param pw the {@link PrintWriter} object to send information to. @@ -255,16 +289,33 @@ abstract class ApexManager { static class ApexManagerImpl extends ApexManager { private final IApexService mApexService; private final Object mLock = new Object(); + + @GuardedBy("mLock") + private Set<ActiveApexInfo> mActiveApexInfosCache; + /** - * A map from {@code APEX packageName} to the {@Link PackageInfo} generated from the {@code - * AndroidManifest.xml} - * - * <p>Note that key of this map is {@code packageName} field of the corresponding {@code - * AndroidManifest.xml}. - */ + * Contains the list of {@code packageName}s of apks-in-apex for given + * {@code apexModuleName}. See {@link #mPackageNameToApexModuleName} to understand the + * difference between {@code packageName} and {@code apexModuleName}. + */ + @GuardedBy("mLock") + private Map<String, List<String>> mApksInApex = new ArrayMap<>(); + @GuardedBy("mLock") private List<PackageInfo> mAllPackagesCache; + /** + * An APEX is a file format that delivers the apex-payload wrapped in an apk container. The + * apk container has a reference name, called {@code packageName}, which is found inside the + * {@code AndroidManifest.xml}. The apex payload inside the container also has a reference + * name, called {@code apexModuleName}, which is found in {@code apex_manifest.json} file. + * + * {@link #mPackageNameToApexModuleName} contains the mapping from {@code packageName} of + * the apk container to {@code apexModuleName} of the apex-payload inside. + */ + @GuardedBy("mLock") + private Map<String, String> mPackageNameToApexModuleName; + ApexManagerImpl(IApexService apexService) { mApexService = apexService; } @@ -291,18 +342,25 @@ abstract class ApexManager { @Override List<ActiveApexInfo> getActiveApexInfos() { - try { - return Arrays.stream(mApexService.getActivePackages()) - .map(apexInfo -> new ActiveApexInfo( - new File( - Environment.getApexDirectory() + File.separator - + apexInfo.moduleName), - new File(apexInfo.preinstalledModulePath))).collect( - Collectors.toList()); - } catch (RemoteException e) { - Slog.e(TAG, "Unable to retrieve packages from apexservice", e); + synchronized (mLock) { + if (mActiveApexInfosCache == null) { + try { + mActiveApexInfosCache = new ArraySet<>(); + final ApexInfo[] activePackages = mApexService.getActivePackages(); + for (int i = 0; i < activePackages.length; i++) { + ApexInfo apexInfo = activePackages[i]; + mActiveApexInfosCache.add(new ActiveApexInfo(apexInfo)); + } + } catch (RemoteException e) { + Slog.e(TAG, "Unable to retrieve packages from apexservice", e); + } + } + if (mActiveApexInfosCache != null) { + return new ArrayList<>(mActiveApexInfosCache); + } else { + return Collections.emptyList(); + } } - return Collections.emptyList(); } @Override @@ -325,6 +383,7 @@ abstract class ApexManager { } try { mAllPackagesCache = new ArrayList<>(); + mPackageNameToApexModuleName = new HashMap<>(); HashSet<String> activePackagesSet = new HashSet<>(); HashSet<String> factoryPackagesSet = new HashSet<>(); final ApexInfo[] allPkgs = mApexService.getAllPackages(); @@ -350,6 +409,7 @@ abstract class ApexManager { final PackageInfo packageInfo = PackageParser.generatePackageInfo(pkg, ai, flags); mAllPackagesCache.add(packageInfo); + mPackageNameToApexModuleName.put(packageInfo.packageName, ai.moduleName); if (ai.isActive) { if (activePackagesSet.contains(packageInfo.packageName)) { throw new IllegalStateException( @@ -366,7 +426,6 @@ abstract class ApexManager { } factoryPackagesSet.add(packageInfo.packageName); } - } } catch (RemoteException re) { Slog.e(TAG, "Unable to retrieve packages from apexservice: " + re.toString()); @@ -533,6 +592,37 @@ abstract class ApexManager { } } + @Override + void registerApkInApex(AndroidPackage pkg) { + synchronized (mLock) { + final Iterator<ActiveApexInfo> it = mActiveApexInfosCache.iterator(); + while (it.hasNext()) { + final ActiveApexInfo aai = it.next(); + if (pkg.getBaseCodePath().startsWith(aai.apexDirectory.getAbsolutePath())) { + List<String> apks = mApksInApex.get(aai.apexModuleName); + if (apks == null) { + apks = Lists.newArrayList(); + mApksInApex.put(aai.apexModuleName, apks); + } + apks.add(pkg.getPackageName()); + } + } + } + } + + @Override + List<String> getApksInApex(String apexPackageName) { + // TODO(b/142712057): Avoid calling populateAllPackagesCacheIfNeeded during boot. + populateAllPackagesCacheIfNeeded(); + synchronized (mLock) { + String moduleName = mPackageNameToApexModuleName.get(apexPackageName); + if (moduleName == null) { + return Collections.emptyList(); + } + return mApksInApex.getOrDefault(moduleName, Collections.emptyList()); + } + } + /** * Dump information about the packages contained in a particular cache * @param packagesCache the cache to print information about. @@ -614,7 +704,6 @@ abstract class ApexManager { * updating APEX packages. */ private static final class ApexManagerFlattenedApex extends ApexManager { - @Override List<ActiveApexInfo> getActiveApexInfos() { // There is no apexd running in case of flattened apex @@ -721,6 +810,16 @@ abstract class ApexManager { } @Override + void registerApkInApex(AndroidPackage pkg) { + // No-op + } + + @Override + List<String> getApksInApex(String apexPackageName) { + return Collections.emptyList(); + } + + @Override void dump(PrintWriter pw, String packageName) { // No-op } diff --git a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java index b25e1e2160c3..ed7139991937 100644 --- a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java +++ b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java @@ -15,35 +15,45 @@ */ package com.android.server.pm; +import static android.app.AppOpsManager.OP_INTERACT_ACROSS_PROFILES; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE; +import android.Manifest; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.ActivityOptions; +import android.app.AppGlobals; import android.app.AppOpsManager; import android.app.IApplicationThread; import android.app.admin.DevicePolicyEventLogger; +import android.app.admin.DevicePolicyManagerInternal; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ICrossProfileApps; +import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.ResolveInfo; import android.os.Binder; +import android.os.IBinder; import android.os.RemoteException; +import android.os.ServiceManager; import android.os.UserHandle; import android.os.UserManager; import android.stats.devicepolicy.DevicePolicyEnums; import android.text.TextUtils; +import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.Preconditions; +import com.android.internal.app.IAppOpsService; +import com.android.internal.util.ArrayUtils; import com.android.server.LocalServices; +import com.android.server.appop.AppOpsService; import com.android.server.wm.ActivityTaskManagerInternal; import java.util.ArrayList; @@ -55,6 +65,9 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub { private Context mContext; private Injector mInjector; + private AppOpsService mAppOpsService; + private final DevicePolicyManagerInternal mDpmi; + private final IPackageManager mIpm; public CrossProfileAppsServiceImpl(Context context) { this(context, new InjectorImpl(context)); @@ -64,6 +77,8 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub { CrossProfileAppsServiceImpl(Context context, Injector injector) { mContext = context; mInjector = injector; + mIpm = AppGlobals.getPackageManager(); + mDpmi = LocalServices.getService(DevicePolicyManagerInternal.class); } @Override @@ -153,6 +168,63 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub { userId); } + @Override + public boolean canRequestInteractAcrossProfiles(String callingPackage) { + Objects.requireNonNull(callingPackage); + verifyCallingPackage(callingPackage); + + final List<UserHandle> targetUserProfiles = getTargetUserProfilesUnchecked( + callingPackage, mInjector.getCallingUserId()); + if (targetUserProfiles.isEmpty()) { + return false; + } + + if (!hasRequestedAppOpPermission( + AppOpsManager.opToPermission(OP_INTERACT_ACROSS_PROFILES), callingPackage)) { + return false; + } + return isCrossProfilePackageWhitelisted(callingPackage); + } + + private boolean hasRequestedAppOpPermission(String permission, String packageName) { + try { + String[] packages = mIpm.getAppOpPermissionPackages(permission); + return ArrayUtils.contains(packages, packageName); + } catch (RemoteException exc) { + Slog.e(TAG, "PackageManager dead. Cannot get permission info"); + return false; + } + } + + @Override + public boolean canInteractAcrossProfiles(String callingPackage) { + Objects.requireNonNull(callingPackage); + verifyCallingPackage(callingPackage); + + final List<UserHandle> targetUserProfiles = getTargetUserProfilesUnchecked( + callingPackage, mInjector.getCallingUserId()); + if (targetUserProfiles.isEmpty()) { + return false; + } + + final int callingUid = mInjector.getCallingUid(); + return isPermissionGranted(Manifest.permission.INTERACT_ACROSS_USERS_FULL, callingUid) + || isPermissionGranted(Manifest.permission.INTERACT_ACROSS_USERS, callingUid) + || isPermissionGranted(Manifest.permission.INTERACT_ACROSS_PROFILES, callingUid) + || AppOpsManager.MODE_ALLOWED == getAppOpsService().noteOperation( + OP_INTERACT_ACROSS_PROFILES, callingUid, callingPackage, /* featureId= */ null, + /*shouldCollectAsyncNotedOp= */false, /*message= */null); + } + + private boolean isCrossProfilePackageWhitelisted(String packageName) { + final long ident = mInjector.clearCallingIdentity(); + try { + return mDpmi.getAllCrossProfilePackages().contains(packageName); + } finally { + mInjector.restoreCallingIdentity(ident); + } + } + private List<UserHandle> getTargetUserProfilesUnchecked( String callingPackage, @UserIdInt int callingUserId) { final long ident = mInjector.clearCallingIdentity(); @@ -239,6 +311,19 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub { mInjector.getAppOpsManager().checkPackage(mInjector.getCallingUid(), callingPackage); } + private static boolean isPermissionGranted(String permission, int uid) { + return PackageManager.PERMISSION_GRANTED == ActivityManager.checkComponentPermission( + permission, uid, /* owningUid= */-1, /* exported= */ true); + } + + private AppOpsService getAppOpsService() { + if (mAppOpsService == null) { + IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE); + mAppOpsService = (AppOpsService) IAppOpsService.Stub.asInterface(b); + } + return mAppOpsService; + } + private static class InjectorImpl implements Injector { private Context mContext; diff --git a/services/core/java/com/android/server/pm/InstallSource.java b/services/core/java/com/android/server/pm/InstallSource.java index 0541797523a4..6684e3f8973e 100644 --- a/services/core/java/com/android/server/pm/InstallSource.java +++ b/services/core/java/com/android/server/pm/InstallSource.java @@ -18,6 +18,8 @@ package com.android.server.pm; import android.annotation.Nullable; +import com.android.internal.util.Preconditions; + import java.util.Objects; /** @@ -29,16 +31,27 @@ final class InstallSource { * An instance of InstallSource representing an absence of knowledge of the source of * a package. Used in preference to null. */ - static final InstallSource EMPTY = new InstallSource(null, null, null, false); + static final InstallSource EMPTY = new InstallSource(null, null, null, false, false, null); /** We also memoize this case because it is common - all un-updated system apps. */ - private static final InstallSource EMPTY_ORPHANED = new InstallSource(null, null, null, true); + private static final InstallSource EMPTY_ORPHANED = new InstallSource( + null, null, null, true, false, null); - /** The package that requested the installation, if known. */ + /** + * The package that requested the installation, if known. May not correspond to a currently + * installed package if {@link #isInitiatingPackageUninstalled} is true. + */ @Nullable final String initiatingPackageName; /** + * The signing details of the initiating package, if known. Always null if + * {@link #initiatingPackageName} is null. + */ + @Nullable + final PackageSignatures initiatingPackageSignatures; + + /** * The package on behalf of which the initiating package requested the installation, if any. * For example if a downloaded APK is installed via the Package Installer this could be the * app that performed the download. This value is provided by the initiating package and not @@ -57,76 +70,120 @@ final class InstallSource { /** Indicates if the package that was the installerPackageName has been uninstalled. */ final boolean isOrphaned; + /** + * Indicates if the package in initiatingPackageName has been uninstalled. Always false if + * {@link #initiatingPackageName} is null. + */ + final boolean isInitiatingPackageUninstalled; + + static InstallSource create(@Nullable String initiatingPackageName, + @Nullable String originatingPackageName, @Nullable String installerPackageName) { + return create(initiatingPackageName, originatingPackageName, installerPackageName, + false, false); + } + static InstallSource create(@Nullable String initiatingPackageName, @Nullable String originatingPackageName, @Nullable String installerPackageName, - boolean isOrphaned) { + boolean isOrphaned, boolean isInitiatingPackageUninstalled) { return createInternal( intern(initiatingPackageName), intern(originatingPackageName), intern(installerPackageName), - isOrphaned); + isOrphaned, isInitiatingPackageUninstalled, null); } private static InstallSource createInternal(@Nullable String initiatingPackageName, @Nullable String originatingPackageName, @Nullable String installerPackageName, - boolean isOrphaned) { + boolean isOrphaned, boolean isInitiatingPackageUninstalled, + @Nullable PackageSignatures initiatingPackageSignatures) { if (initiatingPackageName == null && originatingPackageName == null - && installerPackageName == null) { + && installerPackageName == null && initiatingPackageSignatures == null + && !isInitiatingPackageUninstalled) { return isOrphaned ? EMPTY_ORPHANED : EMPTY; } return new InstallSource(initiatingPackageName, originatingPackageName, - installerPackageName, isOrphaned); + installerPackageName, isOrphaned, isInitiatingPackageUninstalled, + initiatingPackageSignatures + ); } private InstallSource(@Nullable String initiatingPackageName, @Nullable String originatingPackageName, @Nullable String installerPackageName, - boolean isOrphaned) { + boolean isOrphaned, boolean isInitiatingPackageUninstalled, + @Nullable PackageSignatures initiatingPackageSignatures) { + if (initiatingPackageName == null) { + Preconditions.checkArgument(initiatingPackageSignatures == null); + Preconditions.checkArgument(!isInitiatingPackageUninstalled); + } this.initiatingPackageName = initiatingPackageName; this.originatingPackageName = originatingPackageName; this.installerPackageName = installerPackageName; this.isOrphaned = isOrphaned; + this.isInitiatingPackageUninstalled = isInitiatingPackageUninstalled; + this.initiatingPackageSignatures = initiatingPackageSignatures; } /** - * Return an InstallSource the same as this one except with the specified installerPackageName. + * Return an InstallSource the same as this one except with the specified + * {@link #installerPackageName}. */ - InstallSource setInstallerPackage(String installerPackageName) { + InstallSource setInstallerPackage(@Nullable String installerPackageName) { if (Objects.equals(installerPackageName, this.installerPackageName)) { return this; } return createInternal(initiatingPackageName, originatingPackageName, - intern(installerPackageName), isOrphaned); + intern(installerPackageName), isOrphaned, isInitiatingPackageUninstalled, + initiatingPackageSignatures + ); } /** - * Return an InstallSource the same as this one except with the specified value for isOrphaned. + * Return an InstallSource the same as this one except with the specified value for + * {@link #isOrphaned}. */ InstallSource setIsOrphaned(boolean isOrphaned) { if (isOrphaned == this.isOrphaned) { return this; } return createInternal(initiatingPackageName, originatingPackageName, installerPackageName, - isOrphaned); + isOrphaned, isInitiatingPackageUninstalled, initiatingPackageSignatures); } /** - * Return an InstallSource the same as this one except it does not refer to the specified - * installer package name (which is being uninstalled). + * Return an InstallSource the same as this one except with the specified + * {@link #initiatingPackageSignatures}. */ - InstallSource removeInstallerPackage(String packageName) { + InstallSource setInitiatingPackageSignatures(@Nullable PackageSignatures signatures) { + if (signatures == initiatingPackageSignatures) { + return this; + } + return createInternal(initiatingPackageName, originatingPackageName, installerPackageName, + isOrphaned, isInitiatingPackageUninstalled, signatures); + } + + /** + * Return an InstallSource the same as this one updated to reflect that the specified installer + * package name has been uninstalled. + */ + InstallSource removeInstallerPackage(@Nullable String packageName) { if (packageName == null) { return this; } boolean modified = false; - String initiatingPackageName = this.initiatingPackageName; + boolean isInitiatingPackageUninstalled = this.isInitiatingPackageUninstalled; String originatingPackageName = this.originatingPackageName; String installerPackageName = this.installerPackageName; boolean isOrphaned = this.isOrphaned; - if (packageName.equals(initiatingPackageName)) { - initiatingPackageName = null; - modified = true; + if (packageName.equals(this.initiatingPackageName)) { + if (!isInitiatingPackageUninstalled) { + // In this case we deliberately do not clear the package name (and signatures). + // We allow an app to retrieve details of its own install initiator even after + // it has been uninstalled. + isInitiatingPackageUninstalled = true; + modified = true; + } } if (packageName.equals(originatingPackageName)) { originatingPackageName = null; @@ -141,8 +198,9 @@ final class InstallSource { if (!modified) { return this; } + return createInternal(initiatingPackageName, originatingPackageName, installerPackageName, - isOrphaned); + isOrphaned, isInitiatingPackageUninstalled, initiatingPackageSignatures); } @Nullable diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index e2dfa126225f..6331dd46c035 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -188,7 +188,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements } }; - public PackageInstallerService(Context context, PackageManagerService pm, ApexManager am) { + public PackageInstallerService(Context context, PackageManagerService pm) { mContext = context; mPm = pm; mPermissionManager = LocalServices.getService(PermissionManagerServiceInternal.class); @@ -206,9 +206,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements mSessionsDir = new File(Environment.getDataSystemDirectory(), "install_sessions"); mSessionsDir.mkdirs(); - mApexManager = am; - - mStagingManager = new StagingManager(this, am, context); + mApexManager = ApexManager.getInstance(); + mStagingManager = new StagingManager(this, context); } boolean okToSendBroadcasts() { @@ -635,7 +634,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements } } InstallSource installSource = InstallSource.create(installerPackageName, - originatingPackageName, requestedInstallerPackageName, false); + originatingPackageName, requestedInstallerPackageName); session = new PackageInstallerSession(mInternalCallback, mContext, mPm, this, mInstallThread.getLooper(), mStagingManager, sessionId, userId, callingUid, installSource, params, createdMillis, diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index c8689531ed98..165bdebe070f 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -119,7 +119,6 @@ import com.android.internal.content.PackageHelper; import com.android.internal.os.SomeArgs; import com.android.internal.util.ArrayUtils; import com.android.internal.util.IndentingPrintWriter; -import com.android.internal.util.Preconditions; import com.android.server.LocalServices; import com.android.server.pm.Installer.InstallerException; import com.android.server.pm.dex.DexManager; @@ -141,6 +140,7 @@ import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.Objects; +import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; @@ -210,6 +210,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private static final String PROPERTY_NAME_INHERIT_NATIVE = "pi.inherit_native_on_dont_kill"; private static final int[] EMPTY_CHILD_SESSION_ARRAY = {}; + private static final String SYSTEM_DATA_LOADER_PACKAGE = "android"; + // TODO: enforce INSTALL_ALLOW_TEST // TODO: enforce INSTALL_ALLOW_DOWNGRADE @@ -556,6 +558,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { params.dataLoaderParams); } } + + if (isStreamingInstallation() + && this.params.dataLoaderParams.getComponentName().getPackageName() + == SYSTEM_DATA_LOADER_PACKAGE) { + assertShellOrSystemCalling("System data loaders"); + } } public SessionInfo generateInfo() { @@ -771,6 +779,17 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } + private void assertShellOrSystemCalling(String operation) { + switch (Binder.getCallingUid()) { + case android.os.Process.SHELL_UID: + case android.os.Process.ROOT_UID: + case android.os.Process.SYSTEM_UID: + break; + default: + throw new SecurityException(operation + " only supported from shell or system"); + } + } + private void assertCanWrite(boolean reverseMode) { if (isDataLoaderInstallation()) { throw new IllegalStateException( @@ -781,15 +800,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { assertPreparedAndNotSealedLocked("assertCanWrite"); } if (reverseMode) { - switch (Binder.getCallingUid()) { - case android.os.Process.SHELL_UID: - case android.os.Process.ROOT_UID: - case android.os.Process.SYSTEM_UID: - break; - default: - throw new SecurityException( - "Reverse mode only supported from shell or system"); - } + assertShellOrSystemCalling("Reverse mode"); } } @@ -1026,13 +1037,24 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mHandler.obtainMessage(MSG_COMMIT).sendToTarget(); } - private class FileSystemConnector extends IPackageInstallerSessionFileSystemConnector.Stub { + private final class FileSystemConnector extends + IPackageInstallerSessionFileSystemConnector.Stub { + final Set<String> mAddedFiles; + + FileSystemConnector(List<InstallationFile> addedFiles) { + mAddedFiles = addedFiles.stream().map(file -> file.getName()).collect( + Collectors.toSet()); + } + @Override public void writeData(String name, long offsetBytes, long lengthBytes, ParcelFileDescriptor incomingFd) { if (incomingFd == null) { throw new IllegalArgumentException("incomingFd can't be null"); } + if (!mAddedFiles.contains(name)) { + throw new SecurityException("File name is not in the list of added files."); + } try { doWriteInternal(name, offsetBytes, lengthBytes, incomingFd); } catch (IOException e) { @@ -1453,7 +1475,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } mInstallerUid = uid; - mInstallSource = InstallSource.create(packageName, null, packageName, false); + mInstallSource = InstallSource.create(packageName, null, packageName); } } catch (PackageManager.NameNotFoundException e) { onSessionTransferStatus(statusReceiver, packageName, @@ -1684,7 +1706,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { computeProgressLocked(true); // Unpack native libraries for non-incremental installation - if (isIncrementalInstallation()) { + if (!isIncrementalInstallation()) { extractNativeLibraries(stageDir, params.abiOverride, mayInheritNativeLibs()); } } @@ -2391,16 +2413,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @Override public void addFile(String name, long lengthBytes, byte[] metadata) { - if (mIncrementalFileStorages != null) { - try { - mIncrementalFileStorages.addFile(new InstallationFile(name, lengthBytes, metadata)); - //TODO(b/136132412): merge incremental and callback installation schemes - return; - } catch (IOException ex) { - throw new IllegalStateException( - "Failed to add and configure Incremental File: " + name, ex); - } - } if (!isDataLoaderInstallation()) { throw new IllegalStateException( "Cannot add files to non-data loader installation session."); @@ -2413,7 +2425,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { synchronized (mLock) { assertCallerIsOwnerOrRootLocked(); assertPreparedAndNotSealedLocked("addFile"); - mFiles.add(FileInfo.added(name, lengthBytes, metadata)); } } @@ -2464,12 +2475,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { */ private void prepareDataLoader() throws PackageManagerException, StreamingException { - if (!isStreamingInstallation()) { + if (!isDataLoaderInstallation()) { return; } - FileSystemConnector connector = new FileSystemConnector(); - List<InstallationFile> addedFiles = mFiles.stream().filter( file -> sAddedFilter.accept(new File(file.name))).map( file -> new InstallationFile( @@ -2480,6 +2489,20 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { file -> file.name.substring( 0, file.name.length() - REMOVE_MARKER_EXTENSION.length())).collect( Collectors.toList()); + if (mIncrementalFileStorages != null) { + for (InstallationFile file : addedFiles) { + try { + mIncrementalFileStorages.addFile(file); + } catch (IOException ex) { + // TODO(b/146080380): add incremental-specific error code + throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, + "Failed to add and configure Incremental File: " + file.getName(), ex); + } + } + return; + } + + final FileSystemConnector connector = new FileSystemConnector(addedFiles); DataLoaderManager dataLoaderManager = mContext.getSystemService(DataLoaderManager.class); if (dataLoaderManager == null) { @@ -2516,11 +2539,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } }; - final DataLoaderParams params = this.params.dataLoaderParams; - final FileSystemControlParcel control = new FileSystemControlParcel(); control.callback = connector; + final DataLoaderParams params = this.params.dataLoaderParams; + Bundle dataLoaderParams = new Bundle(); dataLoaderParams.putParcelable("componentName", params.getComponentName()); dataLoaderParams.putParcelable("control", control); @@ -3116,7 +3139,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } InstallSource installSource = InstallSource.create(installInitiatingPackageName, - installOriginatingPackageName, installerPackageName, false); + installOriginatingPackageName, installerPackageName); return new PackageInstallerSession(callback, context, pm, sessionProvider, installerThread, stagingManager, sessionId, userId, installerUid, installSource, params, createdMillis, stageDir, stageCid, fileInfosArray, diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 04e7372a92a7..17870ebe9957 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -492,6 +492,7 @@ public class PackageManagerService extends IPackageManager.Stub static final int SCAN_AS_PRODUCT = 1 << 20; static final int SCAN_AS_SYSTEM_EXT = 1 << 21; static final int SCAN_AS_ODM = 1 << 22; + static final int SCAN_AS_APK_IN_APEX = 1 << 23; @IntDef(flag = true, prefix = { "SCAN_" }, value = { SCAN_NO_DEX, @@ -2589,6 +2590,9 @@ public class PackageManagerService extends IPackageManager.Stub & (SCAN_AS_VENDOR | SCAN_AS_ODM | SCAN_AS_PRODUCT | SCAN_AS_SYSTEM_EXT)) != 0) { return true; } + if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0) { + return true; + } return false; } @@ -3332,7 +3336,7 @@ public class PackageManagerService extends IPackageManager.Stub } } - mInstallerService = new PackageInstallerService(mContext, this, mApexManager); + mInstallerService = new PackageInstallerService(mContext, this); final Pair<ComponentName, String> instantAppResolverComponent = getInstantAppResolverLPr(); if (instantAppResolverComponent != null) { @@ -5344,8 +5348,9 @@ public class PackageManagerService extends IPackageManager.Stub if (!mUserManager.exists(userId)) return null; final int callingUid = Binder.getCallingUid(); flags = updateFlagsForComponent(flags, userId); - mPermissionManager.enforceCrossUserPermission(callingUid, userId, - false /* requireFullPermission */, false /* checkShell */, "get service info"); + mPermissionManager.enforceCrossUserOrProfilePermission( + callingUid, userId, false /* requireFullPermission */, false /* checkShell */, + "get service info"); synchronized (mLock) { ParsedService s = mComponentResolver.getService(component); if (DEBUG_PACKAGE_INFO) Log.v( @@ -7795,8 +7800,10 @@ public class PackageManagerService extends IPackageManager.Stub String resolvedType, int flags, int userId, int callingUid, boolean includeInstantApps) { if (!mUserManager.exists(userId)) return Collections.emptyList(); - mPermissionManager.enforceCrossUserPermission(callingUid, userId, - false /*requireFullPermission*/, false /*checkShell*/, + mPermissionManager.enforceCrossUserOrProfilePermission(callingUid, + userId, + false /*requireFullPermission*/, + false /*checkShell*/, "query intent receivers"); final String instantAppPkgName = getInstantAppPackageName(callingUid); flags = updateFlagsForResolve(flags, userId, callingUid, includeInstantApps); @@ -11710,6 +11717,9 @@ public class PackageManagerService extends IPackageManager.Stub mSettings.insertPackageSettingLPw(pkgSetting, pkg); // Add the new setting to mPackages mPackages.put(pkg.getAppInfoPackageName(), pkg); + if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0) { + mApexManager.registerApkInApex(pkg); + } // Add the package's KeySets to the global KeySetManagerService KeySetManagerService ksms = mSettings.mKeySetManagerService; @@ -13452,9 +13462,7 @@ public class PackageManagerService extends IPackageManager.Stub // Okay! targetPackageSetting.setInstallerPackageName(installerPackageName); - if (installerPackageName != null) { - mSettings.mInstallerPackages.add(installerPackageName); - } + mSettings.addInstallerPackageNames(targetPackageSetting.installSource); scheduleWriteSettingsLocked(); } } @@ -15160,9 +15168,10 @@ public class PackageManagerService extends IPackageManager.Stub Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "updateSettings"); final String pkgName = pkg.getPackageName(); - final String installerPackageName = installArgs.installSource.installerPackageName; final int[] installedForUsers = res.origUsers; final int installReason = installArgs.installReason; + InstallSource installSource = installArgs.installSource; + final String installerPackageName = installSource.installerPackageName; if (DEBUG_INSTALL) Slog.d(TAG, "New package installed in " + pkg.getCodePath()); synchronized (mLock) { @@ -15171,7 +15180,7 @@ public class PackageManagerService extends IPackageManager.Stub // For system-bundled packages, we assume that installing an upgraded version // of the package implies that the user actually wants to run that new code, // so we enable the package. - PackageSetting ps = mSettings.mPackages.get(pkgName); + final PackageSetting ps = mSettings.mPackages.get(pkgName); final int userId = installArgs.user.getIdentifier(); if (ps != null) { if (isSystemApp(pkg)) { @@ -15208,8 +15217,16 @@ public class PackageManagerService extends IPackageManager.Stub ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, userId, installerPackageName); } - ps.setInstallSource(installArgs.installSource); - + if (installSource.initiatingPackageName != null) { + final PackageSetting ips = mSettings.mPackages.get( + installSource.initiatingPackageName); + if (ips != null) { + installSource = installSource.setInitiatingPackageSignatures( + ips.signatures); + } + } + ps.setInstallSource(installSource); + mSettings.addInstallerPackageNames(installSource); // When replacing an existing package, preserve the original install reason for all // users that had the package installed before. @@ -15239,7 +15256,6 @@ public class PackageManagerService extends IPackageManager.Stub res.name = pkgName; res.uid = pkg.getUid(); res.pkg = pkg; - mSettings.setInstallerPackageName(pkgName, installerPackageName); res.setReturnCode(PackageManager.INSTALL_SUCCEEDED); //to update install status Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "writeSettings"); @@ -17751,10 +17767,10 @@ public class PackageManagerService extends IPackageManager.Stub ApexManager.ActiveApexInfo apexInfo) { for (int i = 0, size = SYSTEM_PARTITIONS.size(); i < size; i++) { SystemPartition sp = SYSTEM_PARTITIONS.get(i); - if (apexInfo.preinstalledApexPath.getAbsolutePath().startsWith( + if (apexInfo.preInstalledApexPath.getAbsolutePath().startsWith( sp.folder.getAbsolutePath())) { - return new SystemPartition(apexInfo.apexDirectory, sp.scanFlag, - false /* hasOverlays */); + return new SystemPartition(apexInfo.apexDirectory, + sp.scanFlag | SCAN_AS_APK_IN_APEX, false /* hasOverlays */); } } return null; @@ -19374,7 +19390,7 @@ public class PackageManagerService extends IPackageManager.Stub // PermissionController manages default home directly. return false; } - mPermissionManager.setDefaultHome(currentPackageName, userId, (successful) -> { + mPermissionManager.setDefaultHome(packageName, userId, (successful) -> { if (successful) { postPreferredActivityChangedBroadcast(userId); } @@ -19978,19 +19994,25 @@ public class PackageManagerService extends IPackageManager.Stub } } - // All installSource strings are interned, so == is ok here - if (installSource.initiatingPackageName == installSource.installerPackageName) { - // The installer and initiator will often be the same, and when they are - // we can skip doing the same check again. - initiatingPackageName = installerPackageName; + if (installSource.isInitiatingPackageUninstalled) { + // TODO(b/146555198) Allow the app itself to see the info + // (at least for non-instant apps) + initiatingPackageName = null; } else { - initiatingPackageName = installSource.initiatingPackageName; - final PackageSetting ps = mSettings.mPackages.get(initiatingPackageName); - if (ps == null || shouldFilterApplicationLocked(ps, callingUid, userId)) { - initiatingPackageName = null; + // All installSource strings are interned, so == is ok here + if (installSource.initiatingPackageName == installSource.installerPackageName) { + // The installer and initiator will often be the same, and when they are + // we can skip doing the same check again. + initiatingPackageName = installerPackageName; + } else { + initiatingPackageName = installSource.initiatingPackageName; + final PackageSetting ps = mSettings.mPackages.get(initiatingPackageName); + if (ps == null || shouldFilterApplicationLocked(ps, callingUid, userId)) { + initiatingPackageName = null; + } } - } + originatingPackageName = installSource.originatingPackageName; if (originatingPackageName != null) { final PackageSetting ps = mSettings.mPackages.get(originatingPackageName); @@ -20110,8 +20132,7 @@ public class PackageManagerService extends IPackageManager.Stub // Disable any carrier apps. We do this very early in boot to prevent the apps from being // disabled after already being started. CarrierAppUtils.disableCarrierAppsUntilPrivileged(mContext.getOpPackageName(), this, - mPermissionManagerService, mContext.getContentResolver(), - UserHandle.USER_SYSTEM); + mPermissionManagerService, UserHandle.USER_SYSTEM, mContext); disableSkuSpecificApps(); @@ -22779,7 +22800,7 @@ public class PackageManagerService extends IPackageManager.Stub ArrayList<String> systemPackageNames = new ArrayList<>(pkgNames.length); for (String pkgName: pkgNames) { - synchronized (mPackages) { + synchronized (mLock) { if (pkgName == null) { continue; } @@ -23057,6 +23078,11 @@ public class PackageManagerService extends IPackageManager.Stub } @Override + public void setDeviceOwnerProtectedPackages(List<String> packageNames) { + mProtectedPackages.setDeviceOwnerProtectedPackages(packageNames); + } + + @Override public boolean isPackageDataProtected(int userId, String packageName) { return mProtectedPackages.isPackageDataProtected(userId, packageName); } @@ -23435,6 +23461,11 @@ public class PackageManagerService extends IPackageManager.Stub } @Override + public List<String> getApksInApex(String apexPackageName) { + return PackageManagerService.this.mApexManager.getApksInApex(apexPackageName); + } + + @Override public void uninstallApex(String packageName, long versionCode, int userId, IntentSender intentSender, int flags) { final int callerUid = Binder.getCallingUid(); @@ -23615,7 +23646,7 @@ public class PackageManagerService extends IPackageManager.Stub @Nullable public PackageSetting getPackageSetting(String packageName) { - synchronized (mPackages) { + synchronized (mLock) { packageName = resolveInternalPackageNameLPr( packageName, PackageManager.VERSION_CODE_HIGHEST); return mSettings.mPackages.get(packageName); diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 10e2780863d8..5adab378bb73 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -34,7 +34,6 @@ import android.content.IIntentSender; import android.content.Intent; import android.content.IntentSender; import android.content.pm.ApplicationInfo; -import android.content.pm.DataLoaderParams; import android.content.pm.FeatureInfo; import android.content.pm.IPackageDataObserver; import android.content.pm.IPackageInstaller; @@ -137,10 +136,6 @@ class PackageManagerShellCommand extends ShellCommand { private final static String ART_PROFILE_SNAPSHOT_DEBUG_LOCATION = "/data/misc/profman/"; private static final int DEFAULT_WAIT_MS = 60 * 1000; - private static final String DATA_LOADER_PACKAGE = "android"; - private static final String DATA_LOADER_CLASS = - "com.android.server.pm.PackageManagerShellCommandDataLoader"; - final IPackageManager mInterface; final IPermissionManager mPermissionManager; final private WeakHashMap<String, Resources> mResourceCache = @@ -164,7 +159,7 @@ class PackageManagerShellCommand extends ShellCommand { final PrintWriter pw = getOutPrintWriter(); try { - switch(cmd) { + switch (cmd) { case "path": return runPath(); case "dump": @@ -1163,9 +1158,8 @@ class PackageManagerShellCommand extends ShellCommand { private int runStreamingInstall() throws RemoteException { final InstallParams params = makeInstallParams(); if (params.sessionParams.dataLoaderParams == null) { - final DataLoaderParams dataLoaderParams = DataLoaderParams.forStreaming( - new ComponentName(DATA_LOADER_PACKAGE, DATA_LOADER_CLASS), ""); - params.sessionParams.setDataLoaderParams(dataLoaderParams); + params.sessionParams.setDataLoaderParams( + PackageManagerShellCommandDataLoader.getDataLoaderParams(this)); } return doRunInstall(params); } diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java b/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java index 1ee9ab8927bb..a814cb8942e2 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java @@ -17,18 +17,22 @@ package com.android.server.pm; import android.annotation.NonNull; +import android.content.ComponentName; import android.content.pm.DataLoaderParams; import android.content.pm.InstallationFile; import android.os.ParcelFileDescriptor; +import android.os.ShellCommand; import android.service.dataloader.DataLoaderService; import android.text.TextUtils; import android.util.Slog; +import android.util.SparseArray; import libcore.io.IoUtils; -import java.io.File; import java.io.IOException; +import java.lang.ref.WeakReference; import java.nio.charset.StandardCharsets; +import java.security.SecureRandom; import java.util.Collection; /** @@ -37,41 +41,109 @@ import java.util.Collection; public class PackageManagerShellCommandDataLoader extends DataLoaderService { public static final String TAG = "PackageManagerShellCommandDataLoader"; + private static final String PACKAGE = "android"; + private static final String CLASS = PackageManagerShellCommandDataLoader.class.getName(); + + static final SecureRandom sRandom = new SecureRandom(); + static final SparseArray<WeakReference<ShellCommand>> sShellCommands = new SparseArray<>(); + + private static final char ARGS_DELIM = '&'; + private static final String SHELL_COMMAND_ID_PREFIX = "shellCommandId="; + private static final int INVALID_SHELL_COMMAND_ID = -1; + private static final int TOO_MANY_PENDING_SHELL_COMMANDS = 10; + + private static final String STDIN_PATH = "-"; + + static DataLoaderParams getDataLoaderParams(ShellCommand shellCommand) { + int commandId; + synchronized (sShellCommands) { + // Clean up old references. + for (int i = sShellCommands.size() - 1; i >= 0; i--) { + WeakReference<ShellCommand> oldRef = sShellCommands.valueAt(i); + if (oldRef.get() == null) { + sShellCommands.removeAt(i); + } + } + + // Sanity check. + if (sShellCommands.size() > TOO_MANY_PENDING_SHELL_COMMANDS) { + Slog.e(TAG, "Too many pending shell commands: " + sShellCommands.size()); + } + + // Generate new id and put ref to the array. + do { + commandId = sRandom.nextInt(Integer.MAX_VALUE - 1) + 1; + } while (sShellCommands.contains(commandId)); + + sShellCommands.put(commandId, new WeakReference<>(shellCommand)); + } + + final String args = SHELL_COMMAND_ID_PREFIX + commandId; + return DataLoaderParams.forStreaming(new ComponentName(PACKAGE, CLASS), args); + } + + private static int extractShellCommandId(String args) { + int sessionIdIdx = args.indexOf(SHELL_COMMAND_ID_PREFIX); + if (sessionIdIdx < 0) { + Slog.e(TAG, "Missing shell command id param."); + return INVALID_SHELL_COMMAND_ID; + } + sessionIdIdx += SHELL_COMMAND_ID_PREFIX.length(); + int delimIdx = args.indexOf(ARGS_DELIM, sessionIdIdx); + try { + if (delimIdx < 0) { + return Integer.parseInt(args.substring(sessionIdIdx)); + } else { + return Integer.parseInt(args.substring(sessionIdIdx, delimIdx)); + } + } catch (NumberFormatException e) { + Slog.e(TAG, "Incorrect shell command id format.", e); + return INVALID_SHELL_COMMAND_ID; + } + } + static class DataLoader implements DataLoaderService.DataLoader { - private ParcelFileDescriptor mInFd = null; + private DataLoaderParams mParams = null; private FileSystemConnector mConnector = null; - private static final String STDIN_PATH = "-"; - @Override public boolean onCreate(@NonNull DataLoaderParams dataLoaderParams, @NonNull FileSystemConnector connector) { + mParams = dataLoaderParams; mConnector = connector; return true; } + @Override public boolean onPrepareImage(Collection<InstallationFile> addedFiles, Collection<String> removedFiles) { + final int commandId = extractShellCommandId(mParams.getArguments()); + if (commandId == INVALID_SHELL_COMMAND_ID) { + return false; + } + + final WeakReference<ShellCommand> shellCommandRef; + synchronized (sShellCommands) { + shellCommandRef = sShellCommands.get(commandId, null); + } + final ShellCommand shellCommand = + shellCommandRef != null ? shellCommandRef.get() : null; + if (shellCommand == null) { + Slog.e(TAG, "Missing shell command."); + return false; + } try { for (InstallationFile fileInfo : addedFiles) { String filePath = new String(fileInfo.getMetadata(), StandardCharsets.UTF_8); if (STDIN_PATH.equals(filePath) || TextUtils.isEmpty(filePath)) { - // TODO(b/146080380): add support for STDIN installations. - if (mInFd == null) { - Slog.e(TAG, "Invalid stdin file descriptor."); - return false; - } - ParcelFileDescriptor inFd = ParcelFileDescriptor.dup( - mInFd.getFileDescriptor()); + final ParcelFileDescriptor inFd = ParcelFileDescriptor.dup( + shellCommand.getInFileDescriptor()); mConnector.writeData(fileInfo.getName(), 0, fileInfo.getSize(), inFd); } else { - File localFile = new File(filePath); ParcelFileDescriptor incomingFd = null; try { - // TODO(b/146080380): open files via callback into shell command. - incomingFd = ParcelFileDescriptor.open(localFile, - ParcelFileDescriptor.MODE_READ_ONLY); - mConnector.writeData(fileInfo.getName(), 0, localFile.length(), + incomingFd = shellCommand.openFileForSystem(filePath, "r"); + mConnector.writeData(fileInfo.getName(), 0, incomingFd.getStatSize(), incomingFd); } finally { IoUtils.closeQuietly(incomingFd); diff --git a/services/core/java/com/android/server/pm/ProtectedPackages.java b/services/core/java/com/android/server/pm/ProtectedPackages.java index 231168e9c660..4da3cc3be514 100644 --- a/services/core/java/com/android/server/pm/ProtectedPackages.java +++ b/services/core/java/com/android/server/pm/ProtectedPackages.java @@ -24,6 +24,10 @@ import android.util.SparseArray; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.ArrayUtils; + +import java.util.ArrayList; +import java.util.List; /** * Manages package names that need special protection. @@ -49,6 +53,10 @@ public class ProtectedPackages { @GuardedBy("this") private final String mDeviceProvisioningPackage; + @Nullable + @GuardedBy("this") + private List<String> mDeviceOwnerProtectedPackages; + private final Context mContext; public ProtectedPackages(Context context) { @@ -70,6 +78,10 @@ public class ProtectedPackages { : profileOwnerPackages.clone(); } + public synchronized void setDeviceOwnerProtectedPackages(List<String> packageNames) { + mDeviceOwnerProtectedPackages = new ArrayList<String>(packageNames); + } + private synchronized boolean hasDeviceOwnerOrProfileOwner(int userId, String packageName) { if (packageName == null) { return false; @@ -105,7 +117,8 @@ public class ProtectedPackages { * can modify its data or package state. */ private synchronized boolean isProtectedPackage(String packageName) { - return packageName != null && packageName.equals(mDeviceProvisioningPackage); + return packageName != null && (packageName.equals(mDeviceProvisioningPackage) + || ArrayUtils.contains(mDeviceOwnerProtectedPackages, packageName)); } /** diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 9642a1a21cf3..ec84b51577f9 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -280,8 +280,11 @@ public final class Settings { /** Map from package name to settings */ final ArrayMap<String, PackageSetting> mPackages = new ArrayMap<>(); - /** List of packages that installed other packages */ - final ArraySet<String> mInstallerPackages = new ArraySet<>(); + /** + * List of packages that were involved in installing other packages, i.e. are listed + * in at least one app's InstallSource. + */ + private final ArraySet<String> mInstallerPackages = new ArraySet<>(); /** Map from package name to appId and excluded userids */ private final ArrayMap<String, KernelPackageState> mKernelMapping = new ArrayMap<>(); @@ -441,16 +444,6 @@ public final class Settings { return mPermissions.canPropagatePermissionToInstantApp(permName); } - void setInstallerPackageName(String pkgName, String installerPkgName) { - PackageSetting p = mPackages.get(pkgName); - if (p != null) { - p.setInstallerPackageName(installerPkgName); - if (installerPkgName != null) { - mInstallerPackages.add(installerPkgName); - } - } - } - /** Gets and optionally creates a new shared user id. */ SharedUserSetting getSharedUserLPw(String name, int pkgFlags, int pkgPrivateFlags, boolean create) throws PackageManagerException { @@ -2826,6 +2819,9 @@ public final class Settings { if (installSource.initiatingPackageName != null) { serializer.attribute(null, "installInitiator", installSource.initiatingPackageName); } + if (installSource.isInitiatingPackageUninstalled) { + serializer.attribute(null, "installInitiatorUninstalled", "true"); + } if (installSource.originatingPackageName != null) { serializer.attribute(null, "installOriginator", installSource.originatingPackageName); } @@ -2843,6 +2839,11 @@ public final class Settings { pkg.signatures.writeXml(serializer, "sigs", mPastSignatures); + if (installSource.initiatingPackageSignatures != null) { + installSource.initiatingPackageSignatures.writeXml( + serializer, "install-initiator-sigs", mPastSignatures); + } + writePermissionsLPr(serializer, pkg.getPermissionsState().getInstallPermissionStates()); writeSigningKeySetLPr(serializer, pkg.keySetData); @@ -3578,6 +3579,7 @@ public final class Settings { String isOrphaned = null; String installOriginatingPackageName = null; String installInitiatingPackageName = null; + String installInitiatorUninstalled = null; String volumeUuid = null; String categoryHintString = null; String updateAvailable = null; @@ -3623,6 +3625,8 @@ public final class Settings { isOrphaned = parser.getAttributeValue(null, "isOrphaned"); installInitiatingPackageName = parser.getAttributeValue(null, "installInitiator"); installOriginatingPackageName = parser.getAttributeValue(null, "installOriginator"); + installInitiatorUninstalled = parser.getAttributeValue(null, + "installInitiatorUninstalled"); volumeUuid = parser.getAttributeValue(null, "volumeUuid"); categoryHintString = parser.getAttributeValue(null, "categoryHint"); if (categoryHintString != null) { @@ -3777,9 +3781,11 @@ public final class Settings { } if (packageSetting != null) { packageSetting.uidError = "true".equals(uidError); - packageSetting.installSource = InstallSource.create( + InstallSource installSource = InstallSource.create( installInitiatingPackageName, installOriginatingPackageName, - installerPackageName, "true".equals(isOrphaned)); + installerPackageName, "true".equals(isOrphaned), + "true".equals(installInitiatorUninstalled)); + packageSetting.installSource = installSource; packageSetting.volumeUuid = volumeUuid; packageSetting.categoryHint = categoryHint; packageSetting.legacyNativeLibraryPathString = legacyNativeLibraryPathStr; @@ -3809,9 +3815,7 @@ public final class Settings { packageSetting.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, 0, null); } - if (installerPackageName != null) { - mInstallerPackages.add(installerPackageName); - } + addInstallerPackageNames(installSource); int outerDepth = parser.getDepth(); int type; @@ -3857,6 +3861,11 @@ public final class Settings { mKeySetRefs.put(id, 1); } packageSetting.keySetData.addDefinedKeySet(id, alias); + } else if (tagName.equals("install-initiator-sigs")) { + final PackageSignatures signatures = new PackageSignatures(); + signatures.readXml(parser, mPastSignatures); + packageSetting.installSource = + packageSetting.installSource.setInitiatingPackageSignatures(signatures); } else if (tagName.equals(TAG_DOMAIN_VERIFICATION)) { readDomainVerificationLPw(parser, packageSetting); } else { @@ -3870,6 +3879,18 @@ public final class Settings { } } + void addInstallerPackageNames(InstallSource installSource) { + if (installSource.installerPackageName != null) { + mInstallerPackages.add(installSource.installerPackageName); + } + if (installSource.initiatingPackageName != null) { + mInstallerPackages.add(installSource.initiatingPackageName); + } + if (installSource.originatingPackageName != null) { + mInstallerPackages.add(installSource.originatingPackageName); + } + } + private void readDisabledComponentsLPw(PackageSettingBase packageSetting, XmlPullParser parser, int userId) throws IOException, XmlPullParserException { int outerDepth = parser.getDepth(); diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index 9e462cd529bb..2265d010216e 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -33,11 +33,13 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.SessionInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageManagerInternal; import android.content.pm.PackageParser; import android.content.pm.PackageParser.PackageParserException; import android.content.pm.PackageParser.SigningDetails; import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion; import android.content.pm.ParceledListSlice; +import android.content.pm.parsing.AndroidPackage; import android.content.rollback.IRollbackManager; import android.content.rollback.RollbackInfo; import android.content.rollback.RollbackManager; @@ -50,6 +52,8 @@ import android.os.ParcelFileDescriptor; import android.os.PowerManager; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.UserHandle; +import android.os.UserManagerInternal; import android.os.storage.IStorageManager; import android.os.storage.StorageManager; import android.util.IntArray; @@ -61,6 +65,7 @@ import android.util.apk.ApkSignatureVerifier; import com.android.internal.annotations.GuardedBy; import com.android.internal.content.PackageHelper; import com.android.internal.os.BackgroundThread; +import com.android.server.LocalServices; import java.io.File; import java.io.IOException; @@ -93,10 +98,11 @@ public class StagingManager { @GuardedBy("mStagedSessions") private final SparseIntArray mSessionRollbackIds = new SparseIntArray(); - StagingManager(PackageInstallerService pi, ApexManager am, Context context) { + StagingManager(PackageInstallerService pi, Context context) { mPi = pi; - mApexManager = am; mContext = context; + + mApexManager = ApexManager.getInstance(); mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); mPreRebootVerificationHandler = new PreRebootVerificationHandler( BackgroundThread.get().getLooper()); @@ -334,6 +340,88 @@ public class StagingManager { return PackageHelper.getStorageManager().needsCheckpoint(); } + /** + * Apks inside apex are not installed using apk-install flow. They are scanned from the system + * directory directly by PackageManager, as such, RollbackManager need to handle their data + * separately here. + */ + private void snapshotAndRestoreApkInApexUserData(PackageInstallerSession session) { + // We want to process apks inside apex. So current session needs to contain apex. + if (!sessionContainsApex(session)) { + return; + } + + boolean doSnapshotOrRestore = + (session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0 + || session.params.installReason == PackageManager.INSTALL_REASON_ROLLBACK; + if (!doSnapshotOrRestore) { + return; + } + + // Find all the apex sessions that needs processing + List<PackageInstallerSession> apexSessions = new ArrayList<>(); + if (session.isMultiPackage()) { + List<PackageInstallerSession> childrenSessions = new ArrayList<>(); + synchronized (mStagedSessions) { + for (int childSessionId : session.getChildSessionIds()) { + PackageInstallerSession childSession = mStagedSessions.get(childSessionId); + if (childSession != null) { + childrenSessions.add(childSession); + } + } + } + for (PackageInstallerSession childSession : childrenSessions) { + if (sessionContainsApex(childSession)) { + apexSessions.add(childSession); + } + } + } else { + apexSessions.add(session); + } + + // For each apex, process the apks inside it + for (PackageInstallerSession apexSession : apexSessions) { + List<String> apksInApex = mApexManager.getApksInApex(apexSession.getPackageName()); + for (String apk: apksInApex) { + snapshotAndRestoreApkInApexUserData(apk); + } + } + } + + private void snapshotAndRestoreApkInApexUserData(String packageName) { + IRollbackManager rm = IRollbackManager.Stub.asInterface( + ServiceManager.getService(Context.ROLLBACK_SERVICE)); + + PackageManagerInternal mPmi = LocalServices.getService(PackageManagerInternal.class); + AndroidPackage pkg = mPmi.getPackage(packageName); + if (pkg == null) { + Slog.e(TAG, "Could not find package: " + packageName + + "for snapshotting/restoring user data."); + return; + } + final String seInfo = pkg.getSeInfo(); + final UserManagerInternal um = LocalServices.getService(UserManagerInternal.class); + final int[] allUsers = um.getUserIds(); + + int appId = -1; + long ceDataInode = -1; + final PackageSetting ps = (PackageSetting) mPmi.getPackageSetting(packageName); + if (ps != null && rm != null) { + appId = ps.appId; + ceDataInode = ps.getCeDataInode(UserHandle.USER_SYSTEM); + // NOTE: We ignore the user specified in the InstallParam because we know this is + // an update, and hence need to restore data for all installed users. + final int[] installedUsers = ps.queryInstalledUsers(allUsers, true); + + try { + rm.snapshotAndRestoreUserData(packageName, installedUsers, appId, ceDataInode, + seInfo, 0 /*token*/); + } catch (RemoteException re) { + Slog.e(TAG, "Error snapshotting/restoring user data: " + re); + } + } + } + private void resumeSession(@NonNull PackageInstallerSession session) { Slog.d(TAG, "Resuming session " + session.sessionId); @@ -407,6 +495,7 @@ public class StagingManager { abortCheckpoint(); return; } + snapshotAndRestoreApkInApexUserData(session); Slog.i(TAG, "APEX packages in session " + session.sessionId + " were successfully activated. Proceeding with APK packages, if any"); } @@ -529,7 +618,7 @@ public class StagingManager { Arrays.stream(session.getChildSessionIds()) // Retrieve cached sessions matching ids. .mapToObj(i -> mStagedSessions.get(i)) - // Filter only the ones containing APKs.s + // Filter only the ones containing APKs. .filter(childSession -> !isApexSession(childSession)) .collect(Collectors.toList()); } diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 1455d4ae1b10..e5d5b57113c0 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -1132,14 +1132,13 @@ public class UserManagerService extends IUserManager.Stub { } /** - * Returns the user type, e.g. {@link UserManager#USER_TYPE_FULL_GUEST}, of the given userId, - * or null if the user doesn't exist. + * Returns whether the given user (specified by userId) is of the given user type, such as + * {@link UserManager#USER_TYPE_FULL_GUEST}. */ @Override - public @Nullable String getUserTypeForUser(@UserIdInt int userId) { - // TODO(b/142482943): Decide on the appropriate permission requirements. - checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, "getUserTypeForUser"); - return getUserTypeNoChecks(userId); + public boolean isUserOfType(@UserIdInt int userId, String userType) { + checkManageUsersPermission("check user type"); + return userType != null && userType.equals(getUserTypeNoChecks(userId)); } /** 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 d921f313eb48..d8c196674c59 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -4005,23 +4005,130 @@ public class PermissionManagerService extends IPermissionManager.Stub { PackageManagerServiceUtils.enforceShellRestriction(mUserManagerInt, UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId); } - if (!requirePermissionWhenSameUser && userId == UserHandle.getUserId(callingUid)) return; - if (callingUid != Process.SYSTEM_UID && callingUid != Process.ROOT_UID) { - if (requireFullPermission) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message); - } else { - try { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message); - } catch (SecurityException se) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.INTERACT_ACROSS_USERS, message); - } - } + final int callingUserId = UserHandle.getUserId(callingUid); + if (hasCrossUserPermission( + callingUid, callingUserId, userId, requireFullPermission, + requirePermissionWhenSameUser)) { + return; + } + String errorMessage = buildInvalidCrossUserPermissionMessage( + message, requireFullPermission); + Slog.w(TAG, errorMessage); + throw new SecurityException(errorMessage); + } + + /** + * Checks if the request is from the system or an app that has the appropriate cross-user + * permissions defined as follows: + * <ul> + * <li>INTERACT_ACROSS_USERS_FULL if {@code requireFullPermission} is true.</li> + * <li>INTERACT_ACROSS_USERS if the given {@userId} is in a different profile group + * to the caller.</li> + * <li>Otherwise, INTERACT_ACROSS_PROFILES if the given {@userId} is in the same profile group + * as the caller.</li> + * </ul> + * + * @param checkShell whether to prevent shell from access if there's a debugging restriction + * @param message the message to log on security exception + */ + private void enforceCrossUserOrProfilePermission(int callingUid, int userId, + boolean requireFullPermission, boolean checkShell, + String message) { + if (userId < 0) { + throw new IllegalArgumentException("Invalid userId " + userId); + } + if (checkShell) { + PackageManagerServiceUtils.enforceShellRestriction(mUserManagerInt, + UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId); + } + final int callingUserId = UserHandle.getUserId(callingUid); + if (hasCrossUserPermission(callingUid, callingUserId, userId, requireFullPermission, + /*requirePermissionWhenSameUser= */ false)) { + return; + } + final boolean isSameProfileGroup = isSameProfileGroup(callingUserId, userId); + if (isSameProfileGroup + && hasPermission(android.Manifest.permission.INTERACT_ACROSS_PROFILES)) { + return; + } + String errorMessage = buildInvalidCrossUserOrProfilePermissionMessage( + message, requireFullPermission, isSameProfileGroup); + Slog.w(TAG, errorMessage); + throw new SecurityException(errorMessage); + } + + private boolean hasCrossUserPermission( + int callingUid, int callingUserId, int userId, boolean requireFullPermission, + boolean requirePermissionWhenSameUser) { + if (!requirePermissionWhenSameUser && userId == callingUserId) { + return true; + } + if (callingUid == Process.SYSTEM_UID || callingUid == Process.ROOT_UID) { + return true; + } + if (requireFullPermission) { + return hasPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL); + } + return hasPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) + || hasPermission(Manifest.permission.INTERACT_ACROSS_USERS); + } + + private boolean hasPermission(String permission) { + return mContext.checkCallingOrSelfPermission(permission) + == PackageManager.PERMISSION_GRANTED; + } + + private boolean isSameProfileGroup(@UserIdInt int callerUserId, @UserIdInt int userId) { + final long identity = Binder.clearCallingIdentity(); + try { + return UserManagerService.getInstance().isSameProfileGroup(callerUserId, userId); + } finally { + Binder.restoreCallingIdentity(identity); } } + private static String buildInvalidCrossUserPermissionMessage( + String message, boolean requireFullPermission) { + StringBuilder builder = new StringBuilder(); + if (message != null) { + builder.append(message); + builder.append(": "); + } + builder.append("Requires "); + builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL); + if (requireFullPermission) { + builder.append("."); + return builder.toString(); + } + builder.append(" or "); + builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS); + builder.append("."); + return builder.toString(); + } + + private static String buildInvalidCrossUserOrProfilePermissionMessage( + String message, boolean requireFullPermission, boolean isSameProfileGroup) { + StringBuilder builder = new StringBuilder(); + if (message != null) { + builder.append(message); + builder.append(": "); + } + builder.append("Requires "); + builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL); + if (requireFullPermission) { + builder.append("."); + return builder.toString(); + } + builder.append(" or "); + builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS); + if (isSameProfileGroup) { + builder.append(" or "); + builder.append(android.Manifest.permission.INTERACT_ACROSS_PROFILES); + } + builder.append("."); + return builder.toString(); + } + @GuardedBy({"mSettings.mLock", "mLock"}) private int calculateCurrentPermissionFootprintLocked(BasePermission tree) { int size = 0; @@ -4215,6 +4322,17 @@ public class PermissionManagerService extends IPermissionManager.Stub { PermissionManagerService.this.enforceCrossUserPermission(callingUid, userId, requireFullPermission, checkShell, requirePermissionWhenSameUser, message); } + + @Override + public void enforceCrossUserOrProfilePermission(int callingUid, int userId, + boolean requireFullPermission, boolean checkShell, String message) { + PermissionManagerService.this.enforceCrossUserOrProfilePermission(callingUid, + userId, + requireFullPermission, + checkShell, + message); + } + @Override public void enforceGrantRevokeRuntimePermissionPermissions(String message) { PermissionManagerService.this.enforceGrantRevokeRuntimePermissionPermissions(message); diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java index 0f22619fafa6..58a9f42f372d 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java @@ -271,6 +271,15 @@ public abstract class PermissionManagerServiceInternal extends PermissionManager */ public abstract void enforceCrossUserPermission(int callingUid, int userId, boolean requireFullPermission, boolean checkShell, @NonNull String message); + + /** + * Similar to {@link #enforceCrossUserPermission(int, int, boolean, boolean, String)} + * but also allows INTERACT_ACROSS_PROFILES permission if calling user and {@code userId} are + * in the same profile group. + */ + public abstract void enforceCrossUserOrProfilePermission(int callingUid, int userId, + boolean requireFullPermission, boolean checkShell, @NonNull String message); + /** * @see #enforceCrossUserPermission(int, int, boolean, boolean, String) * @param requirePermissionWhenSameUser When {@code true}, still require the cross user diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java index e7269a6d9498..a86c8d7545b1 100644 --- a/services/core/java/com/android/server/policy/PermissionPolicyService.java +++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java @@ -58,6 +58,7 @@ import android.util.SparseBooleanArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IAppOpsCallback; import com.android.internal.app.IAppOpsService; +import com.android.internal.infra.AndroidFuture; import com.android.internal.util.IntPair; import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.FgThread; @@ -68,7 +69,7 @@ import com.android.server.policy.PermissionPolicyInternal.OnInitializedCallback; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; /** * This is a permission policy that governs over all permission mechanism @@ -280,7 +281,7 @@ public final class PermissionPolicyService extends SystemService { if (DEBUG) Slog.i(LOG_TAG, "defaultPermsWereGrantedSinceBoot(" + userId + ")"); // Now call into the permission controller to apply policy around permissions - final CountDownLatch latch = new CountDownLatch(1); + final AndroidFuture<Boolean> future = new AndroidFuture<>(); // We need to create a local manager that does not schedule work on the main // there as we are on the main thread and want to block until the work is @@ -290,22 +291,22 @@ public final class PermissionPolicyService extends SystemService { getUserContext(getContext(), UserHandle.of(userId)), FgThread.getHandler()); permissionControllerManager.grantOrUpgradeDefaultRuntimePermissions( - FgThread.getExecutor(), - (Boolean success) -> { - if (!success) { + FgThread.getExecutor(), successful -> { + if (successful) { + future.complete(null); + } else { // We are in an undefined state now, let us crash and have // rescue party suggest a wipe to recover to a good one. - final String message = "Error granting/upgrading runtime permissions"; + final String message = "Error granting/upgrading runtime permissions" + + " for user " + userId; Slog.wtf(LOG_TAG, message); - throw new IllegalStateException(message); + future.completeExceptionally(new IllegalStateException(message)); } - latch.countDown(); - } - ); + }); try { - latch.await(); - } catch (InterruptedException e) { - /* ignore */ + future.get(); + } catch (InterruptedException | ExecutionException e) { + throw new IllegalStateException(e); } permissionControllerManager.updateUserSensitive(); diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 5a124a717de2..ea83adba4d8a 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -562,10 +562,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { private boolean mScreenshotChordPowerKeyTriggered; private long mScreenshotChordPowerKeyTime; - private static final long MOVING_DISPLAY_TO_TOP_DURATION_MILLIS = 10; - private volatile boolean mMovingDisplayToTopKeyTriggered; - private volatile long mMovingDisplayToTopKeyTime; - // Ringer toggle should reuse timing and triggering from screenshot power and a11y vol up private int mRingerToggleChord = VOLUME_HUSH_OFF; @@ -633,7 +629,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { private static final int MSG_POWER_VERY_LONG_PRESS = 25; private static final int MSG_NOTIFY_USER_ACTIVITY = 26; private static final int MSG_RINGER_TOGGLE_CHORD = 27; - private static final int MSG_MOVE_DISPLAY_TO_TOP = 28; private class PolicyHandler extends Handler { @Override @@ -697,7 +692,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { accessibilityShortcutActivated(); break; case MSG_BUGREPORT_TV: - requestFullBugreport(); + requestFullBugreportOrLaunchHandlerApp(); break; case MSG_ACCESSIBILITY_TV: if (mAccessibilityShortcutController.isAccessibilityShortcutAvailable(false)) { @@ -723,10 +718,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { case MSG_RINGER_TOGGLE_CHORD: handleRingerChordGesture(); break; - case MSG_MOVE_DISPLAY_TO_TOP: - mWindowManagerFuncs.moveDisplayToTop(msg.arg1); - mMovingDisplayToTopKeyTriggered = false; - break; } } } @@ -2545,36 +2536,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { @Override public long interceptKeyBeforeDispatching(IBinder focusedToken, KeyEvent event, int policyFlags) { - final long result = interceptKeyBeforeDispatchingInner(focusedToken, event, policyFlags); - final int eventDisplayId = event.getDisplayId(); - if (result == 0 && !mPerDisplayFocusEnabled - && eventDisplayId != INVALID_DISPLAY && eventDisplayId != mTopFocusedDisplayId) { - // An event is targeting a non-focused display. Try to move the display to top so that - // it can become the focused display to interact with the user. - final long eventDownTime = event.getDownTime(); - if (mMovingDisplayToTopKeyTime < eventDownTime) { - // We have not handled this event yet. Move the display to top, and then tell - // dispatcher to try again later. - mMovingDisplayToTopKeyTime = eventDownTime; - mMovingDisplayToTopKeyTriggered = true; - mHandler.sendMessage( - mHandler.obtainMessage(MSG_MOVE_DISPLAY_TO_TOP, eventDisplayId, 0)); - return MOVING_DISPLAY_TO_TOP_DURATION_MILLIS; - } else if (mMovingDisplayToTopKeyTriggered) { - // The message has not been handled yet. Tell dispatcher to try again later. - return MOVING_DISPLAY_TO_TOP_DURATION_MILLIS; - } - // The target display is still not the top focused display. Drop the event because the - // display may not contain any window which can receive keys. - Slog.w(TAG, "Dropping key targeting non-focused display #" + eventDisplayId - + " keyCode=" + KeyEvent.keyCodeToString(event.getKeyCode())); - return -1; - } - return result; - } - - private long interceptKeyBeforeDispatchingInner(IBinder focusedToken, KeyEvent event, - int policyFlags) { final boolean keyguardOn = keyguardOn(); final int keyCode = event.getKeyCode(); final int repeatCount = event.getRepeatCount(); @@ -3061,12 +3022,14 @@ public class PhoneWindowManager implements WindowManagerPolicy { return mAccessibilityTvScheduled; } - private void requestFullBugreport() { + private void requestFullBugreportOrLaunchHandlerApp() { if ("1".equals(SystemProperties.get("ro.debuggable")) || Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) == 1) { try { - ActivityManager.getService().requestFullBugReport(); + if (!ActivityManager.getService().launchBugReportHandlerApp()) { + ActivityManager.getService().requestFullBugReport(); + } } catch (RemoteException e) { Slog.e(TAG, "Error taking bugreport", e); } @@ -3613,7 +3576,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { final boolean canceled = event.isCanceled(); final int keyCode = event.getKeyCode(); final int displayId = event.getDisplayId(); - final boolean isInjected = (policyFlags & WindowManagerPolicy.FLAG_INJECTED) != 0; // If screen is off then we treat the case where the keyguard is open but hidden @@ -4035,6 +3997,23 @@ public class PhoneWindowManager implements WindowManagerPolicy { PowerManager.WAKE_REASON_WAKE_KEY, "android.policy:KEY"); } + if ((result & ACTION_PASS_TO_USER) != 0) { + // If the key event is targeted to a specific display, then the user is interacting with + // that display. Therefore, give focus to the display that the user is interacting with. + if (!mPerDisplayFocusEnabled + && displayId != INVALID_DISPLAY && displayId != mTopFocusedDisplayId) { + // An event is targeting a non-focused display. Move the display to top so that + // it can become the focused display to interact with the user. + // This should be done asynchronously, once the focus logic is fully moved to input + // from windowmanager. Currently, we need to ensure the setInputWindows completes, + // which would force the focus event to be queued before the current key event. + // TODO(b/70668286): post call to 'moveDisplayToTop' to mHandler instead + Log.i(TAG, "Moving non-focused display " + displayId + " to top " + + "because a key is targeting it"); + mWindowManagerFuncs.moveDisplayToTop(displayId); + } + } + return result; } @@ -5213,7 +5192,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { final Intent dock = createHomeDockIntent(); if (dock != null) { int result = ActivityTaskManager.getService() - .startActivityAsUser(null, null, dock, + .startActivityAsUser(null, mContext.getBasePackageName(), dock, dock.resolveTypeIfNeeded(mContext.getContentResolver()), null, null, 0, ActivityManager.START_FLAG_ONLY_IF_NEEDED, @@ -5224,7 +5203,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } int result = ActivityTaskManager.getService() - .startActivityAsUser(null, null, mHomeIntent, + .startActivityAsUser(null, mContext.getBasePackageName(), mHomeIntent, mHomeIntent.resolveTypeIfNeeded(mContext.getContentResolver()), null, null, 0, ActivityManager.START_FLAG_ONLY_IF_NEEDED, diff --git a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java index fdb14be6b1a4..c36d5ef0415c 100644 --- a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java +++ b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java @@ -17,8 +17,10 @@ package com.android.server.recoverysystem; import android.content.Context; +import android.content.IntentSender; import android.net.LocalSocket; import android.net.LocalSocketAddress; +import android.os.Binder; import android.os.IRecoverySystem; import android.os.IRecoverySystemProgressListener; import android.os.PowerManager; @@ -28,6 +30,9 @@ import android.os.SystemProperties; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.widget.LockSettingsInternal; +import com.android.internal.widget.RebootEscrowListener; +import com.android.server.LocalServices; import com.android.server.SystemService; import libcore.io.IoUtils; @@ -45,7 +50,7 @@ import java.nio.charset.StandardCharsets; * triggers /system/bin/uncrypt via init to de-encrypt an OTA package on the * /data partition so that it can be accessed under the recovery image. */ -public class RecoverySystemService extends IRecoverySystem.Stub { +public class RecoverySystemService extends IRecoverySystem.Stub implements RebootEscrowListener { private static final String TAG = "RecoverySystemService"; private static final boolean DEBUG = false; @@ -67,6 +72,10 @@ public class RecoverySystemService extends IRecoverySystem.Stub { private final Injector mInjector; private final Context mContext; + private boolean mPreparedForReboot; + private String mUnattendedRebootToken; + private IntentSender mPreparedForRebootIntentSender; + static class Injector { protected final Context mContext; @@ -78,6 +87,10 @@ public class RecoverySystemService extends IRecoverySystem.Stub { return mContext; } + public LockSettingsInternal getLockSettingsService() { + return LocalServices.getService(LockSettingsInternal.class); + } + public PowerManager getPowerManager() { return (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); } @@ -120,14 +133,23 @@ public class RecoverySystemService extends IRecoverySystem.Stub { * Handles the lifecycle events for the RecoverySystemService. */ public static final class Lifecycle extends SystemService { + private RecoverySystemService mRecoverySystemService; + public Lifecycle(Context context) { super(context); } @Override + public void onBootPhase(int phase) { + if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { + mRecoverySystemService.onSystemServicesReady(); + } + } + + @Override public void onStart() { - RecoverySystemService recoverySystemService = new RecoverySystemService(getContext()); - publishBinderService(Context.RECOVERY_SERVICE, recoverySystemService); + mRecoverySystemService = new RecoverySystemService(getContext()); + publishBinderService(Context.RECOVERY_SERVICE, mRecoverySystemService); } } @@ -141,6 +163,11 @@ public class RecoverySystemService extends IRecoverySystem.Stub { mContext = injector.getContext(); } + @VisibleForTesting + void onSystemServicesReady() { + mInjector.getLockSettingsService().setRebootEscrowListener(this); + } + @Override // Binder call public boolean uncrypt(String filename, IRecoverySystemProgressListener listener) { if (DEBUG) Slog.d(TAG, "uncrypt: " + filename); @@ -255,6 +282,95 @@ public class RecoverySystemService extends IRecoverySystem.Stub { } } + @Override // Binder call + public boolean requestLskf(String updateToken, IntentSender intentSender) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null); + + if (updateToken == null) { + return false; + } + + // No need to prepare again for the same token. + if (mPreparedForReboot && updateToken.equals(mUnattendedRebootToken)) { + return true; + } + + mPreparedForReboot = false; + mUnattendedRebootToken = updateToken; + mPreparedForRebootIntentSender = intentSender; + + final long origId = Binder.clearCallingIdentity(); + try { + mInjector.getLockSettingsService().prepareRebootEscrow(); + } finally { + Binder.restoreCallingIdentity(origId); + } + + return true; + } + + @Override + public void onPreparedForReboot(boolean ready) { + if (mUnattendedRebootToken == null) { + Slog.w(TAG, "onPreparedForReboot called when mUnattendedRebootToken is null"); + } + + mPreparedForReboot = ready; + if (ready) { + sendPreparedForRebootIntentIfNeeded(); + } + } + + private void sendPreparedForRebootIntentIfNeeded() { + final IntentSender intentSender = mPreparedForRebootIntentSender; + if (intentSender != null) { + try { + intentSender.sendIntent(null, 0, null, null, null); + } catch (IntentSender.SendIntentException e) { + Slog.w(TAG, "Could not send intent for prepared reboot: " + e.getMessage()); + } + } + } + + @Override // Binder call + public boolean clearLskf() { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null); + + mPreparedForReboot = false; + mUnattendedRebootToken = null; + mPreparedForRebootIntentSender = null; + + final long origId = Binder.clearCallingIdentity(); + try { + mInjector.getLockSettingsService().clearRebootEscrow(); + } finally { + Binder.restoreCallingIdentity(origId); + } + + return true; + } + + @Override // Binder call + public boolean rebootWithLskf(String updateToken, String reason) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null); + + if (!mPreparedForReboot) { + return false; + } + + if (updateToken != null && updateToken.equals(mUnattendedRebootToken)) { + if (!mInjector.getLockSettingsService().armRebootEscrow()) { + return false; + } + + PowerManager pm = mInjector.getPowerManager(); + pm.reboot(reason); + return true; + } + + return false; + } + /** * Check if any of the init services is still running. If so, we cannot * start a new uncrypt/setup-bcb/clear-bcb service right away; otherwise diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java index 88c1564fdb60..9f592b85a5e6 100644 --- a/services/core/java/com/android/server/rollback/Rollback.java +++ b/services/core/java/com/android/server/rollback/Rollback.java @@ -62,7 +62,7 @@ class Rollback { private static final String TAG = "RollbackManager"; - @IntDef(flag = true, prefix = { "ROLLBACK_STATE_" }, value = { + @IntDef(prefix = { "ROLLBACK_STATE_" }, value = { ROLLBACK_STATE_ENABLING, ROLLBACK_STATE_AVAILABLE, ROLLBACK_STATE_COMMITTED, @@ -92,6 +92,19 @@ class Rollback { */ static final int ROLLBACK_STATE_DELETED = 4; + @IntDef(flag = true, prefix = { "MATCH_" }, value = { + MATCH_APK_IN_APEX, + }) + @Retention(RetentionPolicy.SOURCE) + @interface RollbackInfoFlags {} + + /** + * {@link RollbackInfo} flag: include {@code RollbackInfo} packages that are apk-in-apex. + * These packages do not have their own sessions. They are embedded in an apex which has a + * session id. + */ + static final int MATCH_APK_IN_APEX = 1; + /** * The session ID for the staged session if this rollback data represents a staged session, * {@code -1} otherwise. @@ -323,8 +336,8 @@ class Rollback { new VersionedPackage(packageName, newVersion), new VersionedPackage(packageName, installedVersion), new IntArray() /* pendingBackups */, new ArrayList<>() /* pendingRestores */, - isApex, new IntArray(), new SparseLongArray() /* ceSnapshotInodes */, - rollbackDataPolicy); + isApex, false /* isApkInApex */, new IntArray(), + new SparseLongArray() /* ceSnapshotInodes */, rollbackDataPolicy); synchronized (mLock) { info.getPackages().add(packageRollbackInfo); @@ -334,6 +347,30 @@ class Rollback { } /** + * Enables this rollback for the provided apk-in-apex. + * + * @return boolean True if the rollback was enabled successfully for the specified package. + */ + boolean enableForPackageInApex(String packageName, long installedVersion, + int rollbackDataPolicy) { + // TODO(b/142712057): Extract the new version number of apk-in-apex + // The new version for the apk-in-apex is set to 0 for now. If the package is then further + // updated via non-staged install flow, then RollbackManagerServiceImpl#onPackageReplaced() + // will be called and this rollback will be deleted. Other ways of package update have not + // been handled yet. + PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo( + new VersionedPackage(packageName, 0 /* newVersion */), + new VersionedPackage(packageName, installedVersion), + new IntArray() /* pendingBackups */, new ArrayList<>() /* pendingRestores */, + false /* isApex */, true /* isApkInApex */, new IntArray(), + new SparseLongArray() /* ceSnapshotInodes */, rollbackDataPolicy); + synchronized (mLock) { + info.getPackages().add(packageRollbackInfo); + } + return true; + } + + /** * Snapshots user data for the provided package and user ids. Does nothing if this rollback is * not in the ENABLING state. */ @@ -428,6 +465,11 @@ class Rollback { parentSessionId); for (PackageRollbackInfo pkgRollbackInfo : info.getPackages()) { + if (pkgRollbackInfo.isApkInApex()) { + // No need to issue a downgrade install request for apk-in-apex. It will + // be rolled back when its parent apex is downgraded. + continue; + } PackageInstaller.SessionParams params = new PackageInstaller.SessionParams( PackageInstaller.SessionParams.MODE_FULL_INSTALL); String installerPackageName = mInstallerPackageName; @@ -453,7 +495,8 @@ class Rollback { this, pkgRollbackInfo.getPackageName()); if (packageCodePaths == null) { sendFailure(context, statusReceiver, RollbackManager.STATUS_FAILURE, - "Backup copy of package inaccessible"); + "Backup copy of package: " + + pkgRollbackInfo.getPackageName() + " is inaccessible"); return; } @@ -696,9 +739,30 @@ class Rollback { } } - int getPackageCount() { + /** + * Returns the number of {@link PackageRollbackInfo} we are storing in this {@link Rollback} + * instance. By default, this method does not include apk-in-apex package in the count. + * + * @param flags Apk-in-apex packages can be included in the count by passing + * {@link Rollback#MATCH_APK_IN_APEX} + * + * @return Counts number of {@link PackageRollbackInfo} stored in the {@link Rollback} + * according to {@code flags} passed + */ + int getPackageCount(@RollbackInfoFlags int flags) { synchronized (mLock) { - return info.getPackages().size(); + List<PackageRollbackInfo> packages = info.getPackages(); + if ((flags & MATCH_APK_IN_APEX) != 0) { + return packages.size(); + } + + int packagesWithoutApkInApex = 0; + for (PackageRollbackInfo rollbackInfo : packages) { + if (!rollbackInfo.isApkInApex()) { + packagesWithoutApkInApex++; + } + } + return packagesWithoutApkInApex; } } diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java index e29d1a765d69..8f8a5c4b14e9 100644 --- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java +++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java @@ -891,9 +891,36 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { } ApplicationInfo appInfo = pkgInfo.applicationInfo; - return rollback.enableForPackage(packageName, newPackage.versionCode, + boolean success = rollback.enableForPackage(packageName, newPackage.versionCode, pkgInfo.getLongVersionCode(), isApex, appInfo.sourceDir, appInfo.splitSourceDirs, session.rollbackDataPolicy); + if (!success) { + return success; + } + + if (isApex) { + // Check if this apex contains apks inside it. If true, then they should be added as + // a RollbackPackageInfo into this rollback + final PackageManagerInternal pmi = LocalServices.getService( + PackageManagerInternal.class); + List<String> apksInApex = pmi.getApksInApex(packageName); + for (String apkInApex : apksInApex) { + // Get information about the currently installed package. + final PackageInfo apkPkgInfo; + try { + apkPkgInfo = getPackageInfo(apkInApex); + } catch (PackageManager.NameNotFoundException e) { + // TODO: Support rolling back fresh package installs rather than + // fail here. Test this case. + Slog.e(TAG, apkInApex + " is not installed"); + return false; + } + success = rollback.enableForPackageInApex( + apkInApex, apkPkgInfo.getLongVersionCode(), session.rollbackDataPolicy); + if (!success) return success; + } + } + return true; } @Override @@ -907,9 +934,13 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { getHandler().post(() -> { snapshotUserDataInternal(packageName, userIds); restoreUserDataInternal(packageName, userIds, appId, seInfo); - final PackageManagerInternal pmi = LocalServices.getService( - PackageManagerInternal.class); - pmi.finishPackageInstall(token, false); + // When this method is called as part of the install flow, a positive token number is + // passed to it. Need to notify the PackageManager when we are done. + if (token > 0) { + final PackageManagerInternal pmi = LocalServices.getService( + PackageManagerInternal.class); + pmi.finishPackageInstall(token, false); + } }); } @@ -1195,7 +1226,11 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { return null; } - if (rollback.getPackageCount() != newRollback.getPackageSessionIdCount()) { + // We are checking if number of packages (excluding apk-in-apex) we enabled for rollback is + // equal to the number of sessions we are installing, to ensure we didn't skip enabling + // of any sessions. If we successfully enable an apex, then we can assume we enabled + // rollback for the embedded apk-in-apex, if any. + if (rollback.getPackageCount(0 /*flags*/) != newRollback.getPackageSessionIdCount()) { Slog.e(TAG, "Failed to enable rollback for all packages in session."); rollback.delete(mAppDataRollbackHelper); return null; diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java index df75a29edd79..bbcd0def05a8 100644 --- a/services/core/java/com/android/server/rollback/RollbackStore.java +++ b/services/core/java/com/android/server/rollback/RollbackStore.java @@ -341,6 +341,7 @@ class RollbackStore { json.put("pendingRestores", convertToJsonArray(pendingRestores)); json.put("isApex", info.isApex()); + json.put("isApkInApex", info.isApkInApex()); // Field is named 'installedUsers' for legacy reasons. json.put("installedUsers", convertToJsonArray(snapshottedUsers)); @@ -364,6 +365,7 @@ class RollbackStore { json.getJSONArray("pendingRestores")); final boolean isApex = json.getBoolean("isApex"); + final boolean isApkInApex = json.getBoolean("isApkInApex"); // Field is named 'installedUsers' for legacy reasons. final IntArray snapshottedUsers = convertToIntArray(json.getJSONArray("installedUsers")); @@ -375,8 +377,8 @@ class RollbackStore { PackageManager.RollbackDataPolicy.RESTORE); return new PackageRollbackInfo(versionRolledBackFrom, versionRolledBackTo, - pendingBackups, pendingRestores, isApex, snapshottedUsers, ceSnapshotInodes, - rollbackDataPolicy); + pendingBackups, pendingRestores, isApex, isApkInApex, snapshottedUsers, + ceSnapshotInodes, rollbackDataPolicy); } private static JSONArray versionedPackagesToJson(List<VersionedPackage> packages) diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java index 9b22f33a20b0..8385f406f13d 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.hardware.audio.common.V2_0.Uuid; import android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback; import android.hardware.soundtrigger.V2_3.ISoundTriggerHw; +import android.hardware.soundtrigger.V2_3.Properties; import android.media.audio.common.AudioConfig; import android.media.audio.common.AudioOffloadInfo; import android.media.soundtrigger_middleware.ConfidenceLevel; @@ -40,7 +41,6 @@ import android.media.soundtrigger_middleware.SoundTriggerModuleProperties; import android.os.HidlMemoryUtil; import java.util.regex.Matcher; -import java.util.regex.Pattern; /** * Utilities for type conversion between SoundTrigger HAL types and SoundTriggerMiddleware service @@ -60,7 +60,8 @@ class ConversionUtil { aidlProperties.maxSoundModels = hidlProperties.maxSoundModels; aidlProperties.maxKeyPhrases = hidlProperties.maxKeyPhrases; aidlProperties.maxUsers = hidlProperties.maxUsers; - aidlProperties.recognitionModes = hidlProperties.recognitionModes; + aidlProperties.recognitionModes = + hidl2aidlRecognitionModes(hidlProperties.recognitionModes); aidlProperties.captureTransition = hidlProperties.captureTransition; aidlProperties.maxBufferMs = hidlProperties.maxBufferMs; aidlProperties.concurrentCapture = hidlProperties.concurrentCapture; @@ -69,6 +70,13 @@ class ConversionUtil { return aidlProperties; } + static @NonNull SoundTriggerModuleProperties hidl2aidlProperties( + @NonNull Properties hidlProperties) { + SoundTriggerModuleProperties aidlProperties = hidl2aidlProperties(hidlProperties.base); + aidlProperties.supportedModelArch = hidlProperties.supportedModelArch; + return aidlProperties; + } + static @NonNull String hidl2aidlUuid(@NonNull Uuid hidlUuid) { if (hidlUuid.node == null || hidlUuid.node.length != 6) { diff --git a/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java index f0a0d8305bc6..dbf91a984bda 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java @@ -74,4 +74,12 @@ class Hw2CompatUtil { config_2_0.data = HidlMemoryUtil.hidlMemoryToByteList(config.data); return config_2_0; } + + static android.hardware.soundtrigger.V2_3.Properties convertProperties_2_0_to_2_3( + android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties properties) { + android.hardware.soundtrigger.V2_3.Properties properties_2_3 = + new android.hardware.soundtrigger.V2_3.Properties(); + properties_2_3.base = properties; + return properties_2_3; + } } diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java b/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java index 81252c9a8c14..2f024a50a276 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java @@ -16,7 +16,6 @@ package com.android.server.soundtrigger_middleware; -import android.hardware.soundtrigger.V2_3.ISoundTriggerHw; import android.hardware.soundtrigger.V2_3.ModelParameterRange; import android.hidl.base.V1_0.IBase; import android.os.IHwBinder; @@ -54,9 +53,10 @@ import android.os.IHwBinder; */ public interface ISoundTriggerHw2 { /** - * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#getProperties(android.hardware.soundtrigger.V2_0.ISoundTriggerHw.getPropertiesCallback + * @see android.hardware.soundtrigger.V2_3.ISoundTriggerHw#getPropertiesEx( + * android.hardware.soundtrigger.V2_3.ISoundTriggerHw.getPropertiesExCallback) */ - android.hardware.soundtrigger.V2_1.ISoundTriggerHw.Properties getProperties(); + android.hardware.soundtrigger.V2_3.Properties getProperties(); /** * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#loadSoundModel_2_1(android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel, diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java index 4a852c4b68e8..3354c561b57a 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java @@ -112,18 +112,23 @@ final class SoundTriggerHw2Compat implements ISoundTriggerHw2 { } @Override - public android.hardware.soundtrigger.V2_1.ISoundTriggerHw.Properties getProperties() { + public android.hardware.soundtrigger.V2_3.Properties getProperties() { try { AtomicInteger retval = new AtomicInteger(-1); - AtomicReference<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.Properties> + AtomicReference<android.hardware.soundtrigger.V2_3.Properties> properties = new AtomicReference<>(); - as2_0().getProperties( - (r, p) -> { - retval.set(r); - properties.set(p); - }); - handleHalStatus(retval.get(), "getProperties"); + try { + as2_3().getProperties_2_3( + (r, p) -> { + retval.set(r); + properties.set(p); + }); + } catch (NotSupported e) { + // Fall-back to the 2.0 version: + return getProperties_2_0(); + } + handleHalStatus(retval.get(), "getProperties_2_3"); return properties.get(); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); @@ -312,6 +317,21 @@ final class SoundTriggerHw2Compat implements ISoundTriggerHw2 { return as2_0().interfaceDescriptor(); } + private android.hardware.soundtrigger.V2_3.Properties getProperties_2_0() + throws RemoteException { + AtomicInteger retval = new AtomicInteger(-1); + AtomicReference<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties> + properties = + new AtomicReference<>(); + as2_0().getProperties( + (r, p) -> { + retval.set(r); + properties.set(p); + }); + handleHalStatus(retval.get(), "getProperties"); + return Hw2CompatUtil.convertProperties_2_0_to_2_3(properties.get()); + } + private int loadSoundModel_2_0( android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel soundModel, Callback callback, int cookie) diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java index 987c05fd8c9d..5a06a2c4b388 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java @@ -20,6 +20,7 @@ import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; +import android.content.PermissionChecker; import android.hardware.soundtrigger.V2_0.ISoundTriggerHw; import android.media.soundtrigger_middleware.ISoundTriggerCallback; import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService; @@ -32,6 +33,7 @@ import android.media.soundtrigger_middleware.RecognitionEvent; import android.media.soundtrigger_middleware.RecognitionStatus; import android.media.soundtrigger_middleware.SoundModel; import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor; +import android.media.soundtrigger_middleware.Status; import android.os.RemoteException; import android.os.ServiceSpecificException; import android.util.Log; @@ -223,23 +225,48 @@ public class SoundTriggerMiddlewareService extends ISoundTriggerMiddlewareServic } /** - * Throws a {@link SecurityException} if caller doesn't have the right permissions to use this - * service. + * Throws a {@link SecurityException} if caller permanently doesn't have the given permission, + * or a {@link ServiceSpecificException} with a {@link Status#TEMPORARY_PERMISSION_DENIED} if + * caller temporarily doesn't have the right permissions to use this service. */ private void checkPermissions() { - mContext.enforceCallingOrSelfPermission(Manifest.permission.RECORD_AUDIO, - "Caller must have the android.permission.RECORD_AUDIO permission."); - mContext.enforceCallingOrSelfPermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD, - "Caller must have the android.permission.CAPTURE_AUDIO_HOTWORD permission."); + enforcePermission(Manifest.permission.RECORD_AUDIO); + enforcePermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD); } /** - * Throws a {@link SecurityException} if caller doesn't have the right permissions to preempt - * active sound trigger sessions. + * Throws a {@link SecurityException} if caller permanently doesn't have the given permission, + * or a {@link ServiceSpecificException} with a {@link Status#TEMPORARY_PERMISSION_DENIED} if + * caller temporarily doesn't have the right permissions to preempt active sound trigger + * sessions. */ private void checkPreemptPermissions() { - mContext.enforceCallingOrSelfPermission(Manifest.permission.PREEMPT_SOUND_TRIGGER, - "Caller must have the android.permission.PREEMPT_SOUND_TRIGGER permission."); + enforcePermission(Manifest.permission.PREEMPT_SOUND_TRIGGER); + } + + /** + * Throws a {@link SecurityException} if caller permanently doesn't have the given permission, + * or a {@link ServiceSpecificException} with a {@link Status#TEMPORARY_PERMISSION_DENIED} if + * caller temporarily doesn't have the given permission. + * + * @param permission The permission to check. + */ + private void enforcePermission(String permission) { + final int status = PermissionChecker.checkCallingOrSelfPermissionForPreflight(mContext, + permission); + switch (status) { + case PermissionChecker.PERMISSION_GRANTED: + return; + case PermissionChecker.PERMISSION_DENIED: + throw new SecurityException( + String.format("Caller must have the %s permission.", permission)); + case PermissionChecker.PERMISSION_DENIED_APP_OP: + throw new ServiceSpecificException(Status.TEMPORARY_PERMISSION_DENIED, + String.format("Caller must have the %s permission.", permission)); + default: + throw new InternalServerError( + new RuntimeException("Unexpected perimission check result.")); + } } /** State of a sound model. */ diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java index b4f4eca5f188..f661b5e0d8a8 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java @@ -22,7 +22,7 @@ import android.app.timedetector.ManualTimeSuggestion; import android.app.timedetector.NetworkTimeSuggestion; import android.app.timedetector.PhoneTimeSuggestion; import android.content.Intent; -import android.util.TimestampedValue; +import android.os.TimestampedValue; import java.io.PrintWriter; diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java index 02656eaf5c6c..da848d87ba71 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java @@ -24,10 +24,10 @@ import android.app.timedetector.ManualTimeSuggestion; import android.app.timedetector.NetworkTimeSuggestion; import android.app.timedetector.PhoneTimeSuggestion; import android.content.Intent; +import android.os.TimestampedValue; import android.telephony.TelephonyManager; import android.util.LocalLog; import android.util.Slog; -import android.util.TimestampedValue; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; diff --git a/services/core/java/com/android/server/utils/quota/CountQuotaTracker.java b/services/core/java/com/android/server/utils/quota/CountQuotaTracker.java new file mode 100644 index 000000000000..7fe4bf849443 --- /dev/null +++ b/services/core/java/com/android/server/utils/quota/CountQuotaTracker.java @@ -0,0 +1,802 @@ +/* + * 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.utils.quota; + +import static android.text.format.DateUtils.MINUTE_IN_MILLIS; + +import static com.android.server.utils.quota.Uptc.string; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.AlarmManager; +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.UserHandle; +import android.util.ArrayMap; +import android.util.LongArrayQueue; +import android.util.Slog; +import android.util.TimeUtils; +import android.util.proto.ProtoOutputStream; +import android.util.quota.CountQuotaTrackerProto; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.IndentingPrintWriter; + +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Class that tracks whether an app has exceeded its defined count quota. + * + * Quotas are applied per userId-package-tag combination (UPTC). Tags can be null. + * + * This tracker tracks the count of instantaneous events. + * + * Limits are applied according to the category the UPTC is placed in. If a UPTC reaches its limit, + * it will be considered out of quota until it is below that limit again. A {@link Category} is a + * basic construct to apply different limits to different groups of UPTCs. For example, standby + * buckets can be a set of categories, or foreground & background could be two categories. If every + * UPTC should have the same limits applied, then only one category is needed + * ({@see Category.SINGLE_CATEGORY}). + * + * Note: all limits are enforced per category unless explicitly stated otherwise. + * + * Test: atest com.android.server.utils.quota.CountQuotaTrackerTest + * + * @hide + */ +public class CountQuotaTracker extends QuotaTracker { + private static final String TAG = CountQuotaTracker.class.getSimpleName(); + private static final boolean DEBUG = false; + + private static final String ALARM_TAG_CLEANUP = "*" + TAG + ".cleanup*"; + + @VisibleForTesting + static class ExecutionStats { + /** + * The time after which this record should be considered invalid (out of date), in the + * elapsed realtime timebase. + */ + public long expirationTimeElapsed; + + /** The window size that's used when counting the number of events. */ + public long windowSizeMs; + /** The maximum number of events allowed within the window size. */ + public int countLimit; + + /** The total number of events that occurred in the window. */ + public int countInWindow; + + /** + * The time after which the app will be under the category quota again. This is only valid + * if {@link #countInWindow} >= {@link #countLimit}. + */ + public long inQuotaTimeElapsed; + + @Override + public String toString() { + return "expirationTime=" + expirationTimeElapsed + ", " + + "windowSizeMs=" + windowSizeMs + ", " + + "countLimit=" + countLimit + ", " + + "countInWindow=" + countInWindow + ", " + + "inQuotaTime=" + inQuotaTimeElapsed; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof ExecutionStats) { + ExecutionStats other = (ExecutionStats) obj; + return this.expirationTimeElapsed == other.expirationTimeElapsed + && this.windowSizeMs == other.windowSizeMs + && this.countLimit == other.countLimit + && this.countInWindow == other.countInWindow + && this.inQuotaTimeElapsed == other.inQuotaTimeElapsed; + } + return false; + } + + @Override + public int hashCode() { + int result = 0; + result = 31 * result + Long.hashCode(expirationTimeElapsed); + result = 31 * result + Long.hashCode(windowSizeMs); + result = 31 * result + countLimit; + result = 31 * result + countInWindow; + result = 31 * result + Long.hashCode(inQuotaTimeElapsed); + return result; + } + } + + /** List of times of all instantaneous events for a UPTC, in chronological order. */ + // TODO(146148168): introduce a bucketized mode that's more efficient but less accurate + @GuardedBy("mLock") + private final UptcMap<LongArrayQueue> mEventTimes = new UptcMap<>(); + + /** Cached calculation results for each app. */ + @GuardedBy("mLock") + private final UptcMap<ExecutionStats> mExecutionStatsCache = new UptcMap<>(); + + private final Handler mHandler; + + @GuardedBy("mLock") + private long mNextCleanupTimeElapsed = 0; + @GuardedBy("mLock") + private final AlarmManager.OnAlarmListener mEventCleanupAlarmListener = () -> + CountQuotaTracker.this.mHandler.obtainMessage(MSG_CLEAN_UP_EVENTS).sendToTarget(); + + /** The rolling window size for each Category's count limit. */ + @GuardedBy("mLock") + private final ArrayMap<Category, Long> mCategoryCountWindowSizesMs = new ArrayMap<>(); + + /** + * The maximum count for each Category. For each max value count in the map, the app will + * not be allowed any more events within the latest time interval of its rolling window size. + * + * @see #mCategoryCountWindowSizesMs + */ + @GuardedBy("mLock") + private final ArrayMap<Category, Integer> mMaxCategoryCounts = new ArrayMap<>(); + + /** The longest period a registered category applies to. */ + @GuardedBy("mLock") + private long mMaxPeriodMs = 0; + + /** Drop any old events. */ + private static final int MSG_CLEAN_UP_EVENTS = 1; + + public CountQuotaTracker(@NonNull Context context, @NonNull Categorizer categorizer) { + this(context, categorizer, new Injector()); + } + + @VisibleForTesting + CountQuotaTracker(@NonNull Context context, @NonNull Categorizer categorizer, + Injector injector) { + super(context, categorizer, injector); + + mHandler = new CqtHandler(context.getMainLooper()); + } + + // Exposed API to users. + + /** + * Record that an instantaneous event happened. + * + * @return true if the UPTC is within quota, false otherwise. + */ + public boolean noteEvent(int userId, @NonNull String packageName, @Nullable String tag) { + synchronized (mLock) { + if (!isEnabledLocked() || isQuotaFreeLocked(userId, packageName)) { + return true; + } + final long nowElapsed = mInjector.getElapsedRealtime(); + + final LongArrayQueue times = mEventTimes + .getOrCreate(userId, packageName, tag, mCreateLongArrayQueue); + times.addLast(nowElapsed); + final ExecutionStats stats = getExecutionStatsLocked(userId, packageName, tag); + stats.countInWindow++; + stats.expirationTimeElapsed = Math.min(stats.expirationTimeElapsed, + nowElapsed + stats.windowSizeMs); + if (stats.countInWindow == stats.countLimit) { + final long windowEdgeElapsed = nowElapsed - stats.windowSizeMs; + while (times.size() > 0 && times.peekFirst() < windowEdgeElapsed) { + times.removeFirst(); + } + stats.inQuotaTimeElapsed = times.peekFirst() + stats.windowSizeMs; + postQuotaStatusChanged(userId, packageName, tag); + } else if (stats.countLimit > 9 + && stats.countInWindow == stats.countLimit * 4 / 5) { + // TODO: log high watermark to statsd + Slog.w(TAG, string(userId, packageName, tag) + + " has reached 80% of it's count limit of " + stats.countLimit); + } + maybeScheduleCleanupAlarmLocked(); + return isWithinQuotaLocked(stats); + } + } + + /** + * Set count limit over a rolling time window for the specified category. + * + * @param category The category these limits apply to. + * @param limit The maximum event count an app can have in the rolling window. Must be + * nonnegative. + * @param timeWindowMs The rolling time window (in milliseconds) to use when checking quota + * usage. Must be at least {@value #MIN_WINDOW_SIZE_MS} and no longer than + * {@value #MAX_WINDOW_SIZE_MS} + */ + public void setCountLimit(@NonNull Category category, int limit, long timeWindowMs) { + if (limit < 0 || timeWindowMs < 0) { + throw new IllegalArgumentException("Limit and window size must be nonnegative."); + } + synchronized (mLock) { + final Integer oldLimit = mMaxCategoryCounts.put(category, limit); + final long newWindowSizeMs = Math.max(MIN_WINDOW_SIZE_MS, + Math.min(timeWindowMs, MAX_WINDOW_SIZE_MS)); + final Long oldWindowSizeMs = mCategoryCountWindowSizesMs.put(category, newWindowSizeMs); + if (oldLimit != null && oldWindowSizeMs != null + && oldLimit == limit && oldWindowSizeMs == newWindowSizeMs) { + // No change. + return; + } + mDeleteOldEventTimesFunctor.updateMaxPeriod(); + mMaxPeriodMs = mDeleteOldEventTimesFunctor.mMaxPeriodMs; + invalidateAllExecutionStatsLocked(); + } + scheduleQuotaCheck(); + } + + /** + * Gets the count limit for the specified category. + */ + public int getLimit(@NonNull Category category) { + synchronized (mLock) { + final Integer limit = mMaxCategoryCounts.get(category); + if (limit == null) { + throw new IllegalArgumentException("Limit for " + category + " not defined"); + } + return limit; + } + } + + /** + * Gets the count time window for the specified category. + */ + public long getWindowSizeMs(@NonNull Category category) { + synchronized (mLock) { + final Long limitMs = mCategoryCountWindowSizesMs.get(category); + if (limitMs == null) { + throw new IllegalArgumentException("Limit for " + category + " not defined"); + } + return limitMs; + } + } + + // Internal implementation. + + @Override + @GuardedBy("mLock") + void dropEverythingLocked() { + mExecutionStatsCache.clear(); + mEventTimes.clear(); + } + + @Override + @GuardedBy("mLock") + @NonNull + Handler getHandler() { + return mHandler; + } + + @Override + @GuardedBy("mLock") + long getInQuotaTimeElapsedLocked(final int userId, @NonNull final String packageName, + @Nullable final String tag) { + return getExecutionStatsLocked(userId, packageName, tag).inQuotaTimeElapsed; + } + + @Override + @GuardedBy("mLock") + void handleRemovedAppLocked(String packageName, int uid) { + if (packageName == null) { + Slog.wtf(TAG, "Told app removed but given null package name."); + return; + } + final int userId = UserHandle.getUserId(uid); + + mEventTimes.delete(userId, packageName); + mExecutionStatsCache.delete(userId, packageName); + } + + @Override + @GuardedBy("mLock") + void handleRemovedUserLocked(int userId) { + mEventTimes.delete(userId); + mExecutionStatsCache.delete(userId); + } + + @Override + @GuardedBy("mLock") + boolean isWithinQuotaLocked(final int userId, @NonNull final String packageName, + @Nullable final String tag) { + if (!isEnabledLocked()) return true; + + // Quota constraint is not enforced when quota is free. + if (isQuotaFreeLocked(userId, packageName)) { + return true; + } + + return isWithinQuotaLocked(getExecutionStatsLocked(userId, packageName, tag)); + } + + @Override + @GuardedBy("mLock") + void maybeUpdateAllQuotaStatusLocked() { + final UptcMap<Boolean> doneMap = new UptcMap<>(); + mEventTimes.forEach((userId, packageName, tag, events) -> { + if (!doneMap.contains(userId, packageName, tag)) { + maybeUpdateStatusForUptcLocked(userId, packageName, tag); + doneMap.add(userId, packageName, tag, Boolean.TRUE); + } + }); + + } + + @Override + void maybeUpdateQuotaStatus(final int userId, @NonNull final String packageName, + @Nullable final String tag) { + synchronized (mLock) { + maybeUpdateStatusForUptcLocked(userId, packageName, tag); + } + } + + @Override + @GuardedBy("mLock") + void onQuotaFreeChangedLocked(boolean isFree) { + // Nothing to do here. + } + + @Override + @GuardedBy("mLock") + void onQuotaFreeChangedLocked(int userId, @NonNull String packageName, boolean isFree) { + maybeUpdateStatusForPkgLocked(userId, packageName); + } + + @GuardedBy("mLock") + private boolean isWithinQuotaLocked(@NonNull final ExecutionStats stats) { + return isUnderCountQuotaLocked(stats); + } + + @GuardedBy("mLock") + private boolean isUnderCountQuotaLocked(@NonNull ExecutionStats stats) { + return stats.countInWindow < stats.countLimit; + } + + /** Returns the execution stats of the app in the most recent window. */ + @GuardedBy("mLock") + @VisibleForTesting + @NonNull + ExecutionStats getExecutionStatsLocked(final int userId, @NonNull final String packageName, + @Nullable final String tag) { + return getExecutionStatsLocked(userId, packageName, tag, true); + } + + @GuardedBy("mLock") + @NonNull + private ExecutionStats getExecutionStatsLocked(final int userId, + @NonNull final String packageName, @Nullable String tag, + final boolean refreshStatsIfOld) { + final ExecutionStats stats = + mExecutionStatsCache.getOrCreate(userId, packageName, tag, mCreateExecutionStats); + if (refreshStatsIfOld) { + final Category category = mCategorizer.getCategory(userId, packageName, tag); + final long countWindowSizeMs = mCategoryCountWindowSizesMs.getOrDefault(category, + Long.MAX_VALUE); + final int countLimit = mMaxCategoryCounts.getOrDefault(category, Integer.MAX_VALUE); + if (stats.expirationTimeElapsed <= mInjector.getElapsedRealtime() + || stats.windowSizeMs != countWindowSizeMs + || stats.countLimit != countLimit) { + // The stats are no longer valid. + stats.windowSizeMs = countWindowSizeMs; + stats.countLimit = countLimit; + updateExecutionStatsLocked(userId, packageName, tag, stats); + } + } + + return stats; + } + + @GuardedBy("mLock") + @VisibleForTesting + void updateExecutionStatsLocked(final int userId, @NonNull final String packageName, + @Nullable final String tag, @NonNull ExecutionStats stats) { + stats.countInWindow = 0; + stats.inQuotaTimeElapsed = 0; + + // This can be used to determine when an app will have enough quota to transition from + // out-of-quota to in-quota. + final long nowElapsed = mInjector.getElapsedRealtime(); + stats.expirationTimeElapsed = nowElapsed + mMaxPeriodMs; + + final LongArrayQueue events = mEventTimes.get(userId, packageName, tag); + if (events == null) { + return; + } + + // The minimum time between the start time and the beginning of the events that were + // looked at --> how much time the stats will be valid for. + long emptyTimeMs = Long.MAX_VALUE - nowElapsed; + + final long eventStartWindowElapsed = nowElapsed - stats.windowSizeMs; + for (int i = events.size() - 1; i >= 0; --i) { + final long eventTimeElapsed = events.get(i); + if (eventTimeElapsed < eventStartWindowElapsed) { + // This event happened before the window. No point in going any further. + break; + } + stats.countInWindow++; + emptyTimeMs = Math.min(emptyTimeMs, eventTimeElapsed - eventStartWindowElapsed); + + if (stats.countInWindow >= stats.countLimit) { + stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed, + eventTimeElapsed + stats.windowSizeMs); + } + } + + stats.expirationTimeElapsed = nowElapsed + emptyTimeMs; + } + + /** Invalidate ExecutionStats for all apps. */ + @GuardedBy("mLock") + private void invalidateAllExecutionStatsLocked() { + final long nowElapsed = mInjector.getElapsedRealtime(); + mExecutionStatsCache.forEach((appStats) -> { + if (appStats != null) { + appStats.expirationTimeElapsed = nowElapsed; + } + }); + } + + @GuardedBy("mLock") + private void invalidateAllExecutionStatsLocked(final int userId, + @NonNull final String packageName) { + final ArrayMap<String, ExecutionStats> appStats = + mExecutionStatsCache.get(userId, packageName); + if (appStats != null) { + final long nowElapsed = mInjector.getElapsedRealtime(); + final int numStats = appStats.size(); + for (int i = 0; i < numStats; ++i) { + final ExecutionStats stats = appStats.valueAt(i); + if (stats != null) { + stats.expirationTimeElapsed = nowElapsed; + } + } + } + } + + @GuardedBy("mLock") + private void invalidateExecutionStatsLocked(final int userId, @NonNull final String packageName, + @Nullable String tag) { + final ExecutionStats stats = mExecutionStatsCache.get(userId, packageName, tag); + if (stats != null) { + stats.expirationTimeElapsed = mInjector.getElapsedRealtime(); + } + } + + private static final class EarliestEventTimeFunctor implements Consumer<LongArrayQueue> { + long earliestTimeElapsed = Long.MAX_VALUE; + + @Override + public void accept(LongArrayQueue events) { + if (events != null && events.size() > 0) { + earliestTimeElapsed = Math.min(earliestTimeElapsed, events.get(0)); + } + } + + void reset() { + earliestTimeElapsed = Long.MAX_VALUE; + } + } + + private final EarliestEventTimeFunctor mEarliestEventTimeFunctor = + new EarliestEventTimeFunctor(); + + /** Schedule a cleanup alarm if necessary and there isn't already one scheduled. */ + @GuardedBy("mLock") + @VisibleForTesting + void maybeScheduleCleanupAlarmLocked() { + if (mNextCleanupTimeElapsed > mInjector.getElapsedRealtime()) { + // There's already an alarm scheduled. Just stick with that one. There's no way we'll + // end up scheduling an earlier alarm. + if (DEBUG) { + Slog.v(TAG, "Not scheduling cleanup since there's already one at " + + mNextCleanupTimeElapsed + " (in " + (mNextCleanupTimeElapsed + - mInjector.getElapsedRealtime()) + "ms)"); + } + return; + } + + mEarliestEventTimeFunctor.reset(); + mEventTimes.forEach(mEarliestEventTimeFunctor); + final long earliestEndElapsed = mEarliestEventTimeFunctor.earliestTimeElapsed; + if (earliestEndElapsed == Long.MAX_VALUE) { + // Couldn't find a good time to clean up. Maybe this was called after we deleted all + // events. + if (DEBUG) { + Slog.d(TAG, "Didn't find a time to schedule cleanup"); + } + return; + } + + // Need to keep events for all apps up to the max period, regardless of their current + // category. + long nextCleanupElapsed = earliestEndElapsed + mMaxPeriodMs; + if (nextCleanupElapsed - mNextCleanupTimeElapsed <= 10 * MINUTE_IN_MILLIS) { + // No need to clean up too often. Delay the alarm if the next cleanup would be too soon + // after it. + nextCleanupElapsed += 10 * MINUTE_IN_MILLIS; + } + mNextCleanupTimeElapsed = nextCleanupElapsed; + scheduleAlarm(AlarmManager.ELAPSED_REALTIME, nextCleanupElapsed, ALARM_TAG_CLEANUP, + mEventCleanupAlarmListener); + if (DEBUG) { + Slog.d(TAG, "Scheduled next cleanup for " + mNextCleanupTimeElapsed); + } + } + + @GuardedBy("mLock") + private boolean maybeUpdateStatusForPkgLocked(final int userId, + @NonNull final String packageName) { + final UptcMap<Boolean> done = new UptcMap<>(); + + if (!mEventTimes.contains(userId, packageName)) { + return false; + } + final ArrayMap<String, LongArrayQueue> events = mEventTimes.get(userId, packageName); + if (events == null) { + Slog.wtf(TAG, + "Events map was null even though mEventTimes said it contained " + + string(userId, packageName, null)); + return false; + } + + // Lambdas can't interact with non-final outer variables. + final boolean[] changed = {false}; + events.forEach((tag, eventList) -> { + if (!done.contains(userId, packageName, tag)) { + changed[0] |= maybeUpdateStatusForUptcLocked(userId, packageName, tag); + done.add(userId, packageName, tag, Boolean.TRUE); + } + }); + + return changed[0]; + } + + /** + * Posts that the quota status for the UPTC has changed if it has changed. Avoid calling if + * there are no {@link QuotaChangeListener}s registered as the work done will be useless. + * + * @return true if the in/out quota status changed + */ + @GuardedBy("mLock") + private boolean maybeUpdateStatusForUptcLocked(final int userId, + @NonNull final String packageName, @Nullable final String tag) { + final boolean oldInQuota = isWithinQuotaLocked( + getExecutionStatsLocked(userId, packageName, tag, false)); + + final boolean newInQuota; + if (!isEnabledLocked() || isQuotaFreeLocked(userId, packageName)) { + newInQuota = true; + } else { + newInQuota = isWithinQuotaLocked( + getExecutionStatsLocked(userId, packageName, tag, true)); + } + + if (!newInQuota) { + maybeScheduleStartAlarmLocked(userId, packageName, tag); + } else { + cancelScheduledStartAlarmLocked(userId, packageName, tag); + } + + if (oldInQuota != newInQuota) { + if (DEBUG) { + Slog.d(TAG, + "Quota status changed from " + oldInQuota + " to " + newInQuota + " for " + + string(userId, packageName, tag)); + } + postQuotaStatusChanged(userId, packageName, tag); + return true; + } + + return false; + } + + private final class DeleteEventTimesFunctor implements Consumer<LongArrayQueue> { + private long mMaxPeriodMs; + + @Override + public void accept(LongArrayQueue times) { + if (times != null) { + // Remove everything older than mMaxPeriodMs time ago. + while (times.size() > 0 + && times.peekFirst() <= mInjector.getElapsedRealtime() - mMaxPeriodMs) { + times.removeFirst(); + } + } + } + + private void updateMaxPeriod() { + long maxPeriodMs = 0; + for (int i = mCategoryCountWindowSizesMs.size() - 1; i >= 0; --i) { + maxPeriodMs = Long.max(maxPeriodMs, mCategoryCountWindowSizesMs.valueAt(i)); + } + mMaxPeriodMs = maxPeriodMs; + } + } + + private final DeleteEventTimesFunctor mDeleteOldEventTimesFunctor = + new DeleteEventTimesFunctor(); + + @GuardedBy("mLock") + @VisibleForTesting + void deleteObsoleteEventsLocked() { + mEventTimes.forEach(mDeleteOldEventTimesFunctor); + } + + private class CqtHandler extends Handler { + CqtHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + synchronized (mLock) { + switch (msg.what) { + case MSG_CLEAN_UP_EVENTS: { + if (DEBUG) { + Slog.d(TAG, "Cleaning up events."); + } + deleteObsoleteEventsLocked(); + maybeScheduleCleanupAlarmLocked(); + break; + } + } + } + } + } + + private Function<Void, LongArrayQueue> mCreateLongArrayQueue = aVoid -> new LongArrayQueue(); + private Function<Void, ExecutionStats> mCreateExecutionStats = aVoid -> new ExecutionStats(); + + //////////////////////// TESTING HELPERS ///////////////////////////// + + @VisibleForTesting + @Nullable + LongArrayQueue getEvents(int userId, String packageName, String tag) { + return mEventTimes.get(userId, packageName, tag); + } + + //////////////////////////// DATA DUMP ////////////////////////////// + + /** Dump state in text format. */ + public void dump(final IndentingPrintWriter pw) { + pw.print(TAG); + pw.println(":"); + pw.increaseIndent(); + + synchronized (mLock) { + super.dump(pw); + pw.println(); + + pw.println("Instantaneous events:"); + pw.increaseIndent(); + mEventTimes.forEach((userId, pkgName, tag, events) -> { + if (events.size() > 0) { + pw.print(string(userId, pkgName, tag)); + pw.println(":"); + pw.increaseIndent(); + pw.print(events.get(0)); + for (int i = 1; i < events.size(); ++i) { + pw.print(", "); + pw.print(events.get(i)); + } + pw.decreaseIndent(); + pw.println(); + } + }); + pw.decreaseIndent(); + + pw.println(); + pw.println("Cached execution stats:"); + pw.increaseIndent(); + mExecutionStatsCache.forEach((userId, pkgName, tag, stats) -> { + if (stats != null) { + pw.print(string(userId, pkgName, tag)); + pw.println(":"); + pw.increaseIndent(); + pw.println(stats); + pw.decreaseIndent(); + } + }); + pw.decreaseIndent(); + + pw.println(); + pw.println("Limits:"); + pw.increaseIndent(); + final int numCategories = mCategoryCountWindowSizesMs.size(); + for (int i = 0; i < numCategories; ++i) { + final Category category = mCategoryCountWindowSizesMs.keyAt(i); + pw.print(category); + pw.print(": "); + pw.print(mMaxCategoryCounts.get(category)); + pw.print(" events in "); + pw.println(TimeUtils.formatDuration(mCategoryCountWindowSizesMs.get(category))); + } + pw.decreaseIndent(); + } + pw.decreaseIndent(); + } + + /** + * Dump state to proto. + * + * @param proto The ProtoOutputStream to write to. + * @param fieldId The field ID of the {@link CountQuotaTrackerProto}. + */ + public void dump(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + + synchronized (mLock) { + super.dump(proto, CountQuotaTrackerProto.BASE_QUOTA_DATA); + + for (int i = 0; i < mCategoryCountWindowSizesMs.size(); ++i) { + final Category category = mCategoryCountWindowSizesMs.keyAt(i); + final long clToken = proto.start(CountQuotaTrackerProto.COUNT_LIMIT); + category.dumpDebug(proto, CountQuotaTrackerProto.CountLimit.CATEGORY); + proto.write(CountQuotaTrackerProto.CountLimit.LIMIT, + mMaxCategoryCounts.get(category)); + proto.write(CountQuotaTrackerProto.CountLimit.WINDOW_SIZE_MS, + mCategoryCountWindowSizesMs.get(category)); + proto.end(clToken); + } + + mExecutionStatsCache.forEach((userId, pkgName, tag, stats) -> { + final boolean isQuotaFree = isIndividualQuotaFreeLocked(userId, pkgName); + + final long usToken = proto.start(CountQuotaTrackerProto.UPTC_STATS); + + (new Uptc(userId, pkgName, tag)) + .dumpDebug(proto, CountQuotaTrackerProto.UptcStats.UPTC); + + proto.write(CountQuotaTrackerProto.UptcStats.IS_QUOTA_FREE, isQuotaFree); + + final LongArrayQueue events = mEventTimes.get(userId, pkgName, tag); + if (events != null) { + for (int j = events.size() - 1; j >= 0; --j) { + final long eToken = proto.start(CountQuotaTrackerProto.UptcStats.EVENTS); + proto.write(CountQuotaTrackerProto.Event.TIMESTAMP_ELAPSED, events.get(j)); + proto.end(eToken); + } + } + + final long statsToken = proto.start( + CountQuotaTrackerProto.UptcStats.EXECUTION_STATS); + proto.write( + CountQuotaTrackerProto.ExecutionStats.EXPIRATION_TIME_ELAPSED, + stats.expirationTimeElapsed); + proto.write( + CountQuotaTrackerProto.ExecutionStats.WINDOW_SIZE_MS, + stats.windowSizeMs); + proto.write(CountQuotaTrackerProto.ExecutionStats.COUNT_LIMIT, stats.countLimit); + proto.write( + CountQuotaTrackerProto.ExecutionStats.COUNT_IN_WINDOW, + stats.countInWindow); + proto.write( + CountQuotaTrackerProto.ExecutionStats.IN_QUOTA_TIME_ELAPSED, + stats.inQuotaTimeElapsed); + proto.end(statsToken); + + proto.end(usToken); + }); + + proto.end(token); + } + } +} diff --git a/services/core/java/com/android/server/utils/quota/UptcMap.java b/services/core/java/com/android/server/utils/quota/UptcMap.java index 80f4f0a88ec3..a3d6ee52db6a 100644 --- a/services/core/java/com/android/server/utils/quota/UptcMap.java +++ b/services/core/java/com/android/server/utils/quota/UptcMap.java @@ -112,37 +112,20 @@ class UptcMap<T> { return data.get(tag); } - /** - * Returns the index for which {@link #getUserIdAtIndex(int)} would return the specified userId, - * or a negative number if the specified userId is not mapped. - */ - public int indexOfUserId(int userId) { - return mData.indexOfKey(userId); - } - - /** - * Returns the index for which {@link #getPackageNameAtIndex(int, int)} would return the - * specified userId, or a negative number if the specified userId and packageName are not mapped - * together. - */ - public int indexOfUserIdAndPackage(int userId, @NonNull String packageName) { - return mData.indexOfKey(userId, packageName); - } - /** Returns the userId at the given index. */ - public int getUserIdAtIndex(int index) { + private int getUserIdAtIndex(int index) { return mData.keyAt(index); } /** Returns the package name at the given index. */ @NonNull - public String getPackageNameAtIndex(int userIndex, int packageIndex) { + private String getPackageNameAtIndex(int userIndex, int packageIndex) { return mData.keyAt(userIndex, packageIndex); } /** Returns the tag at the given index. */ @NonNull - public String getTagAtIndex(int userIndex, int packageIndex, int tagIndex) { + private String getTagAtIndex(int userIndex, int packageIndex, int tagIndex) { // This structure never inserts a null ArrayMap, so if the indices are valid, valueAt() // won't return null. return mData.valueAt(userIndex, packageIndex).keyAt(tagIndex); diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java index 94c2192b620f..23b94bdb74c8 100644 --- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java +++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java @@ -751,10 +751,10 @@ class ActivityMetricsLogger { if (abort) { launchObserverNotifyActivityLaunchCancelled(info); } else { - logAppTransitionFinished(info); if (info.isInterestingToLoggerAndObserver()) { launchObserverNotifyActivityLaunchFinished(info, timestampNs); } + logAppTransitionFinished(info); } info.mPendingDrawActivities.clear(); mTransitionInfoList.remove(info); diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 65d0c4c2515f..26d76a8d6e28 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -243,9 +243,9 @@ import android.app.servertransaction.NewIntentItem; import android.app.servertransaction.PauseActivityItem; import android.app.servertransaction.PipModeChangeItem; import android.app.servertransaction.ResumeActivityItem; +import android.app.servertransaction.StartActivityItem; import android.app.servertransaction.StopActivityItem; import android.app.servertransaction.TopResumedActivityChangeItem; -import android.app.servertransaction.WindowVisibilityItem; import android.app.usage.UsageEvents.Event; import android.content.ComponentName; import android.content.Intent; @@ -4497,7 +4497,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A sleeping = false; app.postPendingUiCleanMsg(true); if (reportToClient) { - makeClientVisible(); + mClientVisibilityDeferred = false; + makeActiveIfNeeded(starting); } else { mClientVisibilityDeferred = true; } @@ -4511,23 +4512,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A handleAlreadyVisible(); } - /** Send visibility change message to the client and pause if needed. */ - void makeClientVisible() { - mClientVisibilityDeferred = false; - try { - mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken, - WindowVisibilityItem.obtain(true /* showWindow */)); - makeActiveIfNeeded(null /* activeActivity*/); - if (isState(STOPPING, STOPPED)) { - // Set state to STARTED in order to have consistent state with client while - // making an non-active activity visible from stopped. - setState(STARTED, "makeClientVisible"); - } - } catch (Exception e) { - Slog.w(TAG, "Exception thrown sending visibility update: " + intent.getComponent(), e); - } - } - void makeInvisible() { if (!mVisibleRequested) { if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Already invisible: " + this); @@ -4556,14 +4540,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A switch (getState()) { case STOPPING: case STOPPED: - if (attachedToProcess()) { - if (DEBUG_VISIBILITY) { - Slog.v(TAG_VISIBILITY, "Scheduling invisibility: " + this); - } - mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), - appToken, WindowVisibilityItem.obtain(false /* showWindow */)); - } - // Reset the flag indicating that an app can enter picture-in-picture once the // activity is hidden supportsEnterPipOnTaskSwitch = false; @@ -4595,17 +4571,17 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A boolean makeActiveIfNeeded(ActivityRecord activeActivity) { if (shouldResumeActivity(activeActivity)) { if (DEBUG_VISIBILITY) { - Slog.v("TAG_VISIBILITY", "Resume visible activity, " + this); + Slog.v(TAG_VISIBILITY, "Resume visible activity, " + this); } return getActivityStack().resumeTopActivityUncheckedLocked(activeActivity /* prev */, null /* options */); } else if (shouldPauseActivity(activeActivity)) { if (DEBUG_VISIBILITY) { - Slog.v("TAG_VISIBILITY", "Pause visible activity, " + this); + Slog.v(TAG_VISIBILITY, "Pause visible activity, " + this); } // An activity must be in the {@link PAUSING} state for the system to validate // the move to {@link PAUSED}. - setState(PAUSING, "makeVisibleIfNeeded"); + setState(PAUSING, "makeActiveIfNeeded"); try { mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken, PauseActivityItem.obtain(finishing, false /* userLeaving */, @@ -4613,6 +4589,17 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } catch (Exception e) { Slog.w(TAG, "Exception thrown sending pause: " + intent.getComponent(), e); } + } else if (shouldStartActivity()) { + if (DEBUG_VISIBILITY) { + Slog.v(TAG_VISIBILITY, "Start visible activity, " + this); + } + setState(STARTED, "makeActiveIfNeeded"); + try { + mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken, + StartActivityItem.obtain()); + } catch (Exception e) { + Slog.w(TAG, "Exception thrown sending start: " + intent.getComponent(), e); + } } return false; } @@ -4656,6 +4643,16 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } /** + * Check if activity should be moved to STARTED state. + * NOTE: This will not check if activity should be made paused or resumed first, so it must only + * be called after checking with {@link #shouldResumeActivity(ActivityRecord)} + * and {@link #shouldPauseActivity(ActivityRecord)}. + */ + private boolean shouldStartActivity() { + return mVisibleRequested && isState(STOPPED); + } + + /** * Check if activity is eligible to be made active (resumed of paused). The activity: * - should be paused, stopped or stopping * - should not be the currently active one or launching behind other tasks @@ -4890,16 +4887,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } setState(STOPPING, "stopIfPossible"); if (DEBUG_VISIBILITY) { - Slog.v(TAG_VISIBILITY, "Stopping visibleRequested=" - + mVisibleRequested + " for " + this); - } - if (!mVisibleRequested) { - setVisibility(false); + Slog.v(TAG_VISIBILITY, "Stopping:" + this); } EventLogTags.writeWmStopActivity( mUserId, System.identityHashCode(this), shortComponentName); mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken, - StopActivityItem.obtain(mVisibleRequested, configChangeFlags)); + StopActivityItem.obtain(configChangeFlags)); + if (stack.shouldSleepOrShutDownActivities()) { setSleeping(true); } @@ -6107,11 +6101,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // This also avoids if the next activity never reports idle (e.g. animating view), // the previous will need to wait until idle timeout to be stopped or destroyed. mStackSupervisor.scheduleProcessStoppingAndFinishingActivities(); - } else { - // Instead of doing the full stop routine here, let's just hide any activities - // we now can, and let them stop when the normal idle happens. - mStackSupervisor.processStoppingActivities(null /* launchedActivity */, - true /* onlyUpdateVisibility */, true /* unused */, null /* unused */); } } Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); @@ -7206,7 +7195,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // {@link ActivityTaskManagerService.activityStopped}). try { mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken, - StopActivityItem.obtain(false /* showWindow */, 0 /* configChanges */)); + StopActivityItem.obtain(0 /* configChanges */)); } catch (RemoteException e) { Slog.w(TAG, "Exception thrown during restart " + this, e); } diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index fe03c06e694e..9fd3ea4fc090 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -163,6 +163,7 @@ import android.view.DisplayCutout; import android.view.DisplayInfo; import android.view.RemoteAnimationTarget; import android.view.SurfaceControl; +import android.view.ITaskOrganizer; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -784,6 +785,10 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn null /* tempTaskBounds */, null /* tempTaskInsetBounds */, null /* tempOtherTaskBounds */, null /* tempOtherTaskInsetBounds */, PRESERVE_WINDOWS, true /* deferResume */); + } else if (overrideWindowingMode != WINDOWING_MODE_PINNED) { + // For pinned stack, resize is now part of the {@link WindowContainerTransaction} + resize(new Rect(newBounds), null /* tempTaskBounds */, + null /* tempTaskInsetBounds */, PRESERVE_WINDOWS, true /* deferResume */); } } if (prevIsAlwaysOnTop != isAlwaysOnTop()) { @@ -799,6 +804,16 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn setWindowingMode(windowingMode, false /* animate */, false /* showRecents */, false /* enteringSplitScreenMode */, false /* deferEnsuringVisibility */, false /* creating */); + + windowingMode = getWindowingMode(); + /* + * Different windowing modes may be managed by different task organizers. If + * getTaskOrganizer returns null, we still call transferToTaskOrganizer to + * make sure we clear it. + */ + final ITaskOrganizer org = + mWmService.mAtmService.mTaskOrganizerController.getTaskOrganizer(windowingMode); + transferToTaskOrganizer(org); } /** @@ -1646,6 +1661,33 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn } /** + * Indicate whether the first task in this stack is controlled by a TaskOrganizer. We aren't + * expecting to use the TaskOrganizer in multiple task per stack scenarios so checking + * the first one is ok. + */ + boolean isControlledByTaskOrganizer() { + return getChildCount() > 0 && getTopMostTask().mTaskOrganizer != null; + } + + private static void transferSingleTaskToOrganizer(Task tr, ITaskOrganizer organizer) { + tr.setTaskOrganizer(organizer); + } + + /** + * Transfer control of the leashes and IWindowContainers to the given ITaskOrganizer. + * This will (or shortly there-after) invoke the taskAppeared callbacks. + * If the tasks had a previous TaskOrganizer, setTaskOrganizer will take care of + * emitting the taskVanished callbacks. + */ + void transferToTaskOrganizer(ITaskOrganizer organizer) { + final PooledConsumer c = PooledLambda.obtainConsumer( + ActivityStack::transferSingleTaskToOrganizer, + PooledLambda.__(Task.class), organizer); + forAllTasks(c); + c.recycle(); + } + + /** * Returns true if the stack should be visible. * * @param starting The currently starting activity or null if there is none. @@ -3573,6 +3615,15 @@ class ActivityStack extends WindowContainer<WindowContainer> implements BoundsAn void animateResizePinnedStack(Rect toBounds, Rect sourceHintBounds, int animationDuration, boolean fromFullscreen) { if (!inPinnedWindowingMode()) return; + + /** + * TODO(b/146594635): Remove all PIP animation code from WM once SysUI handles animation. + * If this PIP Task is controlled by a TaskOrganizer, the animation occurs entirely + * on the TaskOrganizer side, so we just hand over the leash without doing any animation. + * We have to be careful to not schedule the enter-pip callback as the TaskOrganizer + * needs to have flexibility to schedule that at an appropriate point in the animation. + */ + if (isControlledByTaskOrganizer()) return; if (toBounds == null /* toFullscreen */) { final Configuration parentConfig = getParent().getConfiguration(); final ActivityRecord top = topRunningNonOverlayTaskActivity(); diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java index e8564fc80ac3..aa90248b97f8 100644 --- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java @@ -83,6 +83,7 @@ import static com.android.server.wm.Task.LOCK_TASK_AUTH_WHITELISTED; import static com.android.server.wm.Task.REPARENT_KEEP_STACK_AT_FRONT; import static com.android.server.wm.Task.REPARENT_LEAVE_STACK_IN_PLACE; import static com.android.server.wm.Task.REPARENT_MOVE_STACK_TO_FRONT; +import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS; import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; import static com.android.server.wm.WindowContainer.POSITION_TOP; @@ -2076,54 +2077,11 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { boolean processPausingActivities, String reason) { // Stop any activities that are scheduled to do so but have been waiting for the transition // animation to finish. - processStoppingActivities(launchedActivity, false /* onlyUpdateVisibility */, - processPausingActivities, reason); - - final int numFinishingActivities = mFinishingActivities.size(); - if (numFinishingActivities == 0) { - return; - } - - // Finish any activities that are scheduled to do so but have been waiting for the next one - // to start. - final ArrayList<ActivityRecord> finishingActivities = new ArrayList<>(mFinishingActivities); - mFinishingActivities.clear(); - for (int i = 0; i < numFinishingActivities; i++) { - final ActivityRecord r = finishingActivities.get(i); - if (r.isInHistory()) { - r.destroyImmediately(true /* removeFromApp */, "finish-" + reason); - } - } - } - - /** Stop or destroy the stopping activities if they are not interactive. */ - void processStoppingActivities(ActivityRecord launchedActivity, boolean onlyUpdateVisibility, - boolean processPausingActivities, String reason) { - final int numStoppingActivities = mStoppingActivities.size(); - if (numStoppingActivities == 0) { - return; - } ArrayList<ActivityRecord> readyToStopActivities = null; - - final boolean nowVisible = mRootWindowContainer.allResumedActivitiesVisible(); - for (int activityNdx = numStoppingActivities - 1; activityNdx >= 0; --activityNdx) { - final ActivityRecord s = mStoppingActivities.get(activityNdx); - if (nowVisible && s.finishing) { - - // If this activity is finishing, it is sitting on top of - // everyone else but we now know it is no longer needed... - // so get rid of it. Otherwise, we need to go through the - // normal flow and hide it once we determine that it is - // hidden by the activities in front of it. - if (DEBUG_STATES) Slog.v(TAG, "Before stopping, can hide: " + s); - s.setVisibility(false); - } - if (onlyUpdateVisibility) { - continue; - } - - final boolean animating = s.isAnimating(TRANSITION); - if (DEBUG_STATES) Slog.v(TAG, "Stopping " + s + ": nowVisible=" + nowVisible + for (int i = mStoppingActivities.size() - 1; i >= 0; --i) { + final ActivityRecord s = mStoppingActivities.get(i); + final boolean animating = s.isAnimating(TRANSITION | PARENTS); + if (DEBUG_STATES) Slog.v(TAG, "Stopping " + s + ": nowVisible=" + s.nowVisible + " animating=" + animating + " finishing=" + s.finishing); final ActivityStack stack = s.getActivityStack(); @@ -2145,7 +2103,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { } readyToStopActivities.add(s); - mStoppingActivities.remove(activityNdx); + mStoppingActivities.remove(i); } } @@ -2161,6 +2119,22 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { } } } + + final int numFinishingActivities = mFinishingActivities.size(); + if (numFinishingActivities == 0) { + return; + } + + // Finish any activities that are scheduled to do so but have been waiting for the next one + // to start. + final ArrayList<ActivityRecord> finishingActivities = new ArrayList<>(mFinishingActivities); + mFinishingActivities.clear(); + for (int i = 0; i < numFinishingActivities; i++) { + final ActivityRecord r = finishingActivities.get(i); + if (r.isInHistory()) { + r.destroyImmediately(true /* removeFromApp */, "finish-" + reason); + } + } } void removeHistoryRecords(WindowProcessController app) { @@ -2574,6 +2548,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { final PooledConsumer c = PooledLambda.obtainConsumer( ActivityRecord::updatePictureInPictureMode, PooledLambda.__(ActivityRecord.class), targetStackBounds, forceUpdate); + task.getStack().setBounds(targetStackBounds); task.forAllActivities(c); c.recycle(); } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 76c0e4eeecae..ded603c9fd77 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -226,6 +226,7 @@ import android.util.StatsLog; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; import android.view.IRecentsAnimationRunner; +import android.view.ITaskOrganizer; import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationDefinition; import android.view.WindowContainerTransaction; @@ -662,6 +663,12 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { private FontScaleSettingObserver mFontScaleSettingObserver; + /** + * Stores the registration and state of TaskOrganizers in use. + */ + TaskOrganizerController mTaskOrganizerController = + new TaskOrganizerController(this, mGlobalLock); + private int mDeviceOwnerUid = Process.INVALID_UID; private final class FontScaleSettingObserver extends ContentObserver { @@ -1271,6 +1278,14 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { .execute(); } + @Override + public final void registerTaskOrganizer(ITaskOrganizer organizer, int windowingMode) { + enforceCallerIsRecentsOrHasPermission( + MANAGE_ACTIVITY_STACKS, "registerTaskOrganizer()"); + synchronized (mGlobalLock) { + mTaskOrganizerController.registerTaskOrganizer(organizer, windowingMode); + } + } @Override public IBinder requestStartActivityPermissionToken(IBinder delegatorToken) { @@ -3319,6 +3334,18 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } + private void applyWindowContainerChange(ConfigurationContainer cc, + WindowContainerTransaction.Change c) { + sanitizeAndApplyConfigChange(cc, c); + + Rect enterPipBounds = c.getEnterPipBounds(); + if (enterPipBounds != null) { + Task tr = (Task) cc; + mStackSupervisor.updatePictureInPictureMode(tr, + enterPipBounds, true); + } + } + @Override public void applyContainerTransaction(WindowContainerTransaction t) { mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "applyContainerTransaction()"); @@ -3335,7 +3362,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { entries.next(); final ConfigurationContainer cc = ConfigurationContainer.RemoteToken.fromBinder( entry.getKey()).getContainer(); - sanitizeAndApplyConfigChange(cc, entry.getValue()); + applyWindowContainerChange(cc, entry.getValue()); } } } finally { @@ -4057,7 +4084,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { throw new IllegalArgumentException("Stack: " + stack + " doesn't support animated resize."); } - if (animate) { + /** + * TODO(b/146594635): Remove all PIP animation code from WM + * once SysUI handles animation. Don't even try to animate TaskOrganized tasks. + */ + if (animate && !stack.isControlledByTaskOrganizer()) { stack.animateResizePinnedStack(null /* destBounds */, null /* sourceHintBounds */, animationDuration, false /* fromFullscreen */); @@ -7203,7 +7234,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { ActivityManagerServiceDumpProcessesProto.VR_CONTROLLER); if (mController != null) { final long token = proto.start(CONTROLLER); - proto.write(CONTROLLER, mController.toString()); + proto.write(ActivityManagerServiceDumpProcessesProto.Controller.CONTROLLER, + mController.toString()); proto.write(IS_A_MONKEY, mControllerIsAMonkey); proto.end(token); } diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index 09111d07e8c1..014cb76c0064 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -436,10 +436,9 @@ public class AppTransition implements Dump { mNextAppTransition = TRANSIT_UNSET; mNextAppTransitionFlags = 0; setAppTransitionState(APP_STATE_RUNNING); - final AnimationAdapter topOpeningAnim = - (topOpeningApp != null && topOpeningApp.getAnimatingContainer() != null) - ? topOpeningApp.getAnimatingContainer().getAnimation() - : null; + final WindowContainer wc = + topOpeningApp != null ? topOpeningApp.getAnimatingContainer() : null; + final AnimationAdapter topOpeningAnim = wc != null ? wc.getAnimation() : null; int redoLayout = notifyAppTransitionStartingLocked(transit, topOpeningAnim != null ? topOpeningAnim.getDurationHint() : 0, diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java index dd3365c900d7..d0310f1a7607 100644 --- a/services/core/java/com/android/server/wm/ConfigurationContainer.java +++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java @@ -666,4 +666,8 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { return sb.toString(); } } + + RemoteToken getRemoteToken() { + return mRemoteToken; + } } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 0b6a6e049b39..ba9d757d979f 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -205,6 +205,7 @@ import android.view.Display; import android.view.DisplayCutout; import android.view.DisplayInfo; import android.view.Gravity; +import android.view.IDisplayWindowInsetsController; import android.view.ISystemGestureExclusionListener; import android.view.IWindow; import android.view.InputChannel; @@ -218,6 +219,7 @@ import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; import android.view.SurfaceSession; import android.view.View; +import android.view.WindowInsets; import android.view.WindowManager; import android.view.WindowManagerPolicyConstants.PointerEventListener; @@ -570,6 +572,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo */ WindowState mInputMethodTarget; + InsetsControlTarget mInputMethodControlTarget; + /** If true hold off on modifying the animation layer of mInputMethodTarget */ boolean mInputMethodTargetWaitingAnim; @@ -598,6 +602,13 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo private final float mWindowCornerRadius; private final SparseArray<ShellRoot> mShellRoots = new SparseArray<>(); + RemoteInsetsControlTarget mRemoteInsetsControlTarget = null; + private final IBinder.DeathRecipient mRemoteInsetsDeath = + () -> { + synchronized (mWmService.mGlobalLock) { + mRemoteInsetsControlTarget = null; + } + }; private RootWindowContainer mRootWindowContainer; @@ -772,7 +783,11 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // If this is the first layout, we need to initialize the last frames and inset values, // as otherwise we'd immediately cause an unnecessary resize. if (firstLayout) { - w.updateLastFrames(); + // The client may compute its actual requested size according to the first layout, + // so we still request the window to resize if the current frame is empty. + if (!w.getFrameLw().isEmpty()) { + w.updateLastFrames(); + } w.updateLastInsetValues(); w.updateLocationInParentDisplayIfNeeded(); } @@ -1032,31 +1047,13 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // Sets the display content for the children. onDisplayChanged(this); - // Add itself as a child to the root container. - mWmService.mRoot.addChild(this, POSITION_BOTTOM); - - // TODO(b/62541591): evaluate whether this is the best spot to declare the - // {@link DisplayContent} ready for use. - mDisplayReady = true; - - mWmService.mAnimator.addDisplayLocked(mDisplayId); - mInputMonitor = new InputMonitor(mWmService, mDisplayId); + mInputMonitor = new InputMonitor(mWmService, this); mInsetsStateController = new InsetsStateController(this); mInsetsPolicy = new InsetsPolicy(mInsetsStateController, this); - if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Adding display=" + display); + if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Creating display=" + display); mWmService.mDisplayWindowSettings.applySettingsToDisplayLocked(this); - - if (mWmService.mDisplayManagerInternal != null) { - mWmService.mDisplayManagerInternal - .setDisplayInfoOverrideFromWindowManager(mDisplayId, getDisplayInfo()); - configureDisplayPolicy(); - } - - reconfigureDisplayLocked(); - onRequestedOverrideConfigurationChanged(getRequestedOverrideConfiguration()); - mWmService.mDisplayNotificationController.dispatchDisplayAdded(this); } boolean isReady() { @@ -1170,6 +1167,22 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mShellRoots.remove(windowType); } + void setRemoteInsetsController(IDisplayWindowInsetsController controller) { + if (mRemoteInsetsControlTarget != null) { + mRemoteInsetsControlTarget.mRemoteInsetsController.asBinder().unlinkToDeath( + mRemoteInsetsDeath, 0); + mRemoteInsetsControlTarget = null; + } + if (controller != null) { + try { + controller.asBinder().linkToDeath(mRemoteInsetsDeath, 0); + mRemoteInsetsControlTarget = new RemoteInsetsControlTarget(controller); + } catch (RemoteException e) { + return; + } + } + } + /** Changes the display the input window token is housed on to this one. */ void reParentWindowToken(WindowToken token) { final DisplayContent prevDc = token.getDisplayContent(); @@ -3397,6 +3410,14 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } } + boolean isImeAttachedToApp() { + return (mInputMethodTarget != null && mInputMethodTarget.mActivityRecord != null + && mInputMethodTarget.getWindowingMode() == WINDOWING_MODE_FULLSCREEN + // An activity with override bounds should be letterboxed inside its parent bounds, + // so it doesn't fill the screen. + && mInputMethodTarget.mActivityRecord.matchParentBounds()); + } + private void setInputMethodTarget(WindowState target, boolean targetWaitingAnim) { if (target == mInputMethodTarget && mInputMethodTargetWaitingAnim == targetWaitingAnim) { return; @@ -3405,7 +3426,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mInputMethodTarget = target; mInputMethodTargetWaitingAnim = targetWaitingAnim; assignWindowLayers(false /* setLayoutNeeded */); - mInsetsStateController.onImeTargetChanged(target); + mInputMethodControlTarget = computeImeControlTarget(); + mInsetsStateController.onImeTargetChanged(mInputMethodControlTarget); updateImeParent(); } @@ -3430,11 +3452,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // Attach it to app if the target is part of an app and such app is covering the entire // screen. If it's not covering the entire screen the IME might extend beyond the apps // bounds. - if (mInputMethodTarget != null && mInputMethodTarget.mActivityRecord != null - && mInputMethodTarget.getWindowingMode() == WINDOWING_MODE_FULLSCREEN - // An activity with override bounds should be letterboxed inside its parent bounds, - // so it doesn't fill the screen. - && mInputMethodTarget.mActivityRecord.matchParentBounds()) { + if (isImeAttachedToApp()) { return mInputMethodTarget.mActivityRecord.getSurfaceControl(); } @@ -3442,6 +3460,19 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return mWindowContainers.getSurfaceControl(); } + /** + * Computes which control-target the IME should be attached to. + */ + @VisibleForTesting + InsetsControlTarget computeImeControlTarget() { + if (!isImeAttachedToApp() && mRemoteInsetsControlTarget != null) { + return mRemoteInsetsControlTarget; + } + + // Otherwise, we just use the ime target + return mInputMethodTarget; + } + void setLayoutNeeded() { if (DEBUG_LAYOUT) Slog.w(TAG_WM, "setLayoutNeeded: callers=" + Debug.getCallers(3)); mLayoutNeeded = true; @@ -4992,6 +5023,24 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // we create the root surfaces explicitly rather than chaining // up as the default implementation in onParentChanged does. So we // explicitly do NOT call super here. + + if (!isReady()) { + // TODO(b/62541591): evaluate whether this is the best spot to declare the + // {@link DisplayContent} ready for use. + mDisplayReady = true; + + mWmService.mAnimator.addDisplayLocked(mDisplayId); + + if (mWmService.mDisplayManagerInternal != null) { + mWmService.mDisplayManagerInternal + .setDisplayInfoOverrideFromWindowManager(mDisplayId, getDisplayInfo()); + configureDisplayPolicy(); + } + + reconfigureDisplayLocked(); + onRequestedOverrideConfigurationChanged(getRequestedOverrideConfiguration()); + mWmService.mDisplayNotificationController.dispatchDisplayAdded(this); + } } @Override @@ -6684,4 +6733,50 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo Context getDisplayUiContext() { return mDisplayPolicy.getSystemUiContext(); } + + class RemoteInsetsControlTarget implements InsetsControlTarget { + private final IDisplayWindowInsetsController mRemoteInsetsController; + + RemoteInsetsControlTarget(IDisplayWindowInsetsController controller) { + mRemoteInsetsController = controller; + } + + void notifyInsetsChanged() { + try { + mRemoteInsetsController.insetsChanged( + getInsetsStateController().getRawInsetsState()); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to deliver inset state change", e); + } + } + + @Override + public void notifyInsetsControlChanged() { + final InsetsStateController stateController = getInsetsStateController(); + try { + mRemoteInsetsController.insetsControlChanged(stateController.getRawInsetsState(), + stateController.getControlsForDispatch(this)); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to deliver inset state change", e); + } + } + + @Override + public void showInsets(@WindowInsets.Type.InsetsType int types, boolean fromIme) { + try { + mRemoteInsetsController.showInsets(types, fromIme); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to deliver showInsets", e); + } + } + + @Override + public void hideInsets(@WindowInsets.Type.InsetsType int types, boolean fromIme) { + try { + mRemoteInsetsController.hideInsets(types, fromIme); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to deliver showInsets", e); + } + } + } } diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index f9ad03f40723..9c62e9970b48 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -2061,7 +2061,8 @@ public class DisplayPolicy { cf.set(displayFrames.mRestricted); } applyStableConstraints(sysUiFl, fl, cf, displayFrames); - if (adjust != SOFT_INPUT_ADJUST_NOTHING) { + if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE + && adjust != SOFT_INPUT_ADJUST_NOTHING) { vf.set(displayFrames.mCurrent); } else { vf.set(cf); @@ -2138,7 +2139,8 @@ public class DisplayPolicy { applyStableConstraints(sysUiFl, fl, cf, displayFrames); - if (adjust != SOFT_INPUT_ADJUST_NOTHING) { + if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE + && adjust != SOFT_INPUT_ADJUST_NOTHING) { vf.set(displayFrames.mCurrent); } else { vf.set(cf); @@ -2179,7 +2181,8 @@ public class DisplayPolicy { cf.set(displayFrames.mContent); df.set(displayFrames.mContent); } - if (adjust != SOFT_INPUT_ADJUST_NOTHING) { + if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE + && adjust != SOFT_INPUT_ADJUST_NOTHING) { vf.set(displayFrames.mCurrent); } else { vf.set(cf); diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java index 55f5e289377e..c09834f2b3c1 100644 --- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java +++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java @@ -79,12 +79,13 @@ class EnsureActivitiesVisibleHelper { final PooledConsumer f = PooledLambda.obtainConsumer( EnsureActivitiesVisibleHelper::setActivityVisibilityState, this, - PooledLambda.__(ActivityRecord.class), resumeTopActivity); + PooledLambda.__(ActivityRecord.class), starting, resumeTopActivity); mContiner.forAllActivities(f); f.recycle(); } - private void setActivityVisibilityState(ActivityRecord r, final boolean resumeTopActivity) { + private void setActivityVisibilityState(ActivityRecord r, ActivityRecord starting, + final boolean resumeTopActivity) { final boolean isTop = r == mTop; if (mAboveTop && !isTop) { return; @@ -129,7 +130,8 @@ class EnsureActivitiesVisibleHelper { "Skipping: already visible at " + r); if (r.mClientVisibilityDeferred && mNotifyClients) { - r.makeClientVisible(); + r.makeActiveIfNeeded(r.mClientVisibilityDeferred ? null : starting); + r.mClientVisibilityDeferred = false; } r.handleAlreadyVisible(); diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java index 05ede21472f2..fb97ecf8bbc4 100644 --- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java @@ -69,7 +69,7 @@ class ImeInsetsSourceProvider extends InsetsSourceProvider { mShowImeRunner = () -> { // Target should still be the same. if (isImeTargetFromDisplayContentAndImeSame()) { - mDisplayContent.mInputMethodTarget.showInsets( + mDisplayContent.mInputMethodControlTarget.showInsets( WindowInsets.Type.ime(), true /* fromIme */); } abortShowImePostLayout(); diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java index 1f9f8830c34b..5b892f808e2f 100644 --- a/services/core/java/com/android/server/wm/InputManagerCallback.java +++ b/services/core/java/com/android/server/wm/InputManagerCallback.java @@ -10,23 +10,40 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerService.H.ON_POINTER_DOWN_OUTSIDE_FOCUS; +import android.os.Build; import android.os.Debug; import android.os.IBinder; +import android.os.Process; import android.os.RemoteException; +import android.os.SystemClock; +import android.util.ArrayMap; import android.util.Slog; import android.view.IWindow; import android.view.InputApplicationHandle; import android.view.KeyEvent; import android.view.WindowManager; +import com.android.server.am.ActivityManagerService; import com.android.server.input.InputManagerService; import com.android.server.wm.EmbeddedWindowController.EmbeddedWindow; +import java.io.File; import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; final class InputManagerCallback implements InputManagerService.WindowManagerCallbacks { private static final String TAG = TAG_WITH_CLASS_NAME ? "InputManagerCallback" : TAG_WM; + + /** Prevent spamming the traces because pre-dump cannot aware duplicated ANR. */ + private static final long PRE_DUMP_MIN_INTERVAL_MS = TimeUnit.SECONDS.toMillis(20); + /** The timeout to detect if a monitor is held for a while. */ + private static final long PRE_DUMP_MONITOR_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(1); + /** The last time pre-dump was executed. */ + private volatile long mLastPreDumpTimeMs; + private final WindowManagerService mService; // Set to true when the first input device configuration change notification @@ -77,6 +94,77 @@ final class InputManagerCallback implements InputManagerService.WindowManagerCal } /** + * Pre-dump stack trace if the locks of activity manager or window manager (they may be locked + * in the path of reporting ANR) cannot be acquired in time. That provides the stack traces + * before the real blocking symptom has gone. + * <p> + * Do not hold the {@link WindowManagerGlobalLock} while calling this method. + */ + private void preDumpIfLockTooSlow() { + if (!Build.IS_DEBUGGABLE) { + return; + } + final long now = SystemClock.uptimeMillis(); + if (mLastPreDumpTimeMs > 0 && now - mLastPreDumpTimeMs < PRE_DUMP_MIN_INTERVAL_MS) { + return; + } + + final boolean[] shouldDumpSf = { true }; + final ArrayMap<String, Runnable> monitors = new ArrayMap<>(2); + monitors.put(TAG_WM, mService::monitor); + monitors.put("ActivityManager", mService.mAmInternal::monitor); + final CountDownLatch latch = new CountDownLatch(monitors.size()); + // The pre-dump will execute if one of the monitors doesn't complete within the timeout. + for (int i = 0; i < monitors.size(); i++) { + final String name = monitors.keyAt(i); + final Runnable monitor = monitors.valueAt(i); + // Always create new thread to avoid noise of existing threads. Suppose here won't + // create too many threads because it means that watchdog will be triggered first. + new Thread() { + @Override + public void run() { + monitor.run(); + latch.countDown(); + final long elapsed = SystemClock.uptimeMillis() - now; + if (elapsed > PRE_DUMP_MONITOR_TIMEOUT_MS) { + Slog.i(TAG_WM, "Pre-dump acquired " + name + " in " + elapsed + "ms"); + } else if (TAG_WM.equals(name)) { + // Window manager is the main client of SurfaceFlinger. If window manager + // is responsive, the stack traces of SurfaceFlinger may not be important. + shouldDumpSf[0] = false; + } + }; + }.start(); + } + try { + if (latch.await(PRE_DUMP_MONITOR_TIMEOUT_MS, TimeUnit.MILLISECONDS)) { + return; + } + } catch (InterruptedException ignored) { } + mLastPreDumpTimeMs = now; + Slog.i(TAG_WM, "Pre-dump for unresponsive"); + + final ArrayList<Integer> firstPids = new ArrayList<>(1); + firstPids.add(ActivityManagerService.MY_PID); + ArrayList<Integer> nativePids = null; + final int[] pids = shouldDumpSf[0] + ? Process.getPidsForCommands(new String[] { "/system/bin/surfaceflinger" }) + : null; + if (pids != null) { + nativePids = new ArrayList<>(1); + for (int pid : pids) { + nativePids.add(pid); + } + } + + final File tracesFile = ActivityManagerService.dumpStackTraces(firstPids, + null /* processCpuTracker */, null /* lastPids */, nativePids); + if (tracesFile != null) { + tracesFile.renameTo(new File(tracesFile.getParent(), tracesFile.getName() + "_pre")); + } + } + + /** * Notifies the window manager about an application that is not responding. * Returns a new timeout to continue waiting in nanoseconds, or 0 to abort dispatch. * @@ -89,6 +177,9 @@ final class InputManagerCallback implements InputManagerService.WindowManagerCal WindowState windowState = null; boolean aboveSystem = false; int windowPid = INVALID_PID; + + preDumpIfLockTooSlow(); + //TODO(b/141764879) Limit scope of wm lock when input calls notifyANR synchronized (mService.mGlobalLock) { diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index a8ff5002bc58..091f66c0b19a 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -151,10 +151,10 @@ final class InputMonitor { private final UpdateInputWindows mUpdateInputWindows = new UpdateInputWindows(); - public InputMonitor(WindowManagerService service, int displayId) { + InputMonitor(WindowManagerService service, DisplayContent displayContent) { mService = service; - mDisplayContent = mService.mRoot.getDisplayContent(displayId); - mDisplayId = displayId; + mDisplayContent = displayContent; + mDisplayId = displayContent.getDisplayId(); mInputTransaction = mService.mTransactionFactory.get(); mHandler = mService.mAnimationHandler; mUpdateInputForAllWindowsConsumer = new UpdateInputForAllWindowsConsumer(); @@ -279,6 +279,19 @@ final class InputMonitor { // we avoid reintroducing this concept by just choosing one of them here. inputWindowHandle.surfaceInset = child.getAttrs().surfaceInsets.left; + /** + * If the window is in a TaskManaged by a TaskOrganizer then most cropping + * will be applied using the SurfaceControl hierarchy from the Organizer. + * This means we need to make sure that these changes in crop are reflected + * in the input windows, and so ensure this flag is set so that + * the input crop always reflects the surface hierarchy. + * we may have some issues with modal-windows, but I guess we can + * cross that bridge when we come to implementing full-screen TaskOrg + */ + if (child.getTask() != null && child.getTask().isControlledByTaskOrganizer()) { + inputWindowHandle.replaceTouchableRegionWithCrop(null /* Use this surfaces crop */); + } + if (child.mGlobalScale != 1) { // If we are scaling the window, input coordinates need // to be inversely scaled to map from what is on screen diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index 184e7d61c355..5a591ecd4746 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -70,6 +70,9 @@ class InsetsSourceProvider { */ private boolean mServerVisible; + private boolean mSeamlessRotating; + private long mFinishSeamlessRotateFrameNumber = -1; + private final boolean mControllable; InsetsSourceProvider(InsetsSource source, InsetsStateController stateController, @@ -126,6 +129,7 @@ class InsetsSourceProvider { if (win == null) { setServerVisible(false); mSource.setFrame(new Rect()); + mSource.setVisibleFrame(null); } else if (mControllable) { mWin.setControllableInsetProvider(this); if (mControlTarget != null) { @@ -157,6 +161,15 @@ class InsetsSourceProvider { mTmpRect.inset(mWin.mGivenContentInsets); } mSource.setFrame(mTmpRect); + + if (mWin.mGivenVisibleInsets.left != 0 || mWin.mGivenVisibleInsets.top != 0 + || mWin.mGivenVisibleInsets.right != 0 || mWin.mGivenVisibleInsets.bottom != 0) { + mTmpRect.set(mWin.getFrameLw()); + mTmpRect.inset(mWin.mGivenVisibleInsets); + mSource.setVisibleFrame(mTmpRect); + } else { + mSource.setVisibleFrame(null); + } } /** @@ -170,7 +183,9 @@ class InsetsSourceProvider { updateSourceFrame(); if (mControl != null) { final Rect frame = mWin.getWindowFrames().mFrame; - if (mControl.setSurfacePosition(frame.left, frame.top)) { + if (mControl.setSurfacePosition(frame.left, frame.top) && mControlTarget != null) { + // The leash has been stale, we need to create a new one for the client. + updateControlForTarget(mControlTarget, true /* force */); mStateController.notifyControlChanged(mControlTarget); } } @@ -189,6 +204,11 @@ class InsetsSourceProvider { } void updateControlForTarget(@Nullable InsetsControlTarget target, boolean force) { + if (mSeamlessRotating) { + // We are un-rotating the window against the display rotation. We don't want the target + // to control the window for now. + return; + } if (mWin == null) { mControlTarget = target; return; @@ -203,14 +223,42 @@ class InsetsSourceProvider { } mAdapter = new ControlAdapter(); setClientVisible(InsetsState.getDefaultVisibility(mSource.getType())); - mWin.startAnimation(mDisplayContent.getPendingTransaction(), mAdapter, - !mClientVisible /* hidden */); + final Transaction t = mDisplayContent.getPendingTransaction(); + mWin.startAnimation(t, mAdapter, !mClientVisible /* hidden */); + final SurfaceControl leash = mAdapter.mCapturedLeash; + final long frameNumber = mFinishSeamlessRotateFrameNumber; + mFinishSeamlessRotateFrameNumber = -1; + if (frameNumber >= 0 && mWin.mHasSurface && leash != null) { + // We just finished the seamless rotation. We don't want to change the position or the + // window crop of the surface controls (including the leash) until the client finishes + // drawing the new frame of the new orientation. Although we cannot defer the reparent + // operation, it is fine, because reparent won't cause any visual effect. + final SurfaceControl barrier = mWin.mWinAnimator.mSurfaceController.mSurfaceControl; + t.deferTransactionUntil(mWin.getSurfaceControl(), barrier, frameNumber); + t.deferTransactionUntil(leash, barrier, frameNumber); + } mControlTarget = target; - mControl = new InsetsSourceControl(mSource.getType(), mAdapter.mCapturedLeash, + mControl = new InsetsSourceControl(mSource.getType(), leash, new Point(mWin.getWindowFrames().mFrame.left, mWin.getWindowFrames().mFrame.top)); } - boolean onInsetsModified(WindowState caller, InsetsSource modifiedSource) { + void startSeamlessRotation() { + if (!mSeamlessRotating) { + mSeamlessRotating = true; + + // This will revoke the leash and clear the control target. + mWin.cancelAnimation(); + } + } + + void finishSeamlessRotation(boolean timeout) { + if (mSeamlessRotating) { + mSeamlessRotating = false; + mFinishSeamlessRotateFrameNumber = timeout ? -1 : mWin.getFrameNumber(); + } + } + + boolean onInsetsModified(InsetsControlTarget caller, InsetsSource modifiedSource) { if (mControlTarget != caller || modifiedSource.isVisible() == mClientVisible) { return false; } diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java index 152607470ed1..b2234d17984e 100644 --- a/services/core/java/com/android/server/wm/InsetsStateController.java +++ b/services/core/java/com/android/server/wm/InsetsStateController.java @@ -91,6 +91,10 @@ class InsetsStateController { return state; } + InsetsState getRawInsetsState() { + return mState; + } + @Nullable InsetsSourceControl[] getControlsForDispatch(InsetsControlTarget target) { ArrayList<Integer> controlled = mControlTargetTypeMap.get(target); if (controlled == null) { @@ -144,7 +148,7 @@ class InsetsStateController { getImeSourceProvider().onPostInsetsDispatched(); } - void onInsetsModified(WindowState windowState, InsetsState state) { + void onInsetsModified(InsetsControlTarget windowState, InsetsState state) { boolean changed = false; for (int i = state.getSourcesCount() - 1; i >= 0; i--) { final InsetsSource source = state.sourceAt(i); @@ -199,7 +203,7 @@ class InsetsStateController { if (target == previous) { return; } - final InsetsSourceProvider provider = getSourceProvider(type); + final InsetsSourceProvider provider = mProviders.get(type); if (provider == null) { return; } @@ -207,6 +211,7 @@ class InsetsStateController { return; } provider.updateControlForTarget(target, false /* force */); + target = provider.getControlTarget(); if (previous != null) { removeFromControlMaps(previous, type, false /* fake */); mPendingControlChanged.add(previous); @@ -296,6 +301,9 @@ class InsetsStateController { void notifyInsetsChanged() { mDisplayContent.forAllWindows(mDispatchInsetsChanged, true /* traverseTopToBottom */); + if (mDisplayContent.mRemoteInsetsControlTarget != null) { + mDisplayContent.mRemoteInsetsControlTarget.notifyInsetsChanged(); + } } void dump(String prefix, PrintWriter pw) { diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 704ab677f91d..c3e815d10dda 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -1377,6 +1377,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> for (int displayNdx = 0; displayNdx < displays.length; ++displayNdx) { final Display display = displays[displayNdx]; final DisplayContent displayContent = new DisplayContent(display, this); + addChild(displayContent, POSITION_BOTTOM); if (displayContent.mDisplayId == DEFAULT_DISPLAY) { mDefaultDisplay = displayContent; } @@ -1445,6 +1446,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } // The display hasn't been added to ActivityManager yet, create a new record now. displayContent = new DisplayContent(display, this); + addChild(displayContent, POSITION_BOTTOM); return displayContent; } @@ -2166,12 +2168,18 @@ class RootWindowContainer extends WindowContainer<DisplayContent> mService.continueWindowLayout(); } + // TODO(b/146594635): Remove all PIP animation code from WM once SysUI handles animation. // Notify the pinned stack controller to prepare the PiP animation, expect callback - // delivered from SystemUI to WM to start the animation. - final PinnedStackController pinnedStackController = + // delivered from SystemUI to WM to start the animation. Unless we are using + // the TaskOrganizer in which case the animation will be entirely handled + // on that side. + if (mService.mTaskOrganizerController.getTaskOrganizer(WINDOWING_MODE_PINNED) + == null) { + final PinnedStackController pinnedStackController = display.mDisplayContent.getPinnedStackController(); - pinnedStackController.prepareAnimation(sourceHintBounds, aspectRatio, - null /* stackBounds */); + pinnedStackController.prepareAnimation(sourceHintBounds, aspectRatio, + null /* stackBounds */); + } // TODO: revisit the following statement after the animation is moved from WM to SysUI. // Update the visibility of all activities after the they have been reparented to the new diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java index 399c5d3ae45f..eaa0ea72452a 100644 --- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java +++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java @@ -16,8 +16,12 @@ package com.android.server.wm; +import static com.android.server.wm.AnimationSpecProto.ROTATE; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION; import static com.android.server.wm.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC; +import static com.android.server.wm.RotationAnimationSpecProto.DURATION_MS; +import static com.android.server.wm.RotationAnimationSpecProto.END_LUMA; +import static com.android.server.wm.RotationAnimationSpecProto.START_LUMA; import static com.android.server.wm.ScreenRotationAnimationProto.ANIMATION_RUNNING; import static com.android.server.wm.ScreenRotationAnimationProto.STARTED; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; @@ -25,7 +29,9 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerService.TYPE_LAYER_MULTIPLIER; import static com.android.server.wm.WindowStateAnimator.WINDOW_FREEZE_LAYER; +import android.animation.ArgbEvaluator; import android.content.Context; +import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Point; import android.graphics.Rect; @@ -40,7 +46,9 @@ import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.view.animation.Transformation; +import com.android.internal.R; import com.android.server.protolog.common.ProtoLog; +import com.android.server.wm.utils.RotationAnimationUtils; import java.io.PrintWriter; @@ -60,10 +68,10 @@ import java.io.PrintWriter; * animation first rotate the new content into the old orientation to then be able to * animate to the new orientation * - * <li> The exiting Blackframe: <p> - * Because the change of orientation might change the width and height of the content (i.e - * when rotating from portrait to landscape) we "crop" the new content using black frames - * around the screenshot so the new content does not go beyond the screenshot's bounds + * <li> The Background color frame: <p> + * To have the animation seem more seamless, we add a color transitioning background behind the + * exiting and entering layouts. We compute the brightness of the start and end + * layouts and transition from the two brightness values as grayscale underneath the animation * * <li> The entering Blackframe: <p> * The enter Blackframe is similar to the exit Blackframe but is only used when a custom @@ -81,8 +89,6 @@ class ScreenRotationAnimation { */ private static final int SCREEN_FREEZE_LAYER_BASE = WINDOW_FREEZE_LAYER + TYPE_LAYER_MULTIPLIER; private static final int SCREEN_FREEZE_LAYER_ENTER = SCREEN_FREEZE_LAYER_BASE; - private static final int SCREEN_FREEZE_LAYER_SCREENSHOT = SCREEN_FREEZE_LAYER_BASE + 1; - private static final int SCREEN_FREEZE_LAYER_EXIT = SCREEN_FREEZE_LAYER_BASE + 2; private final Context mContext; private final DisplayContent mDisplayContent; @@ -90,16 +96,18 @@ class ScreenRotationAnimation { private final Transformation mRotateExitTransformation = new Transformation(); private final Transformation mRotateEnterTransformation = new Transformation(); // Complete transformations being applied. - private final Transformation mExitTransformation = new Transformation(); private final Transformation mEnterTransformation = new Transformation(); - private final Matrix mFrameInitialMatrix = new Matrix(); private final Matrix mSnapshotInitialMatrix = new Matrix(); - private final Matrix mSnapshotFinalMatrix = new Matrix(); - private final Matrix mExitFrameFinalMatrix = new Matrix(); private final WindowManagerService mService; + /** Only used for custom animations and not screen rotation. */ private SurfaceControl mEnterBlackFrameLayer; - private SurfaceControl mRotationLayer; - private SurfaceControl mSurfaceControl; + /** This layer contains the actual screenshot that is to be faded out. */ + private SurfaceControl mScreenshotLayer; + /** + * Only used for screen rotation and not custom animations. Layered behind all other layers + * to avoid showing any "empty" spots + */ + private SurfaceControl mBackColorSurface; private BlackFrame mEnteringBlackFrame; private int mWidth, mHeight; @@ -120,8 +128,11 @@ class ScreenRotationAnimation { private boolean mFinishAnimReady; private long mFinishAnimStartTime; private boolean mForceDefaultOrientation; - private BlackFrame mExitingBlackFrame; private SurfaceRotationAnimationController mSurfaceRotationAnimationController; + /** Intensity of light/whiteness of the layout before rotation occurs. */ + private float mStartLuma; + /** Intensity of light/whiteness of the layout after rotation occurs. */ + private float mEndLuma; public ScreenRotationAnimation(Context context, DisplayContent displayContent, boolean fixedToUserRotation, boolean isSecure, WindowManagerService service) { @@ -162,9 +173,15 @@ class ScreenRotationAnimation { final SurfaceControl.Transaction t = mService.mTransactionFactory.get(); try { - mRotationLayer = displayContent.makeOverlay() + mBackColorSurface = displayContent.makeChildSurface(null) + .setName("BackColorSurface") + .setColorLayer() + .build(); + + mScreenshotLayer = displayContent.makeOverlay() .setName("RotationLayer") - .setContainerLayer() + .setBufferSize(mWidth, mHeight) + .setSecure(isSecure) .build(); mEnterBlackFrameLayer = displayContent.makeOverlay() @@ -172,26 +189,21 @@ class ScreenRotationAnimation { .setContainerLayer() .build(); - mSurfaceControl = mService.makeSurfaceBuilder(null) - .setName("ScreenshotSurface") - .setParent(mRotationLayer) - .setBufferSize(mWidth, mHeight) - .setSecure(isSecure) - .build(); - // In case display bounds change, screenshot buffer and surface may mismatch so set a // scaling mode. SurfaceControl.Transaction t2 = mService.mTransactionFactory.get(); - t2.setOverrideScalingMode(mSurfaceControl, Surface.SCALING_MODE_SCALE_TO_WINDOW); + t2.setOverrideScalingMode(mScreenshotLayer, Surface.SCALING_MODE_SCALE_TO_WINDOW); t2.apply(true /* sync */); // Capture a screenshot into the surface we just created. final int displayId = display.getDisplayId(); final Surface surface = mService.mSurfaceFactory.get(); - surface.copyFrom(mSurfaceControl); + surface.copyFrom(mScreenshotLayer); SurfaceControl.ScreenshotGraphicBuffer gb = mService.mDisplayManagerInternal.screenshot(displayId); if (gb != null) { + mStartLuma = RotationAnimationUtils.getAvgBorderLuma(gb.getGraphicBuffer(), + gb.getColorSpace()); try { surface.attachAndQueueBufferWithColorSpace(gb.getGraphicBuffer(), gb.getColorSpace()); @@ -202,13 +214,15 @@ class ScreenRotationAnimation { // screenshot surface we display it in also has FLAG_SECURE so that // the user can not screenshot secure layers via the screenshot surface. if (gb.containsSecureLayers()) { - t.setSecure(mSurfaceControl, true); + t.setSecure(mScreenshotLayer, true); } - t.setLayer(mRotationLayer, SCREEN_FREEZE_LAYER_BASE); - t.setLayer(mSurfaceControl, SCREEN_FREEZE_LAYER_SCREENSHOT); - t.setAlpha(mSurfaceControl, 0); - t.show(mRotationLayer); - t.show(mSurfaceControl); + t.setLayer(mScreenshotLayer, SCREEN_FREEZE_LAYER_BASE); + t.reparent(mBackColorSurface, displayContent.getSurfaceControl()); + t.setLayer(mBackColorSurface, -1); + t.setColor(mBackColorSurface, new float[]{mStartLuma, mStartLuma, mStartLuma}); + t.setAlpha(mBackColorSurface, 1); + t.show(mScreenshotLayer); + t.show(mBackColorSurface); } else { Slog.w(TAG, "Unable to take screenshot of display " + displayId); } @@ -218,32 +232,11 @@ class ScreenRotationAnimation { } ProtoLog.i(WM_SHOW_SURFACE_ALLOC, - " FREEZE %s: CREATE", mSurfaceControl); + " FREEZE %s: CREATE", mScreenshotLayer); setRotation(t, originalRotation); t.apply(); } - private static void createRotationMatrix(int rotation, int width, int height, - Matrix outMatrix) { - switch (rotation) { - case Surface.ROTATION_0: - outMatrix.reset(); - break; - case Surface.ROTATION_90: - outMatrix.setRotate(90, 0, 0); - outMatrix.postTranslate(height, 0); - break; - case Surface.ROTATION_180: - outMatrix.setRotate(180, 0, 0); - outMatrix.postTranslate(width, height); - break; - case Surface.ROTATION_270: - outMatrix.setRotate(270, 0, 0); - outMatrix.postTranslate(0, width); - break; - } - } - public void dumpDebug(ProtoOutputStream proto, long fieldId) { final long token = proto.start(fieldId); proto.write(STARTED, mStarted); @@ -252,11 +245,11 @@ class ScreenRotationAnimation { } boolean hasScreenshot() { - return mSurfaceControl != null; + return mScreenshotLayer != null; } private void setRotationTransform(SurfaceControl.Transaction t, Matrix matrix) { - if (mRotationLayer == null) { + if (mScreenshotLayer == null) { return; } matrix.getValues(mTmpFloats); @@ -267,24 +260,19 @@ class ScreenRotationAnimation { x -= mCurrentDisplayRect.left; y -= mCurrentDisplayRect.top; } - t.setPosition(mRotationLayer, x, y); - t.setMatrix(mRotationLayer, + t.setPosition(mScreenshotLayer, x, y); + t.setMatrix(mScreenshotLayer, mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y], mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]); - t.setAlpha(mSurfaceControl, (float) 1.0); - t.setAlpha(mRotationLayer, (float) 1.0); - t.show(mRotationLayer); + t.setAlpha(mScreenshotLayer, (float) 1.0); + t.show(mScreenshotLayer); } public void printTo(String prefix, PrintWriter pw) { - pw.print(prefix); pw.print("mSurface="); pw.print(mSurfaceControl); + pw.print(prefix); pw.print("mSurface="); pw.print(mScreenshotLayer); pw.print(" mWidth="); pw.print(mWidth); pw.print(" mHeight="); pw.println(mHeight); - pw.print(prefix); pw.print("mExitingBlackFrame="); pw.println(mExitingBlackFrame); - if (mExitingBlackFrame != null) { - mExitingBlackFrame.printTo(prefix + " ", pw); - } pw.print(prefix); pw.print("mEnteringBlackFrame="); pw.println(mEnteringBlackFrame); @@ -303,20 +291,10 @@ class ScreenRotationAnimation { pw.print(" "); mRotateExitTransformation.printShortString(pw); pw.println(); pw.print(prefix); pw.print("mRotateEnterAnimation="); pw.print(mRotateEnterAnimation); pw.print(" "); mRotateEnterTransformation.printShortString(pw); pw.println(); - pw.print(prefix); pw.print("mExitTransformation="); - mExitTransformation.printShortString(pw); pw.println(); pw.print(prefix); pw.print("mEnterTransformation="); mEnterTransformation.printShortString(pw); pw.println(); - pw.print(prefix); pw.print("mFrameInitialMatrix="); - mFrameInitialMatrix.printShortString(pw); - pw.println(); pw.print(prefix); pw.print("mSnapshotInitialMatrix="); - mSnapshotInitialMatrix.printShortString(pw); - pw.print(" mSnapshotFinalMatrix="); mSnapshotFinalMatrix.printShortString(pw); - pw.println(); - pw.print(prefix); pw.print("mExitFrameFinalMatrix="); - mExitFrameFinalMatrix.printShortString(pw); - pw.println(); + mSnapshotInitialMatrix.printShortString(pw);pw.println(); pw.print(prefix); pw.print("mForceDefaultOrientation="); pw.print(mForceDefaultOrientation); if (mForceDefaultOrientation) { pw.print(" mOriginalDisplayRect="); pw.print(mOriginalDisplayRect.toShortString()); @@ -331,7 +309,7 @@ class ScreenRotationAnimation { // to the snapshot to make it stay in the same original position // with the current screen rotation. int delta = DisplayContent.deltaRotation(rotation, Surface.ROTATION_0); - createRotationMatrix(delta, mWidth, mHeight, mSnapshotInitialMatrix); + RotationAnimationUtils.createRotationMatrix(delta, mWidth, mHeight, mSnapshotInitialMatrix); setRotationTransform(t, mSnapshotInitialMatrix); } @@ -341,7 +319,7 @@ class ScreenRotationAnimation { */ private boolean startAnimation(SurfaceControl.Transaction t, long maxAnimationDuration, float animationScale, int finalWidth, int finalHeight, int exitAnim, int enterAnim) { - if (mSurfaceControl == null) { + if (mScreenshotLayer == null) { // Can't do animation. return false; } @@ -354,89 +332,58 @@ class ScreenRotationAnimation { // Figure out how the screen has moved from the original rotation. int delta = DisplayContent.deltaRotation(mCurRotation, mOriginalRotation); - mRotateAlphaAnimation = AnimationUtils.loadAnimation(mContext, - com.android.internal.R.anim.screen_rotate_alpha); final boolean customAnim; if (exitAnim != 0 && enterAnim != 0) { customAnim = true; mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, exitAnim); mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, enterAnim); + mRotateAlphaAnimation = AnimationUtils.loadAnimation(mContext, + R.anim.screen_rotate_alpha); } else { customAnim = false; - switch (delta) { + switch (delta) { /* Counter-Clockwise Rotations */ case Surface.ROTATION_0: mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, - com.android.internal.R.anim.screen_rotate_0_exit); + R.anim.screen_rotate_0_exit); mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, - com.android.internal.R.anim.screen_rotate_0_enter); + R.anim.screen_rotate_0_enter); break; case Surface.ROTATION_90: mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, - com.android.internal.R.anim.screen_rotate_plus_90_exit); + R.anim.screen_rotate_plus_90_exit); mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, - com.android.internal.R.anim.screen_rotate_plus_90_enter); + R.anim.screen_rotate_plus_90_enter); break; case Surface.ROTATION_180: mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, - com.android.internal.R.anim.screen_rotate_180_exit); + R.anim.screen_rotate_180_exit); mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, - com.android.internal.R.anim.screen_rotate_180_enter); + R.anim.screen_rotate_180_enter); break; case Surface.ROTATION_270: mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, - com.android.internal.R.anim.screen_rotate_minus_90_exit); + R.anim.screen_rotate_minus_90_exit); mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, - com.android.internal.R.anim.screen_rotate_minus_90_enter); + R.anim.screen_rotate_minus_90_enter); break; } } - // Initialize the animations. This is a hack, redefining what "parent" - // means to allow supplying the last and next size. In this definition - // "%p" is the original (let's call it "previous") size, and "%" is the - // screen's current/new size. - mRotateEnterAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight); mRotateExitAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight); - mAnimRunning = false; - mFinishAnimReady = false; - mFinishAnimStartTime = -1; - mRotateExitAnimation.restrictDuration(maxAnimationDuration); mRotateExitAnimation.scaleCurrentDuration(animationScale); + mRotateEnterAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight); mRotateEnterAnimation.restrictDuration(maxAnimationDuration); mRotateEnterAnimation.scaleCurrentDuration(animationScale); - mRotateAlphaAnimation.restrictDuration(maxAnimationDuration); - mRotateAlphaAnimation.scaleCurrentDuration(animationScale); - if (!customAnim && mExitingBlackFrame == null) { - try { - // Compute the transformation matrix that must be applied - // the the black frame to make it stay in the initial position - // before the new screen rotation. This is different than the - // snapshot transformation because the snapshot is always based - // of the native orientation of the screen, not the orientation - // we were last in. - createRotationMatrix(delta, mOriginalWidth, mOriginalHeight, mFrameInitialMatrix); - - final Rect outer; - final Rect inner; - if (mForceDefaultOrientation) { - // Going from a smaller Display to a larger Display, add curtains to sides - // or top and bottom. Going from a larger to smaller display will result in - // no BlackSurfaces being constructed. - outer = mCurrentDisplayRect; - inner = mOriginalDisplayRect; - } else { - outer = new Rect(-mWidth, -mHeight, mWidth * 2, mHeight * 2); - inner = new Rect(0, 0, mWidth, mHeight); - } - mExitingBlackFrame = new BlackFrame(mService.mTransactionFactory, t, outer, inner, - SCREEN_FREEZE_LAYER_EXIT, mDisplayContent, mForceDefaultOrientation, - mRotationLayer); - } catch (OutOfResourcesException e) { - Slog.w(TAG, "Unable to allocate black surface", e); - } + mAnimRunning = false; + mFinishAnimReady = false; + mFinishAnimStartTime = -1; + + if (customAnim) { + mRotateAlphaAnimation.restrictDuration(maxAnimationDuration); + mRotateAlphaAnimation.scaleCurrentDuration(animationScale); } if (customAnim && mEnteringBlackFrame == null) { @@ -451,7 +398,12 @@ class ScreenRotationAnimation { } } - mSurfaceRotationAnimationController.startAnimation(); + if (customAnim) { + mSurfaceRotationAnimationController.startCustomAnimation(); + } else { + mSurfaceRotationAnimationController.startScreenRotationAnimation(); + } + return true; } @@ -460,11 +412,13 @@ class ScreenRotationAnimation { */ public boolean dismiss(SurfaceControl.Transaction t, long maxAnimationDuration, float animationScale, int finalWidth, int finalHeight, int exitAnim, int enterAnim) { - if (mSurfaceControl == null) { + if (mScreenshotLayer == null) { // Can't do animation. return false; } if (!mStarted) { + mEndLuma = RotationAnimationUtils.getLumaOfSurfaceControl(mDisplayContent.getDisplay(), + mDisplayContent.getWindowingLayer()); startAnimation(t, maxAnimationDuration, animationScale, finalWidth, finalHeight, exitAnim, enterAnim); } @@ -480,28 +434,28 @@ class ScreenRotationAnimation { mSurfaceRotationAnimationController.cancel(); mSurfaceRotationAnimationController = null; } - if (mSurfaceControl != null) { - ProtoLog.i(WM_SHOW_SURFACE_ALLOC, " FREEZE %s: DESTROY", mSurfaceControl); - mSurfaceControl = null; + + if (mScreenshotLayer != null) { + ProtoLog.i(WM_SHOW_SURFACE_ALLOC, " FREEZE %s: DESTROY", mScreenshotLayer); SurfaceControl.Transaction t = mService.mTransactionFactory.get(); - if (mRotationLayer != null) { - if (mRotationLayer.isValid()) { - t.remove(mRotationLayer); - } - mRotationLayer = null; + if (mScreenshotLayer.isValid()) { + t.remove(mScreenshotLayer); } + mScreenshotLayer = null; + if (mEnterBlackFrameLayer != null) { if (mEnterBlackFrameLayer.isValid()) { t.remove(mEnterBlackFrameLayer); } mEnterBlackFrameLayer = null; } + if (mBackColorSurface != null) { + t.remove(mBackColorSurface); + mBackColorSurface = null; + } t.apply(); } - if (mExitingBlackFrame != null) { - mExitingBlackFrame.kill(); - mExitingBlackFrame = null; - } + if (mEnteringBlackFrame != null) { mEnteringBlackFrame.kill(); mEnteringBlackFrame = null; @@ -537,18 +491,28 @@ class ScreenRotationAnimation { * Utility class that runs a {@link ScreenRotationAnimation} on the {@link * SurfaceAnimationRunner}. * <p> - * The rotation animation is divided into the following hierarchy: + * The rotation animation supports both screen rotation and custom animations + * + * For custom animations: + * <ul> + * <li> + * The screenshot layer which has an added animation of it's alpha channel + * ("screen_rotate_alpha") and that will be applied along with the custom animation. + * </li> + * <li> A device layer that is animated with the provided custom animation </li> + * </ul> + * + * For screen rotation: * <ul> - * <li> A first rotation layer, containing the blackframes. This layer is animated by the - * "screen_rotate_X_exit" that applies a scale and rotate and where X is value of the rotation. - * <ul> - * <li> A child layer containing the screenshot on which is added an animation of it's - * alpha channel ("screen_rotate_alpha") and that will rotate with his parent layer.</li> - * </ul> - * <li> A second rotation layer used when custom animations are passed in + * <li> A rotation layer that is both rotated and faded out during a single animation </li> + * <li> A device layer that is both rotated and faded in during a single animation </li> + * <li> A background color layer that transitions colors behind the first two layers </li> + * </ul> + * * {@link ScreenRotationAnimation#startAnimation( * SurfaceControl.Transaction, long, float, int, int, int, int)}. * </ul> + * * <p> * Thus an {@link LocalAnimationAdapter.AnimationSpec} is created for each of * this three {@link SurfaceControl}s which then delegates the animation to the @@ -556,22 +520,35 @@ class ScreenRotationAnimation { */ class SurfaceRotationAnimationController { private SurfaceAnimator mDisplayAnimator; - private SurfaceAnimator mEnterBlackFrameAnimator; private SurfaceAnimator mScreenshotRotationAnimator; private SurfaceAnimator mRotateScreenAnimator; + private SurfaceAnimator mEnterBlackFrameAnimator; + + void startCustomAnimation() { + try { + mService.mSurfaceAnimationRunner.deferStartingAnimations(); + mRotateScreenAnimator = startScreenshotAlphaAnimation(); + mDisplayAnimator = startDisplayRotation(); + if (mEnteringBlackFrame != null) { + mEnterBlackFrameAnimator = startEnterBlackFrameAnimation(); + } + } finally { + mService.mSurfaceAnimationRunner.continueStartingAnimations(); + } + } /** * Start the rotation animation of the display and the screenshot on the * {@link SurfaceAnimationRunner}. */ - void startAnimation() { - mRotateScreenAnimator = startScreenshotAlphaAnimation(); - mDisplayAnimator = startDisplayRotation(); - if (mExitingBlackFrame != null) { + void startScreenRotationAnimation() { + try { + mService.mSurfaceAnimationRunner.deferStartingAnimations(); + mDisplayAnimator = startDisplayRotation(); mScreenshotRotationAnimator = startScreenshotRotationAnimation(); - } - if (mEnteringBlackFrame != null) { - mEnterBlackFrameAnimator = startEnterBlackFrameAnimation(); + startColorAnimation(); + } finally { + mService.mSurfaceAnimationRunner.continueStartingAnimations(); } } @@ -596,8 +573,8 @@ class ScreenRotationAnimation { private SurfaceAnimator startScreenshotAlphaAnimation() { return startAnimation(initializeBuilder() - .setSurfaceControl(mSurfaceControl) - .setAnimationLeashParent(mRotationLayer) + .setSurfaceControl(mScreenshotLayer) + .setAnimationLeashParent(mDisplayContent.getOverlayLayer()) .setWidth(mWidth) .setHeight(mHeight) .build(), @@ -616,13 +593,67 @@ class ScreenRotationAnimation { private SurfaceAnimator startScreenshotRotationAnimation() { return startAnimation(initializeBuilder() - .setSurfaceControl(mRotationLayer) + .setSurfaceControl(mScreenshotLayer) .setAnimationLeashParent(mDisplayContent.getOverlayLayer()) .build(), createWindowAnimationSpec(mRotateExitAnimation), this::onAnimationEnd); } + + /** + * Applies the color change from {@link #mStartLuma} to {@link #mEndLuma} as a + * grayscale color + */ + private void startColorAnimation() { + int colorTransitionMs = mContext.getResources().getInteger( + R.integer.config_screen_rotation_color_transition); + final SurfaceAnimationRunner runner = mService.mSurfaceAnimationRunner; + final float[] rgbTmpFloat = new float[3]; + final int startColor = Color.rgb(mStartLuma, mStartLuma, mStartLuma); + final int endColor = Color.rgb(mEndLuma, mEndLuma, mEndLuma); + final long duration = colorTransitionMs * (long) mService.getCurrentAnimatorScale(); + final ArgbEvaluator va = ArgbEvaluator.getInstance(); + runner.startAnimation( + new LocalAnimationAdapter.AnimationSpec() { + @Override + public long getDuration() { + return duration; + } + + @Override + public void apply(SurfaceControl.Transaction t, SurfaceControl leash, + long currentPlayTime) { + float fraction = (float)currentPlayTime / (float)getDuration(); + int color = (Integer) va.evaluate(fraction, startColor, endColor); + Color middleColor = Color.valueOf(color); + rgbTmpFloat[0] = middleColor.red(); + rgbTmpFloat[1] = middleColor.green(); + rgbTmpFloat[2] = middleColor.blue(); + if (leash.isValid()) { + t.setColor(leash, rgbTmpFloat); + } + } + + @Override + public void dump(PrintWriter pw, String prefix) { + pw.println(prefix + "startLuma=" + mStartLuma + + " endLuma=" + mEndLuma + + " durationMs=" + colorTransitionMs); + } + + @Override + public void dumpDebugInner(ProtoOutputStream proto) { + final long token = proto.start(ROTATE); + proto.write(START_LUMA, mStartLuma); + proto.write(END_LUMA, mEndLuma); + proto.write(DURATION_MS, colorTransitionMs); + proto.end(token); + } + }, + mBackColorSurface, mDisplayContent.getPendingTransaction(), null); + } + private WindowAnimationSpec createWindowAnimationSpec(Animation mAnimation) { return new WindowAnimationSpec(mAnimation, new Point(0, 0) /* position */, false /* canSkipFirstFrame */, 0 /* WindowCornerRadius */); @@ -646,7 +677,6 @@ class ScreenRotationAnimation { LocalAnimationAdapter localAnimationAdapter = new LocalAnimationAdapter( animationSpec, mService.mSurfaceAnimationRunner); - animator.startAnimation(mDisplayContent.getPendingTransaction(), localAnimationAdapter, false); return animator; @@ -692,7 +722,6 @@ class ScreenRotationAnimation { if (mEnterBlackFrameAnimator != null) { mEnterBlackFrameAnimator.cancelAnimation(); } - if (mScreenshotRotationAnimator != null) { mScreenshotRotationAnimator.cancelAnimation(); } diff --git a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java index 50cea2ed52cc..5633b6be87b9 100644 --- a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java +++ b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java @@ -78,6 +78,10 @@ class SurfaceAnimationRunner { @GuardedBy("mLock") private boolean mAnimationStartDeferred; + /** + * There should only ever be one instance of this class. Usual spot for it is with + * {@link WindowManagerService} + */ SurfaceAnimationRunner(Supplier<Transaction> transactionFactory, PowerManagerInternal powerManagerInternal) { this(null /* callbackProvider */, null /* animatorFactory */, diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java index 976730ec4337..5286a6e32958 100644 --- a/services/core/java/com/android/server/wm/SurfaceAnimator.java +++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java @@ -322,12 +322,16 @@ class SurfaceAnimator { if (DEBUG_ANIM) Slog.i(TAG, "Reparenting to leash"); final SurfaceControl.Builder builder = mAnimatable.makeAnimationLeash() .setParent(mAnimatable.getAnimationLeashParent()) + .setHidden(hidden) .setName(surface + " - animation-leash"); final SurfaceControl leash = builder.build(); t.setWindowCrop(leash, width, height); + + // TODO: rely on builder.setHidden(hidden) instead of show and setAlpha when b/138459974 is + // fixed. t.show(leash); - // TODO: change this back to use show instead of alpha when b/138459974 is fixed. t.setAlpha(leash, hidden ? 0 : 1); + t.reparent(surface, leash); return leash; } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 9a140daad417..5cb7091bbed0 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -117,9 +117,11 @@ import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.res.Configuration; +import android.graphics.Point; import android.graphics.Rect; import android.os.Debug; import android.os.IBinder; +import android.os.Parcel; import android.os.RemoteException; import android.os.SystemClock; import android.os.Trace; @@ -130,6 +132,7 @@ import android.util.DisplayMetrics; import android.util.Slog; import android.util.proto.ProtoOutputStream; import android.view.DisplayInfo; +import android.view.ITaskOrganizer; import android.view.RemoteAnimationTarget; import android.view.Surface; import android.view.SurfaceControl; @@ -425,6 +428,14 @@ class Task extends WindowContainer<WindowContainer> { } /** + * The TaskOrganizer which is delegated presentation of this task. If set the Task will + * emit an IWindowContainer (allowing access to it's SurfaceControl leash) to the organizers + * taskAppeared callback, and emit a taskRemoved callback when the Task is vanished. + */ + ITaskOrganizer mTaskOrganizer; + + + /** * Don't use constructor directly. Use {@link #create(ActivityTaskManagerService, int, * ActivityInfo, Intent, TaskDescription)} instead. */ @@ -445,6 +456,22 @@ class Task extends WindowContainer<WindowContainer> { _voiceSession, _voiceInteractor, stack); } + class TaskToken extends RemoteToken { + TaskToken(ConfigurationContainer container) { + super(container); + } + + @Override + public SurfaceControl getLeash() { + // We need to copy the SurfaceControl instead of returning the original + // because the Parcel FLAGS PARCELABLE_WRITE_RETURN_VALUE cause SurfaceControls + // to release themselves. + SurfaceControl sc = new SurfaceControl(); + sc.copyFrom(getSurfaceControl()); + return sc; + } + } + /** Don't use constructor directly. This is only used by XML parser. */ Task(ActivityTaskManagerService atmService, int _taskId, Intent _intent, Intent _affinityIntent, String _affinity, String _rootAffinity, @@ -469,7 +496,7 @@ class Task extends WindowContainer<WindowContainer> { mTaskDescription = _lastTaskDescription; // Tasks have no set orientation value (including SCREEN_ORIENTATION_UNSPECIFIED). setOrientation(SCREEN_ORIENTATION_UNSET); - mRemoteToken = new RemoteToken(this); + mRemoteToken = new TaskToken(this); affinityIntent = _affinityIntent; affinity = _affinity; rootAffinity = _rootAffinity; @@ -2179,6 +2206,10 @@ class Task extends WindowContainer<WindowContainer> { void removeImmediately() { if (DEBUG_STACK) Slog.i(TAG, "removeTask: removing taskId=" + mTaskId); EventLogTags.writeWmTaskRemoved(mTaskId, "removeTask"); + + // If applicable let the TaskOrganizer know the Task is vanishing. + setTaskOrganizer(null); + super.removeImmediately(); } @@ -2567,6 +2598,12 @@ class Task extends WindowContainer<WindowContainer> { } boolean shouldAnimate() { + /** + * Animations are handled by the TaskOrganizer implementation. + */ + if (isControlledByTaskOrganizer()) { + return false; + } // Don't animate while the task runs recents animation but only if we are in the mode // where we cancel with deferred screenshot, which means that the controller has // transformed the task. @@ -3444,4 +3481,91 @@ class Task extends WindowContainer<WindowContainer> { XmlUtils.skipCurrentTag(in); } } + + boolean isControlledByTaskOrganizer() { + return mTaskOrganizer != null; + } + + @Override + protected void reparentSurfaceControl(SurfaceControl.Transaction t, SurfaceControl newParent) { + /** + * Avoid yanking back control from the TaskOrganizer, which has presumably reparented the + * Surface in to its own hierarchy. + */ + if (isControlledByTaskOrganizer()) { + return; + } + super.reparentSurfaceControl(t, newParent); + } + + private void sendTaskAppeared() { + if (mSurfaceControl != null && mTaskOrganizer != null) { + mAtmService.mTaskOrganizerController.onTaskAppeared(mTaskOrganizer, this); + } + } + + private void sendTaskVanished() { + if (mTaskOrganizer != null) { + mAtmService.mTaskOrganizerController.onTaskVanished(mTaskOrganizer, this); + } + } + + void setTaskOrganizer(ITaskOrganizer organizer) { + // Let the old organizer know it has lost control. + if (mTaskOrganizer != null) { + sendTaskVanished(); + } + mTaskOrganizer = organizer; + sendTaskAppeared(); + } + + // Called on Binder death. + void taskOrganizerDied() { + mTaskOrganizer = null; + } + + @Override + void setSurfaceControl(SurfaceControl sc) { + super.setSurfaceControl(sc); + // If the TaskOrganizer was set before we created the SurfaceControl, we need to + // emit the callbacks now. + sendTaskAppeared(); + } + + @Override + public void updateSurfacePosition() { + // Avoid fighting with the TaskOrganizer over Surface position. + if (isControlledByTaskOrganizer()) { + getPendingTransaction().setPosition(mSurfaceControl, 0, 0); + scheduleAnimation(); + return; + } else { + super.updateSurfacePosition(); + } + } + + @Override + void getRelativeDisplayedPosition(Point outPos) { + // In addition to updateSurfacePosition, we keep other code that sets + // position from fighting with the TaskOrganizer + if (isControlledByTaskOrganizer()) { + outPos.set(0, 0); + return; + } + super.getRelativeDisplayedPosition(outPos); + } + + @Override + public void setWindowingMode(int windowingMode) { + super.setWindowingMode(windowingMode); + windowingMode = getWindowingMode(); + /* + * Different windowing modes may be managed by different task organizers. If + * getTaskOrganizer returns null, we still call transferToTaskOrganizer to + * make sure we clear it. + */ + final ITaskOrganizer org = + mAtmService.mTaskOrganizerController.getTaskOrganizer(windowingMode); + setTaskOrganizer(org); + } } diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java new file mode 100644 index 000000000000..283be4010677 --- /dev/null +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -0,0 +1,167 @@ +/* + * 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.wm; + +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; + +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Slog; +import android.view.ITaskOrganizer; +import android.view.SurfaceControl; + +import java.util.ArrayList; +import java.util.HashMap; + +/** + * Stores the TaskOrganizers associated with a given windowing mode and + * their associated state. + */ +class TaskOrganizerController { + private static final String TAG = "TaskOrganizerController"; + + private WindowManagerGlobalLock mGlobalLock; + + private class DeathRecipient implements IBinder.DeathRecipient { + int mWindowingMode; + ITaskOrganizer mTaskOrganizer; + + DeathRecipient(ITaskOrganizer organizer, int windowingMode) { + mTaskOrganizer = organizer; + mWindowingMode = windowingMode; + } + + @Override + public void binderDied() { + synchronized (mGlobalLock) { + final TaskOrganizerState state = mTaskOrganizerStates.get(mTaskOrganizer); + for (int i = 0; i < state.mOrganizedTasks.size(); i++) { + state.mOrganizedTasks.get(i).taskOrganizerDied(); + } + mTaskOrganizerStates.remove(mTaskOrganizer); + if (mTaskOrganizersForWindowingMode.get(mWindowingMode) == mTaskOrganizer) { + mTaskOrganizersForWindowingMode.remove(mWindowingMode); + } + } + } + }; + + class TaskOrganizerState { + ITaskOrganizer mOrganizer; + DeathRecipient mDeathRecipient; + + ArrayList<Task> mOrganizedTasks = new ArrayList<>(); + + void addTask(Task t) { + mOrganizedTasks.add(t); + } + + void removeTask(Task t) { + mOrganizedTasks.remove(t); + } + + TaskOrganizerState(ITaskOrganizer organizer, DeathRecipient deathRecipient) { + mOrganizer = organizer; + mDeathRecipient = deathRecipient; + } + }; + + + final HashMap<Integer, TaskOrganizerState> mTaskOrganizersForWindowingMode = new HashMap(); + final HashMap<ITaskOrganizer, TaskOrganizerState> mTaskOrganizerStates = new HashMap(); + + final HashMap<Integer, ITaskOrganizer> mTaskOrganizersByPendingSyncId = new HashMap(); + + final ActivityTaskManagerService mService; + + TaskOrganizerController(ActivityTaskManagerService atm, WindowManagerGlobalLock lock) { + mService = atm; + mGlobalLock = lock; + } + + private void clearIfNeeded(int windowingMode) { + final TaskOrganizerState oldState = mTaskOrganizersForWindowingMode.get(windowingMode); + if (oldState != null) { + oldState.mOrganizer.asBinder().unlinkToDeath(oldState.mDeathRecipient, 0); + } + } + + /** + * Register a TaskOrganizer to manage tasks as they enter the given windowing mode. + * If there was already a TaskOrganizer for this windowing mode it will be evicted + * and receive taskVanished callbacks in the process. + */ + void registerTaskOrganizer(ITaskOrganizer organizer, int windowingMode) { + if (windowingMode != WINDOWING_MODE_PINNED) { + throw new UnsupportedOperationException( + "As of now only Pinned windowing mode is supported for registerTaskOrganizer"); + + } + clearIfNeeded(windowingMode); + DeathRecipient dr = new DeathRecipient(organizer, windowingMode); + try { + organizer.asBinder().linkToDeath(dr, 0); + } catch (RemoteException e) { + Slog.e(TAG, "TaskOrganizer failed to register death recipient"); + } + + final TaskOrganizerState state = new TaskOrganizerState(organizer, dr); + mTaskOrganizersForWindowingMode.put(windowingMode, state); + + mTaskOrganizerStates.put(organizer, state); + } + + ITaskOrganizer getTaskOrganizer(int windowingMode) { + final TaskOrganizerState state = mTaskOrganizersForWindowingMode.get(windowingMode); + if (state == null) { + return null; + } + return state.mOrganizer; + } + + private void sendTaskAppeared(ITaskOrganizer organizer, Task task) { + try { + organizer.taskAppeared(task.getRemoteToken(), task.getTaskInfo()); + } catch (Exception e) { + Slog.e(TAG, "Exception sending taskAppeared callback" + e); + } + } + + private void sendTaskVanished(ITaskOrganizer organizer, Task task) { + try { + organizer.taskVanished(task.getRemoteToken()); + } catch (Exception e) { + Slog.e(TAG, "Exception sending taskVanished callback" + e); + } + } + + void onTaskAppeared(ITaskOrganizer organizer, Task task) { + TaskOrganizerState state = mTaskOrganizerStates.get(organizer); + + state.addTask(task); + sendTaskAppeared(organizer, task); + } + + void onTaskVanished(ITaskOrganizer organizer, Task task) { + final TaskOrganizerState state = mTaskOrganizerStates.get(organizer); + sendTaskVanished(organizer, task); + + // This could trigger TaskAppeared for other tasks in the same stack so make sure + // we do this AFTER sending taskVanished. + state.removeTask(task); + } +} diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index cefef37a1363..f3880fa5dd09 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -342,7 +342,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< if (mSurfaceControl == null) { // If we don't yet have a surface, but we now have a parent, we should // build a surface. - mSurfaceControl = makeSurface().build(); + setSurfaceControl(makeSurface().build()); getPendingTransaction().show(mSurfaceControl); updateSurfacePosition(); } else { @@ -496,7 +496,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< mParent.getPendingTransaction().merge(getPendingTransaction()); } - mSurfaceControl = null; + setSurfaceControl(null); mLastSurfacePosition.set(0, 0); scheduleAnimation(); } @@ -777,7 +777,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< * otherwise. */ boolean isWaitingForTransitionStart() { - return getActivity(app -> app.isWaitingForTransitionStart()) != null; + return false; } /** @@ -2209,4 +2209,8 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } return mParent.getDimmer(); } + + void setSurfaceControl(SurfaceControl sc) { + mSurfaceControl = sc; + } } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 223e9b9e3df2..74fdba1cd13e 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -205,6 +205,7 @@ import android.view.DisplayInfo; import android.view.Gravity; import android.view.IAppTransitionAnimationSpecsFuture; import android.view.IDisplayFoldListener; +import android.view.IDisplayWindowInsetsController; import android.view.IDisplayWindowListener; import android.view.IDisplayWindowRotationController; import android.view.IDockedStackListener; @@ -2768,6 +2769,7 @@ public class WindowManagerService extends IWindowManager.Stub true /* includingParents */); } } + syncInputTransactions(); } /** @@ -3727,6 +3729,48 @@ public class WindowManagerService extends IWindowManager.Stub } @Override + public void setDisplayWindowInsetsController( + int displayId, IDisplayWindowInsetsController insetsController) { + if (mContext.checkCallingOrSelfPermission(MANAGE_APP_TOKENS) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Must hold permission " + MANAGE_APP_TOKENS); + } + final long origId = Binder.clearCallingIdentity(); + try { + synchronized (mGlobalLock) { + final DisplayContent dc = mRoot.getDisplayContent(displayId); + if (dc == null) { + return; + } + dc.setRemoteInsetsController(insetsController); + } + } finally { + Binder.restoreCallingIdentity(origId); + } + } + + @Override + public void modifyDisplayWindowInsets(int displayId, InsetsState state) { + if (mContext.checkCallingOrSelfPermission(MANAGE_APP_TOKENS) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Must hold permission " + MANAGE_APP_TOKENS); + } + final long origId = Binder.clearCallingIdentity(); + try { + synchronized (mGlobalLock) { + final DisplayContent dc = mRoot.getDisplayContent(displayId); + if (dc == null || dc.mRemoteInsetsControlTarget == null) { + return; + } + dc.getInsetsStateController().onInsetsModified( + dc.mRemoteInsetsControlTarget, state); + } + } finally { + Binder.restoreCallingIdentity(origId); + } + } + + @Override public int watchRotation(IRotationWatcher watcher, int displayId) { final DisplayContent displayContent; synchronized (mGlobalLock) { @@ -7314,7 +7358,8 @@ public class WindowManagerService extends IWindowManager.Stub // If there was a pending IME show(), reset it as IME has been // requested to be hidden. dc.getInsetsStateController().getImeSourceProvider().abortShowImePostLayout(); - dc.mInputMethodTarget.hideInsets(WindowInsets.Type.ime(), true /* fromIme */); + dc.mInputMethodControlTarget.hideInsets(WindowInsets.Type.ime(), + true /* fromIme */); } } } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 4e768da18041..ba40f623ea66 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -688,6 +688,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } if (mForceSeamlesslyRotate || requested) { + if (mControllableInsetProvider != null) { + mControllableInsetProvider.startSeamlessRotation(); + } mPendingSeamlessRotate = new SeamlessRotator(oldRotation, rotation, getDisplayInfo()); mPendingSeamlessRotate.unrotate(transaction, this); getDisplayContent().getDisplayRotation().markForSeamlessRotation(this, @@ -702,6 +705,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mPendingSeamlessRotate = null; getDisplayContent().getDisplayRotation().markForSeamlessRotation(this, false /* seamlesslyRotated */); + if (mControllableInsetProvider != null) { + mControllableInsetProvider.finishSeamlessRotation(timeout); + } } } @@ -792,8 +798,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mSeq = seq; mPowerManagerWrapper = powerManagerWrapper; mForceSeamlesslyRotate = token.mRoundedCornerOverlay; - mRequestedInsetsState = - getDisplayContent().getInsetsPolicy().getInsetsForDispatch(this); + mRequestedInsetsState = new InsetsState( + getDisplayContent().getInsetsPolicy().getInsetsForDispatch(this), + true /* copySources */); if (DEBUG) { Slog.v(TAG, "Window " + this + " client=" + c.asBinder() + " token=" + token + " (" + mAttrs.token + ")" + " params=" + a); diff --git a/services/core/java/com/android/server/wm/utils/RotationAnimationUtils.java b/services/core/java/com/android/server/wm/utils/RotationAnimationUtils.java new file mode 100644 index 000000000000..94f66768d5ef --- /dev/null +++ b/services/core/java/com/android/server/wm/utils/RotationAnimationUtils.java @@ -0,0 +1,97 @@ +/* + * 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.wm.utils; + +import android.graphics.Bitmap; +import android.graphics.ColorSpace; +import android.graphics.GraphicBuffer; +import android.graphics.Matrix; +import android.graphics.Point; +import android.graphics.Rect; +import android.view.Display; +import android.view.Surface; +import android.view.SurfaceControl; + + +/** Helper functions for the {@link com.android.server.wm.ScreenRotationAnimation} class*/ +public class RotationAnimationUtils { + + /** + * Converts the provided {@link GraphicBuffer} and converts it to a bitmap to then sample the + * luminance at the borders of the bitmap + * @return the average luminance of all the pixels at the borders of the bitmap + */ + public static float getAvgBorderLuma(GraphicBuffer graphicBuffer, ColorSpace colorSpace) { + Bitmap hwBitmap = Bitmap.wrapHardwareBuffer(graphicBuffer, colorSpace); + if (hwBitmap == null) { + return 0; + } + + Bitmap swaBitmap = hwBitmap.copy(Bitmap.Config.ARGB_8888, false); + float totalLuma = 0; + int height = swaBitmap.getHeight(); + int width = swaBitmap.getWidth(); + int i; + for (i = 0; i < width; i++) { + totalLuma += swaBitmap.getColor(i, 0).luminance(); + totalLuma += swaBitmap.getColor(i, height - 1).luminance(); + } + for (i = 0; i < height; i++) { + totalLuma += swaBitmap.getColor(0, i).luminance(); + totalLuma += swaBitmap.getColor(width - 1, i).luminance(); + } + return totalLuma / (2 * width + 2 * height); + } + + /** + * Gets the average border luma by taking a screenshot of the {@param surfaceControl}. + * @see #getAvgBorderLuma(GraphicBuffer, ColorSpace) + */ + public static float getLumaOfSurfaceControl(Display display, SurfaceControl surfaceControl) { + if (surfaceControl == null) { + return 0; + } + + Point size = new Point(); + display.getSize(size); + Rect crop = new Rect(0, 0, size.x, size.y); + SurfaceControl.ScreenshotGraphicBuffer buffer = + SurfaceControl.captureLayers(surfaceControl, crop, 1); + return RotationAnimationUtils.getAvgBorderLuma(buffer.getGraphicBuffer(), + buffer.getColorSpace()); + } + + public static void createRotationMatrix(int rotation, int width, int height, Matrix outMatrix) { + switch (rotation) { + case Surface.ROTATION_0: + outMatrix.reset(); + break; + case Surface.ROTATION_90: + outMatrix.setRotate(90, 0, 0); + outMatrix.postTranslate(height, 0); + break; + case Surface.ROTATION_180: + outMatrix.setRotate(180, 0, 0); + outMatrix.postTranslate(width, height); + break; + case Surface.ROTATION_270: + outMatrix.setRotate(270, 0, 0); + outMatrix.postTranslate(0, width); + break; + } + } +} diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index 1ad6e86bae84..03969b01f3f9 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -13,6 +13,7 @@ cc_library_static { ], srcs: [ + ":graphicsstats_proto", "BroadcastRadio/JavaRef.cpp", "BroadcastRadio/NativeCallbackThread.cpp", "BroadcastRadio/BroadcastRadioService.cpp", @@ -103,6 +104,11 @@ cc_defaults { "libinputflinger", "libinputflinger_base", "libinputservice", + "libprotobuf-cpp-lite", + "libprotoutil", + "libstatspull", + "libstatssocket", + "libstatslog", "libschedulerservicehidl", "libsensorservice", "libsensorservicehidl", diff --git a/services/core/jni/com_android_server_GraphicsStatsService.cpp b/services/core/jni/com_android_server_GraphicsStatsService.cpp index d1d253b94713..9353fbd71214 100644 --- a/services/core/jni/com_android_server_GraphicsStatsService.cpp +++ b/services/core/jni/com_android_server_GraphicsStatsService.cpp @@ -23,6 +23,16 @@ #include <nativehelper/ScopedUtfChars.h> #include <JankTracker.h> #include <service/GraphicsStatsService.h> +#include <stats_pull_atom_callback.h> +#include <stats_event.h> +#include <statslog.h> +#include <google/protobuf/io/zero_copy_stream_impl_lite.h> +#include <android/util/ProtoOutputStream.h> +#include "android/graphics/Utils.h" +#include "core_jni_helpers.h" +#include "protos/graphicsstats.pb.h" +#include <cstring> +#include <memory> namespace android { @@ -77,6 +87,20 @@ static void finishDump(JNIEnv*, jobject, jlong dumpPtr) { GraphicsStatsService::finishDump(dump); } +static jlong finishDumpInMemory(JNIEnv* env, jobject, jlong dumpPtr) { + GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr); + std::vector<uint8_t>* result = new std::vector<uint8_t>(); + GraphicsStatsService::finishDumpInMemory(dump, + [](void* buffer, int bufferOffset, int bufferSize, int totalSize, void* param1, void* param2) { + std::vector<uint8_t>* outBuffer = reinterpret_cast<std::vector<uint8_t>*>(param2); + if (outBuffer->size() < totalSize) { + outBuffer->resize(totalSize); + } + std::memcpy(outBuffer->data() + bufferOffset, buffer, bufferSize); + }, env, result); + return reinterpret_cast<jlong>(result); +} + static void saveBuffer(JNIEnv* env, jobject clazz, jstring jpath, jstring jpackage, jlong versionCode, jlong startTime, jlong endTime, jbyteArray jdata) { ScopedByteArrayRO buffer(env, jdata); @@ -93,19 +117,173 @@ static void saveBuffer(JNIEnv* env, jobject clazz, jstring jpath, jstring jpacka GraphicsStatsService::saveBuffer(path, package, versionCode, startTime, endTime, data); } +static jobject gGraphicsStatsServiceObject = nullptr; +static jmethodID gGraphicsStatsService_pullGraphicsStatsMethodID; + +static JNIEnv* getJNIEnv() { + JavaVM* vm = AndroidRuntime::getJavaVM(); + JNIEnv* env = nullptr; + if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { + if (vm->AttachCurrentThreadAsDaemon(&env, nullptr) != JNI_OK) { + LOG_ALWAYS_FATAL("Failed to AttachCurrentThread!"); + } + } + return env; +} + +using namespace google::protobuf; + +// Field ids taken from FrameTimingHistogram message in atoms.proto +#define TIME_MILLIS_BUCKETS_FIELD_NUMBER 1 +#define FRAME_COUNTS_FIELD_NUMBER 2 + +static void writeCpuHistogram(stats_event* event, + const uirenderer::protos::GraphicsStatsProto& stat) { + util::ProtoOutputStream proto; + for (int bucketIndex = 0; bucketIndex < stat.histogram_size(); bucketIndex++) { + auto& bucket = stat.histogram(bucketIndex); + proto.write(android::util::FIELD_TYPE_INT32 | android::util::FIELD_COUNT_REPEATED | + TIME_MILLIS_BUCKETS_FIELD_NUMBER /* field id */, + (int)bucket.render_millis()); + } + for (int bucketIndex = 0; bucketIndex < stat.histogram_size(); bucketIndex++) { + auto& bucket = stat.histogram(bucketIndex); + proto.write(android::util::FIELD_TYPE_INT64 | android::util::FIELD_COUNT_REPEATED | + FRAME_COUNTS_FIELD_NUMBER /* field id */, + (long long)bucket.frame_count()); + } + std::vector<uint8_t> outVector; + proto.serializeToVector(&outVector); + stats_event_write_byte_array(event, outVector.data(), outVector.size()); +} + +static void writeGpuHistogram(stats_event* event, + const uirenderer::protos::GraphicsStatsProto& stat) { + util::ProtoOutputStream proto; + for (int bucketIndex = 0; bucketIndex < stat.gpu_histogram_size(); bucketIndex++) { + auto& bucket = stat.gpu_histogram(bucketIndex); + proto.write(android::util::FIELD_TYPE_INT32 | android::util::FIELD_COUNT_REPEATED | + TIME_MILLIS_BUCKETS_FIELD_NUMBER /* field id */, + (int)bucket.render_millis()); + } + for (int bucketIndex = 0; bucketIndex < stat.gpu_histogram_size(); bucketIndex++) { + auto& bucket = stat.gpu_histogram(bucketIndex); + proto.write(android::util::FIELD_TYPE_INT64 | android::util::FIELD_COUNT_REPEATED | + FRAME_COUNTS_FIELD_NUMBER /* field id */, + (long long)bucket.frame_count()); + } + std::vector<uint8_t> outVector; + proto.serializeToVector(&outVector); + stats_event_write_byte_array(event, outVector.data(), outVector.size()); +} + +// graphicsStatsPullCallback is invoked by statsd service to pull GRAPHICS_STATS atom. +static bool graphicsStatsPullCallback(int32_t atom_tag, pulled_stats_event_list* data, + const void* cookie) { + JNIEnv* env = getJNIEnv(); + if (!env) { + return false; + } + if (gGraphicsStatsServiceObject == nullptr) { + ALOGE("Failed to get graphicsstats service"); + return false; + } + + for (bool lastFullDay : {true, false}) { + jlong jdata = (jlong) env->CallLongMethod( + gGraphicsStatsServiceObject, + gGraphicsStatsService_pullGraphicsStatsMethodID, + (jboolean)(lastFullDay ? JNI_TRUE : JNI_FALSE)); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + ALOGE("Failed to invoke graphicsstats service"); + return false; + } + if (!jdata) { + // null means data is not available for that day. + continue; + } + android::uirenderer::protos::GraphicsStatsServiceDumpProto serviceDump; + std::vector<uint8_t>* buffer = reinterpret_cast<std::vector<uint8_t>*>(jdata); + std::unique_ptr<std::vector<uint8_t>> bufferRelease(buffer); + int dataSize = buffer->size(); + if (!dataSize) { + // Data is not available for that day. + continue; + } + io::ArrayInputStream input{buffer->data(), dataSize}; + bool success = serviceDump.ParseFromZeroCopyStream(&input); + if (!success) { + ALOGW("Parse failed on GraphicsStatsPuller error='%s' dataSize='%d'", + serviceDump.InitializationErrorString().c_str(), dataSize); + return false; + } + + for (int stat_index = 0; stat_index < serviceDump.stats_size(); stat_index++) { + auto& stat = serviceDump.stats(stat_index); + stats_event* event = add_stats_event_to_pull_data(data); + stats_event_set_atom_id(event, android::util::GRAPHICS_STATS); + stats_event_write_string8(event, stat.package_name().c_str()); + stats_event_write_int64(event, (int64_t)stat.version_code()); + stats_event_write_int64(event, (int64_t)stat.stats_start()); + stats_event_write_int64(event, (int64_t)stat.stats_end()); + stats_event_write_int32(event, (int32_t)stat.pipeline()); + stats_event_write_int32(event, (int32_t)stat.summary().total_frames()); + stats_event_write_int32(event, (int32_t)stat.summary().missed_vsync_count()); + stats_event_write_int32(event, (int32_t)stat.summary().high_input_latency_count()); + stats_event_write_int32(event, (int32_t)stat.summary().slow_ui_thread_count()); + stats_event_write_int32(event, (int32_t)stat.summary().slow_bitmap_upload_count()); + stats_event_write_int32(event, (int32_t)stat.summary().slow_draw_count()); + stats_event_write_int32(event, (int32_t)stat.summary().missed_deadline_count()); + writeCpuHistogram(event, stat); + writeGpuHistogram(event, stat); + // TODO: fill in UI mainline module version, when the feature is available. + stats_event_write_int64(event, (int64_t)0); + stats_event_write_bool(event, !lastFullDay); + stats_event_build(event); + } + } + return true; +} + +// Register a puller for GRAPHICS_STATS atom with the statsd service. +static void nativeInit(JNIEnv* env, jobject javaObject) { + gGraphicsStatsServiceObject = env->NewGlobalRef(javaObject); + pull_atom_metadata metadata = {.cool_down_ns = 10 * 1000000, // 10 milliseconds + .timeout_ns = 2 * NS_PER_SEC, // 2 seconds + .additive_fields = nullptr, + .additive_fields_size = 0}; + register_stats_pull_atom_callback(android::util::GRAPHICS_STATS, &graphicsStatsPullCallback, + &metadata, nullptr); +} + +static void nativeDestructor(JNIEnv* env, jobject javaObject) { + //TODO: Unregister the puller callback when a new API is available. + env->DeleteGlobalRef(gGraphicsStatsServiceObject); + gGraphicsStatsServiceObject = nullptr; +} + static const JNINativeMethod sMethods[] = { { "nGetAshmemSize", "()I", (void*) getAshmemSize }, { "nCreateDump", "(IZ)J", (void*) createDump }, { "nAddToDump", "(JLjava/lang/String;Ljava/lang/String;JJJ[B)V", (void*) addToDump }, { "nAddToDump", "(JLjava/lang/String;)V", (void*) addFileToDump }, { "nFinishDump", "(J)V", (void*) finishDump }, + { "nFinishDumpInMemory", "(J)J", (void*) finishDumpInMemory }, { "nSaveBuffer", "(Ljava/lang/String;Ljava/lang/String;JJJ[B)V", (void*) saveBuffer }, + { "nativeInit", "()V", (void*) nativeInit }, + { "nativeDestructor", "()V", (void*)nativeDestructor } }; int register_android_server_GraphicsStatsService(JNIEnv* env) { + jclass graphicsStatsService_class = FindClassOrDie(env, + "com/android/server/GraphicsStatsService"); + gGraphicsStatsService_pullGraphicsStatsMethodID = GetMethodIDOrDie(env, + graphicsStatsService_class, "pullGraphicsStats", "(Z)J"); return jniRegisterNativeMethods(env, "com/android/server/GraphicsStatsService", sMethods, NELEM(sMethods)); } -} // namespace android
\ No newline at end of file +} // namespace android diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 2e8e5e7b706a..c0891d739788 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -1757,6 +1757,12 @@ static jboolean nativeCanDispatchToDisplay(JNIEnv* env, jclass /* clazz */, jlon return im->getInputManager()->getReader()->canDispatchToDisplay(deviceId, displayId); } +static void nativeNotifyPortAssociationsChanged(JNIEnv* env, jclass /* clazz */, jlong ptr) { + NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); + im->getInputManager()->getReader()->requestRefreshConfiguration( + InputReaderConfiguration::CHANGE_DISPLAY_INFO); +} + // ---------------------------------------------------------------------------- static const JNINativeMethod gInputManagerMethods[] = { @@ -1842,6 +1848,8 @@ static const JNINativeMethod gInputManagerMethods[] = { (void*) nativeSetCustomPointerIcon }, { "nativeCanDispatchToDisplay", "(JII)Z", (void*) nativeCanDispatchToDisplay }, + { "nativeNotifyPortAssociationsChanged", "(J)V", + (void*) nativeNotifyPortAssociationsChanged }, }; #define FIND_CLASS(var, className) \ diff --git a/services/devicepolicy/Android.bp b/services/devicepolicy/Android.bp index 380ee942af98..cdbe77a3d64c 100644 --- a/services/devicepolicy/Android.bp +++ b/services/devicepolicy/Android.bp @@ -18,8 +18,3 @@ java_library_static { "compat-changeid-annotation-processor", ], } - -platform_compat_config { - name: "services-devicepolicy-platform-compat-config", - src: ":services.devicepolicy", -} diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java index 5a1d552bf325..8641059aebd5 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java @@ -64,4 +64,8 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub { } public void setLocationEnabled(ComponentName who, boolean locationEnabled) {} + + public boolean isOrganizationOwnedDeviceWithManagedProfile() { + return false; + } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 398ce50101e5..d5ff2802499c 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -367,6 +367,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { private static final String TAG_TRANSFER_OWNERSHIP_BUNDLE = "transfer-ownership-bundle"; + private static final String TAG_PROTECTED_PACKAGES = "protected-packages"; + private static final int REQUEST_EXPIRE_PASSWORD = 5571; private static final long MS_PER_DAY = TimeUnit.DAYS.toMillis(1); @@ -742,6 +744,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // This is the list of component allowed to start lock task mode. List<String> mLockTaskPackages = new ArrayList<>(); + // List of packages protected by device owner + List<String> mProtectedPackages = new ArrayList<>(); + // Bitfield of feature flags to be enabled during LockTask mode. // We default on the power button menu, in order to be consistent with pre-P behaviour. int mLockTaskFeatures = DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS; @@ -3245,6 +3250,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { out.endTag(null, TAG_OWNER_INSTALLED_CA_CERT); } + for (int i = 0, size = policy.mProtectedPackages.size(); i < size; i++) { + String packageName = policy.mProtectedPackages.get(i); + out.startTag(null, TAG_PROTECTED_PACKAGES); + out.attribute(null, ATTR_NAME, packageName); + out.endTag(null, TAG_PROTECTED_PACKAGES); + } + out.endTag(null, "policies"); out.endDocument(); @@ -3358,6 +3370,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { policy.mAdminMap.clear(); policy.mAffiliationIds.clear(); policy.mOwnerInstalledCaCerts.clear(); + policy.mProtectedPackages.clear(); while ((type=parser.next()) != XmlPullParser.END_DOCUMENT && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { @@ -3455,6 +3468,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { policy.mCurrentInputMethodSet = true; } else if (TAG_OWNER_INSTALLED_CA_CERT.equals(tag)) { policy.mOwnerInstalledCaCerts.add(parser.getAttributeValue(null, ATTR_ALIAS)); + } else if (TAG_PROTECTED_PACKAGES.equals(tag)) { + policy.mProtectedPackages.add(parser.getAttributeValue(null, ATTR_NAME)); } else { Slog.w(LOG_TAG, "Unknown tag: " + tag); XmlUtils.skipCurrentTag(parser); @@ -3486,6 +3501,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { updateMaximumTimeToLockLocked(userHandle); updateLockTaskPackagesLocked(policy.mLockTaskPackages, userHandle); updateLockTaskFeaturesLocked(policy.mLockTaskFeatures, userHandle); + updateProtectedPackagesLocked(policy.mProtectedPackages); if (policy.mStatusBarDisabled) { setStatusBarDisabledInternal(policy.mStatusBarDisabled, userHandle); } @@ -3511,6 +3527,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } + private void updateProtectedPackagesLocked(List<String> packages) { + mInjector.getPackageManagerInternal().setDeviceOwnerProtectedPackages(packages); + } + private void updateLockTaskFeaturesLocked(int flags, int userId) { long ident = mInjector.binderClearCallingIdentity(); try { @@ -8349,6 +8369,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { policy.mLockTaskPackages.clear(); updateLockTaskPackagesLocked(policy.mLockTaskPackages, userId); policy.mLockTaskFeatures = DevicePolicyManager.LOCK_TASK_FEATURE_NONE; + policy.mProtectedPackages.clear(); + updateProtectedPackagesLocked(policy.mProtectedPackages); saveSettingsLocked(userId); try { @@ -8585,6 +8607,24 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override + public boolean isOrganizationOwnedDeviceWithManagedProfile() { + if (!mHasFeature) { + return false; + } + enforceManageUsers(); + + return mInjector.binderWithCleanCallingIdentity(() -> { + for (UserInfo ui : mUserManager.getUsers()) { + if (ui.isManagedProfile() && isProfileOwnerOfOrganizationOwnedDevice(ui.id)) { + return true; + } + } + + return false; + }); + } + + @Override public boolean checkDeviceIdentifierAccess(String packageName, int pid, int uid) { ensureCallerIdentityMatchesIfNotSystem(packageName, pid, uid); @@ -9065,6 +9105,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { pw.increaseIndent(); pw.print("mPasswordOwner="); pw.println(policy.mPasswordOwner); pw.decreaseIndent(); + pw.println(); + pw.increaseIndent(); + pw.print("mProtectedPackages="); pw.println(policy.mProtectedPackages); + pw.decreaseIndent(); } } @@ -11679,6 +11723,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return mStateCache; } + @Override + public List<String> getAllCrossProfilePackages() { + return DevicePolicyManagerService.this.getAllCrossProfilePackages(); + } + } private Intent createShowAdminSupportIntent(ComponentName admin, int userId) { @@ -14558,8 +14607,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { for (int i = 0; i < users.length; i++) { final ComponentName componentName = getProfileOwner(users[i]); if (componentName != null) { - admins.add(getActiveAdminForCallerLocked( - componentName, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER)); + ActiveAdmin admin = getActiveAdminUncheckedLocked(componentName, users[i]); + if (admin != null) { + admins.add(admin); + } } } return admins; @@ -14698,4 +14749,42 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return DevicePolicyConstants.loadFromString( mInjector.settingsGlobalGetString(Global.DEVICE_POLICY_CONSTANTS)); } + + @Override + public void setProtectedPackages(ComponentName who, List<String> packages) { + Preconditions.checkNotNull(who, "ComponentName is null"); + Preconditions.checkNotNull(packages, "packages is null"); + + enforceDeviceOwner(who); + synchronized (getLockObject()) { + final int userHandle = mInjector.userHandleGetCallingUserId(); + setProtectedPackagesLocked(userHandle, packages); + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.SET_PACKAGES_PROTECTED) + .setAdmin(who) + .setStrings(packages.toArray(new String[packages.size()])) + .write(); + } + } + + private void setProtectedPackagesLocked(int userHandle, List<String> packages) { + final DevicePolicyData policy = getUserData(userHandle); + policy.mProtectedPackages = packages; + + // Store the settings persistently. + saveSettingsLocked(userHandle); + updateProtectedPackagesLocked(packages); + } + + @Override + public List<String> getProtectedPackages(ComponentName who) { + Preconditions.checkNotNull(who, "ComponentName is null"); + + enforceDeviceOwner(who); + final int userHandle = mInjector.binderGetCallingUserHandle().getIdentifier(); + synchronized (getLockObject()) { + final List<String> packages = getUserData(userHandle).mProtectedPackages; + return packages == null ? Collections.EMPTY_LIST : packages; + } + } } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 08221f927531..b6a8ca447213 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -126,6 +126,7 @@ import com.android.server.om.OverlayManagerService; import com.android.server.os.BugreportManagerService; import com.android.server.os.DeviceIdentifiersPolicyService; import com.android.server.os.SchedulingPolicyService; +import com.android.server.people.PeopleService; import com.android.server.pm.BackgroundDexOptService; import com.android.server.pm.CrossProfileAppsService; import com.android.server.pm.DataLoaderManagerService; @@ -750,6 +751,7 @@ public final class SystemServer { // Now that we have the bare essentials of the OS up and running, take // note that we just booted, which might send out a rescue party if // we're stuck in a runtime restart loop. + RescueParty.registerHealthObserver(mSystemContext); RescueParty.noteBoot(mSystemContext); // Manages LEDs and display backlight so we need it to bring up the display. @@ -1917,6 +1919,10 @@ public final class SystemServer { t.traceBegin("StartCrossProfileAppsService"); mSystemServiceManager.startService(CrossProfileAppsService.class); t.traceEnd(); + + t.traceBegin("StartPeopleService"); + mSystemServiceManager.startService(PeopleService.class); + t.traceEnd(); } if (!isWatch) { diff --git a/services/net/java/android/net/ip/IpClientCallbacks.java b/services/net/java/android/net/ip/IpClientCallbacks.java index 61cd88aac921..c93e5c5e4759 100644 --- a/services/net/java/android/net/ip/IpClientCallbacks.java +++ b/services/net/java/android/net/ip/IpClientCallbacks.java @@ -17,6 +17,7 @@ package android.net.ip; import android.net.DhcpResults; +import android.net.DhcpResultsParcelable; import android.net.Layer2PacketParcelable; import android.net.LinkProperties; @@ -69,6 +70,18 @@ public class IpClientCallbacks { public void onNewDhcpResults(DhcpResults dhcpResults) {} /** + * Callback called when new DHCP results are available. + * + * <p>This is purely advisory and not an indication of provisioning success or failure. This is + * only here for callers that want to expose DHCPv4 results to other APIs + * (e.g., WifiInfo#setInetAddress). + * + * <p>DHCPv4 or static IPv4 configuration failure or success can be determined by whether or not + * the passed-in DhcpResults object is null. + */ + public void onNewDhcpResults(DhcpResultsParcelable dhcpResults) {} + + /** * Indicates that provisioning was successful. */ public void onProvisioningSuccess(LinkProperties newLp) {} diff --git a/services/net/java/android/net/ip/IpClientUtil.java b/services/net/java/android/net/ip/IpClientUtil.java index 4d60e6239376..7f723b1c232b 100644 --- a/services/net/java/android/net/ip/IpClientUtil.java +++ b/services/net/java/android/net/ip/IpClientUtil.java @@ -119,6 +119,7 @@ public class IpClientUtil { @Override public void onNewDhcpResults(DhcpResultsParcelable dhcpResults) { mCb.onNewDhcpResults(fromStableParcelable(dhcpResults)); + mCb.onNewDhcpResults(dhcpResults); } @Override diff --git a/services/people/Android.bp b/services/people/Android.bp new file mode 100644 index 000000000000..d64097a03ae9 --- /dev/null +++ b/services/people/Android.bp @@ -0,0 +1,5 @@ +java_library_static { + name: "services.people", + srcs: ["java/**/*.java"], + libs: ["services.core"], +} diff --git a/services/people/java/com/android/server/people/PeopleService.java b/services/people/java/com/android/server/people/PeopleService.java new file mode 100644 index 000000000000..ef3da6085fb1 --- /dev/null +++ b/services/people/java/com/android/server/people/PeopleService.java @@ -0,0 +1,141 @@ +/* + * 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.people; + +import android.app.prediction.AppPredictionContext; +import android.app.prediction.AppPredictionSessionId; +import android.app.prediction.AppTarget; +import android.app.prediction.AppTargetEvent; +import android.app.prediction.IPredictionCallback; +import android.content.Context; +import android.content.pm.ParceledListSlice; +import android.os.RemoteException; +import android.util.ArrayMap; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.SystemService; + +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +/** + * A service that manages the people and conversations provided by apps. + */ +public class PeopleService extends SystemService { + + private static final String TAG = "PeopleService"; + + /** + * Initializes the system service. + * + * @param context The system server context. + */ + public PeopleService(Context context) { + super(context); + } + + @Override + public void onStart() { + publishLocalService(PeopleServiceInternal.class, new LocalService()); + } + + @VisibleForTesting + final class LocalService extends PeopleServiceInternal { + + private Map<AppPredictionSessionId, SessionInfo> mSessions = new ArrayMap<>(); + + @Override + public void onCreatePredictionSession(AppPredictionContext context, + AppPredictionSessionId sessionId) { + mSessions.put(sessionId, new SessionInfo(context)); + } + + @Override + public void notifyAppTargetEvent(AppPredictionSessionId sessionId, AppTargetEvent event) { + runForSession(sessionId, + sessionInfo -> sessionInfo.getPredictor().onAppTargetEvent(event)); + } + + @Override + public void notifyLaunchLocationShown(AppPredictionSessionId sessionId, + String launchLocation, ParceledListSlice targetIds) { + runForSession(sessionId, + sessionInfo -> sessionInfo.getPredictor().onLaunchLocationShown( + launchLocation, targetIds.getList())); + } + + @Override + public void sortAppTargets(AppPredictionSessionId sessionId, ParceledListSlice targets, + IPredictionCallback callback) { + runForSession(sessionId, + sessionInfo -> sessionInfo.getPredictor().onSortAppTargets( + targets.getList(), + targetList -> invokePredictionCallback(callback, targetList))); + } + + @Override + public void registerPredictionUpdates(AppPredictionSessionId sessionId, + IPredictionCallback callback) { + runForSession(sessionId, sessionInfo -> sessionInfo.addCallback(callback)); + } + + @Override + public void unregisterPredictionUpdates(AppPredictionSessionId sessionId, + IPredictionCallback callback) { + runForSession(sessionId, sessionInfo -> sessionInfo.removeCallback(callback)); + } + + @Override + public void requestPredictionUpdate(AppPredictionSessionId sessionId) { + runForSession(sessionId, + sessionInfo -> sessionInfo.getPredictor().onRequestPredictionUpdate()); + } + + @Override + public void onDestroyPredictionSession(AppPredictionSessionId sessionId) { + runForSession(sessionId, sessionInfo -> { + sessionInfo.onDestroy(); + mSessions.remove(sessionId); + }); + } + + @VisibleForTesting + SessionInfo getSessionInfo(AppPredictionSessionId sessionId) { + return mSessions.get(sessionId); + } + + private void runForSession(AppPredictionSessionId sessionId, Consumer<SessionInfo> method) { + SessionInfo sessionInfo = mSessions.get(sessionId); + if (sessionInfo == null) { + Slog.e(TAG, "Failed to find the session: " + sessionId); + return; + } + method.accept(sessionInfo); + } + + private void invokePredictionCallback(IPredictionCallback callback, + List<AppTarget> targets) { + try { + callback.onResult(new ParceledListSlice<>(targets)); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to calling callback" + e); + } + } + } +} diff --git a/services/people/java/com/android/server/people/SessionInfo.java b/services/people/java/com/android/server/people/SessionInfo.java new file mode 100644 index 000000000000..df7cedf7626f --- /dev/null +++ b/services/people/java/com/android/server/people/SessionInfo.java @@ -0,0 +1,72 @@ +/* + * 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.people; + +import android.app.prediction.AppPredictionContext; +import android.app.prediction.AppTarget; +import android.app.prediction.IPredictionCallback; +import android.content.pm.ParceledListSlice; +import android.os.RemoteCallbackList; +import android.os.RemoteException; +import android.util.Slog; + +import com.android.server.people.prediction.ConversationPredictor; + +import java.util.List; + +/** Manages the information and callbacks in an app prediction request session. */ +class SessionInfo { + + private static final String TAG = "SessionInfo"; + + private final ConversationPredictor mConversationPredictor; + private final RemoteCallbackList<IPredictionCallback> mCallbacks = + new RemoteCallbackList<>(); + + SessionInfo(AppPredictionContext predictionContext) { + mConversationPredictor = new ConversationPredictor(predictionContext, + this::updatePredictions); + } + + void addCallback(IPredictionCallback callback) { + mCallbacks.register(callback); + } + + void removeCallback(IPredictionCallback callback) { + mCallbacks.unregister(callback); + } + + ConversationPredictor getPredictor() { + return mConversationPredictor; + } + + void onDestroy() { + mCallbacks.kill(); + } + + private void updatePredictions(List<AppTarget> targets) { + int callbackCount = mCallbacks.beginBroadcast(); + for (int i = 0; i < callbackCount; i++) { + try { + mCallbacks.getBroadcastItem(i).onResult(new ParceledListSlice<>(targets)); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to calling callback" + e); + } + } + mCallbacks.finishBroadcast(); + } +} diff --git a/services/people/java/com/android/server/people/prediction/ConversationPredictor.java b/services/people/java/com/android/server/people/prediction/ConversationPredictor.java new file mode 100644 index 000000000000..de71d292ff7d --- /dev/null +++ b/services/people/java/com/android/server/people/prediction/ConversationPredictor.java @@ -0,0 +1,85 @@ +/* + * 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.people.prediction; + +import android.annotation.MainThread; +import android.app.prediction.AppPredictionContext; +import android.app.prediction.AppTarget; +import android.app.prediction.AppTargetEvent; +import android.app.prediction.AppTargetId; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.function.Consumer; + +/** + * Predictor that predicts the conversations or apps the user is most likely to open. + */ +public class ConversationPredictor { + + private final AppPredictionContext mPredictionContext; + private final Consumer<List<AppTarget>> mUpdatePredictionsMethod; + private final ExecutorService mCallbackExecutor; + + public ConversationPredictor(AppPredictionContext predictionContext, + Consumer<List<AppTarget>> updatePredictionsMethod) { + mPredictionContext = predictionContext; + mUpdatePredictionsMethod = updatePredictionsMethod; + mCallbackExecutor = Executors.newSingleThreadExecutor(); + } + + /** + * Called by the client app to indicate a target launch. + */ + @MainThread + public void onAppTargetEvent(AppTargetEvent event) { + } + + /** + * Called by the client app to indicate a particular location has been shown to the user. + */ + @MainThread + public void onLaunchLocationShown(String launchLocation, List<AppTargetId> targetIds) { + } + + /** + * Called by the client app to request sorting of the provided targets based on the prediction + * ranking. + */ + @MainThread + public void onSortAppTargets(List<AppTarget> targets, Consumer<List<AppTarget>> callback) { + mCallbackExecutor.execute(() -> callback.accept(targets)); + } + + /** + * Called by the client app to request target predictions. + */ + @MainThread + public void onRequestPredictionUpdate() { + List<AppTarget> targets = new ArrayList<>(); + mCallbackExecutor.execute(() -> mUpdatePredictionsMethod.accept(targets)); + } + + @VisibleForTesting + public Consumer<List<AppTarget>> getUpdatePredictionsMethod() { + return mUpdatePredictionsMethod; + } +} diff --git a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java index 8632ca4c2898..8b2f15c2babb 100644 --- a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java +++ b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java @@ -81,7 +81,10 @@ import org.robolectric.shadows.ShadowLooper; import org.robolectric.shadows.ShadowPackageManager; import java.io.File; +import java.io.FileDescriptor; import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; import java.util.List; /** @@ -1238,13 +1241,49 @@ public class UserBackupManagerServiceTest { assertThat(service.getAncestralSerialNumber()).isEqualTo(testSerialNumber2); } + /** + * Test that {@link UserBackupManagerService#dump()} for system user does not prefix dump with + * "User 0:". + */ + @Test + public void testDump_forSystemUser_DoesNotHaveUserPrefix() throws Exception { + mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); + UserBackupManagerService service = + BackupManagerServiceTestUtils.createUserBackupManagerServiceAndRunTasks( + UserHandle.USER_SYSTEM, + mContext, + mBackupThread, + mBaseStateDir, + mDataDir, + mTransportManager); + + StringWriter dump = new StringWriter(); + service.dump(new FileDescriptor(), new PrintWriter(dump), new String[0]); + + assertThat(dump.toString()).startsWith("Backup Manager is "); + } + + /** + * Test that {@link UserBackupManagerService#dump()} for non-system user prefixes dump with + * "User <userid>:". + */ + @Test + public void testDump_forNonSystemUser_HasUserPrefix() throws Exception { + mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); + UserBackupManagerService service = createUserBackupManagerServiceAndRunTasks(); + + StringWriter dump = new StringWriter(); + service.dump(new FileDescriptor(), new PrintWriter(dump), new String[0]); + + assertThat(dump.toString()).startsWith("User " + USER_ID + ":" + "Backup Manager is "); + } + private File createTestFile() throws IOException { File testFile = new File(mContext.getFilesDir(), "test"); testFile.createNewFile(); return testFile; } - /** * We can't mock the void method {@link #schedule(Context, long, BackupManagerConstants)} so we * extend {@link ShadowKeyValueBackupJob} and throw an exception at the end of the method. diff --git a/services/robotests/src/com/android/server/location/LocationRequestStatisticsTest.java b/services/robotests/src/com/android/server/location/LocationRequestStatisticsTest.java new file mode 100644 index 000000000000..4cbdbd178944 --- /dev/null +++ b/services/robotests/src/com/android/server/location/LocationRequestStatisticsTest.java @@ -0,0 +1,74 @@ +/* + * 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.location; + +import static com.google.common.truth.Truth.assertThat; + +import android.platform.test.annotations.Presubmit; + +import com.android.internal.util.IndentingPrintWriter; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +import java.io.PrintWriter; +import java.io.StringWriter; + +/** + * Unit tests for {@link LocationRequestStatistics}. + */ +@RunWith(RobolectricTestRunner.class) +@Presubmit +public class LocationRequestStatisticsTest { + + /** + * Check adding and removing requests & strings + */ + @Test + public void testRequestSummary() { + LocationRequestStatistics.RequestSummary summary = + new LocationRequestStatistics.RequestSummary( + "com.example", "gps", 1000); + StringWriter stringWriter = new StringWriter(); + summary.dump(new IndentingPrintWriter(new PrintWriter(stringWriter), " "), 1234); + assertThat(stringWriter.toString()).startsWith("At"); + + StringWriter stringWriterRemove = new StringWriter(); + summary = new LocationRequestStatistics.RequestSummary( + "com.example", "gps", + LocationRequestStatistics.RequestSummary.REQUEST_ENDED_INTERVAL); + summary.dump(new IndentingPrintWriter(new PrintWriter(stringWriterRemove), " "), 2345); + assertThat(stringWriterRemove.toString()).contains("-"); + } + + /** + * Check summary list size capping + */ + @Test + public void testSummaryList() { + LocationRequestStatistics statistics = new LocationRequestStatistics(); + statistics.history.addRequest("com.example", "gps", 1000); + assertThat(statistics.history.mList.size()).isEqualTo(1); + // Try (not) to overflow + for (int i = 0; i < LocationRequestStatistics.RequestSummaryLimitedHistory.MAX_SIZE; i++) { + statistics.history.addRequest("com.example", "gps", 1000); + } + assertThat(statistics.history.mList.size()).isEqualTo( + LocationRequestStatistics.RequestSummaryLimitedHistory.MAX_SIZE); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java index 0d6020c3d06d..30d89d31ba64 100644 --- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java @@ -32,6 +32,7 @@ import static org.mockito.ArgumentMatchers.isNull; import android.content.ContentResolver; import android.content.Context; +import android.content.pm.VersionedPackage; import android.os.RecoverySystem; import android.os.SystemProperties; import android.os.UserHandle; @@ -39,6 +40,8 @@ import android.provider.DeviceConfig; import android.provider.Settings; import com.android.dx.mockito.inline.extended.ExtendedMockito; +import com.android.server.PackageWatchdog.PackageHealthObserverImpact; +import com.android.server.RescueParty.RescuePartyObserver; import com.android.server.am.SettingsToPropertiesMapper; import com.android.server.utils.FlagNamespaceUtils; @@ -57,13 +60,15 @@ import java.util.HashMap; * Test RescueParty. */ public class RescuePartyTest { - private static final int PERSISTENT_APP_UID = 12; private static final long CURRENT_NETWORK_TIME_MILLIS = 0L; private static final String FAKE_NATIVE_NAMESPACE1 = "native1"; private static final String FAKE_NATIVE_NAMESPACE2 = "native2"; private static final String[] FAKE_RESET_NATIVE_NAMESPACES = {FAKE_NATIVE_NAMESPACE1, FAKE_NATIVE_NAMESPACE2}; + private static VersionedPackage sFailingPackage = new VersionedPackage("com.package.name", 1); + private static final String PROP_DISABLE_RESCUE = "persist.sys.disable_rescue"; + private MockitoSession mSession; @Mock(answer = Answers.RETURNS_DEEP_STUBS) @@ -182,25 +187,25 @@ public class RescuePartyTest { @Test public void testPersistentAppCrashDetectionWithExecutionForAllRescueLevels() { - notePersistentAppCrash(RescueParty.TRIGGER_COUNT); + notePersistentAppCrash(); verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS); assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS, SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE)); - notePersistentAppCrash(RescueParty.TRIGGER_COUNT); + notePersistentAppCrash(); verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES); assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES, SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE)); - notePersistentAppCrash(RescueParty.TRIGGER_COUNT); + notePersistentAppCrash(); verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS); assertEquals(RescueParty.LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS, SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE)); - notePersistentAppCrash(RescueParty.TRIGGER_COUNT); + notePersistentAppCrash(); verify(() -> RecoverySystem.rebootPromptAndWipeUserData(mMockContext, RescueParty.TAG)); assertEquals(RescueParty.LEVEL_FACTORY_RESET, @@ -221,20 +226,6 @@ public class RescuePartyTest { } @Test - public void testPersistentAppCrashDetectionWithWrongInterval() { - notePersistentAppCrash(RescueParty.TRIGGER_COUNT - 1); - - // last persistent app crash is just outside of the boot loop detection window - doReturn(CURRENT_NETWORK_TIME_MILLIS - + RescueParty.PERSISTENT_APP_CRASH_TRIGGER_WINDOW_MILLIS + 1) - .when(() -> RescueParty.getElapsedRealtime()); - notePersistentAppCrash(/*numTimes=*/1); - - assertEquals(RescueParty.LEVEL_NONE, - SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE)); - } - - @Test public void testBootLoopDetectionWithProperInterval() { noteBoot(RescueParty.TRIGGER_COUNT - 1); @@ -249,21 +240,6 @@ public class RescuePartyTest { } @Test - public void testPersistentAppCrashDetectionWithProperInterval() { - notePersistentAppCrash(RescueParty.TRIGGER_COUNT - 1); - - // last persistent app crash is just inside of the boot loop detection window - doReturn(CURRENT_NETWORK_TIME_MILLIS - + RescueParty.PERSISTENT_APP_CRASH_TRIGGER_WINDOW_MILLIS) - .when(() -> RescueParty.getElapsedRealtime()); - notePersistentAppCrash(/*numTimes=*/1); - - verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS); - assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS, - SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE)); - } - - @Test public void testBootLoopDetectionWithWrongTriggerCount() { noteBoot(RescueParty.TRIGGER_COUNT - 1); assertEquals(RescueParty.LEVEL_NONE, @@ -271,13 +247,6 @@ public class RescuePartyTest { } @Test - public void testPersistentAppCrashDetectionWithWrongTriggerCount() { - notePersistentAppCrash(RescueParty.TRIGGER_COUNT - 1); - assertEquals(RescueParty.LEVEL_NONE, - SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE)); - } - - @Test public void testIsAttemptingFactoryReset() { noteBoot(RescueParty.TRIGGER_COUNT * 4); @@ -319,6 +288,77 @@ public class RescuePartyTest { FAKE_NATIVE_NAMESPACE2, /*makeDefault=*/true)); } + @Test + public void testExplicitlyEnablingAndDisablingRescue() { + SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(false)); + SystemProperties.set(PROP_DISABLE_RESCUE, Boolean.toString(true)); + assertEquals(RescuePartyObserver.getInstance(mMockContext).execute(sFailingPackage, + PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING), false); + + SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true)); + assertTrue(RescuePartyObserver.getInstance(mMockContext).execute(sFailingPackage, + PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING)); + } + + @Test + public void testHealthCheckLevels() { + RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext); + + // Ensure that no action is taken for cases where the failure reason is unknown + SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString( + RescueParty.LEVEL_FACTORY_RESET)); + assertEquals(observer.onHealthCheckFailed(null, PackageWatchdog.FAILURE_REASON_UNKNOWN), + PackageHealthObserverImpact.USER_IMPACT_NONE); + + /* + For the following cases, ensure that the returned user impact corresponds with the user + impact of the next available rescue level, not the current one. + */ + SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString( + RescueParty.LEVEL_NONE)); + assertEquals(observer.onHealthCheckFailed(null, + PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING), + PackageHealthObserverImpact.USER_IMPACT_LOW); + + SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString( + RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS)); + assertEquals(observer.onHealthCheckFailed(null, + PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING), + PackageHealthObserverImpact.USER_IMPACT_LOW); + + + SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString( + RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES)); + assertEquals(observer.onHealthCheckFailed(null, + PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING), + PackageHealthObserverImpact.USER_IMPACT_HIGH); + + + SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString( + RescueParty.LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS)); + assertEquals(observer.onHealthCheckFailed(null, + PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING), + PackageHealthObserverImpact.USER_IMPACT_HIGH); + + + SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString( + RescueParty.LEVEL_FACTORY_RESET)); + assertEquals(observer.onHealthCheckFailed(null, + PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING), + PackageHealthObserverImpact.USER_IMPACT_HIGH); + } + + @Test + public void testRescueLevelIncrementsWhenExecuted() { + RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext); + SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString( + RescueParty.LEVEL_NONE)); + observer.execute(sFailingPackage, + PackageWatchdog.FAILURE_REASON_APP_CRASH); + assertEquals(SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, -1), + RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS); + } + private void verifySettingsResets(int resetMode) { verify(() -> Settings.Global.resetToDefaultsAsUser(mMockContentResolver, null, resetMode, UserHandle.USER_SYSTEM)); @@ -332,9 +372,8 @@ public class RescuePartyTest { } } - private void notePersistentAppCrash(int numTimes) { - for (int i = 0; i < numTimes; i++) { - RescueParty.noteAppCrash(mMockContext, PERSISTENT_APP_UID); - } + private void notePersistentAppCrash() { + RescuePartyObserver.getInstance(mMockContext).execute(new VersionedPackage( + "com.package.name", 1), PackageWatchdog.FAILURE_REASON_UNKNOWN); } } diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java index 0504d14317c2..067f23a5bb23 100644 --- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java @@ -16,6 +16,8 @@ package com.android.server.appop; import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; +import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME; +import static android.app.AppOpsManager.FILTER_BY_UID; import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.MODE_ERRORED; import static android.app.AppOpsManager.MODE_FOREGROUND; @@ -287,7 +289,7 @@ public class AppOpsServiceTest { mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null); AppOpsManager.HistoricalOps historicalOps = new AppOpsManager.HistoricalOps(0, 15000); - historicalOps.increaseAccessCount(OP_READ_SMS, mMyUid, sMyPackageName, + historicalOps.increaseAccessCount(OP_READ_SMS, mMyUid, sMyPackageName, null, AppOpsManager.UID_STATE_PERSISTENT, 0, 1); mAppOpsService.addHistoricalOps(historicalOps); @@ -300,8 +302,8 @@ public class AppOpsServiceTest { }); // First, do a fetch to ensure it's written - mAppOpsService.getHistoricalOps(mMyUid, sMyPackageName, null, 0, Long.MAX_VALUE, 0, - callback); + mAppOpsService.getHistoricalOps(mMyUid, sMyPackageName, null, null, + FILTER_BY_UID | FILTER_BY_PACKAGE_NAME, 0, Long.MAX_VALUE, 0, callback); latchRef.get().await(5, TimeUnit.SECONDS); assertThat(latchRef.get().getCount()).isEqualTo(0); @@ -312,8 +314,8 @@ public class AppOpsServiceTest { latchRef.set(new CountDownLatch(1)); - mAppOpsService.getHistoricalOps(mMyUid, sMyPackageName, null, 0, Long.MAX_VALUE, 0, - callback); + mAppOpsService.getHistoricalOps(mMyUid, sMyPackageName, null, null, + FILTER_BY_UID | FILTER_BY_PACKAGE_NAME, 0, Long.MAX_VALUE, 0, callback); latchRef.get().await(5, TimeUnit.SECONDS); assertThat(latchRef.get().getCount()).isEqualTo(0); diff --git a/services/tests/mockingservicestests/src/com/android/server/utils/quota/CountQuotaTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/utils/quota/CountQuotaTrackerTest.java new file mode 100644 index 000000000000..80aec73035bc --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/utils/quota/CountQuotaTrackerTest.java @@ -0,0 +1,848 @@ +/* + * 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.utils.quota; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; +import static com.android.server.utils.quota.Category.SINGLE_CATEGORY; +import static com.android.server.utils.quota.QuotaTracker.MAX_WINDOW_SIZE_MS; +import static com.android.server.utils.quota.QuotaTracker.MIN_WINDOW_SIZE_MS; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.app.AlarmManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Handler; +import android.os.Looper; +import android.os.SystemClock; +import android.os.UserHandle; +import android.util.LongArrayQueue; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.LocalServices; +import com.android.server.utils.quota.CountQuotaTracker.ExecutionStats; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; + +/** + * Tests for {@link CountQuotaTracker}. + */ +@RunWith(AndroidJUnit4.class) +public class CountQuotaTrackerTest { + private static final long SECOND_IN_MILLIS = 1000L; + private static final long MINUTE_IN_MILLIS = 60 * SECOND_IN_MILLIS; + private static final long HOUR_IN_MILLIS = 60 * MINUTE_IN_MILLIS; + private static final String TAG_CLEANUP = "*CountQuotaTracker.cleanup*"; + private static final String TAG_QUOTA_CHECK = "*QuotaTracker.quota_check*"; + private static final String TEST_PACKAGE = "com.android.frameworks.mockingservicestests"; + private static final String TEST_TAG = "testing"; + private static final int TEST_UID = 10987; + private static final int TEST_USER_ID = 0; + + /** A {@link Category} to represent the ACTIVE standby bucket. */ + private static final Category ACTIVE_BUCKET_CATEGORY = new Category("ACTIVE"); + + /** A {@link Category} to represent the WORKING_SET standby bucket. */ + private static final Category WORKING_SET_BUCKET_CATEGORY = new Category("WORKING_SET"); + + /** A {@link Category} to represent the FREQUENT standby bucket. */ + private static final Category FREQUENT_BUCKET_CATEGORY = new Category("FREQUENT"); + + /** A {@link Category} to represent the RARE standby bucket. */ + private static final Category RARE_BUCKET_CATEGORY = new Category("RARE"); + + private CountQuotaTracker mQuotaTracker; + private final CategorizerForTest mCategorizer = new CategorizerForTest(); + private final InjectorForTest mInjector = new InjectorForTest(); + private final TestQuotaChangeListener mQuotaChangeListener = new TestQuotaChangeListener(); + private BroadcastReceiver mReceiver; + private MockitoSession mMockingSession; + @Mock + private AlarmManager mAlarmManager; + @Mock + private Context mContext; + + static class CategorizerForTest implements Categorizer { + private Category mCategoryToUse = SINGLE_CATEGORY; + + @Override + public Category getCategory(int userId, + String packageName, String tag) { + return mCategoryToUse; + } + } + + private static class InjectorForTest extends QuotaTracker.Injector { + private long mElapsedTime = SystemClock.elapsedRealtime(); + + @Override + long getElapsedRealtime() { + return mElapsedTime; + } + + @Override + boolean isAlarmManagerReady() { + return true; + } + } + + private static class TestQuotaChangeListener implements QuotaChangeListener { + + @Override + public void onQuotaStateChanged(int userId, String packageName, String tag) { + + } + } + + @Before + public void setUp() { + mMockingSession = mockitoSession() + .initMocks(this) + .strictness(Strictness.LENIENT) + .mockStatic(LocalServices.class) + .startMocking(); + + when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper()); + when(mContext.getSystemService(AlarmManager.class)).thenReturn(mAlarmManager); + + // Freeze the clocks at 24 hours after this moment in time. Several tests create sessions + // in the past, and QuotaController sometimes floors values at 0, so if the test time + // causes sessions with negative timestamps, they will fail. + advanceElapsedClock(24 * HOUR_IN_MILLIS); + + // Initialize real objects. + // Capture the listeners. + ArgumentCaptor<BroadcastReceiver> receiverCaptor = + ArgumentCaptor.forClass(BroadcastReceiver.class); + mQuotaTracker = new CountQuotaTracker(mContext, mCategorizer, mInjector); + mQuotaTracker.setEnabled(true); + mQuotaTracker.setQuotaFree(false); + mQuotaTracker.registerQuotaChangeListener(mQuotaChangeListener); + verify(mContext, atLeastOnce()).registerReceiverAsUser( + receiverCaptor.capture(), eq(UserHandle.ALL), any(), any(), any()); + mReceiver = receiverCaptor.getValue(); + } + + @After + public void tearDown() { + if (mMockingSession != null) { + mMockingSession.finishMocking(); + } + } + + /** + * Returns true if the two {@link LongArrayQueue}s have the same size and the same elements in + * the same order. + */ + private static boolean longArrayQueueEquals(LongArrayQueue queue1, LongArrayQueue queue2) { + if (queue1 == queue2) { + return true; + } else if (queue1 == null || queue2 == null) { + return false; + } + if (queue1.size() == queue2.size()) { + for (int i = 0; i < queue1.size(); ++i) { + if (queue1.get(i) != queue2.get(i)) { + return false; + } + } + return true; + } + return false; + } + + private void advanceElapsedClock(long incrementMs) { + mInjector.mElapsedTime += incrementMs; + } + + private void logEvents(int count) { + logEvents(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, count); + } + + private void logEvents(int userId, String pkgName, String tag, int count) { + for (int i = 0; i < count; ++i) { + mQuotaTracker.noteEvent(userId, pkgName, tag); + } + } + + private void logEventAt(long timeElapsed) { + logEventAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, timeElapsed); + } + + private void logEventAt(int userId, String pkgName, String tag, long timeElapsed) { + long now = mInjector.getElapsedRealtime(); + mInjector.mElapsedTime = timeElapsed; + mQuotaTracker.noteEvent(userId, pkgName, tag); + mInjector.mElapsedTime = now; + } + + private void logEventsAt(int userId, String pkgName, String tag, long timeElapsed, int count) { + for (int i = 0; i < count; ++i) { + logEventAt(userId, pkgName, tag, timeElapsed); + } + } + + @Test + public void testDeleteObsoleteEventsLocked() { + // Count window size should only apply to event list. + mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 7, 2 * HOUR_IN_MILLIS); + + final long now = mInjector.getElapsedRealtime(); + + logEventAt(now - 6 * HOUR_IN_MILLIS); + logEventAt(now - 5 * HOUR_IN_MILLIS); + logEventAt(now - 4 * HOUR_IN_MILLIS); + logEventAt(now - 3 * HOUR_IN_MILLIS); + logEventAt(now - 2 * HOUR_IN_MILLIS); + logEventAt(now - HOUR_IN_MILLIS); + logEventAt(now - 1); + + LongArrayQueue expectedEvents = new LongArrayQueue(); + expectedEvents.addLast(now - HOUR_IN_MILLIS); + expectedEvents.addLast(now - 1); + + mQuotaTracker.deleteObsoleteEventsLocked(); + + LongArrayQueue remainingEvents = mQuotaTracker.getEvents(TEST_USER_ID, TEST_PACKAGE, + TEST_TAG); + assertTrue(longArrayQueueEquals(expectedEvents, remainingEvents)); + } + + @Test + public void testAppRemoval() { + final long now = mInjector.getElapsedRealtime(); + logEventAt(TEST_USER_ID, "com.android.test.remove", "tag1", now - (6 * HOUR_IN_MILLIS)); + logEventAt(TEST_USER_ID, "com.android.test.remove", "tag2", + now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS)); + logEventAt(TEST_USER_ID, "com.android.test.remove", "tag3", now - (HOUR_IN_MILLIS)); + // Test that another app isn't affected. + LongArrayQueue expected1 = new LongArrayQueue(); + expected1.addLast(now - 10 * MINUTE_IN_MILLIS); + LongArrayQueue expected2 = new LongArrayQueue(); + expected2.addLast(now - 70 * MINUTE_IN_MILLIS); + logEventAt(TEST_USER_ID, "com.android.test.stay", "tag1", now - 10 * MINUTE_IN_MILLIS); + logEventAt(TEST_USER_ID, "com.android.test.stay", "tag2", now - 70 * MINUTE_IN_MILLIS); + + Intent removal = new Intent(Intent.ACTION_PACKAGE_FULLY_REMOVED, + Uri.fromParts("package", "com.android.test.remove", null)); + removal.putExtra(Intent.EXTRA_UID, TEST_UID); + mReceiver.onReceive(mContext, removal); + assertNull( + mQuotaTracker.getEvents(TEST_USER_ID, "com.android.test.remove", "tag1")); + assertNull( + mQuotaTracker.getEvents(TEST_USER_ID, "com.android.test.remove", "tag2")); + assertNull( + mQuotaTracker.getEvents(TEST_USER_ID, "com.android.test.remove", "tag3")); + assertTrue(longArrayQueueEquals(expected1, + mQuotaTracker.getEvents(TEST_USER_ID, "com.android.test.stay", "tag1"))); + assertTrue(longArrayQueueEquals(expected2, + mQuotaTracker.getEvents(TEST_USER_ID, "com.android.test.stay", "tag2"))); + } + + @Test + public void testUserRemoval() { + final long now = mInjector.getElapsedRealtime(); + logEventAt(TEST_USER_ID, TEST_PACKAGE, "tag1", now - (6 * HOUR_IN_MILLIS)); + logEventAt(TEST_USER_ID, TEST_PACKAGE, "tag2", + now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS)); + logEventAt(TEST_USER_ID, TEST_PACKAGE, "tag3", now - (HOUR_IN_MILLIS)); + // Test that another user isn't affected. + LongArrayQueue expected = new LongArrayQueue(); + expected.addLast(now - (70 * MINUTE_IN_MILLIS)); + expected.addLast(now - (10 * MINUTE_IN_MILLIS)); + logEventAt(10, TEST_PACKAGE, "tag4", now - (70 * MINUTE_IN_MILLIS)); + logEventAt(10, TEST_PACKAGE, "tag4", now - 10 * MINUTE_IN_MILLIS); + + Intent removal = new Intent(Intent.ACTION_USER_REMOVED); + removal.putExtra(Intent.EXTRA_USER_HANDLE, TEST_USER_ID); + mReceiver.onReceive(mContext, removal); + assertNull(mQuotaTracker.getEvents(TEST_USER_ID, TEST_PACKAGE, "tag1")); + assertNull(mQuotaTracker.getEvents(TEST_USER_ID, TEST_PACKAGE, "tag2")); + assertNull(mQuotaTracker.getEvents(TEST_USER_ID, TEST_PACKAGE, "tag3")); + longArrayQueueEquals(expected, mQuotaTracker.getEvents(10, TEST_PACKAGE, "tag4")); + } + + @Test + public void testUpdateExecutionStatsLocked_NoTimer() { + mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 3, 24 * HOUR_IN_MILLIS); + final long now = mInjector.getElapsedRealtime(); + + // Added in chronological order. + logEventAt(now - 4 * HOUR_IN_MILLIS); + logEventAt(now - HOUR_IN_MILLIS); + logEventAt(now - 5 * MINUTE_IN_MILLIS); + logEventAt(now - MINUTE_IN_MILLIS); + + // Test an app that hasn't had any activity. + ExecutionStats expectedStats = new ExecutionStats(); + ExecutionStats inputStats = new ExecutionStats(); + + inputStats.windowSizeMs = expectedStats.windowSizeMs = 12 * HOUR_IN_MILLIS; + inputStats.countLimit = expectedStats.countLimit = 3; + // Invalid time is now +24 hours since there are no sessions at all for the app. + expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS; + mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, "com.android.test.not.run", TEST_TAG, + inputStats); + assertEquals(expectedStats, inputStats); + + // Now test app that has had activity. + + inputStats.windowSizeMs = expectedStats.windowSizeMs = MINUTE_IN_MILLIS; + // Invalid time is now since there was an event exactly windowSizeMs ago. + expectedStats.expirationTimeElapsed = now; + expectedStats.countInWindow = 1; + mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats); + assertEquals(expectedStats, inputStats); + + inputStats.windowSizeMs = expectedStats.windowSizeMs = 3 * MINUTE_IN_MILLIS; + expectedStats.expirationTimeElapsed = now + 2 * MINUTE_IN_MILLIS; + expectedStats.countInWindow = 1; + mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats); + assertEquals(expectedStats, inputStats); + + inputStats.windowSizeMs = expectedStats.windowSizeMs = 4 * MINUTE_IN_MILLIS; + expectedStats.expirationTimeElapsed = now + 3 * MINUTE_IN_MILLIS; + expectedStats.countInWindow = 1; + mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats); + assertEquals(expectedStats, inputStats); + + inputStats.windowSizeMs = expectedStats.windowSizeMs = 49 * MINUTE_IN_MILLIS; + // Invalid time is now +44 minutes since the earliest session in the window is now-5 + // minutes. + expectedStats.expirationTimeElapsed = now + 44 * MINUTE_IN_MILLIS; + expectedStats.countInWindow = 2; + mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats); + assertEquals(expectedStats, inputStats); + + inputStats.windowSizeMs = expectedStats.windowSizeMs = 50 * MINUTE_IN_MILLIS; + expectedStats.expirationTimeElapsed = now + 45 * MINUTE_IN_MILLIS; + expectedStats.countInWindow = 2; + mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats); + assertEquals(expectedStats, inputStats); + + inputStats.windowSizeMs = expectedStats.windowSizeMs = HOUR_IN_MILLIS; + // Invalid time is now since the event is at the very edge of the window + // cutoff time. + expectedStats.expirationTimeElapsed = now; + expectedStats.countInWindow = 3; + // App is at event count limit but the oldest session is at the edge of the window, so + // in quota time is now. + expectedStats.inQuotaTimeElapsed = now; + mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats); + assertEquals(expectedStats, inputStats); + + inputStats.windowSizeMs = expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS; + expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS; + expectedStats.countInWindow = 3; + expectedStats.inQuotaTimeElapsed = now + HOUR_IN_MILLIS; + mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats); + assertEquals(expectedStats, inputStats); + + inputStats.windowSizeMs = expectedStats.windowSizeMs = 5 * HOUR_IN_MILLIS; + expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS; + expectedStats.countInWindow = 4; + expectedStats.inQuotaTimeElapsed = now + 4 * HOUR_IN_MILLIS; + mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats); + assertEquals(expectedStats, inputStats); + + inputStats.windowSizeMs = expectedStats.windowSizeMs = 6 * HOUR_IN_MILLIS; + expectedStats.expirationTimeElapsed = now + 2 * HOUR_IN_MILLIS; + expectedStats.countInWindow = 4; + expectedStats.inQuotaTimeElapsed = now + 5 * HOUR_IN_MILLIS; + mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats); + assertEquals(expectedStats, inputStats); + } + + /** + * Tests that getExecutionStatsLocked returns the correct stats. + */ + @Test + public void testGetExecutionStatsLocked_Values() { + // The handler could cause changes to the cached stats, so prevent it from operating in + // this test. + Handler handler = mQuotaTracker.getHandler(); + spyOn(handler); + doNothing().when(handler).handleMessage(any()); + + mQuotaTracker.setCountLimit(RARE_BUCKET_CATEGORY, 3, 24 * HOUR_IN_MILLIS); + mQuotaTracker.setCountLimit(FREQUENT_BUCKET_CATEGORY, 4, 8 * HOUR_IN_MILLIS); + mQuotaTracker.setCountLimit(WORKING_SET_BUCKET_CATEGORY, 9, 2 * HOUR_IN_MILLIS); + mQuotaTracker.setCountLimit(ACTIVE_BUCKET_CATEGORY, 10, 10 * MINUTE_IN_MILLIS); + + final long now = mInjector.getElapsedRealtime(); + + logEventAt(now - 23 * HOUR_IN_MILLIS); + logEventAt(now - 7 * HOUR_IN_MILLIS); + logEventAt(now - 5 * HOUR_IN_MILLIS); + logEventAt(now - 2 * HOUR_IN_MILLIS); + logEventAt(now - 5 * MINUTE_IN_MILLIS); + + ExecutionStats expectedStats = new ExecutionStats(); + + // Active + expectedStats.expirationTimeElapsed = now + 5 * MINUTE_IN_MILLIS; + expectedStats.windowSizeMs = 10 * MINUTE_IN_MILLIS; + expectedStats.countLimit = 10; + expectedStats.countInWindow = 1; + mCategorizer.mCategoryToUse = ACTIVE_BUCKET_CATEGORY; + assertEquals(expectedStats, + mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG)); + + // Working + expectedStats.expirationTimeElapsed = now; + expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS; + expectedStats.countLimit = 9; + expectedStats.countInWindow = 2; + mCategorizer.mCategoryToUse = WORKING_SET_BUCKET_CATEGORY; + assertEquals(expectedStats, + mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG)); + + // Frequent + expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS; + expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS; + expectedStats.countLimit = 4; + expectedStats.countInWindow = 4; + expectedStats.inQuotaTimeElapsed = now + HOUR_IN_MILLIS; + mCategorizer.mCategoryToUse = FREQUENT_BUCKET_CATEGORY; + assertEquals(expectedStats, + mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG)); + + // Rare + expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS; + expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS; + expectedStats.countLimit = 3; + expectedStats.countInWindow = 5; + expectedStats.inQuotaTimeElapsed = now + 19 * HOUR_IN_MILLIS; + mCategorizer.mCategoryToUse = RARE_BUCKET_CATEGORY; + assertEquals(expectedStats, + mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG)); + } + + /** + * Tests that getExecutionStatsLocked returns the correct stats soon after device startup. + */ + @Test + public void testGetExecutionStatsLocked_Values_BeginningOfTime() { + // Set time to 3 minutes after boot. + mInjector.mElapsedTime = 3 * MINUTE_IN_MILLIS; + + logEventAt(30_000); + logEventAt(MINUTE_IN_MILLIS); + logEventAt(2 * MINUTE_IN_MILLIS); + + mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 2 * HOUR_IN_MILLIS); + + ExecutionStats expectedStats = new ExecutionStats(); + + expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS; + expectedStats.countLimit = 10; + expectedStats.countInWindow = 3; + expectedStats.expirationTimeElapsed = 2 * HOUR_IN_MILLIS + 30_000; + assertEquals(expectedStats, + mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG)); + } + + @Test + public void testisWithinQuota_GlobalQuotaFree() { + mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 0, 2 * HOUR_IN_MILLIS); + mQuotaTracker.setQuotaFree(true); + assertTrue(mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, null)); + assertTrue(mQuotaTracker.isWithinQuota(TEST_USER_ID, "com.android.random.app", null)); + } + + @Test + public void testisWithinQuota_UptcQuotaFree() { + mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 0, 2 * HOUR_IN_MILLIS); + mQuotaTracker.setQuotaFree(TEST_USER_ID, TEST_PACKAGE, true); + assertTrue(mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, null)); + assertFalse( + mQuotaTracker.isWithinQuota(TEST_USER_ID, "com.android.random.app", null)); + } + + @Test + public void testisWithinQuota_UnderCount() { + mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 2 * HOUR_IN_MILLIS); + logEvents(5); + assertTrue(mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, TEST_TAG)); + } + + @Test + public void testisWithinQuota_OverCount() { + mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 25, HOUR_IN_MILLIS); + logEvents(TEST_USER_ID, "com.android.test.spam", TEST_TAG, 30); + assertFalse(mQuotaTracker.isWithinQuota(TEST_USER_ID, "com.android.test.spam", TEST_TAG)); + } + + @Test + public void testisWithinQuota_EqualsCount() { + mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 25, HOUR_IN_MILLIS); + logEvents(25); + assertFalse(mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, TEST_TAG)); + } + + @Test + public void testisWithinQuota_DifferentCategories() { + mQuotaTracker.setCountLimit(RARE_BUCKET_CATEGORY, 3, 24 * HOUR_IN_MILLIS); + mQuotaTracker.setCountLimit(FREQUENT_BUCKET_CATEGORY, 4, 24 * HOUR_IN_MILLIS); + mQuotaTracker.setCountLimit(WORKING_SET_BUCKET_CATEGORY, 5, 24 * HOUR_IN_MILLIS); + mQuotaTracker.setCountLimit(ACTIVE_BUCKET_CATEGORY, 6, 24 * HOUR_IN_MILLIS); + + for (int i = 0; i < 7; ++i) { + logEvents(1); + + mCategorizer.mCategoryToUse = RARE_BUCKET_CATEGORY; + assertEquals("Rare has incorrect quota status with " + (i + 1) + " events", + i < 2, + mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, TEST_TAG)); + mCategorizer.mCategoryToUse = FREQUENT_BUCKET_CATEGORY; + assertEquals("Frequent has incorrect quota status with " + (i + 1) + " events", + i < 3, + mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, TEST_TAG)); + mCategorizer.mCategoryToUse = WORKING_SET_BUCKET_CATEGORY; + assertEquals("Working has incorrect quota status with " + (i + 1) + " events", + i < 4, + mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, TEST_TAG)); + mCategorizer.mCategoryToUse = ACTIVE_BUCKET_CATEGORY; + assertEquals("Active has incorrect quota status with " + (i + 1) + " events", + i < 5, + mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, TEST_TAG)); + } + } + + @Test + public void testMaybeScheduleCleanupAlarmLocked() { + mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 5, 24 * HOUR_IN_MILLIS); + + // No sessions saved yet. + mQuotaTracker.maybeScheduleCleanupAlarmLocked(); + verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_CLEANUP), any(), any()); + + // Test with only one timing session saved. + final long now = mInjector.getElapsedRealtime(); + logEventAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 6 * HOUR_IN_MILLIS); + mQuotaTracker.maybeScheduleCleanupAlarmLocked(); + verify(mAlarmManager, timeout(1000).times(1)) + .set(anyInt(), eq(now + 18 * HOUR_IN_MILLIS), eq(TAG_CLEANUP), any(), any()); + + // Test with new (more recent) timing sessions saved. AlarmManger shouldn't be called again. + logEventAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 3 * HOUR_IN_MILLIS); + logEventAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - HOUR_IN_MILLIS); + mQuotaTracker.maybeScheduleCleanupAlarmLocked(); + verify(mAlarmManager, times(1)) + .set(anyInt(), eq(now + 18 * HOUR_IN_MILLIS), eq(TAG_CLEANUP), any(), any()); + } + + /** + * Tests that maybeScheduleStartAlarm schedules an alarm for the right time. + */ + @Test + public void testMaybeScheduleStartAlarmLocked() { + // logEvent calls maybeScheduleCleanupAlarmLocked which interferes with these tests + // because it schedules an alarm too. Prevent it from doing so. + spyOn(mQuotaTracker); + doNothing().when(mQuotaTracker).maybeScheduleCleanupAlarmLocked(); + + mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 8 * HOUR_IN_MILLIS); + + // No sessions saved yet. + mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); + verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + + // Test with timing sessions out of window. + final long now = mInjector.getElapsedRealtime(); + logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 10 * HOUR_IN_MILLIS, 20); + mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); + verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + + // Test with timing sessions in window but still in quota. + final long start = now - (6 * HOUR_IN_MILLIS); + final long expectedAlarmTime = start + 8 * HOUR_IN_MILLIS; + logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, start, 5); + mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); + verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + + // Add some more sessions, but still in quota. + logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 3 * HOUR_IN_MILLIS, 1); + logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - HOUR_IN_MILLIS, 3); + mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); + verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + + // Test when out of quota. + logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - HOUR_IN_MILLIS, 1); + mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); + verify(mAlarmManager, timeout(1000).times(1)) + .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any()); + + // Alarm already scheduled, so make sure it's not scheduled again. + mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); + verify(mAlarmManager, times(1)) + .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any()); + } + + /** Tests that the start alarm is properly rescheduled if the app's category is changed. */ + @Test + public void testMaybeScheduleStartAlarmLocked_CategoryChange() { + // logEvent calls maybeScheduleCleanupAlarmLocked which interferes with these tests + // because it schedules an alarm too. Prevent it from doing so. + spyOn(mQuotaTracker); + doNothing().when(mQuotaTracker).maybeScheduleCleanupAlarmLocked(); + + mQuotaTracker.setCountLimit(RARE_BUCKET_CATEGORY, 10, 24 * HOUR_IN_MILLIS); + mQuotaTracker.setCountLimit(FREQUENT_BUCKET_CATEGORY, 10, 8 * HOUR_IN_MILLIS); + mQuotaTracker.setCountLimit(WORKING_SET_BUCKET_CATEGORY, 10, 2 * HOUR_IN_MILLIS); + mQuotaTracker.setCountLimit(ACTIVE_BUCKET_CATEGORY, 10, 10 * MINUTE_IN_MILLIS); + + final long now = mInjector.getElapsedRealtime(); + + // Affects rare bucket + logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 12 * HOUR_IN_MILLIS, 9); + // Affects frequent and rare buckets + logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 4 * HOUR_IN_MILLIS, 4); + // Affects working, frequent, and rare buckets + final long outOfQuotaTime = now - HOUR_IN_MILLIS; + logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, outOfQuotaTime, 7); + // Affects all buckets + logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 5 * MINUTE_IN_MILLIS, 3); + + InOrder inOrder = inOrder(mAlarmManager); + + // Start in ACTIVE bucket. + mCategorizer.mCategoryToUse = ACTIVE_BUCKET_CATEGORY; + mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); + inOrder.verify(mAlarmManager, never()) + .set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + inOrder.verify(mAlarmManager, never()).cancel(any(AlarmManager.OnAlarmListener.class)); + + // And down from there. + final long expectedWorkingAlarmTime = outOfQuotaTime + (2 * HOUR_IN_MILLIS); + mCategorizer.mCategoryToUse = WORKING_SET_BUCKET_CATEGORY; + mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); + inOrder.verify(mAlarmManager, timeout(1000).times(1)) + .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any()); + + final long expectedFrequentAlarmTime = outOfQuotaTime + (8 * HOUR_IN_MILLIS); + mCategorizer.mCategoryToUse = FREQUENT_BUCKET_CATEGORY; + mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); + inOrder.verify(mAlarmManager, timeout(1000).times(1)) + .set(anyInt(), eq(expectedFrequentAlarmTime), eq(TAG_QUOTA_CHECK), any(), any()); + + final long expectedRareAlarmTime = outOfQuotaTime + (24 * HOUR_IN_MILLIS); + mCategorizer.mCategoryToUse = RARE_BUCKET_CATEGORY; + mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); + inOrder.verify(mAlarmManager, timeout(1000).times(1)) + .set(anyInt(), eq(expectedRareAlarmTime), eq(TAG_QUOTA_CHECK), any(), any()); + + // And back up again. + mCategorizer.mCategoryToUse = FREQUENT_BUCKET_CATEGORY; + mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); + inOrder.verify(mAlarmManager, timeout(1000).times(1)) + .set(anyInt(), eq(expectedFrequentAlarmTime), eq(TAG_QUOTA_CHECK), any(), any()); + + mCategorizer.mCategoryToUse = WORKING_SET_BUCKET_CATEGORY; + mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); + inOrder.verify(mAlarmManager, timeout(1000).times(1)) + .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any()); + + mCategorizer.mCategoryToUse = ACTIVE_BUCKET_CATEGORY; + mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); + inOrder.verify(mAlarmManager, timeout(1000).times(1)) + .cancel(any(AlarmManager.OnAlarmListener.class)); + inOrder.verify(mAlarmManager, timeout(1000).times(0)) + .set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + } + + @Test + public void testConstantsUpdating_ValidValues() { + mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 0, 60_000); + assertEquals(0, mQuotaTracker.getLimit(SINGLE_CATEGORY)); + assertEquals(60_000, mQuotaTracker.getWindowSizeMs(SINGLE_CATEGORY)); + } + + @Test + public void testConstantsUpdating_InvalidValues() { + // Test negatives. + try { + mQuotaTracker.setCountLimit(SINGLE_CATEGORY, -1, 5000); + fail("Negative count limit didn't throw an exception"); + } catch (IllegalArgumentException e) { + // Success + } + try { + mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 1, -1); + fail("Negative count window size didn't throw an exception"); + } catch (IllegalArgumentException e) { + // Success + } + + // Test window sizes too low. + mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 0, 1); + assertEquals(MIN_WINDOW_SIZE_MS, mQuotaTracker.getWindowSizeMs(SINGLE_CATEGORY)); + + // Test window sizes too high. + mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 0, 365 * 24 * HOUR_IN_MILLIS); + assertEquals(MAX_WINDOW_SIZE_MS, mQuotaTracker.getWindowSizeMs(SINGLE_CATEGORY)); + } + + /** Tests that events aren't counted when global quota is free. */ + @Test + public void testLogEvent_GlobalQuotaFree() { + mQuotaTracker.setQuotaFree(true); + mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 2 * HOUR_IN_MILLIS); + + ExecutionStats stats = + mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); + assertEquals(0, stats.countInWindow); + + for (int i = 0; i < 10; ++i) { + mQuotaTracker.noteEvent(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); + advanceElapsedClock(10 * SECOND_IN_MILLIS); + + mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, stats); + assertEquals(0, stats.countInWindow); + } + } + + /** + * Tests that events are counted when global quota is not free. + */ + @Test + public void testLogEvent_GlobalQuotaNotFree() { + mQuotaTracker.setQuotaFree(false); + mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 2 * HOUR_IN_MILLIS); + + ExecutionStats stats = + mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); + assertEquals(0, stats.countInWindow); + + for (int i = 0; i < 10; ++i) { + mQuotaTracker.noteEvent(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); + advanceElapsedClock(10 * SECOND_IN_MILLIS); + + mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, stats); + assertEquals(i + 1, stats.countInWindow); + } + } + + /** Tests that events aren't counted when the uptc quota is free. */ + @Test + public void testLogEvent_UptcQuotaFree() { + mQuotaTracker.setQuotaFree(TEST_USER_ID, TEST_PACKAGE, true); + mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 2 * HOUR_IN_MILLIS); + + ExecutionStats stats = + mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); + assertEquals(0, stats.countInWindow); + + for (int i = 0; i < 10; ++i) { + mQuotaTracker.noteEvent(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); + advanceElapsedClock(10 * SECOND_IN_MILLIS); + + mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, stats); + assertEquals(0, stats.countInWindow); + } + } + + /** + * Tests that events are counted when UPTC quota is not free. + */ + @Test + public void testLogEvent_UptcQuotaNotFree() { + mQuotaTracker.setQuotaFree(TEST_USER_ID, TEST_PACKAGE, false); + mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 2 * HOUR_IN_MILLIS); + + ExecutionStats stats = + mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); + assertEquals(0, stats.countInWindow); + + for (int i = 0; i < 10; ++i) { + mQuotaTracker.noteEvent(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); + advanceElapsedClock(10 * SECOND_IN_MILLIS); + + mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, stats); + assertEquals(i + 1, stats.countInWindow); + } + } + + /** + * Tests that QuotaChangeListeners are notified when a UPTC reaches its count quota. + */ + @Test + public void testTracking_OutOfQuota() { + spyOn(mQuotaChangeListener); + + mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 2 * HOUR_IN_MILLIS); + logEvents(9); + + mQuotaTracker.noteEvent(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); + + // Wait for some extra time to allow for processing. + verify(mQuotaChangeListener, timeout(3 * SECOND_IN_MILLIS).times(1)) + .onQuotaStateChanged(eq(TEST_USER_ID), eq(TEST_PACKAGE), eq(TEST_TAG)); + assertFalse(mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, TEST_TAG)); + } + + /** + * Tests that QuotaChangeListeners are not incorrectly notified after a UPTC event is logged + * quota times. + */ + @Test + public void testTracking_InQuota() { + spyOn(mQuotaChangeListener); + + mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 5, MINUTE_IN_MILLIS); + + // Log an event once per minute. This is well below the quota, so listeners should not be + // notified. + for (int i = 0; i < 10; i++) { + advanceElapsedClock(MINUTE_IN_MILLIS); + mQuotaTracker.noteEvent(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); + } + + // Wait for some extra time to allow for processing. + verify(mQuotaChangeListener, timeout(3 * SECOND_IN_MILLIS).times(0)) + .onQuotaStateChanged(eq(TEST_USER_ID), eq(TEST_PACKAGE), eq(TEST_TAG)); + assertTrue(mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, TEST_TAG)); + } +} diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index 12323401c664..ace15eb41261 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -26,6 +26,7 @@ android_test { "services.core", "services.devicepolicy", "services.net", + "services.people", "services.usage", "guava", "androidx.test.core", diff --git a/services/tests/servicestests/src/com/android/server/CountryDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/CountryDetectorServiceTest.java index e9c5ce7127de..d5483ffa5445 100644 --- a/services/tests/servicestests/src/com/android/server/CountryDetectorServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/CountryDetectorServiceTest.java @@ -19,8 +19,11 @@ package com.android.server; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.when; import android.content.Context; +import android.content.res.Resources; import android.location.Country; import android.location.CountryListener; import android.location.ICountryListener; @@ -31,6 +34,10 @@ import android.os.RemoteException; import androidx.test.core.app.ApplicationProvider; +import com.android.internal.R; +import com.android.server.location.ComprehensiveCountryDetector; +import com.android.server.location.CustomCountryDetectorTestClass; + import com.google.common.truth.Expect; import org.junit.Before; @@ -38,12 +45,18 @@ import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) public class CountryDetectorServiceTest { + private static final String VALID_CUSTOM_TEST_CLASS = + "com.android.server.location.CustomCountryDetectorTestClass"; + private static final String INVALID_CUSTOM_TEST_CLASS = + "com.android.server.location.MissingCountryDetectorTestClass"; + private static class CountryListenerTester extends ICountryListener.Stub { private Country mCountry; @@ -83,12 +96,11 @@ public class CountryDetectorServiceTest { } } - @Rule - public final Expect expect = Expect.create(); - @Spy - private Context mContext = ApplicationProvider.getApplicationContext(); - @Spy - private Handler mHandler = new Handler(Looper.myLooper()); + @Rule public final Expect expect = Expect.create(); + @Spy private Context mContext = ApplicationProvider.getApplicationContext(); + @Spy private Handler mHandler = new Handler(Looper.myLooper()); + @Mock private Resources mResources; + private CountryDetectorServiceTester mCountryDetectorService; @BeforeClass @@ -108,10 +120,12 @@ public class CountryDetectorServiceTest { message.getCallback().run(); return true; }).when(mHandler).sendMessageAtTime(any(Message.class), anyLong()); + + doReturn(mResources).when(mContext).getResources(); } @Test - public void countryListener_add_successful() throws RemoteException { + public void addCountryListener_validListener_listenerAdded() throws RemoteException { CountryListenerTester countryListener = new CountryListenerTester(); mCountryDetectorService.systemRunning(); @@ -122,7 +136,7 @@ public class CountryDetectorServiceTest { } @Test - public void countryListener_remove_successful() throws RemoteException { + public void removeCountryListener_validListener_listenerRemoved() throws RemoteException { CountryListenerTester countryListener = new CountryListenerTester(); mCountryDetectorService.systemRunning(); @@ -133,8 +147,31 @@ public class CountryDetectorServiceTest { expect.that(mCountryDetectorService.isListenerSet()).isFalse(); } + @Test(expected = RemoteException.class) + public void addCountryListener_serviceNotReady_throwsException() throws RemoteException { + CountryListenerTester countryListener = new CountryListenerTester(); + + expect.that(mCountryDetectorService.isSystemReady()).isFalse(); + mCountryDetectorService.addCountryListener(countryListener); + } + + @Test(expected = RemoteException.class) + public void removeCountryListener_serviceNotReady_throwsException() throws RemoteException { + CountryListenerTester countryListener = new CountryListenerTester(); + + expect.that(mCountryDetectorService.isSystemReady()).isFalse(); + mCountryDetectorService.removeCountryListener(countryListener); + } + @Test - public void countryListener_notify_successful() throws RemoteException { + public void detectCountry_serviceNotReady_returnNull() { + expect.that(mCountryDetectorService.isSystemReady()).isFalse(); + + expect.that(mCountryDetectorService.detectCountry()).isNull(); + } + + @Test + public void notifyReceivers_twoListenersRegistered_bothNotified() throws RemoteException { CountryListenerTester countryListenerA = new CountryListenerTester(); CountryListenerTester countryListenerB = new CountryListenerTester(); Country country = new Country("US", Country.COUNTRY_SOURCE_NETWORK); @@ -151,4 +188,26 @@ public class CountryDetectorServiceTest { expect.that(countryListenerA.getCountry().equalsIgnoreSource(country)).isTrue(); expect.that(countryListenerB.getCountry().equalsIgnoreSource(country)).isTrue(); } + + @Test + public void initialize_deviceWithCustomDetector_useCustomDetectorClass() { + when(mResources.getString(R.string.config_customCountryDetector)) + .thenReturn(VALID_CUSTOM_TEST_CLASS); + + mCountryDetectorService.initialize(); + + expect.that(mCountryDetectorService.getCountryDetector()) + .isInstanceOf(CustomCountryDetectorTestClass.class); + } + + @Test + public void initialize_deviceWithInvalidCustomDetector_useDefaultDetector() { + when(mResources.getString(R.string.config_customCountryDetector)) + .thenReturn(INVALID_CUSTOM_TEST_CLASS); + + mCountryDetectorService.initialize(); + + expect.that(mCountryDetectorService.getCountryDetector()) + .isInstanceOf(ComprehensiveCountryDetector.class); + } } diff --git a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java index 1829fb79699f..2fb2021de200 100644 --- a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java @@ -29,6 +29,7 @@ import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; @@ -56,7 +57,6 @@ import android.net.wifi.ScanResult; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; -import android.net.wifi.WifiSsid; import android.os.Binder; import android.os.Handler; import android.os.HandlerThread; @@ -182,8 +182,8 @@ public class NetworkScoreServiceTest { } private ScanResult createScanResult(String ssid, String bssid) { - ScanResult result = new ScanResult(); - result.wifiSsid = WifiSsid.createFromAsciiEncoded(ssid); + ScanResult result = mock(ScanResult.class); + result.SSID = ssid; result.BSSID = bssid; return result; } @@ -794,7 +794,7 @@ public class NetworkScoreServiceTest { @Test public void testScanResultsScoreCacheFilter_invalidScanResults() throws Exception { List<ScanResult> invalidScanResults = Lists.newArrayList( - new ScanResult(), + mock(ScanResult.class), createScanResult("", SCORED_NETWORK.networkKey.wifiKey.bssid), createScanResult(WifiManager.UNKNOWN_SSID, SCORED_NETWORK.networkKey.wifiKey.bssid), createScanResult(SSID, null), diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java index d11d98766b01..591c3a385e23 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java @@ -85,6 +85,7 @@ import android.view.accessibility.IAccessibilityInteractionConnection; import android.view.accessibility.IAccessibilityInteractionConnectionCallback; import com.android.server.accessibility.AccessibilityWindowManager.RemoteAccessibilityConnection; +import com.android.server.accessibility.test.MessageCapturingHandler; import com.android.server.wm.WindowManagerInternal; import org.junit.Before; diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java new file mode 100644 index 000000000000..75239db92121 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java @@ -0,0 +1,128 @@ +/* + * 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.accessibility; + +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.Manifest; +import android.app.PendingIntent; +import android.app.RemoteAction; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.graphics.drawable.Icon; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; +import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; + +import androidx.test.InstrumentationRegistry; + +import com.android.server.LocalServices; +import com.android.server.accessibility.AccessibilityManagerService.AccessibilityDisplayListener; +import com.android.server.wm.ActivityTaskManagerInternal; +import com.android.server.wm.WindowManagerInternal; + +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * APCT tests for {@link AccessibilityManagerService}. + */ +public class AccessibilityManagerServiceTest extends AndroidTestCase { + private static final String TAG = "A11Y_MANAGER_SERVICE_TEST"; + private static final int ACTION_ID = 20; + private static final String LABEL = "label"; + private static final String INTENT_ACTION = "TESTACTION"; + private static final String DESCRIPTION = "description"; + private static final PendingIntent TEST_PENDING_INTENT = PendingIntent.getBroadcast( + InstrumentationRegistry.getTargetContext(), 0, new Intent(INTENT_ACTION), 0); + private static final RemoteAction TEST_ACTION = new RemoteAction( + Icon.createWithContentUri("content://test"), + LABEL, + DESCRIPTION, + TEST_PENDING_INTENT); + private static final AccessibilityAction NEW_ACCESSIBILITY_ACTION = + new AccessibilityAction(ACTION_ID, LABEL); + + @Mock private PackageManager mMockPackageManager; + @Mock private WindowManagerInternal mMockWindowManagerService; + @Mock private AccessibilitySecurityPolicy mMockSecurityPolicy; + @Mock private SystemActionPerformer mMockSystemActionPerformer; + @Mock private AccessibilityWindowManager mMockA11yWindowManager; + @Mock private AccessibilityDisplayListener mMockA11yDisplayListener; + @Mock private ActivityTaskManagerInternal mMockActivityTaskManagerInternal; + + private AccessibilityManagerService mA11yms; + + @Override + protected void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + LocalServices.removeServiceForTest(WindowManagerInternal.class); + LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class); + LocalServices.addService(WindowManagerInternal.class, mMockWindowManagerService); + LocalServices.addService( + ActivityTaskManagerInternal.class, mMockActivityTaskManagerInternal); + mA11yms = new AccessibilityManagerService( + InstrumentationRegistry.getContext(), + mMockPackageManager, + mMockSecurityPolicy, + mMockSystemActionPerformer, + mMockA11yWindowManager, + mMockA11yDisplayListener); + } + + @SmallTest + public void testRegisterSystemActionWithoutPermission() throws Exception { + doThrow(SecurityException.class).when(mMockSecurityPolicy).enforceCallingPermission( + Manifest.permission.MANAGE_ACCESSIBILITY, + AccessibilityManagerService.FUNCTION_REGISTER_SYSTEM_ACTION); + + try { + mA11yms.registerSystemAction(TEST_ACTION, ACTION_ID); + fail(); + } catch (SecurityException expected) { + } + verify(mMockSystemActionPerformer, never()).registerSystemAction(ACTION_ID, TEST_ACTION); + } + + @SmallTest + public void testRegisterSystemAction() throws Exception { + mA11yms.registerSystemAction(TEST_ACTION, ACTION_ID); + verify(mMockSystemActionPerformer).registerSystemAction(ACTION_ID, TEST_ACTION); + } + + @SmallTest + public void testUnregisterSystemActionWithoutPermission() throws Exception { + doThrow(SecurityException.class).when(mMockSecurityPolicy).enforceCallingPermission( + Manifest.permission.MANAGE_ACCESSIBILITY, + AccessibilityManagerService.FUNCTION_UNREGISTER_SYSTEM_ACTION); + + try { + mA11yms.unregisterSystemAction(ACTION_ID); + fail(); + } catch (SecurityException expected) { + } + verify(mMockSystemActionPerformer, never()).unregisterSystemAction(ACTION_ID); + } + + @SmallTest + public void testUnregisterSystemAction() throws Exception { + mA11yms.unregisterSystemAction(ACTION_ID); + verify(mMockSystemActionPerformer).unregisterSystemAction(ACTION_ID); + } +} diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java index 99dd9a12eb72..2ce70b6f0889 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java @@ -44,6 +44,7 @@ import android.os.UserHandle; import android.testing.DexmakerShareClassLoaderRule; import android.view.Display; +import com.android.server.accessibility.test.MessageCapturingHandler; import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerInternal; diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java index 67075edb0143..9db5a080c093 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java @@ -53,6 +53,7 @@ import android.view.accessibility.AccessibilityWindowInfo; import android.view.accessibility.IAccessibilityInteractionConnection; import com.android.server.accessibility.AccessibilityWindowManager.RemoteAccessibilityConnection; +import com.android.server.accessibility.test.MessageCapturingHandler; import com.android.server.wm.WindowManagerInternal; import com.android.server.wm.WindowManagerInternal.WindowsForAccessibilityCallback; diff --git a/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureControllerTest.java index 44a514f7623c..96ae102e53f3 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureControllerTest.java @@ -31,6 +31,8 @@ import android.accessibilityservice.FingerprintGestureController; import android.accessibilityservice.FingerprintGestureController.FingerprintGestureCallback; import android.accessibilityservice.IAccessibilityServiceConnection; +import com.android.server.accessibility.test.MessageCapturingHandler; + import org.junit.Before; import org.junit.Test; import org.mockito.Mock; diff --git a/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureDispatcherTest.java b/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureDispatcherTest.java index de7bc443b8c5..30d00ad716e6 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureDispatcherTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureDispatcherTest.java @@ -31,6 +31,7 @@ import android.hardware.fingerprint.IFingerprintService; import android.view.KeyEvent; import com.android.server.accessibility.FingerprintGestureDispatcher.FingerprintGestureClient; +import com.android.server.accessibility.test.MessageCapturingHandler; import org.junit.After; import org.junit.Before; diff --git a/services/tests/servicestests/src/com/android/server/accessibility/KeyEventDispatcherTest.java b/services/tests/servicestests/src/com/android/server/accessibility/KeyEventDispatcherTest.java index 23ce483be107..41235560dc91 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/KeyEventDispatcherTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/KeyEventDispatcherTest.java @@ -44,6 +44,7 @@ import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; import com.android.server.accessibility.KeyEventDispatcher.KeyEventFilter; +import com.android.server.accessibility.test.MessageCapturingHandler; import com.android.server.policy.WindowManagerPolicy; import org.hamcrest.Description; diff --git a/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java index 322653b4115c..78e651b7a3c1 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java @@ -33,6 +33,7 @@ import android.view.KeyEvent; import androidx.test.runner.AndroidJUnit4; +import com.android.server.accessibility.test.MessageCapturingHandler; import com.android.server.policy.WindowManagerPolicy; import org.hamcrest.Description; @@ -212,4 +213,4 @@ public class KeyboardInterceptorTest { description.appendText("Matches key event"); } } -}
\ No newline at end of file +} diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java index 773b8778c7bf..82c6498bd9be 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java @@ -47,6 +47,7 @@ import android.view.MagnificationSpec; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; +import com.android.server.accessibility.test.MessageCapturingHandler; import com.android.server.wm.WindowManagerInternal; import com.android.server.wm.WindowManagerInternal.MagnificationCallbacks; diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java index 36e854ca77cd..1ac4a8ed96d0 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java @@ -54,6 +54,7 @@ import android.view.accessibility.AccessibilityEvent; import androidx.test.runner.AndroidJUnit4; +import com.android.server.accessibility.test.MessageCapturingHandler; import com.android.server.accessibility.utils.MotionEventMatcher; import org.hamcrest.Description; diff --git a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java index 8da927dcb4ab..c8baca610bdc 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java @@ -37,6 +37,7 @@ import android.hardware.display.DisplayManager; import android.os.IBinder; import android.view.accessibility.AccessibilityEvent; +import com.android.server.accessibility.test.MessageCapturingHandler; import com.android.server.wm.WindowManagerInternal; import org.junit.After; diff --git a/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java b/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java index d4182f3d31e2..5a1ad8655ab0 100644 --- a/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java @@ -672,6 +672,30 @@ public final class AdbDebuggingManagerTest { connectionTime2, mKeyStore.getLastConnectionTime(TEST_KEY_2)); } + @Test + public void testClearAuthorizationsBeforeAdbEnabled() throws Exception { + // The adb key store is not instantiated until adb is enabled; however if the user attempts + // to clear the adb authorizations when adb is disabled after a boot a NullPointerException + // was thrown as deleteKeyStore is invoked against the key store. This test ensures the + // key store can be successfully cleared when adb is disabled. + mHandler = mManager.new AdbDebuggingHandler(FgThread.get().getLooper()); + + clearKeyStore(); + } + + @Test + public void testClearAuthorizationsDeletesKeyFiles() throws Exception { + mAdbKeyFile.createNewFile(); + mAdbKeyXmlFile.createNewFile(); + + clearKeyStore(); + + assertFalse("The adb key file should have been deleted after revocation of the grants", + mAdbKeyFile.exists()); + assertFalse("The adb xml key file should have been deleted after revocation of the grants", + mAdbKeyXmlFile.exists()); + } + /** * Runs an adb test with the provided configuration. * diff --git a/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java index f3c76b609c25..8871348d0027 100644 --- a/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java +++ b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java @@ -28,6 +28,7 @@ import static org.mockito.Mockito.when; import android.app.admin.DevicePolicyManagerInternal; import android.appwidget.AppWidgetManager; +import android.appwidget.AppWidgetManagerInternal; import android.appwidget.AppWidgetProviderInfo; import android.appwidget.PendingHostUpdate; import android.content.BroadcastReceiver; @@ -80,6 +81,7 @@ public class AppWidgetServiceImplTest extends InstrumentationTestCase { super.setUp(); LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class); LocalServices.removeServiceForTest(ShortcutServiceInternal.class); + LocalServices.removeServiceForTest(AppWidgetManagerInternal.class); mTestContext = new TestContext(); mPkgName = mTestContext.getOpPackageName(); diff --git a/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java index 2326dfda7e12..d44476ed971d 100644 --- a/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java @@ -27,6 +27,7 @@ import static junit.framework.Assert.fail; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; @@ -59,6 +60,7 @@ import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -83,6 +85,8 @@ public class BackupManagerServiceTest { @Mock private UserBackupManagerService mUserBackupManagerService; @Mock + private UserBackupManagerService mNonSystemUserBackupManagerService; + @Mock private Context mContextMock; @Mock private PrintWriter mPrintWriterMock; @@ -105,7 +109,7 @@ public class BackupManagerServiceTest { mUserServices = new SparseArray<>(); mUserServices.append(UserHandle.USER_SYSTEM, mUserBackupManagerService); - mUserServices.append(NON_USER_SYSTEM, mUserBackupManagerService); + mUserServices.append(NON_USER_SYSTEM, mNonSystemUserBackupManagerService); when(mUserManagerMock.getUserInfo(UserHandle.USER_SYSTEM)).thenReturn(mUserInfoMock); when(mUserManagerMock.getUserInfo(NON_USER_SYSTEM)).thenReturn(mUserInfoMock); @@ -512,6 +516,26 @@ public class BackupManagerServiceTest { mService.dump(mFileDescriptorStub, mPrintWriterMock, new String[0]); verifyNoMoreInteractions(mUserBackupManagerService); + verifyNoMoreInteractions(mNonSystemUserBackupManagerService); + } + + /** + * Test that {@link BackupManagerService#dump()} dumps system user information before non-system + * user information. + */ + + @Test + public void testDump_systemUserFirst() { + String[] args = new String[0]; + mService.dumpWithoutCheckingPermission(mFileDescriptorStub, mPrintWriterMock, args); + + InOrder inOrder = + inOrder(mUserBackupManagerService, mNonSystemUserBackupManagerService); + inOrder.verify(mUserBackupManagerService) + .dump(mFileDescriptorStub, mPrintWriterMock, args); + inOrder.verify(mNonSystemUserBackupManagerService) + .dump(mFileDescriptorStub, mPrintWriterMock, args); + inOrder.verifyNoMoreInteractions(); } @Test diff --git a/services/tests/servicestests/src/com/android/server/compat/ApplicationInfoBuilder.java b/services/tests/servicestests/src/com/android/server/compat/ApplicationInfoBuilder.java new file mode 100644 index 000000000000..d0767ccb6f87 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/compat/ApplicationInfoBuilder.java @@ -0,0 +1,58 @@ +/* + * 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.compat; + +import android.content.pm.ApplicationInfo; + +class ApplicationInfoBuilder { + private boolean mIsDebuggable; + private int mTargetSdk; + private String mPackageName; + + private ApplicationInfoBuilder() { + mTargetSdk = -1; + } + + static ApplicationInfoBuilder create() { + return new ApplicationInfoBuilder(); + } + + ApplicationInfoBuilder withTargetSdk(int targetSdk) { + mTargetSdk = targetSdk; + return this; + } + + ApplicationInfoBuilder debuggable() { + mIsDebuggable = true; + return this; + } + + ApplicationInfoBuilder withPackageName(String packageName) { + mPackageName = packageName; + return this; + } + + ApplicationInfo build() { + final ApplicationInfo applicationInfo = new ApplicationInfo(); + if (mIsDebuggable) { + applicationInfo.flags |= ApplicationInfo.FLAG_DEBUGGABLE; + } + applicationInfo.packageName = mPackageName; + applicationInfo.targetSdkVersion = mTargetSdk; + return applicationInfo; + } +} diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigBuilder.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigBuilder.java new file mode 100644 index 000000000000..328c71dbc7db --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigBuilder.java @@ -0,0 +1,99 @@ +/* + * 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.compat; + +import android.content.Context; + +import com.android.internal.compat.AndroidBuildClassifier; + +import java.util.ArrayList; + +/** + * Helper class for creating a CompatConfig. + */ +class CompatConfigBuilder { + private ArrayList<CompatChange> mChanges; + private AndroidBuildClassifier mBuildClassifier; + private Context mContext; + + private CompatConfigBuilder(AndroidBuildClassifier buildClassifier, Context context) { + mChanges = new ArrayList<>(); + mBuildClassifier = buildClassifier; + mContext = context; + } + + static CompatConfigBuilder create(AndroidBuildClassifier buildClassifier, Context context) { + return new CompatConfigBuilder(buildClassifier, context); + } + + CompatConfigBuilder addTargetSdkChangeWithId(int sdk, long id) { + mChanges.add(new CompatChange(id, "", sdk, false, "")); + return this; + } + + CompatConfigBuilder addTargetSdkDisabledChangeWithId(int sdk, long id) { + mChanges.add(new CompatChange(id, "", sdk, true, "")); + return this; + } + + CompatConfigBuilder addTargetSdkChangeWithIdAndName(int sdk, long id, String name) { + mChanges.add(new CompatChange(id, name, sdk, false, "")); + return this; + } + + CompatConfigBuilder addTargetSdkChangeWithIdAndDescription(int sdk, long id, + String description) { + mChanges.add(new CompatChange(id, "", sdk, false, description)); + return this; + } + + CompatConfigBuilder addEnabledChangeWithId(long id) { + mChanges.add(new CompatChange(id, "", -1, false, "")); + return this; + } + + CompatConfigBuilder addEnabledChangeWithIdAndName(long id, String name) { + mChanges.add(new CompatChange(id, name, -1, false, "")); + return this; + } + CompatConfigBuilder addEnabledChangeWithIdAndDescription(long id, String description) { + mChanges.add(new CompatChange(id, "", -1, false, description)); + return this; + } + + CompatConfigBuilder addDisabledChangeWithId(long id) { + mChanges.add(new CompatChange(id, "", -1, true, "")); + return this; + } + + CompatConfigBuilder addDisabledChangeWithIdAndName(long id, String name) { + mChanges.add(new CompatChange(id, name, -1, true, "")); + return this; + } + + CompatConfigBuilder addDisabledChangeWithIdAndDescription(long id, String description) { + mChanges.add(new CompatChange(id, "", -1, true, description)); + return this; + } + + CompatConfig build() { + CompatConfig config = new CompatConfig(mBuildClassifier, mContext); + for (CompatChange change : mChanges) { + config.addChange(change); + } + return config; + } +} diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java index cb99c118a407..407f67e2fd8e 100644 --- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java @@ -18,12 +18,25 @@ package com.android.server.compat; 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.mock; +import static org.mockito.Mockito.when; +import static org.testng.Assert.assertThrows; + +import android.content.Context; import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.compat.AndroidBuildClassifier; + +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import java.io.File; import java.io.FileOutputStream; @@ -34,12 +47,12 @@ import java.util.UUID; @RunWith(AndroidJUnit4.class) public class CompatConfigTest { - private ApplicationInfo makeAppInfo(String pName, int targetSdkVersion) { - ApplicationInfo ai = new ApplicationInfo(); - ai.packageName = pName; - ai.targetSdkVersion = targetSdkVersion; - return ai; - } + @Mock + private Context mContext; + @Mock + PackageManager mPackageManager; + @Mock + private AndroidBuildClassifier mBuildClassifier; private File createTempDir() { String base = System.getProperty("java.io.tmpdir"); @@ -54,112 +67,206 @@ public class CompatConfigTest { os.close(); } + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + when(mContext.getPackageManager()).thenReturn(mPackageManager); + // Assume userdebug/eng non-final build + when(mBuildClassifier.isDebuggableBuild()).thenReturn(true); + when(mBuildClassifier.isFinalBuild()).thenReturn(false); + } + + @Test + public void testUnknownChangeEnabled() throws Exception { + CompatConfig compatConfig = new CompatConfig(mBuildClassifier, mContext); + assertThat(compatConfig.isChangeEnabled(1234L, ApplicationInfoBuilder.create().build())) + .isTrue(); + } + @Test - public void testUnknownChangeEnabled() { - CompatConfig pc = new CompatConfig(); - assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 1))).isTrue(); + public void testDisabledChangeDisabled() throws Exception { + CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext) + .addDisabledChangeWithId(1234L) + .build(); + + assertThat(compatConfig.isChangeEnabled(1234L, ApplicationInfoBuilder.create().build())) + .isFalse(); } @Test - public void testDisabledChangeDisabled() { - CompatConfig pc = new CompatConfig(); - pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, true, "")); - assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 1))).isFalse(); + public void testTargetSdkChangeDisabled() throws Exception { + CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext) + .addTargetSdkChangeWithId(2, 1234L) + .build(); + + assertThat(compatConfig.isChangeEnabled(1234L, + ApplicationInfoBuilder.create().withTargetSdk(2).build())) + .isFalse(); } @Test - public void testTargetSdkChangeDisabled() { - CompatConfig pc = new CompatConfig(); - pc.addChange(new CompatChange(1234L, "MY_CHANGE", 2, false, null)); - assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 2))).isFalse(); + public void testTargetSdkChangeEnabled() throws Exception { + CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext) + .addTargetSdkChangeWithId(2, 1234L) + .build(); + + assertThat(compatConfig.isChangeEnabled(1234L, + ApplicationInfoBuilder.create().withTargetSdk(3).build())).isTrue(); } @Test - public void testTargetSdkChangeEnabled() { - CompatConfig pc = new CompatConfig(); - pc.addChange(new CompatChange(1234L, "MY_CHANGE", 2, false, "")); - assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 3))).isTrue(); + public void testDisabledOverrideTargetSdkChange() throws Exception { + CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext) + .addTargetSdkDisabledChangeWithId(2, 1234L) + .build(); + + assertThat(compatConfig.isChangeEnabled(1234L, + ApplicationInfoBuilder.create().withTargetSdk(3).build())).isFalse(); } @Test - public void testDisabledOverrideTargetSdkChange() { - CompatConfig pc = new CompatConfig(); - pc.addChange(new CompatChange(1234L, "MY_CHANGE", 2, true, null)); - assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 3))).isFalse(); + public void testGetDisabledChanges() throws Exception { + CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext) + .addDisabledChangeWithId(1234L) + .addEnabledChangeWithId(2345L) + .build(); + + assertThat(compatConfig.getDisabledChanges( + ApplicationInfoBuilder.create().build())).asList().containsExactly(1234L); } @Test - public void testGetDisabledChanges() { - CompatConfig pc = new CompatConfig(); - pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, true, null)); - pc.addChange(new CompatChange(2345L, "OTHER_CHANGE", -1, false, null)); - assertThat(pc.getDisabledChanges( - makeAppInfo("com.some.package", 2))).asList().containsExactly(1234L); + public void testGetDisabledChangesSorted() throws Exception { + CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext) + .addDisabledChangeWithId(1234L) + .addDisabledChangeWithId(123L) + .addDisabledChangeWithId(12L) + .build(); + + assertThat(compatConfig.getDisabledChanges(ApplicationInfoBuilder.create().build())) + .asList().containsExactly(12L, 123L, 1234L); } @Test - public void testGetDisabledChangesSorted() { - CompatConfig pc = new CompatConfig(); - pc.addChange(new CompatChange(1234L, "MY_CHANGE", 2, true, null)); - pc.addChange(new CompatChange(123L, "OTHER_CHANGE", 2, true, null)); - pc.addChange(new CompatChange(12L, "THIRD_CHANGE", 2, true, null)); - assertThat(pc.getDisabledChanges( - makeAppInfo("com.some.package", 2))).asList().containsExactly(12L, 123L, 1234L); + public void testPackageOverrideEnabled() throws Exception { + CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext) + .addDisabledChangeWithId(1234L) + .build(); + + compatConfig.addOverride(1234L, "com.some.package", true); + + assertThat(compatConfig.isChangeEnabled(1234L, ApplicationInfoBuilder.create() + .withPackageName("com.some.package").build())).isTrue(); + assertThat(compatConfig.isChangeEnabled(1234L, ApplicationInfoBuilder.create() + .withPackageName("com.other.package").build())).isFalse(); } @Test - public void testPackageOverrideEnabled() { - CompatConfig pc = new CompatConfig(); - pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, true, null)); // disabled - pc.addOverride(1234L, "com.some.package", true); - assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 2))).isTrue(); - assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.other.package", 2))).isFalse(); + public void testPackageOverrideDisabled() throws Exception { + CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext) + .addEnabledChangeWithId(1234L) + .build(); + + compatConfig.addOverride(1234L, "com.some.package", false); + + assertThat(compatConfig.isChangeEnabled(1234L, ApplicationInfoBuilder.create() + .withPackageName("com.some.package").build())).isFalse(); + assertThat(compatConfig.isChangeEnabled(1234L, ApplicationInfoBuilder.create() + .withPackageName("com.other.package").build())).isTrue(); } @Test - public void testPackageOverrideDisabled() { - CompatConfig pc = new CompatConfig(); - pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, false, null)); - pc.addOverride(1234L, "com.some.package", false); - assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 2))).isFalse(); - assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.other.package", 2))).isTrue(); + public void testPackageOverrideUnknownPackage() throws Exception { + CompatConfig compatConfig = new CompatConfig(mBuildClassifier, mContext); + + compatConfig.addOverride(1234L, "com.some.package", false); + + assertThat(compatConfig.isChangeEnabled(1234L, ApplicationInfoBuilder.create() + .withPackageName("com.some.package").build())).isFalse(); + assertThat(compatConfig.isChangeEnabled(1234L, ApplicationInfoBuilder.create() + .withPackageName("com.other.package").build())).isTrue(); } @Test - public void testPackageOverrideUnknownPackage() { - CompatConfig pc = new CompatConfig(); - pc.addOverride(1234L, "com.some.package", false); - assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 2))).isFalse(); - assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.other.package", 2))).isTrue(); + public void testPreventAddOverride() throws Exception { + final long changeId = 1234L; + CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext) + .addDisabledChangeWithId(1234L) + .build(); + ApplicationInfo applicationInfo = ApplicationInfoBuilder.create() + .withPackageName("com.some.package") + .build(); + PackageManager packageManager = mock(PackageManager.class); + when(mContext.getPackageManager()).thenReturn(packageManager); + when(packageManager.getApplicationInfo(eq("com.some.package"), anyInt())) + .thenReturn(applicationInfo); + + // Force the validator to prevent overriding the change by using a user build. + when(mBuildClassifier.isDebuggableBuild()).thenReturn(false); + when(mBuildClassifier.isFinalBuild()).thenReturn(true); + + assertThrows(SecurityException.class, + () -> compatConfig.addOverride(1234L, "com.some.package", true) + ); + assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isFalse(); } @Test - public void testPackageOverrideUnknownChange() { - CompatConfig pc = new CompatConfig(); - assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 1))).isTrue(); + public void testPreventRemoveOverride() throws Exception { + CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext) + .addDisabledChangeWithId(1234L) + .build(); + ApplicationInfo applicationInfo = ApplicationInfoBuilder.create() + .withPackageName("com.some.package") + .build(); + when(mPackageManager.getApplicationInfo(eq("com.some.package"), anyInt())) + .thenReturn(applicationInfo); + // Assume the override was allowed to be added. + compatConfig.addOverride(1234L, "com.some.package", true); + + // Validator allows turning on the change. + assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isTrue(); + + // Reject all override attempts. + // Force the validator to prevent overriding the change by using a user build. + when(mBuildClassifier.isDebuggableBuild()).thenReturn(false); + when(mBuildClassifier.isFinalBuild()).thenReturn(true); + // Try to turn off change, but validator prevents it. + assertThrows(SecurityException.class, + () -> compatConfig.removeOverride(1234L, "com.some.package")); + assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isTrue(); } @Test - public void testRemovePackageOverride() { - CompatConfig pc = new CompatConfig(); - pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, false, null)); - pc.addOverride(1234L, "com.some.package", false); - pc.removeOverride(1234L, "com.some.package"); - assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 2))).isTrue(); + public void testRemovePackageOverride() throws Exception { + CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext) + .addEnabledChangeWithId(1234L) + .build(); + ApplicationInfo applicationInfo = ApplicationInfoBuilder.create() + .withPackageName("com.some.package") + .build(); + + assertThat(compatConfig.addOverride(1234L, "com.some.package", false)).isTrue(); + assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isFalse(); + + compatConfig.removeOverride(1234L, "com.some.package"); + assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isTrue(); } @Test - public void testLookupChangeId() { - CompatConfig pc = new CompatConfig(); - pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, false, null)); - pc.addChange(new CompatChange(2345L, "ANOTHER_CHANGE", -1, false, null)); - assertThat(pc.lookupChangeId("MY_CHANGE")).isEqualTo(1234L); + public void testLookupChangeId() throws Exception { + CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext) + .addEnabledChangeWithIdAndName(1234L, "MY_CHANGE") + .addEnabledChangeWithIdAndName(2345L, "MY_OTHER_CHANGE") + .build(); + + assertThat(compatConfig.lookupChangeId("MY_CHANGE")).isEqualTo(1234L); } @Test - public void testLookupChangeIdNotPresent() { - CompatConfig pc = new CompatConfig(); - assertThat(pc.lookupChangeId("MY_CHANGE")).isEqualTo(-1L); + public void testLookupChangeIdNotPresent() throws Exception { + CompatConfig compatConfig = new CompatConfig(mBuildClassifier, mContext); + assertThat(compatConfig.lookupChangeId("MY_CHANGE")).isEqualTo(-1L); } @Test @@ -172,14 +279,17 @@ public class CompatConfigTest { File dir = createTempDir(); writeToFile(dir, "platform_compat_config.xml", configXml); - - CompatConfig pc = new CompatConfig(); - pc.initConfigFromLib(dir); - - assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 1))).isFalse(); - assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 3))).isTrue(); - assertThat(pc.isChangeEnabled(1235L, makeAppInfo("com.some.package", 5))).isFalse(); - assertThat(pc.isChangeEnabled(1236L, makeAppInfo("com.some.package", 1))).isTrue(); + CompatConfig compatConfig = new CompatConfig(mBuildClassifier, mContext); + compatConfig.initConfigFromLib(dir); + + assertThat(compatConfig.isChangeEnabled(1234L, + ApplicationInfoBuilder.create().withTargetSdk(1).build())).isFalse(); + assertThat(compatConfig.isChangeEnabled(1234L, + ApplicationInfoBuilder.create().withTargetSdk(3).build())).isTrue(); + assertThat(compatConfig.isChangeEnabled(1235L, + ApplicationInfoBuilder.create().withTargetSdk(5).build())).isFalse(); + assertThat(compatConfig.isChangeEnabled(1236L, + ApplicationInfoBuilder.create().withTargetSdk(1).build())).isTrue(); } @Test @@ -195,15 +305,16 @@ public class CompatConfigTest { File dir = createTempDir(); writeToFile(dir, "libcore_platform_compat_config.xml", configXml1); writeToFile(dir, "frameworks_platform_compat_config.xml", configXml2); - - CompatConfig pc = new CompatConfig(); - pc.initConfigFromLib(dir); - - assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 1))).isFalse(); - assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 3))).isTrue(); - assertThat(pc.isChangeEnabled(1235L, makeAppInfo("com.some.package", 5))).isFalse(); - assertThat(pc.isChangeEnabled(1236L, makeAppInfo("com.some.package", 1))).isTrue(); + CompatConfig compatConfig = new CompatConfig(mBuildClassifier, mContext); + compatConfig.initConfigFromLib(dir); + + assertThat(compatConfig.isChangeEnabled(1234L, + ApplicationInfoBuilder.create().withTargetSdk(1).build())).isFalse(); + assertThat(compatConfig.isChangeEnabled(1234L, + ApplicationInfoBuilder.create().withTargetSdk(3).build())).isTrue(); + assertThat(compatConfig.isChangeEnabled(1235L, + ApplicationInfoBuilder.create().withTargetSdk(5).build())).isFalse(); + assertThat(compatConfig.isChangeEnabled(1236L, + ApplicationInfoBuilder.create().withTargetSdk(1).build())).isTrue(); } } - - diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatibilityChangeConfigBuilder.java b/services/tests/servicestests/src/com/android/server/compat/CompatibilityChangeConfigBuilder.java new file mode 100644 index 000000000000..793296e88169 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/compat/CompatibilityChangeConfigBuilder.java @@ -0,0 +1,52 @@ +/* + * 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.compat; + +import android.compat.Compatibility; + +import com.android.internal.compat.CompatibilityChangeConfig; + +import java.util.HashSet; +import java.util.Set; + +class CompatibilityChangeConfigBuilder { + private Set<Long> mEnabled; + private Set<Long> mDisabled; + + private CompatibilityChangeConfigBuilder() { + mEnabled = new HashSet<>(); + mDisabled = new HashSet<>(); + } + + static CompatibilityChangeConfigBuilder create() { + return new CompatibilityChangeConfigBuilder(); + } + + CompatibilityChangeConfigBuilder enable(Long id) { + mEnabled.add(id); + return this; + } + + CompatibilityChangeConfigBuilder disable(Long id) { + mDisabled.add(id); + return this; + } + + CompatibilityChangeConfig build() { + return new CompatibilityChangeConfig(new Compatibility.ChangeConfig(mEnabled, mDisabled)); + } +} diff --git a/services/tests/servicestests/src/com/android/server/compat/OverrideValidatorImplTest.java b/services/tests/servicestests/src/com/android/server/compat/OverrideValidatorImplTest.java new file mode 100644 index 000000000000..ecd07bdc4544 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/compat/OverrideValidatorImplTest.java @@ -0,0 +1,383 @@ +/* + * 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.compat; + +import static com.android.internal.compat.OverrideAllowedState.ALLOWED; +import static com.android.internal.compat.OverrideAllowedState.DISABLED_NON_TARGET_SDK; +import static com.android.internal.compat.OverrideAllowedState.DISABLED_NOT_DEBUGGABLE; +import static com.android.internal.compat.OverrideAllowedState.DISABLED_TARGET_SDK_TOO_HIGH; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.pm.PackageManager; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.compat.AndroidBuildClassifier; +import com.android.internal.compat.IOverrideValidator; +import com.android.internal.compat.OverrideAllowedState; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +public class OverrideValidatorImplTest { + private static final String PACKAGE_NAME = "my.package"; + private static final int TARGET_SDK = 10; + private static final int TARGET_SDK_BEFORE = 9; + private static final int TARGET_SDK_AFTER = 11; + + @Mock + private PackageManager mPackageManager; + @Mock + Context mContext; + + private AndroidBuildClassifier debuggableBuild() { + AndroidBuildClassifier buildClassifier = mock(AndroidBuildClassifier.class); + when(buildClassifier.isDebuggableBuild()).thenReturn(true); + return buildClassifier; + } + + private AndroidBuildClassifier betaBuild() { + AndroidBuildClassifier buildClassifier = mock(AndroidBuildClassifier.class); + when(buildClassifier.isDebuggableBuild()).thenReturn(false); + when(buildClassifier.isFinalBuild()).thenReturn(false); + return buildClassifier; + } + + private AndroidBuildClassifier finalBuild() { + AndroidBuildClassifier buildClassifier = mock(AndroidBuildClassifier.class); + when(buildClassifier.isDebuggableBuild()).thenReturn(false); + when(buildClassifier.isFinalBuild()).thenReturn(true); + return buildClassifier; + } + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + when(mContext.getPackageManager()).thenReturn(mPackageManager); + } + + @Test + public void getOverrideAllowedState_debugBuildAnyChangeDebugApp_allowOverride() + throws Exception { + CompatConfig config = CompatConfigBuilder.create(debuggableBuild(), mContext) + .addTargetSdkChangeWithId(TARGET_SDK_BEFORE, 1) + .addTargetSdkChangeWithId(TARGET_SDK, 2) + .addTargetSdkChangeWithId(TARGET_SDK_AFTER, 3) + .addEnabledChangeWithId(4) + .addDisabledChangeWithId(5).build(); + IOverrideValidator overrideValidator = config.getOverrideValidator(); + when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) + .thenReturn(ApplicationInfoBuilder.create() + .debuggable() + .withTargetSdk(TARGET_SDK) + .withPackageName(PACKAGE_NAME).build()); + + OverrideAllowedState stateTargetSdkLessChange = + overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME); + OverrideAllowedState stateTargetSdkEqualChange = + overrideValidator.getOverrideAllowedState(2, PACKAGE_NAME); + OverrideAllowedState stateTargetSdkAfterChange = + overrideValidator.getOverrideAllowedState(3, PACKAGE_NAME); + OverrideAllowedState stateEnabledChange = + overrideValidator.getOverrideAllowedState(4, PACKAGE_NAME); + OverrideAllowedState stateDisabledChange = + overrideValidator.getOverrideAllowedState(5, PACKAGE_NAME); + + assertThat(stateTargetSdkLessChange) + .isEqualTo(new OverrideAllowedState(ALLOWED, -1, -1)); + assertThat(stateTargetSdkEqualChange) + .isEqualTo(new OverrideAllowedState(ALLOWED, -1, -1)); + assertThat(stateTargetSdkAfterChange) + .isEqualTo(new OverrideAllowedState(ALLOWED, -1, -1)); + assertThat(stateEnabledChange) + .isEqualTo(new OverrideAllowedState(ALLOWED, -1, -1)); + assertThat(stateDisabledChange) + .isEqualTo(new OverrideAllowedState(ALLOWED, -1, -1)); + } + + @Test + public void getOverrideAllowedState_debugBuildAnyChangeReleaseApp_allowOverride() + throws Exception { + CompatConfig config = CompatConfigBuilder.create(debuggableBuild(), mContext) + .addTargetSdkChangeWithId(TARGET_SDK_BEFORE, 1) + .addTargetSdkChangeWithId(TARGET_SDK, 2) + .addTargetSdkChangeWithId(TARGET_SDK_AFTER, 3) + .addEnabledChangeWithId(4) + .addDisabledChangeWithId(5).build(); + IOverrideValidator overrideValidator = config.getOverrideValidator(); + when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) + .thenReturn(ApplicationInfoBuilder.create() + .withPackageName(PACKAGE_NAME) + .withTargetSdk(TARGET_SDK).build()); + + OverrideAllowedState stateTargetSdkLessChange = + overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME); + OverrideAllowedState stateTargetSdkEqualChange = + overrideValidator.getOverrideAllowedState(2, PACKAGE_NAME); + OverrideAllowedState stateTargetSdkAfterChange = + overrideValidator.getOverrideAllowedState(3, PACKAGE_NAME); + OverrideAllowedState stateEnabledChange = + overrideValidator.getOverrideAllowedState(4, PACKAGE_NAME); + OverrideAllowedState stateDisabledChange = + overrideValidator.getOverrideAllowedState(5, PACKAGE_NAME); + + assertThat(stateTargetSdkLessChange) + .isEqualTo(new OverrideAllowedState(ALLOWED, -1, -1)); + assertThat(stateTargetSdkEqualChange) + .isEqualTo(new OverrideAllowedState(ALLOWED, -1, -1)); + assertThat(stateTargetSdkAfterChange) + .isEqualTo(new OverrideAllowedState(ALLOWED, -1, -1)); + assertThat(stateEnabledChange) + .isEqualTo(new OverrideAllowedState(ALLOWED, -1, -1)); + assertThat(stateDisabledChange) + .isEqualTo(new OverrideAllowedState(ALLOWED, -1, -1)); + } + + @Test + public void getOverrideAllowedState_betaBuildTargetSdkChangeDebugApp_allowOverride() + throws Exception { + CompatConfig config = CompatConfigBuilder.create(betaBuild(), mContext) + .addTargetSdkChangeWithId(TARGET_SDK_BEFORE, 1) + .addTargetSdkChangeWithId(TARGET_SDK, 2) + .addTargetSdkChangeWithId(TARGET_SDK_AFTER, 3).build(); + IOverrideValidator overrideValidator = config.getOverrideValidator(); + when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) + .thenReturn(ApplicationInfoBuilder.create() + .debuggable() + .withTargetSdk(TARGET_SDK) + .withPackageName(PACKAGE_NAME).build()); + + OverrideAllowedState stateTargetSdkLessChange = + overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME); + OverrideAllowedState stateTargetSdkEqualChange = + overrideValidator.getOverrideAllowedState(2, PACKAGE_NAME); + OverrideAllowedState stateTargetSdkAfterChange = + overrideValidator.getOverrideAllowedState(3, PACKAGE_NAME); + + assertThat(stateTargetSdkLessChange) + .isEqualTo(new OverrideAllowedState(ALLOWED, TARGET_SDK, TARGET_SDK_BEFORE)); + assertThat(stateTargetSdkEqualChange) + .isEqualTo(new OverrideAllowedState(ALLOWED, TARGET_SDK, TARGET_SDK)); + assertThat(stateTargetSdkAfterChange) + .isEqualTo(new OverrideAllowedState(ALLOWED, TARGET_SDK, TARGET_SDK_AFTER)); + } + + @Test + public void getOverrideAllowedState_betaBuildEnabledChangeDebugApp_rejectOverride() + throws Exception { + CompatConfig config = CompatConfigBuilder.create(betaBuild(), mContext) + .addEnabledChangeWithId(1).build(); + IOverrideValidator overrideValidator = config.getOverrideValidator(); + when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) + .thenReturn(ApplicationInfoBuilder.create() + .withPackageName(PACKAGE_NAME) + .debuggable() + .build()); + + OverrideAllowedState allowedState = + overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME); + + assertThat(allowedState) + .isEqualTo(new OverrideAllowedState(DISABLED_NON_TARGET_SDK, -1, -1)); + } + + @Test + public void getOverrideAllowedState_betaBuildDisabledChangeDebugApp_rejectOverride() + throws Exception { + CompatConfig config = CompatConfigBuilder.create(betaBuild(), mContext) + .addDisabledChangeWithId(1).build(); + IOverrideValidator overrideValidator = config.getOverrideValidator(); + when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) + .thenReturn(ApplicationInfoBuilder.create() + .debuggable() + .withPackageName(PACKAGE_NAME).build()); + + OverrideAllowedState allowedState = + overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME); + + assertThat(allowedState) + .isEqualTo(new OverrideAllowedState(DISABLED_NON_TARGET_SDK, -1, -1)); + } + + @Test + public void getOverrideAllowedState_betaBuildAnyChangeReleaseApp_rejectOverride() + throws Exception { + CompatConfig config = CompatConfigBuilder.create(betaBuild(), mContext) + .addTargetSdkChangeWithId(TARGET_SDK_BEFORE, 1) + .addTargetSdkChangeWithId(TARGET_SDK, 2) + .addTargetSdkChangeWithId(TARGET_SDK_AFTER, 3) + .addEnabledChangeWithId(4) + .addDisabledChangeWithId(5).build(); + IOverrideValidator overrideValidator = config.getOverrideValidator(); + when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) + .thenReturn(ApplicationInfoBuilder.create() + .withPackageName(PACKAGE_NAME) + .withTargetSdk(TARGET_SDK).build()); + + OverrideAllowedState stateTargetSdkLessChange = + overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME); + OverrideAllowedState stateTargetSdkEqualChange = + overrideValidator.getOverrideAllowedState(2, PACKAGE_NAME); + OverrideAllowedState stateTargetSdkAfterChange = + overrideValidator.getOverrideAllowedState(3, PACKAGE_NAME); + OverrideAllowedState stateEnabledChange = + overrideValidator.getOverrideAllowedState(4, PACKAGE_NAME); + OverrideAllowedState stateDisabledChange = + overrideValidator.getOverrideAllowedState(5, PACKAGE_NAME); + + assertThat(stateTargetSdkLessChange) + .isEqualTo(new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1)); + assertThat(stateTargetSdkEqualChange) + .isEqualTo(new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1)); + assertThat(stateTargetSdkAfterChange) + .isEqualTo(new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1)); + assertThat(stateEnabledChange) + .isEqualTo(new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1)); + assertThat(stateDisabledChange) + .isEqualTo(new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1)); + } + + @Test + public void getOverrideAllowedState_finalBuildTargetSdkChangeDebugAppOptin_allowOverride() + throws Exception { + CompatConfig config = CompatConfigBuilder.create(finalBuild(), mContext) + .addTargetSdkChangeWithId(TARGET_SDK_AFTER, 1).build(); + IOverrideValidator overrideValidator = config.getOverrideValidator(); + when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) + .thenReturn(ApplicationInfoBuilder.create() + .debuggable() + .withTargetSdk(TARGET_SDK) + .withPackageName(PACKAGE_NAME).build()); + + OverrideAllowedState allowedState = + overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME); + + assertThat(allowedState) + .isEqualTo(new OverrideAllowedState(ALLOWED, TARGET_SDK, TARGET_SDK_AFTER)); + } + + @Test + public void getOverrideAllowedState_finalBuildTargetSdkChangeDebugAppOptout_rejectOverride() + throws Exception { + CompatConfig config = CompatConfigBuilder.create(finalBuild(), mContext) + .addTargetSdkChangeWithId(TARGET_SDK_BEFORE, 1) + .addTargetSdkChangeWithId(TARGET_SDK, 2).build(); + IOverrideValidator overrideValidator = config.getOverrideValidator(); + when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) + .thenReturn(ApplicationInfoBuilder.create() + .withPackageName(PACKAGE_NAME) + .withTargetSdk(TARGET_SDK) + .debuggable() + .build()); + + OverrideAllowedState stateTargetSdkLessChange = + overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME); + OverrideAllowedState stateTargetSdkEqualChange = + overrideValidator.getOverrideAllowedState(2, PACKAGE_NAME); + + assertThat(stateTargetSdkLessChange).isEqualTo( + new OverrideAllowedState(DISABLED_TARGET_SDK_TOO_HIGH, TARGET_SDK, + TARGET_SDK_BEFORE)); + assertThat(stateTargetSdkEqualChange).isEqualTo( + new OverrideAllowedState(DISABLED_TARGET_SDK_TOO_HIGH, TARGET_SDK, TARGET_SDK)); + } + + @Test + public void getOverrideAllowedState_finalBuildEnabledChangeDebugApp_rejectOverride() + throws Exception { + CompatConfig config = CompatConfigBuilder.create(finalBuild(), mContext) + .addEnabledChangeWithId(1).build(); + IOverrideValidator overrideValidator = config.getOverrideValidator(); + when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) + .thenReturn(ApplicationInfoBuilder.create() + .withPackageName(PACKAGE_NAME) + .debuggable().build()); + + OverrideAllowedState allowedState = + overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME); + + assertThat(allowedState) + .isEqualTo(new OverrideAllowedState(DISABLED_NON_TARGET_SDK, -1, -1)); + } + + @Test + public void getOverrideAllowedState_finalBuildDisabledChangeDebugApp_rejectOverride() + throws Exception { + CompatConfig config = CompatConfigBuilder.create(finalBuild(), mContext) + .addDisabledChangeWithId(1).build(); + IOverrideValidator overrideValidator = config.getOverrideValidator(); + when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) + .thenReturn(ApplicationInfoBuilder.create() + .withPackageName(PACKAGE_NAME) + .debuggable().build()); + + OverrideAllowedState allowedState = + overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME); + + assertThat(allowedState) + .isEqualTo(new OverrideAllowedState(DISABLED_NON_TARGET_SDK, -1, -1)); + } + + @Test + public void getOverrideAllowedState_finalBuildAnyChangeReleaseApp_rejectOverride() + throws Exception { + CompatConfig config = CompatConfigBuilder.create(finalBuild(), mContext) + .addTargetSdkChangeWithId(TARGET_SDK_BEFORE, 1) + .addTargetSdkChangeWithId(TARGET_SDK, 2) + .addTargetSdkChangeWithId(TARGET_SDK_AFTER, 3) + .addEnabledChangeWithId(4) + .addDisabledChangeWithId(5).build(); + IOverrideValidator overrideValidator = config.getOverrideValidator(); + when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) + .thenReturn(ApplicationInfoBuilder.create() + .withPackageName(PACKAGE_NAME) + .withTargetSdk(TARGET_SDK).build()); + + OverrideAllowedState stateTargetSdkLessChange = + overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME); + OverrideAllowedState stateTargetSdkEqualChange = + overrideValidator.getOverrideAllowedState(2, PACKAGE_NAME); + OverrideAllowedState stateTargetSdkAfterChange = + overrideValidator.getOverrideAllowedState(3, PACKAGE_NAME); + OverrideAllowedState stateEnabledChange = + overrideValidator.getOverrideAllowedState(4, PACKAGE_NAME); + OverrideAllowedState stateDisabledChange = + overrideValidator.getOverrideAllowedState(5, PACKAGE_NAME); + + assertThat(stateTargetSdkLessChange) + .isEqualTo(new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1)); + assertThat(stateTargetSdkEqualChange) + .isEqualTo(new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1)); + assertThat(stateTargetSdkAfterChange) + .isEqualTo(new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1)); + assertThat(stateEnabledChange) + .isEqualTo(new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1)); + assertThat(stateDisabledChange) + .isEqualTo(new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1)); + } +} diff --git a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java index c406876c5cee..ce5d6d9be770 100644 --- a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java +++ b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java @@ -26,21 +26,20 @@ import static org.mockito.Mockito.when; import static org.mockito.internal.verification.VerificationModeFactory.times; import static org.testng.Assert.assertThrows; -import android.compat.Compatibility; import android.content.Context; import android.content.pm.PackageManager; -import com.android.internal.compat.CompatibilityChangeConfig; +import androidx.test.runner.AndroidJUnit4; -import com.google.common.collect.ImmutableSet; +import com.android.internal.compat.AndroidBuildClassifier; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.MockitoAnnotations; -@RunWith(MockitoJUnitRunner.class) +@RunWith(AndroidJUnit4.class) public class PlatformCompatTest { private static final String PACKAGE_NAME = "my.package"; @@ -50,84 +49,77 @@ public class PlatformCompatTest { private PackageManager mPackageManager; @Mock CompatChange.ChangeListener mListener1, mListener2; - + PlatformCompat mPlatformCompat; + CompatConfig mCompatConfig; + @Mock + private AndroidBuildClassifier mBuildClassifier; @Before public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); when(mContext.getPackageManager()).thenReturn(mPackageManager); when(mPackageManager.getPackageUid(eq(PACKAGE_NAME), eq(0))).thenThrow( new PackageManager.NameNotFoundException()); - CompatConfig.get().clearChanges(); + mCompatConfig = new CompatConfig(mBuildClassifier, mContext); + mPlatformCompat = new PlatformCompat(mContext, mCompatConfig); + // Assume userdebug/eng non-final build + when(mBuildClassifier.isDebuggableBuild()).thenReturn(true); + when(mBuildClassifier.isFinalBuild()).thenReturn(false); } @Test - public void testRegisterListenerToSameIdThrows() { - PlatformCompat pc = new PlatformCompat(mContext); - + public void testRegisterListenerToSameIdThrows() throws Exception { // Registering a listener to change 1 is successful. - pc.registerListener(1, mListener1); + mPlatformCompat.registerListener(1, mListener1); // Registering a listener to change 2 is successful. - pc.registerListener(2, mListener1); + mPlatformCompat.registerListener(2, mListener1); // Trying to register another listener to change id 1 fails. - assertThrows(IllegalStateException.class, () -> pc.registerListener(1, mListener1)); + assertThrows(IllegalStateException.class, + () -> mPlatformCompat.registerListener(1, mListener1)); } @Test - public void testRegisterListenerReturn() { - PlatformCompat pc = new PlatformCompat(mContext); - - pc.setOverrides( - new CompatibilityChangeConfig( - new Compatibility.ChangeConfig(ImmutableSet.of(1L), ImmutableSet.of())), + public void testRegisterListenerReturn() throws Exception { + mPlatformCompat.setOverrides( + CompatibilityChangeConfigBuilder.create().enable(1L).build(), PACKAGE_NAME); // Change id 1 is known (added in setOverrides). - assertThat(pc.registerListener(1, mListener1)).isTrue(); + assertThat(mPlatformCompat.registerListener(1, mListener1)).isTrue(); // Change 2 is unknown. - assertThat(pc.registerListener(2, mListener1)).isFalse(); + assertThat(mPlatformCompat.registerListener(2, mListener1)).isFalse(); } @Test - public void testListenerCalledOnSetOverrides() { - PlatformCompat pc = new PlatformCompat(mContext); + public void testListenerCalledOnSetOverrides() throws Exception { + mPlatformCompat.registerListener(1, mListener1); + mPlatformCompat.registerListener(2, mListener1); - pc.registerListener(1, mListener1); - pc.registerListener(2, mListener1); - - pc.setOverrides( - new CompatibilityChangeConfig( - new Compatibility.ChangeConfig(ImmutableSet.of(1L), ImmutableSet.of(2L))), + mPlatformCompat.setOverrides( + CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(), PACKAGE_NAME); verify(mListener1, times(2)).onCompatChange(PACKAGE_NAME); } @Test - public void testListenerNotCalledOnWrongPackage() { - PlatformCompat pc = new PlatformCompat(mContext); - - pc.registerListener(1, mListener1); - pc.registerListener(2, mListener1); + public void testListenerNotCalledOnWrongPackage() throws Exception { + mPlatformCompat.registerListener(1, mListener1); + mPlatformCompat.registerListener(2, mListener1); - pc.setOverridesForTest( - new CompatibilityChangeConfig( - new Compatibility.ChangeConfig(ImmutableSet.of(1L), ImmutableSet.of(2L))), + mPlatformCompat.setOverrides( + CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(), PACKAGE_NAME); verify(mListener1, never()).onCompatChange("other.package"); } @Test - public void testListenerCalledOnSetOverridesTwoListeners() { - PlatformCompat pc = new PlatformCompat(mContext); - pc.registerListener(1, mListener1); + public void testListenerCalledOnSetOverridesTwoListeners() throws Exception { + mPlatformCompat.registerListener(1, mListener1); - final ImmutableSet<Long> enabled = ImmutableSet.of(1L); - final ImmutableSet<Long> disabled = ImmutableSet.of(2L); - - pc.setOverrides( - new CompatibilityChangeConfig( - new Compatibility.ChangeConfig(enabled, disabled)), + mPlatformCompat.setOverrides( + CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(), PACKAGE_NAME); verify(mListener1, times(1)).onCompatChange(PACKAGE_NAME); @@ -136,11 +128,10 @@ public class PlatformCompatTest { reset(mListener1); reset(mListener2); - pc.registerListener(2, mListener2); + mPlatformCompat.registerListener(2, mListener2); - pc.setOverrides( - new CompatibilityChangeConfig( - new Compatibility.ChangeConfig(enabled, disabled)), + mPlatformCompat.setOverrides( + CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(), PACKAGE_NAME); verify(mListener1, times(1)).onCompatChange(PACKAGE_NAME); @@ -148,31 +139,23 @@ public class PlatformCompatTest { } @Test - public void testListenerCalledOnSetOverridesForTest() { - PlatformCompat pc = new PlatformCompat(mContext); - - pc.registerListener(1, mListener1); - pc.registerListener(2, mListener1); + public void testListenerCalledOnSetOverridesForTest() throws Exception { + mPlatformCompat.registerListener(1, mListener1); + mPlatformCompat.registerListener(2, mListener1); - pc.setOverridesForTest( - new CompatibilityChangeConfig( - new Compatibility.ChangeConfig(ImmutableSet.of(1L), ImmutableSet.of(2L))), + mPlatformCompat.setOverrides( + CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(), PACKAGE_NAME); verify(mListener1, times(2)).onCompatChange(PACKAGE_NAME); } @Test - public void testListenerCalledOnSetOverridesTwoListenersForTest() { - PlatformCompat pc = new PlatformCompat(mContext); - pc.registerListener(1, mListener1); + public void testListenerCalledOnSetOverridesTwoListenersForTest() throws Exception { + mPlatformCompat.registerListener(1, mListener1); - final ImmutableSet<Long> enabled = ImmutableSet.of(1L); - final ImmutableSet<Long> disabled = ImmutableSet.of(2L); - - pc.setOverridesForTest( - new CompatibilityChangeConfig( - new Compatibility.ChangeConfig(enabled, disabled)), + mPlatformCompat.setOverrides( + CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(), PACKAGE_NAME); verify(mListener1, times(1)).onCompatChange(PACKAGE_NAME); @@ -181,10 +164,10 @@ public class PlatformCompatTest { reset(mListener1); reset(mListener2); - pc.registerListener(2, mListener2); - pc.setOverridesForTest( - new CompatibilityChangeConfig( - new Compatibility.ChangeConfig(enabled, disabled)), + mPlatformCompat.registerListener(2, mListener2); + + mPlatformCompat.setOverrides( + CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(), PACKAGE_NAME); verify(mListener1, times(1)).onCompatChange(PACKAGE_NAME); @@ -192,15 +175,12 @@ public class PlatformCompatTest { } @Test - public void testListenerCalledOnClearOverrides() { - PlatformCompat pc = new PlatformCompat(mContext); + public void testListenerCalledOnClearOverrides() throws Exception { + mPlatformCompat.registerListener(1, mListener1); + mPlatformCompat.registerListener(2, mListener2); - pc.registerListener(1, mListener1); - pc.registerListener(2, mListener2); - - pc.setOverrides( - new CompatibilityChangeConfig( - new Compatibility.ChangeConfig(ImmutableSet.of(1L), ImmutableSet.of())), + mPlatformCompat.setOverrides( + CompatibilityChangeConfigBuilder.create().enable(1L).build(), PACKAGE_NAME); verify(mListener1, times(1)).onCompatChange(PACKAGE_NAME); verify(mListener2, never()).onCompatChange(PACKAGE_NAME); @@ -208,21 +188,18 @@ public class PlatformCompatTest { reset(mListener1); reset(mListener2); - pc.clearOverrides(PACKAGE_NAME); + mPlatformCompat.clearOverrides(PACKAGE_NAME); verify(mListener1, times(1)).onCompatChange(PACKAGE_NAME); verify(mListener2, never()).onCompatChange(PACKAGE_NAME); } @Test - public void testListenerCalledOnClearOverridesMultipleOverrides() { - PlatformCompat pc = new PlatformCompat(mContext); - - pc.registerListener(1, mListener1); - pc.registerListener(2, mListener2); + public void testListenerCalledOnClearOverridesMultipleOverrides() throws Exception { + mPlatformCompat.registerListener(1, mListener1); + mPlatformCompat.registerListener(2, mListener2); - pc.setOverrides( - new CompatibilityChangeConfig( - new Compatibility.ChangeConfig(ImmutableSet.of(1L), ImmutableSet.of(2L))), + mPlatformCompat.setOverrides( + CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(), PACKAGE_NAME); verify(mListener1, times(1)).onCompatChange(PACKAGE_NAME); verify(mListener2, times(1)).onCompatChange(PACKAGE_NAME); @@ -230,21 +207,18 @@ public class PlatformCompatTest { reset(mListener1); reset(mListener2); - pc.clearOverrides(PACKAGE_NAME); + mPlatformCompat.clearOverrides(PACKAGE_NAME); verify(mListener1, times(1)).onCompatChange(PACKAGE_NAME); verify(mListener2, times(1)).onCompatChange(PACKAGE_NAME); } @Test - public void testListenerCalledOnClearOverrideExists() { - PlatformCompat pc = new PlatformCompat(mContext); + public void testListenerCalledOnClearOverrideExists() throws Exception { + mPlatformCompat.registerListener(1, mListener1); + mPlatformCompat.registerListener(2, mListener2); - pc.registerListener(1, mListener1); - pc.registerListener(2, mListener2); - - pc.setOverrides( - new CompatibilityChangeConfig( - new Compatibility.ChangeConfig(ImmutableSet.of(1L), ImmutableSet.of())), + mPlatformCompat.setOverrides( + CompatibilityChangeConfigBuilder.create().enable(1L).build(), PACKAGE_NAME); verify(mListener1, times(1)).onCompatChange(PACKAGE_NAME); verify(mListener2, never()).onCompatChange(PACKAGE_NAME); @@ -252,21 +226,17 @@ public class PlatformCompatTest { reset(mListener1); reset(mListener2); - pc.clearOverride(1, PACKAGE_NAME); + mPlatformCompat.clearOverride(1, PACKAGE_NAME); verify(mListener1, times(1)).onCompatChange(PACKAGE_NAME); verify(mListener2, never()).onCompatChange(PACKAGE_NAME); } @Test - public void testListenerCalledOnClearOverrideDoesntExist() { - PlatformCompat pc = new PlatformCompat(mContext); - - pc.registerListener(1, mListener1); + public void testListenerCalledOnClearOverrideDoesntExist() throws Exception { + mPlatformCompat.registerListener(1, mListener1); - pc.clearOverride(1, PACKAGE_NAME); + mPlatformCompat.clearOverride(1, PACKAGE_NAME); // Listener not called when a non existing override is removed. verify(mListener1, never()).onCompatChange(PACKAGE_NAME); } - - } 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 ee94dd698175..175c7565a005 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -3636,6 +3636,29 @@ public class DevicePolicyManagerTest extends DpmTestBase { verify(getServices().settings).settingsGlobalPutInt(Settings.Global.AUTO_TIME_ZONE, 0); } + public void testIsOrganizationOwnedDevice() throws Exception { + setupProfileOwner(); + // Set up the user manager to return correct user info + UserInfo managedProfileUserInfo = new UserInfo(DpmMockContext.CALLER_USER_HANDLE, + "managed profile", + UserInfo.FLAG_MANAGED_PROFILE); + when(getServices().userManager.getUsers()) + .thenReturn(Arrays.asList(managedProfileUserInfo)); + + // Any caller without the MANAGE_USERS permission should get a security exception. + assertExpectException(SecurityException.class, null, () -> + dpm.isOrganizationOwnedDeviceWithManagedProfile()); + // But when the right permission is granted, this should succeed. + mContext.permissions.add(android.Manifest.permission.MANAGE_USERS); + assertFalse(dpm.isOrganizationOwnedDeviceWithManagedProfile()); + configureProfileOwnerOfOrgOwnedDevice(admin1, DpmMockContext.CALLER_USER_HANDLE); + assertTrue(dpm.isOrganizationOwnedDeviceWithManagedProfile()); + + // A random caller from another user should also be able to get the right result. + mContext.binder.callingUid = DpmMockContext.ANOTHER_UID; + assertTrue(dpm.isOrganizationOwnedDeviceWithManagedProfile()); + } + public void testSetTime() throws Exception { mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; setupDeviceOwner(); @@ -5525,6 +5548,36 @@ public class DevicePolicyManagerTest extends DpmTestBase { assertTrue(dpm.isPackageAllowedToAccessCalendar(testPackage)); } + public void testSetProtectedPackages_asDO() throws Exception { + final List<String> testPackages = new ArrayList<>(); + testPackages.add("package_1"); + testPackages.add("package_2"); + + mContext.callerPermissions.add(android.Manifest.permission.MANAGE_DEVICE_ADMINS); + setDeviceOwner(); + + dpm.setProtectedPackages(admin1, testPackages); + + verify(getServices().packageManagerInternal).setDeviceOwnerProtectedPackages(testPackages); + + assertEquals(testPackages, dpm.getProtectedPackages(admin1)); + } + + public void testSetProtectedPackages_failingAsPO() throws Exception { + final List<String> testPackages = new ArrayList<>(); + testPackages.add("package_1"); + testPackages.add("package_2"); + + mContext.callerPermissions.add(android.Manifest.permission.MANAGE_DEVICE_ADMINS); + setAsProfileOwner(admin1); + + assertExpectException(SecurityException.class, /* messageRegex= */ null, + () -> dpm.setProtectedPackages(admin1, testPackages)); + + assertExpectException(SecurityException.class, /* messageRegex= */ null, + () -> dpm.getProtectedPackages(admin1)); + } + private void configureProfileOwnerOfOrgOwnedDevice(ComponentName who, int userId) { when(getServices().userManager.getProfileParent(eq(UserHandle.of(userId)))) .thenReturn(UserHandle.SYSTEM); diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java index ebca240819e8..25d077823a3f 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java @@ -78,7 +78,7 @@ public class DisplayModeDirectorTest { int displayId = 0; // With no votes present, DisplayModeDirector should allow any refresh rate. - assertEquals(new DisplayModeDirector.DesiredDisplayModeSpecs(/*defaultModeId=*/60, + assertEquals(new DisplayModeDirector.DesiredDisplayModeSpecs(/*baseModeId=*/60, new DisplayModeDirector.RefreshRateRange(0f, Float.POSITIVE_INFINITY)), createDisplayModeDirectorWithDisplayFpsRange(60, 90).getDesiredDisplayModeSpecs( displayId)); @@ -105,7 +105,7 @@ public class DisplayModeDirectorTest { director.injectVotesByDisplay(votesByDisplay); assertEquals( new DisplayModeDirector.DesiredDisplayModeSpecs( - /*defaultModeId=*/minFps + i, + /*baseModeId=*/minFps + i, new DisplayModeDirector.RefreshRateRange(minFps + i, maxFps - i)), director.getDesiredDisplayModeSpecs(displayId)); } @@ -126,7 +126,7 @@ public class DisplayModeDirectorTest { votes.put(DisplayModeDirector.Vote.MIN_PRIORITY, DisplayModeDirector.Vote.forRefreshRates(70, 80)); director.injectVotesByDisplay(votesByDisplay); - assertEquals(new DisplayModeDirector.DesiredDisplayModeSpecs(/*defaultModeId=*/70, + assertEquals(new DisplayModeDirector.DesiredDisplayModeSpecs(/*baseModeId=*/70, new DisplayModeDirector.RefreshRateRange(70, 80)), director.getDesiredDisplayModeSpecs(displayId)); } diff --git a/services/tests/servicestests/src/com/android/server/integrity/model/BitTrackedInputStreamTest.java b/services/tests/servicestests/src/com/android/server/integrity/model/BitTrackedInputStreamTest.java new file mode 100644 index 000000000000..1eb5eb51504a --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/integrity/model/BitTrackedInputStreamTest.java @@ -0,0 +1,142 @@ +/* + * 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.integrity.model; + +import static com.android.server.integrity.model.ComponentBitSize.ATOMIC_FORMULA_START; +import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_END; +import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_START; +import static com.android.server.integrity.model.ComponentBitSize.CONNECTOR_BITS; +import static com.android.server.integrity.model.ComponentBitSize.EFFECT_BITS; +import static com.android.server.integrity.model.ComponentBitSize.KEY_BITS; +import static com.android.server.integrity.model.ComponentBitSize.OPERATOR_BITS; +import static com.android.server.integrity.model.ComponentBitSize.SEPARATOR_BITS; +import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS; +import static com.android.server.integrity.utils.TestUtils.getBits; +import static com.android.server.integrity.utils.TestUtils.getBytes; +import static com.android.server.integrity.utils.TestUtils.getValueBits; + +import static com.google.common.truth.Truth.assertThat; + +import static org.testng.Assert.assertThrows; + +import android.content.integrity.AtomicFormula; +import android.content.integrity.CompoundFormula; +import android.content.integrity.Rule; + +import com.android.server.integrity.parser.BinaryFileOperations; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.io.IOException; + +@RunWith(JUnit4.class) +public class BitTrackedInputStreamTest { + private static final String COMPOUND_FORMULA_START_BITS = + getBits(COMPOUND_FORMULA_START, SEPARATOR_BITS); + private static final String COMPOUND_FORMULA_END_BITS = + getBits(COMPOUND_FORMULA_END, SEPARATOR_BITS); + private static final String ATOMIC_FORMULA_START_BITS = + getBits(ATOMIC_FORMULA_START, SEPARATOR_BITS); + private static final String NOT = getBits(CompoundFormula.NOT, CONNECTOR_BITS); + private static final String PACKAGE_NAME = getBits(AtomicFormula.PACKAGE_NAME, KEY_BITS); + private static final String EQ = getBits(AtomicFormula.EQ, OPERATOR_BITS); + private static final String DENY = getBits(Rule.DENY, EFFECT_BITS); + + private static final String IS_NOT_HASHED = "0"; + private static final String START_BIT = "1"; + private static final String END_BIT = "1"; + + @Test + public void testBitOperationsCountBitsCorrectly() throws IOException { + String packageName = "com.test.app"; + byte[] testInput = + getBytes( + START_BIT + + COMPOUND_FORMULA_START_BITS + + NOT + + ATOMIC_FORMULA_START_BITS + + PACKAGE_NAME + + EQ + + IS_NOT_HASHED + + getBits(packageName.length(), VALUE_SIZE_BITS) + + getValueBits(packageName) + + COMPOUND_FORMULA_END_BITS + + DENY + + END_BIT); + + BitTrackedInputStream bitTrackedInputStream = new BitTrackedInputStream(testInput); + + // Right after construction, the read bits count should be 0. + assertThat(bitTrackedInputStream.getReadBitsCount()).isEqualTo(0); + + // Get next 10 bits should result with 10 bits read. + bitTrackedInputStream.getNext(10); + assertThat(bitTrackedInputStream.getReadBitsCount()).isEqualTo(10); + + // When we move the cursor 8 bytes, we should point to 64 bits. + bitTrackedInputStream.setCursorToByteLocation(8); + assertThat(bitTrackedInputStream.getReadBitsCount()).isEqualTo(64); + + // Read until the end and the total size of the input stream should be available. + while (bitTrackedInputStream.hasNext()) { + bitTrackedInputStream.getNext(1); + } + assertThat(bitTrackedInputStream.getReadBitsCount()).isEqualTo(128); + } + + @Test + public void testBitInputStreamOperationsStillWork() throws IOException { + String packageName = "com.test.app"; + byte[] testInput = + getBytes( + IS_NOT_HASHED + + getBits(packageName.length(), VALUE_SIZE_BITS) + + getValueBits(packageName)); + + BitTrackedInputStream bitTrackedInputStream = new BitTrackedInputStream(testInput); + assertThat(bitTrackedInputStream.getReadBitsCount()).isEqualTo(0); + + // Read until the string parameter. + String stringValue = BinaryFileOperations.getStringValue(bitTrackedInputStream); + + // Verify that the read bytes are counted. + assertThat(stringValue).isEqualTo(packageName); + assertThat(bitTrackedInputStream.getReadBitsCount()).isGreaterThan(0); + } + + @Test + public void testBitTrackedInputStream_moveCursorForwardFailsIfAlreadyRead() throws IOException { + String packageName = "com.test.app"; + byte[] testInput = + getBytes( + IS_NOT_HASHED + + getBits(packageName.length(), VALUE_SIZE_BITS) + + getValueBits(packageName)); + + BitTrackedInputStream bitTrackedInputStream = new BitTrackedInputStream(testInput); + + // Read more than two bytes. + bitTrackedInputStream.getNext(20); + + // Ask to move the cursor to the second byte. + assertThrows( + IllegalStateException.class, + () -> bitTrackedInputStream.setCursorToByteLocation(2)); + } +} diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/ByteTrackedOutputStreamTest.java b/services/tests/servicestests/src/com/android/server/integrity/model/ByteTrackedOutputStreamTest.java index 5ecb8b5c8169..c7cc343dd77e 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/serializer/ByteTrackedOutputStreamTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/model/ByteTrackedOutputStreamTest.java @@ -14,12 +14,10 @@ * limitations under the License. */ -package com.android.server.integrity.serializer; +package com.android.server.integrity.model; import static com.google.common.truth.Truth.assertThat; -import com.android.server.integrity.model.BitOutputStream; - import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/BinaryFileOperationsTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/BinaryFileOperationsTest.java new file mode 100644 index 000000000000..94f68a59072e --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/integrity/parser/BinaryFileOperationsTest.java @@ -0,0 +1,127 @@ +/* + * 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.integrity.parser; + +import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS; +import static com.android.server.integrity.parser.BinaryFileOperations.getBooleanValue; +import static com.android.server.integrity.parser.BinaryFileOperations.getIntValue; +import static com.android.server.integrity.parser.BinaryFileOperations.getStringValue; +import static com.android.server.integrity.utils.TestUtils.getBits; +import static com.android.server.integrity.utils.TestUtils.getBytes; +import static com.android.server.integrity.utils.TestUtils.getValueBits; + +import static com.google.common.truth.Truth.assertThat; + +import com.android.server.integrity.IntegrityUtils; +import com.android.server.integrity.model.BitInputStream; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; + +@RunWith(JUnit4.class) +public class BinaryFileOperationsTest { + + private static final String IS_NOT_HASHED = "0"; + private static final String IS_HASHED = "1"; + private static final String PACKAGE_NAME = "com.test.app"; + private static final String APP_CERTIFICATE = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; + + @Test + public void testGetStringValue() throws IOException { + byte[] stringBytes = + getBytes( + IS_NOT_HASHED + + getBits(PACKAGE_NAME.length(), VALUE_SIZE_BITS) + + getValueBits(PACKAGE_NAME)); + ByteBuffer rule = ByteBuffer.allocate(stringBytes.length); + rule.put(stringBytes); + BitInputStream inputStream = new BitInputStream(rule.array()); + + String resultString = getStringValue(inputStream); + + assertThat(resultString).isEqualTo(PACKAGE_NAME); + } + + @Test + public void testGetHashedStringValue() throws IOException { + byte[] ruleBytes = + getBytes( + IS_HASHED + + getBits(APP_CERTIFICATE.length(), VALUE_SIZE_BITS) + + getValueBits(APP_CERTIFICATE)); + ByteBuffer rule = ByteBuffer.allocate(ruleBytes.length); + rule.put(ruleBytes); + BitInputStream inputStream = new BitInputStream(rule.array()); + + String resultString = getStringValue(inputStream); + + assertThat(resultString) + .isEqualTo(IntegrityUtils.getHexDigest( + APP_CERTIFICATE.getBytes(StandardCharsets.UTF_8))); + } + + @Test + public void testGetStringValue_withSizeAndHashingInfo() throws IOException { + byte[] ruleBytes = getBytes(getValueBits(PACKAGE_NAME)); + ByteBuffer rule = ByteBuffer.allocate(ruleBytes.length); + rule.put(ruleBytes); + BitInputStream inputStream = new BitInputStream(rule.array()); + + String resultString = getStringValue(inputStream, + PACKAGE_NAME.length(), /* isHashedValue= */false); + + assertThat(resultString).isEqualTo(PACKAGE_NAME); + } + + @Test + public void testGetIntValue() throws IOException { + int randomValue = 15; + byte[] ruleBytes = getBytes(getBits(randomValue, /* numOfBits= */ 32)); + ByteBuffer rule = ByteBuffer.allocate(ruleBytes.length); + rule.put(ruleBytes); + BitInputStream inputStream = new BitInputStream(rule.array()); + + assertThat(getIntValue(inputStream)).isEqualTo(randomValue); + } + + @Test + public void testGetBooleanValue_true() throws IOException { + String booleanValue = "1"; + byte[] ruleBytes = getBytes(booleanValue); + ByteBuffer rule = ByteBuffer.allocate(ruleBytes.length); + rule.put(ruleBytes); + BitInputStream inputStream = new BitInputStream(rule.array()); + + assertThat(getBooleanValue(inputStream)).isEqualTo(true); + } + + @Test + public void testGetBooleanValue_false() throws IOException { + String booleanValue = "0"; + byte[] ruleBytes = getBytes(booleanValue); + ByteBuffer rule = ByteBuffer.allocate(ruleBytes.length); + rule.put(ruleBytes); + BitInputStream inputStream = new BitInputStream(rule.array()); + + assertThat(getBooleanValue(inputStream)).isEqualTo(false); + } +} diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java index 9cc0ed85a044..51f5c755754c 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java @@ -48,6 +48,7 @@ import java.io.ByteArrayInputStream; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -97,8 +98,10 @@ public class RuleBinaryParserTest { private static final byte[] DEFAULT_FORMAT_VERSION_BYTES = getBytes(getBits(DEFAULT_FORMAT_VERSION, FORMAT_VERSION_BITS)); + private static final List<RuleIndexRange> NO_INDEXING = Collections.emptyList(); + @Test - public void testBinaryStream_validCompoundFormula() throws Exception { + public void testBinaryStream_validCompoundFormula_noIndexing() throws Exception { String packageName = "com.test.app"; String ruleBits = START_BIT @@ -131,13 +134,13 @@ public class RuleBinaryParserTest { /* isHashedValue= */ false))), Rule.DENY); - List<Rule> rules = binaryParser.parse(inputStream); + List<Rule> rules = binaryParser.parse(inputStream, NO_INDEXING); assertThat(rules).isEqualTo(Collections.singletonList(expectedRule)); } @Test - public void testBinaryString_validCompoundFormula_notConnector() throws Exception { + public void testBinaryString_validCompoundFormula_notConnector_noIndexing() throws Exception { String packageName = "com.test.app"; String ruleBits = START_BIT @@ -175,7 +178,7 @@ public class RuleBinaryParserTest { } @Test - public void testBinaryString_validCompoundFormula_andConnector() throws Exception { + public void testBinaryString_validCompoundFormula_andConnector_noIndexing() throws Exception { String packageName = "com.test.app"; String appCertificate = "test_cert"; String ruleBits = @@ -223,7 +226,7 @@ public class RuleBinaryParserTest { } @Test - public void testBinaryString_validCompoundFormula_orConnector() throws Exception { + public void testBinaryString_validCompoundFormula_orConnector_noIndexing() throws Exception { String packageName = "com.test.app"; String appCertificate = "test_cert"; String ruleBits = @@ -272,7 +275,7 @@ public class RuleBinaryParserTest { } @Test - public void testBinaryString_validAtomicFormula_stringValue() throws Exception { + public void testBinaryString_validAtomicFormula_stringValue_noIndexing() throws Exception { String packageName = "com.test.app"; String ruleBits = START_BIT @@ -304,7 +307,7 @@ public class RuleBinaryParserTest { } @Test - public void testBinaryString_validAtomicFormula_hashedValue() throws Exception { + public void testBinaryString_validAtomicFormula_hashedValue_noIndexing() throws Exception { String appCertificate = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; String ruleBits = START_BIT @@ -337,7 +340,7 @@ public class RuleBinaryParserTest { } @Test - public void testBinaryString_validAtomicFormula_integerValue() throws Exception { + public void testBinaryString_validAtomicFormula_integerValue_noIndexing() throws Exception { int versionCode = 1; String ruleBits = START_BIT @@ -365,7 +368,7 @@ public class RuleBinaryParserTest { } @Test - public void testBinaryString_validAtomicFormula_booleanValue() throws Exception { + public void testBinaryString_validAtomicFormula_booleanValue_noIndexing() throws Exception { String isPreInstalled = "1"; String ruleBits = START_BIT @@ -392,7 +395,7 @@ public class RuleBinaryParserTest { } @Test - public void testBinaryString_invalidAtomicFormula() throws Exception { + public void testBinaryString_invalidAtomicFormula_noIndexing() { int versionCode = 1; String ruleBits = START_BIT @@ -415,7 +418,7 @@ public class RuleBinaryParserTest { } @Test - public void testBinaryString_withNoRuleList() throws RuleParseException { + public void testBinaryString_withNoRuleList_noIndexing() throws RuleParseException { ByteBuffer rule = ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length); rule.put(DEFAULT_FORMAT_VERSION_BYTES); RuleParser binaryParser = new RuleBinaryParser(); @@ -426,7 +429,7 @@ public class RuleBinaryParserTest { } @Test - public void testBinaryString_withEmptyRule() throws RuleParseException { + public void testBinaryString_withEmptyRule_noIndexing() { String ruleBits = START_BIT; byte[] ruleBytes = getBytes(ruleBits); ByteBuffer rule = @@ -442,7 +445,7 @@ public class RuleBinaryParserTest { } @Test - public void testBinaryString_invalidCompoundFormula_invalidNumberOfFormulas() throws Exception { + public void testBinaryString_invalidCompoundFormula_invalidNumberOfFormulas_noIndexing() { String packageName = "com.test.app"; String appCertificate = "test_cert"; String ruleBits = @@ -478,7 +481,7 @@ public class RuleBinaryParserTest { } @Test - public void testBinaryString_invalidRule_invalidOperator() throws Exception { + public void testBinaryString_invalidRule_invalidOperator_noIndexing() { int versionCode = 1; String ruleBits = START_BIT @@ -506,7 +509,7 @@ public class RuleBinaryParserTest { } @Test - public void testBinaryString_invalidRule_invalidEffect() throws Exception { + public void testBinaryString_invalidRule_invalidEffect_noIndexing() { String packageName = "com.test.app"; String ruleBits = START_BIT @@ -536,7 +539,7 @@ public class RuleBinaryParserTest { } @Test - public void testBinaryString_invalidRule_invalidConnector() throws Exception { + public void testBinaryString_invalidRule_invalidConnector_noIndexing() { String packageName = "com.test.app"; String ruleBits = START_BIT @@ -566,7 +569,7 @@ public class RuleBinaryParserTest { } @Test - public void testBinaryString_invalidRule_invalidKey() throws Exception { + public void testBinaryString_invalidRule_invalidKey_noIndexing() { String packageName = "com.test.app"; String ruleBits = START_BIT @@ -596,7 +599,7 @@ public class RuleBinaryParserTest { } @Test - public void testBinaryString_invalidRule_invalidSeparator() throws Exception { + public void testBinaryString_invalidRule_invalidSeparator_noIndexing() { String packageName = "com.test.app"; String ruleBits = START_BIT @@ -626,7 +629,7 @@ public class RuleBinaryParserTest { } @Test - public void testBinaryString_invalidRule_invalidEndMarker() throws Exception { + public void testBinaryString_invalidRule_invalidEndMarker_noIndexing() { String packageName = "com.test.app"; String ruleBits = START_BIT @@ -653,4 +656,65 @@ public class RuleBinaryParserTest { /* expectedExceptionMessageRegex */ "A rule must end with a '1' bit", () -> binaryParser.parse(rule.array())); } + + @Test + public void testBinaryStream_multipleRules_indexingIdentifiesParsesIndexRangeCorrectly() + throws Exception { + String packageName2 = "com.test.2"; + + byte[] ruleBytes1 = getBytes(getRulesWithPackageName("com.test.1")); + byte[] ruleBytes2 = getBytes(getRulesWithPackageName(packageName2)); + byte[] ruleBytes3 = getBytes(getRulesWithPackageName("com.test.3")); + + ByteBuffer rule = + ByteBuffer.allocate( + DEFAULT_FORMAT_VERSION_BYTES.length + + ruleBytes1.length + + ruleBytes2.length + + ruleBytes3.length); + rule.put(DEFAULT_FORMAT_VERSION_BYTES); + rule.put(ruleBytes1); + rule.put(ruleBytes2); + rule.put(ruleBytes3); + InputStream inputStream = new ByteArrayInputStream(rule.array()); + + RuleParser binaryParser = new RuleBinaryParser(); + + List<RuleIndexRange> indexRanges = new ArrayList<>(); + indexRanges.add( + new RuleIndexRange( + DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes1.length, + DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes1.length + + ruleBytes2.length)); + List<Rule> rules = binaryParser.parse(inputStream, indexRanges); + + Rule expectedRule = + new Rule( + new CompoundFormula( + CompoundFormula.NOT, + Collections.singletonList( + new AtomicFormula.StringAtomicFormula( + AtomicFormula.PACKAGE_NAME, + packageName2, + /* isHashedValue= */ false))), + Rule.DENY); + + assertThat(rules).isEqualTo(Collections.singletonList(expectedRule)); + } + + private static String getRulesWithPackageName(String packageName) { + return START_BIT + + COMPOUND_FORMULA_START_BITS + + NOT + + ATOMIC_FORMULA_START_BITS + + PACKAGE_NAME + + EQ + + IS_NOT_HASHED + + getBits(packageName.length(), VALUE_SIZE_BITS) + + getValueBits(packageName) + + COMPOUND_FORMULA_END_BITS + + DENY + + END_BIT; + + } } diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java new file mode 100644 index 000000000000..742952e056bc --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java @@ -0,0 +1,168 @@ +/* + * 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.integrity.parser; + +import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS; +import static com.android.server.integrity.model.IndexingFileConstants.END_INDEXING_KEY; +import static com.android.server.integrity.model.IndexingFileConstants.START_INDEXING_KEY; +import static com.android.server.integrity.utils.TestUtils.getBits; +import static com.android.server.integrity.utils.TestUtils.getBytes; +import static com.android.server.integrity.utils.TestUtils.getValueBits; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.integrity.AppInstallMetadata; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.util.List; + +@RunWith(JUnit4.class) +public class RuleIndexingControllerTest { + + @Test + public void verifyIndexRangeSearchIsCorrect() throws IOException { + InputStream inputStream = obtainDefaultIndexingMapForTest(); + + RuleIndexingController indexingController = new RuleIndexingController(inputStream); + + AppInstallMetadata appInstallMetadata = + new AppInstallMetadata.Builder() + .setPackageName("ddd") + .setAppCertificate("777") + .build(); + + List<RuleIndexRange> resultingIndexes = + indexingController.identifyRulesToEvaluate(appInstallMetadata); + + assertThat(resultingIndexes) + .containsExactly( + new RuleIndexRange(200, 300), + new RuleIndexRange(700, 800), + new RuleIndexRange(900, 945)); + } + + @Test + public void verifyIndexRangeSearchIsCorrect_keysInFirstAndLastBlock() throws IOException { + InputStream inputStream = obtainDefaultIndexingMapForTest(); + + RuleIndexingController indexingController = new RuleIndexingController(inputStream); + + AppInstallMetadata appInstallMetadata = + new AppInstallMetadata.Builder() + .setPackageName("bbb") + .setAppCertificate("999") + .build(); + + List<RuleIndexRange> resultingIndexes = + indexingController.identifyRulesToEvaluate(appInstallMetadata); + + assertThat(resultingIndexes) + .containsExactly( + new RuleIndexRange(100, 200), + new RuleIndexRange(800, 900), + new RuleIndexRange(900, 945)); + } + + @Test + public void verifyIndexRangeSearchIsCorrect_keysMatchWithValues() throws IOException { + InputStream inputStream = obtainDefaultIndexingMapForTest(); + + RuleIndexingController indexingController = new RuleIndexingController(inputStream); + + AppInstallMetadata appInstallMetadata = + new AppInstallMetadata.Builder() + .setPackageName("ccc") + .setAppCertificate("444") + .build(); + + List<RuleIndexRange> resultingIndexes = + indexingController.identifyRulesToEvaluate(appInstallMetadata); + + assertThat(resultingIndexes) + .containsExactly( + new RuleIndexRange(200, 300), + new RuleIndexRange(700, 800), + new RuleIndexRange(900, 945)); + } + + @Test + public void verifyIndexRangeSearchIsCorrect_noIndexesAvailable() throws IOException { + byte[] stringBytes = + getBytes( + getKeyValueString(START_INDEXING_KEY, 100) + + getKeyValueString(END_INDEXING_KEY, 500) + + getKeyValueString(START_INDEXING_KEY, 500) + + getKeyValueString(END_INDEXING_KEY, 900) + + getKeyValueString(START_INDEXING_KEY, 900) + + getKeyValueString(END_INDEXING_KEY, 945)); + ByteBuffer rule = ByteBuffer.allocate(stringBytes.length); + rule.put(stringBytes); + InputStream inputStream = new ByteArrayInputStream(rule.array()); + + RuleIndexingController indexingController = new RuleIndexingController(inputStream); + + AppInstallMetadata appInstallMetadata = + new AppInstallMetadata.Builder() + .setPackageName("ccc") + .setAppCertificate("444") + .build(); + + List<RuleIndexRange> resultingIndexes = + indexingController.identifyRulesToEvaluate(appInstallMetadata); + + assertThat(resultingIndexes) + .containsExactly( + new RuleIndexRange(100, 500), + new RuleIndexRange(500, 900), + new RuleIndexRange(900, 945)); + } + + private static InputStream obtainDefaultIndexingMapForTest() { + byte[] stringBytes = + getBytes( + getKeyValueString(START_INDEXING_KEY, 100) + + getKeyValueString("ccc", 200) + + getKeyValueString("eee", 300) + + getKeyValueString("hhh", 400) + + getKeyValueString(END_INDEXING_KEY, 500) + + getKeyValueString(START_INDEXING_KEY, 500) + + getKeyValueString("111", 600) + + getKeyValueString("444", 700) + + getKeyValueString("888", 800) + + getKeyValueString(END_INDEXING_KEY, 900) + + getKeyValueString(START_INDEXING_KEY, 900) + + getKeyValueString(END_INDEXING_KEY, 945)); + ByteBuffer rule = ByteBuffer.allocate(stringBytes.length); + rule.put(stringBytes); + return new ByteArrayInputStream(rule.array()); + } + + private static String getKeyValueString(String key, int value) { + String isNotHashed = "0"; + return isNotHashed + + getBits(key.length(), VALUE_SIZE_BITS) + + getValueBits(key) + + getBits(value, /* numOfBits= */ 32); + } +} diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleXmlParserTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleXmlParserTest.java index a14197b17529..6944aee7fcb9 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleXmlParserTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleXmlParserTest.java @@ -73,7 +73,7 @@ public class RuleXmlParserTest { /* isHashedValue= */ false))), Rule.DENY); - List<Rule> rules = xmlParser.parse(inputStream); + List<Rule> rules = xmlParser.parse(inputStream, Collections.emptyList()); assertThat(rules).isEqualTo(Collections.singletonList(expectedRule)); } @@ -623,7 +623,7 @@ public class RuleXmlParserTest { assertExpectException( RuleParseException.class, /* expectedExceptionMessageRegex */ "Rules must start with RuleList <RL> tag", - () -> xmlParser.parse(inputStream)); + () -> xmlParser.parse(inputStream, Collections.emptyList())); } private String generateTagWithAttribute( diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java index 97aa3102e2d0..eb6698b0d479 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java @@ -27,9 +27,9 @@ import static com.android.server.integrity.model.ComponentBitSize.KEY_BITS; import static com.android.server.integrity.model.ComponentBitSize.OPERATOR_BITS; import static com.android.server.integrity.model.ComponentBitSize.SEPARATOR_BITS; import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS; -import static com.android.server.integrity.serializer.RuleBinarySerializer.END_INDEXING_KEY; -import static com.android.server.integrity.serializer.RuleBinarySerializer.INDEXING_BLOCK_SIZE; -import static com.android.server.integrity.serializer.RuleBinarySerializer.START_INDEXING_KEY; +import static com.android.server.integrity.model.IndexingFileConstants.END_INDEXING_KEY; +import static com.android.server.integrity.model.IndexingFileConstants.INDEXING_BLOCK_SIZE; +import static com.android.server.integrity.model.IndexingFileConstants.START_INDEXING_KEY; import static com.android.server.integrity.utils.TestUtils.getBits; import static com.android.server.integrity.utils.TestUtils.getBytes; import static com.android.server.integrity.utils.TestUtils.getValueBits; diff --git a/services/tests/servicestests/src/com/android/server/location/CustomCountryDetectorTestClass.java b/services/tests/servicestests/src/com/android/server/location/CustomCountryDetectorTestClass.java new file mode 100644 index 000000000000..e159012f1b54 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/location/CustomCountryDetectorTestClass.java @@ -0,0 +1,36 @@ +/* + * 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.location; + +import android.content.Context; +import android.location.Country; + +public class CustomCountryDetectorTestClass extends CountryDetectorBase { + public CustomCountryDetectorTestClass(Context ctx) { + super(ctx); + } + + @Override + public Country detectCountry() { + return null; + } + + @Override + public void stop() { + + } +} diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java new file mode 100644 index 000000000000..54c552b06c23 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java @@ -0,0 +1,76 @@ +/* + * 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.locksettings; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import javax.crypto.spec.SecretKeySpec; + +/** + * atest FrameworksServicesTests:RebootEscrowDataTest + */ +@RunWith(AndroidJUnit4.class) +public class RebootEscrowDataTest { + private static byte[] getTestSp() { + byte[] testSp = new byte[10]; + for (int i = 0; i < testSp.length; i++) { + testSp[i] = (byte) i; + } + return testSp; + } + + @Test(expected = NullPointerException.class) + public void fromEntries_failsOnNull() throws Exception { + RebootEscrowData.fromSyntheticPassword((byte) 2, null); + } + + @Test(expected = NullPointerException.class) + public void fromEncryptedData_failsOnNullData() throws Exception { + byte[] testSp = getTestSp(); + RebootEscrowData expected = RebootEscrowData.fromSyntheticPassword((byte) 2, testSp); + SecretKeySpec key = RebootEscrowData.fromKeyBytes(expected.getKey()); + RebootEscrowData.fromEncryptedData(key, null); + } + + @Test(expected = NullPointerException.class) + public void fromEncryptedData_failsOnNullKey() throws Exception { + byte[] testSp = getTestSp(); + RebootEscrowData expected = RebootEscrowData.fromSyntheticPassword((byte) 2, testSp); + RebootEscrowData.fromEncryptedData(null, expected.getBlob()); + } + + @Test + public void fromEntries_loopback_success() throws Exception { + byte[] testSp = getTestSp(); + RebootEscrowData expected = RebootEscrowData.fromSyntheticPassword((byte) 2, testSp); + + SecretKeySpec key = RebootEscrowData.fromKeyBytes(expected.getKey()); + RebootEscrowData actual = RebootEscrowData.fromEncryptedData(key, expected.getBlob()); + + assertThat(actual.getSpVersion(), is(expected.getSpVersion())); + assertThat(actual.getIv(), is(expected.getIv())); + assertThat(actual.getKey(), is(expected.getKey())); + assertThat(actual.getBlob(), is(expected.getBlob())); + assertThat(actual.getSyntheticPassword(), is(expected.getSyntheticPassword())); + } +} diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java new file mode 100644 index 000000000000..78a5a0b69fff --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java @@ -0,0 +1,171 @@ +/* + * 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.locksettings; + +import static android.content.pm.UserInfo.FLAG_FULL; +import static android.content.pm.UserInfo.FLAG_PRIMARY; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.pm.UserInfo; +import android.hardware.rebootescrow.IRebootEscrow; +import android.os.RemoteException; +import android.os.UserManager; +import android.platform.test.annotations.Presubmit; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.widget.RebootEscrowListener; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; +import java.util.ArrayList; + +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class RebootEscrowManagerTests { + protected static final int PRIMARY_USER_ID = 0; + protected static final int NONSECURE_USER_ID = 10; + private static final byte FAKE_SP_VERSION = 1; + private static final byte[] FAKE_AUTH_TOKEN = new byte[] { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + }; + + private Context mContext; + private UserManager mUserManager; + private RebootEscrowManager.Callbacks mCallbacks; + private IRebootEscrow mRebootEscrow; + + LockSettingsStorageTestable mStorage; + + private RebootEscrowManager mService; + + static class MockInjector extends RebootEscrowManager.Injector { + private final IRebootEscrow mRebootEscrow; + private final UserManager mUserManager; + + MockInjector(Context context, UserManager userManager, IRebootEscrow rebootEscrow) { + super(context); + mRebootEscrow = rebootEscrow; + mUserManager = userManager; + } + + @Override + public UserManager getUserManager() { + return mUserManager; + } + + @Override + public IRebootEscrow getRebootEscrow() { + return mRebootEscrow; + } + } + + @Before + public void setUp_baseServices() throws Exception { + mContext = mock(Context.class); + mUserManager = mock(UserManager.class); + mCallbacks = mock(RebootEscrowManager.Callbacks.class); + mRebootEscrow = mock(IRebootEscrow.class); + + mStorage = new LockSettingsStorageTestable(mContext, + new File(InstrumentationRegistry.getContext().getFilesDir(), "locksettings")); + + ArrayList<UserInfo> users = new ArrayList<>(); + users.add(new UserInfo(PRIMARY_USER_ID, "primary", FLAG_PRIMARY)); + users.add(new UserInfo(NONSECURE_USER_ID, "non-secure", FLAG_FULL)); + when(mUserManager.getUsers()).thenReturn(users); + when(mCallbacks.isUserSecure(PRIMARY_USER_ID)).thenReturn(true); + when(mCallbacks.isUserSecure(NONSECURE_USER_ID)).thenReturn(false); + mService = new RebootEscrowManager(new MockInjector(mContext, mUserManager, mRebootEscrow), + mCallbacks, mStorage); + } + + @Test + public void prepareRebootEscrow_Success() throws Exception { + RebootEscrowListener mockListener = mock(RebootEscrowListener.class); + mService.setRebootEscrowListener(mockListener); + mService.prepareRebootEscrow(); + + clearInvocations(mRebootEscrow); + mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN); + verify(mockListener).onPreparedForReboot(eq(true)); + verify(mRebootEscrow, never()).storeKey(any()); + } + + @Test + public void prepareRebootEscrow_ClearCredentials_Success() throws Exception { + RebootEscrowListener mockListener = mock(RebootEscrowListener.class); + mService.setRebootEscrowListener(mockListener); + mService.prepareRebootEscrow(); + mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN); + verify(mockListener).onPreparedForReboot(eq(true)); + + clearInvocations(mRebootEscrow); + mService.clearRebootEscrow(); + verify(mockListener).onPreparedForReboot(eq(false)); + verify(mRebootEscrow).storeKey(eq(new byte[32])); + } + + @Test + public void armService_Success() throws Exception { + RebootEscrowListener mockListener = mock(RebootEscrowListener.class); + mService.setRebootEscrowListener(mockListener); + mService.prepareRebootEscrow(); + + clearInvocations(mRebootEscrow); + mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN); + verify(mockListener).onPreparedForReboot(eq(true)); + verify(mRebootEscrow, never()).storeKey(any()); + + assertTrue(mService.armRebootEscrowIfNeeded()); + verify(mRebootEscrow).storeKey(any()); + } + + @Test + public void armService_NoInitialization_Failure() throws Exception { + assertFalse(mService.armRebootEscrowIfNeeded()); + verifyNoMoreInteractions(mRebootEscrow); + } + + @Test + public void armService_RebootEscrowServiceException_Failure() throws Exception { + doThrow(RemoteException.class).when(mRebootEscrow).storeKey(any()); + assertFalse(mService.armRebootEscrowIfNeeded()); + verifyNoMoreInteractions(mRebootEscrow); + } +} diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java index 821d97acc230..670bd8107bed 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java @@ -67,13 +67,25 @@ import javax.crypto.KeyGenerator; public class PlatformKeyManagerTest { private static final String DATABASE_FILE_NAME = "recoverablekeystore.db"; - private static final int USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS = 15; + private static final int MIN_GENERATION_ID = 1000000; + private static final int PRIMARY_USER_ID_FIXTURE = 0; private static final int USER_ID_FIXTURE = 42; private static final long USER_SID = 4200L; private static final String KEY_ALGORITHM = "AES"; private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore"; private static final String TESTING_KEYSTORE_KEY_ALIAS = "testing-key-store-key-alias"; + private static final String ENCRYPTION_KEY_ALIAS_1 = + "com.android.server.locksettings.recoverablekeystore/platform/42/1000000/encrypt"; + private static final String DECRYPTION_KEY_ALIAS_1 = + "com.android.server.locksettings.recoverablekeystore/platform/42/1000000/decrypt"; + private static final String DECRYPTION_KEY_FOR_ALIAS_PRIMARY_USER_1 = + "com.android.server.locksettings.recoverablekeystore/platform/0/1000000/decrypt"; + private static final String ENCRYPTION_KEY_ALIAS_2 = + "com.android.server.locksettings.recoverablekeystore/platform/42/1000001/encrypt"; + private static final String DECRYPTION_KEY_ALIAS_2 = + "com.android.server.locksettings.recoverablekeystore/platform/42/1000001/decrypt"; + @Mock private Context mContext; @Mock private KeyStoreProxy mKeyStoreProxy; @Mock private KeyguardManager mKeyguardManager; @@ -114,7 +126,7 @@ public class PlatformKeyManagerTest { mPlatformKeyManager.init(USER_ID_FIXTURE); verify(mKeyStoreProxy).setEntry( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"), + eq(ENCRYPTION_KEY_ALIAS_1), any(), any()); } @@ -156,7 +168,7 @@ public class PlatformKeyManagerTest { mPlatformKeyManager.init(USER_ID_FIXTURE); verify(mKeyStoreProxy).setEntry( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"), + eq(DECRYPTION_KEY_ALIAS_1), any(), any()); } @@ -187,19 +199,33 @@ public class PlatformKeyManagerTest { } @Test - public void init_createsDecryptKeyWithAuthenticationRequired() throws Exception { + public void init_primaryUser_createsDecryptKeyWithUnlockedDeviceRequired() throws Exception { + mPlatformKeyManager.init(PRIMARY_USER_ID_FIXTURE); + + assertTrue(getDecryptKeyProtectionForPrimaryUser().isUnlockedDeviceRequired()); + } + + @Test + public void init_primaryUser_createsDecryptKeyWithoutAuthenticationRequired() throws Exception { + mPlatformKeyManager.init(PRIMARY_USER_ID_FIXTURE); + + assertFalse(getDecryptKeyProtectionForPrimaryUser().isUserAuthenticationRequired()); + } + + @Test + public void init_secondaryUser_createsDecryptKeyWithoutUnlockedDeviceRequired() + throws Exception { mPlatformKeyManager.init(USER_ID_FIXTURE); - assertTrue(getDecryptKeyProtection().isUserAuthenticationRequired()); + assertFalse(getDecryptKeyProtection().isUnlockedDeviceRequired()); } @Test - public void init_createsDecryptKeyWithAuthenticationValidFor15Seconds() throws Exception { + public void init_secondaryUserUser_createsDecryptKeyWithAuthenticationRequired() + throws Exception { mPlatformKeyManager.init(USER_ID_FIXTURE); - assertEquals( - USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS, - getDecryptKeyProtection().getUserAuthenticationValidityDurationSeconds()); + assertTrue(getDecryptKeyProtection().isUserAuthenticationRequired()); } @Test @@ -219,7 +245,7 @@ public class PlatformKeyManagerTest { mPlatformKeyManager.init(USER_ID_FIXTURE); verify(mKeyStoreProxy, never()).setEntry( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"), + eq(DECRYPTION_KEY_ALIAS_1), any(), any()); } @@ -231,7 +257,7 @@ public class PlatformKeyManagerTest { expectThrows(RemoteException.class, () -> mPlatformKeyManager.init(USER_ID_FIXTURE)); verify(mKeyStoreProxy, never()).setEntry( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"), + eq(DECRYPTION_KEY_ALIAS_1), any(), any()); } @@ -251,15 +277,15 @@ public class PlatformKeyManagerTest { public void init_savesGenerationIdToDatabase() throws Exception { mPlatformKeyManager.init(USER_ID_FIXTURE); - assertEquals(1, + assertEquals(MIN_GENERATION_ID, mRecoverableKeyStoreDb.getPlatformKeyGenerationId(USER_ID_FIXTURE)); } @Test - public void init_setsGenerationIdTo1() throws Exception { + public void init_setsGenerationId() throws Exception { mPlatformKeyManager.init(USER_ID_FIXTURE); - assertEquals(1, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE)); + assertEquals(MIN_GENERATION_ID, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE)); } @Test @@ -268,22 +294,20 @@ public class PlatformKeyManagerTest { mPlatformKeyManager.init(USER_ID_FIXTURE); - assertEquals(2, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE)); + assertEquals(MIN_GENERATION_ID + 1, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE)); } @Test public void init_doesNotIncrementGenerationIdIfKeyAvailable() throws Exception { mPlatformKeyManager.init(USER_ID_FIXTURE); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/decrypt")).thenReturn(true); + .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/encrypt")).thenReturn(true); + .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true); mPlatformKeyManager.init(USER_ID_FIXTURE); - assertEquals(1, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE)); + assertEquals(MIN_GENERATION_ID, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE)); } @Test @@ -294,226 +318,194 @@ public class PlatformKeyManagerTest { @Test public void getDecryptKey_getsDecryptKeyWithCorrectAlias() throws Exception { when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/decrypt")).thenReturn(true); + .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/encrypt")).thenReturn(true); + .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true); when(mKeyStoreProxy.getKey( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"), + eq(DECRYPTION_KEY_ALIAS_1), any())).thenReturn(generateAndroidKeyStoreKey()); mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE); verify(mKeyStoreProxy).getKey( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"), + eq(DECRYPTION_KEY_ALIAS_1), any()); } @Test public void getDecryptKey_generatesNewKeyIfOldDecryptKeyWasRemoved() throws Exception { when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/encrypt")).thenReturn(true); + .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/decrypt")).thenReturn(false); // was removed + .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(false); // was removed when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/2/encrypt")).thenReturn(true); // new version is available + .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true); // new version when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/2/decrypt")).thenReturn(true); + .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true); when(mKeyStoreProxy.getKey( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"), + eq(DECRYPTION_KEY_ALIAS_2), any())).thenReturn(generateAndroidKeyStoreKey()); mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE); verify(mKeyStoreProxy).containsAlias( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt")); + eq(DECRYPTION_KEY_ALIAS_1)); // Attempt to get regenerated key. verify(mKeyStoreProxy).getKey( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"), + eq(DECRYPTION_KEY_ALIAS_2), any()); } @Test public void getDecryptKey_generatesNewKeyIfOldEncryptKeyWasRemoved() throws Exception { when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/encrypt")).thenReturn(false); // was removed + .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(false); // was removed when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/decrypt")).thenReturn(true); + .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/2/encrypt")).thenReturn(true); + .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/2/decrypt")).thenReturn(true); + .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true); mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE); verify(mKeyStoreProxy).containsAlias( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt")); + eq(ENCRYPTION_KEY_ALIAS_1)); // Attempt to get regenerated key. verify(mKeyStoreProxy).getKey( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"), + eq(DECRYPTION_KEY_ALIAS_2), any()); } @Test public void getEncryptKey_generatesNewKeyIfOldOneIsInvalid() throws Exception { doThrow(new UnrecoverableKeyException()).when(mKeyStoreProxy).getKey( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"), + eq(ENCRYPTION_KEY_ALIAS_1), any()); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/encrypt")).thenReturn(true); + .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/decrypt")).thenReturn(true); + .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/2/encrypt")).thenReturn(true); + .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/2/decrypt")).thenReturn(true); + .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true); mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE); verify(mKeyStoreProxy).getKey( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"), + eq(ENCRYPTION_KEY_ALIAS_1), any()); // Attempt to get regenerated key. verify(mKeyStoreProxy).getKey( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"), + eq(ENCRYPTION_KEY_ALIAS_2), any()); } @Test public void getDecryptKey_generatesNewKeyIfOldOneIsInvalid() throws Exception { doThrow(new UnrecoverableKeyException()).when(mKeyStoreProxy).getKey( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"), + eq(DECRYPTION_KEY_ALIAS_1), any()); when(mKeyStoreProxy.getKey( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"), + eq(DECRYPTION_KEY_ALIAS_2), any())).thenReturn(generateAndroidKeyStoreKey()); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/encrypt")).thenReturn(true); + .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/decrypt")).thenReturn(true); + .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/2/encrypt")).thenReturn(true); + .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/2/decrypt")).thenReturn(true); + .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true); mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE); verify(mKeyStoreProxy).containsAlias( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt")); + eq(DECRYPTION_KEY_ALIAS_1)); // Attempt to get regenerated key. verify(mKeyStoreProxy).getKey( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"), + eq(DECRYPTION_KEY_ALIAS_2), any()); } @Test public void getEncryptKey_generatesNewKeyIfDecryptKeyIsUnrecoverable() throws Exception { doThrow(new UnrecoverableKeyException()).when(mKeyStoreProxy).getKey( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"), + eq(DECRYPTION_KEY_ALIAS_1), any()); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/encrypt")).thenReturn(true); + .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/decrypt")).thenReturn(false); // was removed + .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(false); // was removed when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/2/encrypt")).thenReturn(true); // new version is available + .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true); // new version when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/2/decrypt")).thenReturn(true); + .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true); mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE); // Attempt to get regenerated key. verify(mKeyStoreProxy).getKey( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"), + eq(ENCRYPTION_KEY_ALIAS_2), any()); } @Test public void getEncryptKey_generatesNewKeyIfOldDecryptKeyWasRemoved() throws Exception { when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/encrypt")).thenReturn(true); + .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/decrypt")).thenReturn(false); // was removed + .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(false); // was removed when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/2/encrypt")).thenReturn(true); // new version is available + .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true); // new version when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/2/decrypt")).thenReturn(true); + .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true); mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE); verify(mKeyStoreProxy).containsAlias( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt")); + eq(ENCRYPTION_KEY_ALIAS_1)); // Attempt to get regenerated key. verify(mKeyStoreProxy).getKey( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"), + eq(ENCRYPTION_KEY_ALIAS_2), any()); } @Test public void getEncryptKey_generatesNewKeyIfOldEncryptKeyWasRemoved() throws Exception { when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/encrypt")).thenReturn(false); // was removed + .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(false); // was removed when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/decrypt")).thenReturn(true); + .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/2/encrypt")).thenReturn(true); + .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/2/decrypt")).thenReturn(true); + .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true); mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE); verify(mKeyStoreProxy).containsAlias( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt")); + eq(ENCRYPTION_KEY_ALIAS_1)); // Attempt to get regenerated key. verify(mKeyStoreProxy).getKey( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"), + eq(ENCRYPTION_KEY_ALIAS_2), any()); } @Test public void getEncryptKey_getsEncryptKeyWithCorrectAlias() throws Exception { when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/encrypt")).thenReturn(true); + .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true); when(mKeyStoreProxy - .containsAlias("com.android.server.locksettings.recoverablekeystore/" - + "platform/42/1/decrypt")).thenReturn(true); + .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true); mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE); verify(mKeyStoreProxy).getKey( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"), + eq(ENCRYPTION_KEY_ALIAS_1), any()); } @@ -523,7 +515,7 @@ public class PlatformKeyManagerTest { mPlatformKeyManager.regenerate(USER_ID_FIXTURE); - assertEquals(2, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE)); + assertEquals(MIN_GENERATION_ID + 1, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE)); } @Test @@ -533,17 +525,17 @@ public class PlatformKeyManagerTest { mPlatformKeyManager.regenerate(USER_ID_FIXTURE); verify(mKeyStoreProxy).deleteEntry( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt")); + eq(ENCRYPTION_KEY_ALIAS_1)); verify(mKeyStoreProxy).deleteEntry( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt")); + eq(DECRYPTION_KEY_ALIAS_1)); mPlatformKeyManager.regenerate(USER_ID_FIXTURE); // Removes second generation keys. verify(mKeyStoreProxy).deleteEntry( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt")); + eq(ENCRYPTION_KEY_ALIAS_2)); verify(mKeyStoreProxy).deleteEntry( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt")); + eq(DECRYPTION_KEY_ALIAS_2)); } @Test @@ -553,7 +545,7 @@ public class PlatformKeyManagerTest { mPlatformKeyManager.regenerate(USER_ID_FIXTURE); verify(mKeyStoreProxy).setEntry( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"), + eq(ENCRYPTION_KEY_ALIAS_2), any(), any()); } @@ -565,14 +557,14 @@ public class PlatformKeyManagerTest { mPlatformKeyManager.regenerate(USER_ID_FIXTURE); verify(mKeyStoreProxy).setEntry( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"), + eq(DECRYPTION_KEY_ALIAS_2), any(), any()); } private KeyProtection getEncryptKeyProtection() throws Exception { verify(mKeyStoreProxy).setEntry( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"), + eq(ENCRYPTION_KEY_ALIAS_1), any(), mProtectionParameterCaptor.capture()); return (KeyProtection) mProtectionParameterCaptor.getValue(); @@ -580,7 +572,15 @@ public class PlatformKeyManagerTest { private KeyProtection getDecryptKeyProtection() throws Exception { verify(mKeyStoreProxy).setEntry( - eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"), + eq(DECRYPTION_KEY_ALIAS_1), + any(), + mProtectionParameterCaptor.capture()); + return (KeyProtection) mProtectionParameterCaptor.getValue(); + } + + private KeyProtection getDecryptKeyProtectionForPrimaryUser() throws Exception { + verify(mKeyStoreProxy).setEntry( + eq(DECRYPTION_KEY_FOR_ALIAS_PRIMARY_USER_1), any(), mProtectionParameterCaptor.capture()); return (KeyProtection) mProtectionParameterCaptor.getValue(); diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java index 68900175cc8f..ac7447006444 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java @@ -29,6 +29,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeast; @@ -84,7 +85,7 @@ import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Map; import java.util.Random; -import java.util.concurrent.ExecutorService; +import java.util.concurrent.ScheduledExecutorService; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; @@ -157,7 +158,7 @@ public class RecoverableKeyStoreManagerTest { @Mock private PlatformKeyManager mPlatformKeyManager; @Mock private ApplicationKeyStorage mApplicationKeyStorage; @Mock private CleanupManager mCleanupManager; - @Mock private ExecutorService mExecutorService; + @Mock private ScheduledExecutorService mExecutorService; @Spy private TestOnlyInsecureCertificateHelper mTestOnlyInsecureCertificateHelper; private RecoverableKeyStoreDb mRecoverableKeyStoreDb; @@ -1253,7 +1254,7 @@ public class RecoverableKeyStoreManagerTest { mRecoverableKeyStoreManager.lockScreenSecretAvailable( LockPatternUtils.CREDENTIAL_TYPE_PATTERN, "password".getBytes(), 11); - verify(mExecutorService).execute(any()); + verify(mExecutorService).schedule(any(Runnable.class), anyLong(), any()); } @Test @@ -1263,7 +1264,7 @@ public class RecoverableKeyStoreManagerTest { "password".getBytes(), 11); - verify(mExecutorService).execute(any()); + verify(mExecutorService).schedule(any(Runnable.class), anyLong(), any()); } private static byte[] encryptedApplicationKey( 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 28e6830515f0..c7dbad83e384 100644 --- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java @@ -1273,11 +1273,11 @@ public class NetworkPolicyManagerServiceTest { history.recordData(start, end, new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(1440), 0L, 0L, 0L, 0)); stats.clear(); - stats.addValues(IFACE_ALL, UID_A, SET_ALL, TAG_ALL, + stats.addEntry(IFACE_ALL, UID_A, SET_ALL, TAG_ALL, DataUnit.MEGABYTES.toBytes(480), 0, 0, 0, 0); - stats.addValues(IFACE_ALL, UID_B, SET_ALL, TAG_ALL, + stats.addEntry(IFACE_ALL, UID_B, SET_ALL, TAG_ALL, DataUnit.MEGABYTES.toBytes(480), 0, 0, 0, 0); - stats.addValues(IFACE_ALL, UID_C, SET_ALL, TAG_ALL, + stats.addEntry(IFACE_ALL, UID_C, SET_ALL, TAG_ALL, DataUnit.MEGABYTES.toBytes(480), 0, 0, 0, 0); reset(mNotifManager); @@ -1301,9 +1301,9 @@ public class NetworkPolicyManagerServiceTest { history.recordData(start, end, new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(1440), 0L, 0L, 0L, 0)); stats.clear(); - stats.addValues(IFACE_ALL, UID_A, SET_ALL, TAG_ALL, + stats.addEntry(IFACE_ALL, UID_A, SET_ALL, TAG_ALL, DataUnit.MEGABYTES.toBytes(960), 0, 0, 0, 0); - stats.addValues(IFACE_ALL, UID_B, SET_ALL, TAG_ALL, + stats.addEntry(IFACE_ALL, UID_B, SET_ALL, TAG_ALL, DataUnit.MEGABYTES.toBytes(480), 0, 0, 0, 0); reset(mNotifManager); diff --git a/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java b/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java new file mode 100644 index 000000000000..d3166b91dc9e --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java @@ -0,0 +1,111 @@ +/* + * 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.people; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.prediction.AppPredictionContext; +import android.app.prediction.AppPredictionSessionId; +import android.app.prediction.AppTarget; +import android.app.prediction.IPredictionCallback; +import android.content.Context; +import android.content.pm.ParceledListSlice; +import android.os.Binder; +import android.os.RemoteException; + +import com.android.server.LocalServices; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +@RunWith(JUnit4.class) +public final class PeopleServiceTest { + + private static final String APP_PREDICTION_SHARE_UI_SURFACE = "share"; + private static final int APP_PREDICTION_TARGET_COUNT = 4; + private static final String TEST_PACKAGE_NAME = "com.example"; + + private PeopleServiceInternal mServiceInternal; + private PeopleService.LocalService mLocalService; + private AppPredictionSessionId mSessionId; + private AppPredictionContext mPredictionContext; + + @Mock private Context mContext; + @Mock private IPredictionCallback mCallback; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + when(mContext.getPackageName()).thenReturn(TEST_PACKAGE_NAME); + when(mCallback.asBinder()).thenReturn(new Binder()); + + PeopleService service = new PeopleService(mContext); + service.onStart(); + + mServiceInternal = LocalServices.getService(PeopleServiceInternal.class); + mLocalService = (PeopleService.LocalService) mServiceInternal; + + mSessionId = new AppPredictionSessionId("abc"); + mPredictionContext = new AppPredictionContext.Builder(mContext) + .setUiSurface(APP_PREDICTION_SHARE_UI_SURFACE) + .setPredictedTargetCount(APP_PREDICTION_TARGET_COUNT) + .build(); + } + + @After + public void tearDown() { + LocalServices.removeServiceForTest(PeopleServiceInternal.class); + } + + @Test + public void testRegisterCallbacks() throws RemoteException { + mServiceInternal.onCreatePredictionSession(mPredictionContext, mSessionId); + + SessionInfo sessionInfo = mLocalService.getSessionInfo(mSessionId); + + mServiceInternal.registerPredictionUpdates(mSessionId, mCallback); + + Consumer<List<AppTarget>> updatePredictionMethod = + sessionInfo.getPredictor().getUpdatePredictionsMethod(); + updatePredictionMethod.accept(new ArrayList<>()); + updatePredictionMethod.accept(new ArrayList<>()); + + verify(mCallback, times(2)).onResult(any(ParceledListSlice.class)); + + mServiceInternal.unregisterPredictionUpdates(mSessionId, mCallback); + + updatePredictionMethod.accept(new ArrayList<>()); + + // After the un-registration, the callback should no longer be called. + verify(mCallback, times(2)).onResult(any(ParceledListSlice.class)); + + mServiceInternal.onDestroyPredictionSession(mSessionId); + } +} diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java index c478ec472e61..15327b6e5463 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java @@ -156,8 +156,7 @@ public class PackageInstallerSessionTest { if (isMultiPackage) { params.isMultiPackage = true; } - InstallSource installSource = InstallSource.create("testInstaller", null, "testInstaller", - false); + InstallSource installSource = InstallSource.create("testInstaller", null, "testInstaller"); return new PackageInstallerSession( /* callback */ null, /* context */null, diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java index 1e760ccba9f3..ac27a087ba8e 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java @@ -139,18 +139,18 @@ public class UserManagerServiceUserInfoTest { assertEquals("A Name", mUserManagerService.getUserInfo(TEST_ID).name); } - /** Test UMS.getUserTypeForUser(). */ + /** Test UMS.isUserOfType(). */ @Test - public void testGetUserTypeForUser() throws Exception { - final String typeSys = mUserManagerService.getUserTypeForUser(UserHandle.USER_SYSTEM); - assertTrue("System user was of invalid type " + typeSys, - typeSys.equals(USER_TYPE_SYSTEM_HEADLESS) || typeSys.equals(USER_TYPE_FULL_SYSTEM)); + public void testIsUserOfType() throws Exception { + assertTrue("System user was of invalid type", + mUserManagerService.isUserOfType(UserHandle.USER_SYSTEM, USER_TYPE_SYSTEM_HEADLESS) + || mUserManagerService.isUserOfType(UserHandle.USER_SYSTEM, USER_TYPE_FULL_SYSTEM)); final int testId = 100; final String typeName = "A type"; UserInfo userInfo = createUser(testId, 0, typeName); mUserManagerService.putUserInfo(userInfo); - assertEquals(typeName, mUserManagerService.getUserTypeForUser(testId)); + assertTrue(mUserManagerService.isUserOfType(testId, typeName)); } /** Tests upgradeIfNecessaryLP (but without locking) for upgrading from version 8 to 9+. */ @@ -169,22 +169,22 @@ public class UserManagerServiceUserInfoTest { mUserManagerService.upgradeIfNecessaryLP(null, versionToTest - 1); - assertEquals(USER_TYPE_PROFILE_MANAGED, mUserManagerService.getUserTypeForUser(100)); + assertTrue(mUserManagerService.isUserOfType(100, USER_TYPE_PROFILE_MANAGED)); assertTrue((mUserManagerService.getUserInfo(100).flags & FLAG_PROFILE) != 0); - assertEquals(USER_TYPE_FULL_GUEST, mUserManagerService.getUserTypeForUser(101)); + assertTrue(mUserManagerService.isUserOfType(101, USER_TYPE_FULL_GUEST)); - assertEquals(USER_TYPE_FULL_RESTRICTED, mUserManagerService.getUserTypeForUser(102)); + assertTrue(mUserManagerService.isUserOfType(102, USER_TYPE_FULL_RESTRICTED)); assertTrue((mUserManagerService.getUserInfo(102).flags & FLAG_PROFILE) == 0); - assertEquals(USER_TYPE_FULL_SECONDARY, mUserManagerService.getUserTypeForUser(103)); + assertTrue(mUserManagerService.isUserOfType(103, USER_TYPE_FULL_SECONDARY)); assertTrue((mUserManagerService.getUserInfo(103).flags & FLAG_PROFILE) == 0); - assertEquals(USER_TYPE_SYSTEM_HEADLESS, mUserManagerService.getUserTypeForUser(104)); + assertTrue(mUserManagerService.isUserOfType(104, USER_TYPE_SYSTEM_HEADLESS)); - assertEquals(USER_TYPE_FULL_SYSTEM, mUserManagerService.getUserTypeForUser(105)); + assertTrue(mUserManagerService.isUserOfType(105, USER_TYPE_FULL_SYSTEM)); - assertEquals(USER_TYPE_FULL_DEMO, mUserManagerService.getUserTypeForUser(106)); + assertTrue(mUserManagerService.isUserOfType(106, USER_TYPE_FULL_DEMO)); } /** Creates a UserInfo with the given flags and userType. */ diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java index a7275fc74734..77376f069525 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java @@ -341,8 +341,8 @@ public final class UserManagerTest { assertThat(mUserManager.getUserBadgeNoBackgroundResId(userId)) .isEqualTo(userTypeDetails.getBadgeNoBackground()); assertThat(mUserManager.isProfile(userId)).isEqualTo(userTypeDetails.isProfile()); - assertThat(mUserManager.getUserTypeForUser(asHandle(userId))) - .isEqualTo(userTypeDetails.getName()); + assertThat(mUserManager.isUserOfType(asHandle(userId), userTypeDetails.getName())) + .isTrue(); final int badgeIndex = userInfo.profileBadge; assertThat(mUserManager.getUserBadgeColor(userId)).isEqualTo( diff --git a/services/tests/servicestests/src/com/android/server/power/NotifierTest.java b/services/tests/servicestests/src/com/android/server/power/NotifierTest.java new file mode 100644 index 000000000000..7666ab9ed5ae --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/power/NotifierTest.java @@ -0,0 +1,287 @@ +/* + * 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.power; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.res.Resources; +import android.hardware.SensorManager; +import android.hardware.display.AmbientDisplayConfiguration; +import android.os.BatteryStats; +import android.os.Handler; +import android.os.Looper; +import android.os.ServiceManager; +import android.os.Vibrator; +import android.os.test.TestLooper; +import android.provider.Settings; +import android.testing.TestableContext; + +import androidx.test.InstrumentationRegistry; + +import com.android.internal.app.IBatteryStats; +import com.android.internal.os.BatteryStatsImpl; +import com.android.server.LocalServices; +import com.android.server.policy.WindowManagerPolicy; +import com.android.server.power.batterysaver.BatterySaverPolicy; +import com.android.server.power.batterysaver.BatterySavingStats; +import com.android.server.statusbar.StatusBarManagerInternal; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link com.android.server.power.Notifier} + */ +public class NotifierTest { + private static final String SYSTEM_PROPERTY_QUIESCENT = "ro.boot.quiescent"; + private static final int USER_ID = 0; + + @Mock private BatterySaverPolicy mBatterySaverPolicyMock; + @Mock private PowerManagerService.NativeWrapper mNativeWrapperMock; + @Mock private Notifier mNotifierMock; + @Mock private WirelessChargerDetector mWirelessChargerDetectorMock; + @Mock private AmbientDisplayConfiguration mAmbientDisplayConfigurationMock; + @Mock private SystemPropertiesWrapper mSystemPropertiesMock; + @Mock private InattentiveSleepWarningController mInattentiveSleepWarningControllerMock; + @Mock private BatteryStatsImpl mBatteryStats; + @Mock private Vibrator mVibrator; + @Mock private StatusBarManagerInternal mStatusBarManagerInternal; + + private PowerManagerService mService; + private Context mContextSpy; + private Resources mResourcesSpy; + private TestLooper mTestLooper = new TestLooper(); + private Notifier mNotifier; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + LocalServices.removeServiceForTest(StatusBarManagerInternal.class); + LocalServices.addService(StatusBarManagerInternal.class, mStatusBarManagerInternal); + + mContextSpy = spy(new TestableContext(InstrumentationRegistry.getContext())); + mResourcesSpy = spy(mContextSpy.getResources()); + when(mContextSpy.getResources()).thenReturn(mResourcesSpy); + when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), anyString())).thenReturn(""); + when(mContextSpy.getSystemService(Vibrator.class)).thenReturn(mVibrator); + + mService = new PowerManagerService(mContextSpy, mInjector); + } + + @Test + public void testVibrateEnabled_wiredCharging() { + createNotifier(); + + // GIVEN the charging vibration is enabled + enableChargingVibration(true); + + // WHEN wired charging starts + mNotifier.onWiredChargingStarted(USER_ID); + mTestLooper.dispatchAll(); + + // THEN the device vibrates once + verify(mVibrator, times(1)).vibrate(any(), any()); + } + + @Test + public void testVibrateDisabled_wiredCharging() { + createNotifier(); + + // GIVEN the charging vibration is disabled + enableChargingVibration(false); + + // WHEN wired charging starts + mNotifier.onWiredChargingStarted(USER_ID); + mTestLooper.dispatchAll(); + + // THEN the device doesn't vibrate + verify(mVibrator, never()).vibrate(any(), any()); + } + + @Test + public void testVibrateEnabled_wirelessCharging() { + createNotifier(); + + // GIVEN the charging vibration is enabled + enableChargingVibration(true); + + // WHEN wireless charging starts + mNotifier.onWirelessChargingStarted(5, USER_ID); + mTestLooper.dispatchAll(); + + // THEN the device vibrates once + verify(mVibrator, times(1)).vibrate(any(), any()); + } + + @Test + public void testVibrateDisabled_wirelessCharging() { + createNotifier(); + + // GIVEN the charging vibration is disabeld + enableChargingVibration(false); + + // WHEN wireless charging starts + mNotifier.onWirelessChargingStarted(5, USER_ID); + mTestLooper.dispatchAll(); + + // THEN the device doesn't vibrate + verify(mVibrator, never()).vibrate(any(), any()); + } + + @Test + public void testVibrateEnabled_dndOn() { + createNotifier(); + + // GIVEN the charging vibration is enabled but dnd is on + enableChargingVibration(true); + enableChargingFeedback( + /* chargingFeedbackEnabled */ true, + /* dndOn */ true); + + // WHEN wired charging starts + mNotifier.onWiredChargingStarted(USER_ID); + mTestLooper.dispatchAll(); + + // THEN the device doesn't vibrate + verify(mVibrator, never()).vibrate(any(), any()); + } + + @Test + public void testWirelessAnimationEnabled() { + // GIVEN the wireless charging animation is enabled + when(mResourcesSpy.getBoolean( + com.android.internal.R.bool.config_showBuiltinWirelessChargingAnim)) + .thenReturn(true); + createNotifier(); + + // WHEN wireless charging starts + mNotifier.onWirelessChargingStarted(5, USER_ID); + mTestLooper.dispatchAll(); + + // THEN the charging animation is triggered + verify(mStatusBarManagerInternal, times(1)).showChargingAnimation(5); + } + + @Test + public void testWirelessAnimationDisabled() { + // GIVEN the wireless charging animation is disabled + when(mResourcesSpy.getBoolean( + com.android.internal.R.bool.config_showBuiltinWirelessChargingAnim)) + .thenReturn(false); + createNotifier(); + + // WHEN wireless charging starts + mNotifier.onWirelessChargingStarted(5, USER_ID); + mTestLooper.dispatchAll(); + + // THEN the charging animation never gets called + verify(mStatusBarManagerInternal, never()).showChargingAnimation(anyInt()); + } + + private final PowerManagerService.Injector mInjector = new PowerManagerService.Injector() { + @Override + Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats, + SuspendBlocker suspendBlocker, WindowManagerPolicy policy) { + return mNotifierMock; + } + + @Override + SuspendBlocker createSuspendBlocker(PowerManagerService service, String name) { + return super.createSuspendBlocker(service, name); + } + + @Override + BatterySaverPolicy createBatterySaverPolicy( + Object lock, Context context, BatterySavingStats batterySavingStats) { + return mBatterySaverPolicyMock; + } + + @Override + PowerManagerService.NativeWrapper createNativeWrapper() { + return mNativeWrapperMock; + } + + @Override + WirelessChargerDetector createWirelessChargerDetector( + SensorManager sensorManager, SuspendBlocker suspendBlocker, Handler handler) { + return mWirelessChargerDetectorMock; + } + + @Override + AmbientDisplayConfiguration createAmbientDisplayConfiguration(Context context) { + return mAmbientDisplayConfigurationMock; + } + + @Override + InattentiveSleepWarningController createInattentiveSleepWarningController() { + return mInattentiveSleepWarningControllerMock; + } + + @Override + public SystemPropertiesWrapper createSystemPropertiesWrapper() { + return mSystemPropertiesMock; + } + }; + + private void enableChargingFeedback(boolean chargingFeedbackEnabled, boolean dndOn) { + // enable/disable charging feedback + Settings.Secure.putIntForUser( + mContextSpy.getContentResolver(), + Settings.Secure.CHARGING_SOUNDS_ENABLED, + chargingFeedbackEnabled ? 1 : 0, + USER_ID); + + // toggle on/off dnd + Settings.Global.putInt( + mContextSpy.getContentResolver(), + Settings.Global.ZEN_MODE, + dndOn ? Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS + : Settings.Global.ZEN_MODE_OFF); + } + + private void enableChargingVibration(boolean enable) { + enableChargingFeedback(true, false); + + Settings.Secure.putIntForUser( + mContextSpy.getContentResolver(), + Settings.Secure.CHARGING_VIBRATION_ENABLED, + enable ? 1 : 0, + USER_ID); + } + + private void createNotifier() { + mNotifier = new Notifier( + mTestLooper.getLooper(), + mContextSpy, + IBatteryStats.Stub.asInterface(ServiceManager.getService( + BatteryStats.SERVICE_NAME)), + mInjector.createSuspendBlocker(mService, "testBlocker"), + null); + } +} diff --git a/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTest.java b/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTest.java index 1f312bf1296d..d5cdbeb121b0 100644 --- a/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTest.java @@ -20,16 +20,19 @@ import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; 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.doNothing; 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; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.content.Context; +import android.content.IntentSender; import android.os.Handler; import android.os.IPowerManager; import android.os.IRecoverySystemProgressListener; @@ -40,6 +43,8 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.widget.LockSettingsInternal; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -58,6 +63,7 @@ public class RecoverySystemServiceTest { private Context mContext; private IPowerManager mIPowerManager; private FileWriter mUncryptUpdateFileWriter; + private LockSettingsInternal mLockSettingsInternal; @Before public void setup() { @@ -65,6 +71,9 @@ public class RecoverySystemServiceTest { mSystemProperties = new RecoverySystemServiceTestable.FakeSystemProperties(); mUncryptSocket = mock(RecoverySystemService.UncryptSocket.class); mUncryptUpdateFileWriter = mock(FileWriter.class); + mLockSettingsInternal = mock(LockSettingsInternal.class); + + when(mLockSettingsInternal.armRebootEscrow()).thenReturn(true); Looper looper = InstrumentationRegistry.getContext().getMainLooper(); mIPowerManager = mock(IPowerManager.class); @@ -72,7 +81,7 @@ public class RecoverySystemServiceTest { new Handler(looper)); mRecoverySystemService = new RecoverySystemServiceTestable(mContext, mSystemProperties, - powerManager, mUncryptUpdateFileWriter, mUncryptSocket); + powerManager, mUncryptUpdateFileWriter, mUncryptSocket, mLockSettingsInternal); } @Test @@ -194,4 +203,100 @@ public class RecoverySystemServiceTest { verify(mUncryptSocket).sendAck(); verify(mUncryptSocket).close(); } + + @Test(expected = SecurityException.class) + public void requestLskf_protected() { + doThrow(SecurityException.class).when(mContext).enforceCallingOrSelfPermission( + eq(android.Manifest.permission.RECOVERY), any()); + mRecoverySystemService.requestLskf("test", null); + } + + + @Test + public void requestLskf_nullToken_failure() { + assertThat(mRecoverySystemService.requestLskf(null, null), is(false)); + } + + @Test + public void requestLskf_success() throws Exception { + IntentSender intentSender = mock(IntentSender.class); + assertThat(mRecoverySystemService.requestLskf("test", intentSender), is(true)); + mRecoverySystemService.onPreparedForReboot(true); + verify(intentSender).sendIntent(any(), anyInt(), any(), any(), any()); + } + + @Test + public void requestLskf_subsequentRequestClearsPrepared() throws Exception { + IntentSender intentSender = mock(IntentSender.class); + assertThat(mRecoverySystemService.requestLskf("test", intentSender), is(true)); + mRecoverySystemService.onPreparedForReboot(true); + verify(intentSender).sendIntent(any(), anyInt(), any(), any(), any()); + + assertThat(mRecoverySystemService.requestLskf("test2", null), is(true)); + assertThat(mRecoverySystemService.rebootWithLskf("test", null), is(false)); + assertThat(mRecoverySystemService.rebootWithLskf("test2", "foobar"), is(false)); + + mRecoverySystemService.onPreparedForReboot(true); + assertThat(mRecoverySystemService.rebootWithLskf("test2", "foobar"), is(true)); + verify(intentSender).sendIntent(any(), anyInt(), any(), any(), any()); + verify(mIPowerManager).reboot(anyBoolean(), eq("foobar"), anyBoolean()); + } + + + @Test + public void requestLskf_requestedButNotPrepared() throws Exception { + IntentSender intentSender = mock(IntentSender.class); + assertThat(mRecoverySystemService.requestLskf("test", intentSender), is(true)); + verify(intentSender, never()).sendIntent(any(), anyInt(), any(), any(), any()); + } + + @Test(expected = SecurityException.class) + public void clearLskf_protected() { + doThrow(SecurityException.class).when(mContext).enforceCallingOrSelfPermission( + eq(android.Manifest.permission.RECOVERY), any()); + mRecoverySystemService.clearLskf(); + } + + @Test + public void clearLskf_requestedThenCleared() throws Exception { + IntentSender intentSender = mock(IntentSender.class); + assertThat(mRecoverySystemService.requestLskf("test", intentSender), is(true)); + mRecoverySystemService.onPreparedForReboot(true); + verify(intentSender).sendIntent(any(), anyInt(), any(), any(), any()); + + assertThat(mRecoverySystemService.clearLskf(), is(true)); + verify(mLockSettingsInternal).clearRebootEscrow(); + } + + @Test + public void startup_setRebootEscrowListener() throws Exception { + mRecoverySystemService.onSystemServicesReady(); + verify(mLockSettingsInternal).setRebootEscrowListener(any()); + } + + @Test(expected = SecurityException.class) + public void rebootWithLskf_protected() { + doThrow(SecurityException.class).when(mContext).enforceCallingOrSelfPermission( + eq(android.Manifest.permission.RECOVERY), any()); + mRecoverySystemService.rebootWithLskf("test1", null); + } + + @Test + public void rebootWithLskf_Success() throws Exception { + assertThat(mRecoverySystemService.requestLskf("test", null), is(true)); + mRecoverySystemService.onPreparedForReboot(true); + assertThat(mRecoverySystemService.rebootWithLskf("test", "ab-update"), is(true)); + verify(mIPowerManager).reboot(anyBoolean(), eq("ab-update"), anyBoolean()); + } + + @Test + public void rebootWithLskf_withoutPrepare_Failure() throws Exception { + assertThat(mRecoverySystemService.rebootWithLskf("test1", null), is(false)); + } + + @Test + public void rebootWithLskf_withNullUpdateToken_Failure() throws Exception { + assertThat(mRecoverySystemService.rebootWithLskf(null, null), is(false)); + verifyNoMoreInteractions(mIPowerManager); + } } diff --git a/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTestable.java b/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTestable.java index a986b71d556f..131e4f321a6c 100644 --- a/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTestable.java +++ b/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTestable.java @@ -19,6 +19,8 @@ package com.android.server.recoverysystem; import android.content.Context; import android.os.PowerManager; +import com.android.internal.widget.LockSettingsInternal; + import java.io.FileWriter; public class RecoverySystemServiceTestable extends RecoverySystemService { @@ -27,15 +29,17 @@ public class RecoverySystemServiceTestable extends RecoverySystemService { private final PowerManager mPowerManager; private final FileWriter mUncryptPackageFileWriter; private final UncryptSocket mUncryptSocket; + private final LockSettingsInternal mLockSettingsInternal; MockInjector(Context context, FakeSystemProperties systemProperties, PowerManager powerManager, FileWriter uncryptPackageFileWriter, - UncryptSocket uncryptSocket) { + UncryptSocket uncryptSocket, LockSettingsInternal lockSettingsInternal) { super(context); mSystemProperties = systemProperties; mPowerManager = powerManager; mUncryptPackageFileWriter = uncryptPackageFileWriter; mUncryptSocket = uncryptSocket; + mLockSettingsInternal = lockSettingsInternal; } @Override @@ -76,13 +80,18 @@ public class RecoverySystemServiceTestable extends RecoverySystemService { @Override public void threadSleep(long millis) { } + + @Override + public LockSettingsInternal getLockSettingsService() { + return mLockSettingsInternal; + } } RecoverySystemServiceTestable(Context context, FakeSystemProperties systemProperties, PowerManager powerManager, FileWriter uncryptPackageFileWriter, - UncryptSocket uncryptSocket) { + UncryptSocket uncryptSocket, LockSettingsInternal lockSettingsInternal) { super(new MockInjector(context, systemProperties, powerManager, uncryptPackageFileWriter, - uncryptSocket)); + uncryptSocket, lockSettingsInternal)); } public static class FakeSystemProperties { diff --git a/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java b/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java index a83d94001cf8..f871203728c0 100644 --- a/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java +++ b/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java @@ -98,7 +98,7 @@ public class AppDataRollbackHelperTest { final int[] installedUsers) { return new PackageRollbackInfo( new VersionedPackage(packageName, 2), new VersionedPackage(packageName, 1), - new IntArray(), new ArrayList<>(), false, IntArray.wrap(installedUsers), + new IntArray(), new ArrayList<>(), false, false, IntArray.wrap(installedUsers), new SparseLongArray()); } diff --git a/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java b/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java index 757a884f8ded..d0d2edc59861 100644 --- a/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java +++ b/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java @@ -87,13 +87,15 @@ public class RollbackStoreTest { + "[{'versionRolledBackFrom':{'packageName':'blah','longVersionCode':55}," + "'versionRolledBackTo':{'packageName':'blah1','longVersionCode':50},'pendingBackups':" + "[59,1245,124544],'pendingRestores':[{'userId':498,'appId':32322,'seInfo':'wombles'}," - + "{'userId':-895,'appId':1,'seInfo':'pingu'}],'isApex':false,'installedUsers':" + + "{'userId':-895,'appId':1,'seInfo':'pingu'}],'isApex':false,'isApkInApex':false," + + "'installedUsers':" + "[498468432,1111,98464],'ceSnapshotInodes':[{'userId':1,'ceSnapshotInode':-6}," + "{'userId':2222,'ceSnapshotInode':81641654445},{'userId':546546," + "'ceSnapshotInode':345689375}]},{'versionRolledBackFrom':{'packageName':'chips'," + "'longVersionCode':28},'versionRolledBackTo':{'packageName':'com.chips.test'," + "'longVersionCode':48},'pendingBackups':[5],'pendingRestores':[{'userId':18," - + "'appId':-12,'seInfo':''}],'isApex':false,'installedUsers':[55,79]," + + "'appId':-12,'seInfo':''}],'isApex':false,'isApkInApex':false," + + "'installedUsers':[55,79]," + "'ceSnapshotInodes':[]}],'isStaged':false,'causePackages':[{'packageName':'hello'," + "'longVersionCode':23},{'packageName':'something','longVersionCode':999}]," + "'committedSessionId':45654465},'timestamp':'2019-10-01T12:29:08.855Z'," @@ -155,7 +157,7 @@ public class RollbackStoreTest { PackageRollbackInfo pkgInfo1 = new PackageRollbackInfo(new VersionedPackage("com.made.up", 18), new VersionedPackage("com.something.else", 5), new IntArray(), - new ArrayList<>(), false, new IntArray(), new SparseLongArray()); + new ArrayList<>(), false, false, new IntArray(), new SparseLongArray()); pkgInfo1.getPendingBackups().add(8); pkgInfo1.getPendingBackups().add(888); pkgInfo1.getPendingBackups().add(88885); @@ -175,7 +177,7 @@ public class RollbackStoreTest { PackageRollbackInfo pkgInfo2 = new PackageRollbackInfo( new VersionedPackage("another.package", 2), new VersionedPackage("com.test.ing", 48888), new IntArray(), new ArrayList<>(), - false, new IntArray(), new SparseLongArray()); + false, false, new IntArray(), new SparseLongArray()); pkgInfo2.getPendingBackups().add(57); pkgInfo2.getPendingRestores().add( @@ -205,7 +207,7 @@ public class RollbackStoreTest { PackageRollbackInfo pkgInfo1 = new PackageRollbackInfo(new VersionedPackage("blah", 55), new VersionedPackage("blah1", 50), new IntArray(), new ArrayList<>(), - false, new IntArray(), new SparseLongArray()); + false, false, new IntArray(), new SparseLongArray()); pkgInfo1.getPendingBackups().add(59); pkgInfo1.getPendingBackups().add(1245); pkgInfo1.getPendingBackups().add(124544); @@ -224,7 +226,7 @@ public class RollbackStoreTest { PackageRollbackInfo pkgInfo2 = new PackageRollbackInfo(new VersionedPackage("chips", 28), new VersionedPackage("com.chips.test", 48), new IntArray(), new ArrayList<>(), - false, new IntArray(), new SparseLongArray()); + false, false, new IntArray(), new SparseLongArray()); pkgInfo2.getPendingBackups().add(5); pkgInfo2.getPendingRestores().add( diff --git a/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java b/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java index e368d634b968..164c88382828 100644 --- a/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java +++ b/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java @@ -295,7 +295,8 @@ public class RollbackUnitTest { String packageName, long fromVersion, long toVersion, boolean isApex) { return new PackageRollbackInfo(new VersionedPackage(packageName, fromVersion), new VersionedPackage(packageName, toVersion), - new IntArray(), new ArrayList<>(), isApex, new IntArray(), new SparseLongArray()); + new IntArray(), new ArrayList<>(), isApex, false, new IntArray(), + new SparseLongArray()); } private static class PackageRollbackInfoForPackage implements diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java index 82f32f88d3a2..0f75816082fd 100644 --- a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java +++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java @@ -60,8 +60,6 @@ import android.os.IHwBinder; import android.os.IHwInterface; import android.os.RemoteException; -import androidx.test.runner.AndroidJUnit4; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -144,7 +142,11 @@ public class SoundTriggerMiddlewareImplTest { properties.maxSoundModels = 456; properties.maxKeyPhrases = 567; properties.maxUsers = 678; - properties.recognitionModes = 789; + properties.recognitionModes = + android.hardware.soundtrigger.V2_0.RecognitionMode.VOICE_TRIGGER + | android.hardware.soundtrigger.V2_0.RecognitionMode.USER_IDENTIFICATION + | android.hardware.soundtrigger.V2_0.RecognitionMode.USER_AUTHENTICATION + | android.hardware.soundtrigger.V2_0.RecognitionMode.GENERIC_TRIGGER; properties.captureTransition = true; properties.maxBufferMs = 321; properties.concurrentCapture = supportConcurrentCapture; @@ -153,7 +155,16 @@ public class SoundTriggerMiddlewareImplTest { return properties; } - private static void validateDefaultProperties(SoundTriggerModuleProperties properties, + private static android.hardware.soundtrigger.V2_3.Properties createDefaultProperties_2_3( + boolean supportConcurrentCapture) { + android.hardware.soundtrigger.V2_3.Properties properties = + new android.hardware.soundtrigger.V2_3.Properties(); + properties.base = createDefaultProperties(supportConcurrentCapture); + properties.supportedModelArch = "supportedModelArch"; + return properties; + } + + private void validateDefaultProperties(SoundTriggerModuleProperties properties, boolean supportConcurrentCapture) { assertEquals("implementor", properties.implementor); assertEquals("description", properties.description); @@ -162,14 +173,29 @@ public class SoundTriggerMiddlewareImplTest { assertEquals(456, properties.maxSoundModels); assertEquals(567, properties.maxKeyPhrases); assertEquals(678, properties.maxUsers); - assertEquals(789, properties.recognitionModes); + assertEquals(RecognitionMode.GENERIC_TRIGGER + | RecognitionMode.USER_AUTHENTICATION + | RecognitionMode.USER_IDENTIFICATION + | RecognitionMode.VOICE_TRIGGER, properties.recognitionModes); assertTrue(properties.captureTransition); assertEquals(321, properties.maxBufferMs); assertEquals(supportConcurrentCapture, properties.concurrentCapture); assertTrue(properties.triggerInEvent); assertEquals(432, properties.powerConsumptionMw); + + if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) { + assertEquals("supportedModelArch", properties.supportedModelArch); + } else { + assertEquals("", properties.supportedModelArch); + } } + private void verifyNotGetProperties() throws RemoteException { + if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) { + verify((android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver, + never()).getProperties(any()); + } + } private static android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionEvent createRecognitionEvent_2_0( int hwHandle, @@ -285,6 +311,22 @@ public class SoundTriggerMiddlewareImplTest { properties); return null; }).when(mHalDriver).getProperties(any()); + + if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) { + android.hardware.soundtrigger.V2_3.ISoundTriggerHw driver = + (android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver; + doAnswer(invocation -> { + android.hardware.soundtrigger.V2_3.Properties properties = + createDefaultProperties_2_3( + supportConcurrentCapture); + ((android.hardware.soundtrigger.V2_3.ISoundTriggerHw.getProperties_2_3Callback) + invocation.getArgument( + 0)).onValues(0, + properties); + return null; + }).when(driver).getProperties_2_3(any()); + } + mService = new SoundTriggerMiddlewareImpl(mHalDriver, mAudioSessionProvider); } @@ -711,6 +753,7 @@ public class SoundTriggerMiddlewareImplTest { SoundTriggerModuleProperties properties = allDescriptors[0].properties; validateDefaultProperties(properties, true); + verifyNotGetProperties(); } @Test diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java index 71b568cc06c5..ae5369204428 100644 --- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java @@ -37,7 +37,7 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; -import android.util.TimestampedValue; +import android.os.TimestampedValue; import androidx.test.runner.AndroidJUnit4; diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java index ca6fd08092df..aaf9799de777 100644 --- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java +++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java @@ -30,7 +30,7 @@ import android.content.Intent; import android.icu.util.Calendar; import android.icu.util.GregorianCalendar; import android.icu.util.TimeZone; -import android.util.TimestampedValue; +import android.os.TimestampedValue; import androidx.test.runner.AndroidJUnit4; diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyTest.java index 239d413c12d2..f1e9191ddb4f 100644 --- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyTest.java +++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyTest.java @@ -18,7 +18,7 @@ package com.android.server.timedetector; import static org.junit.Assert.assertEquals; -import android.util.TimestampedValue; +import android.os.TimestampedValue; import androidx.test.runner.AndroidJUnit4; 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 12ba219d0365..1dd7e64690c7 100644 --- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java @@ -365,8 +365,9 @@ public class AppStandbyControllerTests { public void testSetAppStandbyBucket() throws Exception { // For a known package, standby bucket should be set properly reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1); + mInjector.mElapsedRealtime = HOUR_MS; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE, - REASON_MAIN_TIMEOUT, HOUR_MS); + REASON_MAIN_TIMEOUT); assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1)); // For an unknown package, standby bucket should not be set, hence NEVER is returned @@ -374,7 +375,7 @@ public class AppStandbyControllerTests { mController.clearAppIdleForPackage(PACKAGE_UNKNOWN, USER_ID); isPackageInstalled = false; // Mock package is not installed mController.setAppStandbyBucket(PACKAGE_UNKNOWN, USER_ID, STANDBY_BUCKET_ACTIVE, - REASON_MAIN_TIMEOUT, HOUR_MS); + REASON_MAIN_TIMEOUT); isPackageInstalled = true; // Reset mocked variable for other tests assertEquals(STANDBY_BUCKET_NEVER, getStandbyBucket(mController, PACKAGE_UNKNOWN)); } @@ -468,12 +469,13 @@ public class AppStandbyControllerTests { @Test public void testPredictionTimedout() throws Exception { // Set it to timeout or usage, so that prediction can override it + mInjector.mElapsedRealtime = HOUR_MS; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, - REASON_MAIN_TIMEOUT, HOUR_MS); + REASON_MAIN_TIMEOUT); assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1)); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE, - REASON_MAIN_PREDICTED, HOUR_MS); + REASON_MAIN_PREDICTED); assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1)); // Fast forward 12 hours @@ -497,29 +499,31 @@ public class AppStandbyControllerTests { @Test public void testOverrides() throws Exception { // Can force to NEVER + mInjector.mElapsedRealtime = HOUR_MS; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER, - REASON_MAIN_FORCED, 1 * HOUR_MS); + REASON_MAIN_FORCED); assertEquals(STANDBY_BUCKET_NEVER, getStandbyBucket(mController, PACKAGE_1)); // Prediction can't override FORCED reason mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, - REASON_MAIN_FORCED, 1 * HOUR_MS); + REASON_MAIN_FORCED); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET, - REASON_MAIN_PREDICTED, 1 * HOUR_MS); + REASON_MAIN_PREDICTED); assertEquals(STANDBY_BUCKET_FREQUENT, getStandbyBucket(mController, PACKAGE_1)); // Prediction can't override NEVER + mInjector.mElapsedRealtime = 2 * HOUR_MS; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER, - REASON_MAIN_DEFAULT, 2 * HOUR_MS); + REASON_MAIN_DEFAULT); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE, - REASON_MAIN_PREDICTED, 2 * HOUR_MS); + REASON_MAIN_PREDICTED); assertEquals(STANDBY_BUCKET_NEVER, getStandbyBucket(mController, PACKAGE_1)); // Prediction can't set to NEVER mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE, - REASON_MAIN_USAGE, 2 * HOUR_MS); + REASON_MAIN_USAGE); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER, - REASON_MAIN_PREDICTED, 2 * HOUR_MS); + REASON_MAIN_PREDICTED); assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1)); } @@ -530,7 +534,7 @@ public class AppStandbyControllerTests { mInjector.mElapsedRealtime = 2000; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, - REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime); + REASON_MAIN_PREDICTED); assertBucket(STANDBY_BUCKET_ACTIVE); // bucketing works after timeout @@ -554,15 +558,17 @@ public class AppStandbyControllerTests { assertBucket(STANDBY_BUCKET_ACTIVE); mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET, - REASON_MAIN_PREDICTED, 1000); + REASON_MAIN_PREDICTED); assertBucket(STANDBY_BUCKET_ACTIVE); + mInjector.mElapsedRealtime = 2000 + mController.mStrongUsageTimeoutMillis; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, - REASON_MAIN_PREDICTED, 2000 + mController.mStrongUsageTimeoutMillis); + REASON_MAIN_PREDICTED); assertBucket(STANDBY_BUCKET_WORKING_SET); + mInjector.mElapsedRealtime = 2000 + mController.mNotificationSeenTimeoutMillis; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, - REASON_MAIN_PREDICTED, 2000 + mController.mNotificationSeenTimeoutMillis); + REASON_MAIN_PREDICTED); assertBucket(STANDBY_BUCKET_FREQUENT); } @@ -582,18 +588,18 @@ public class AppStandbyControllerTests { // Still in ACTIVE after first USER_INTERACTION times out mInjector.mElapsedRealtime = mController.mStrongUsageTimeoutMillis + 1000; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, - REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime); + REASON_MAIN_PREDICTED); assertBucket(STANDBY_BUCKET_ACTIVE); // Both timed out, so NOTIFICATION_SEEN timeout should be effective mInjector.mElapsedRealtime = mController.mStrongUsageTimeoutMillis * 2 + 2000; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, - REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime); + REASON_MAIN_PREDICTED); assertBucket(STANDBY_BUCKET_WORKING_SET); mInjector.mElapsedRealtime = mController.mNotificationSeenTimeoutMillis + 2000; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, - REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime); + REASON_MAIN_PREDICTED); assertBucket(STANDBY_BUCKET_RARE); } @@ -625,7 +631,7 @@ public class AppStandbyControllerTests { mInjector.mElapsedRealtime = 1 * RARE_THRESHOLD + 100; // Make sure app is in NEVER bucket mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER, - REASON_MAIN_FORCED, mInjector.mElapsedRealtime); + REASON_MAIN_FORCED); mController.checkIdleStates(USER_ID); assertBucket(STANDBY_BUCKET_NEVER); @@ -670,7 +676,7 @@ public class AppStandbyControllerTests { // Predict to ACTIVE mInjector.mElapsedRealtime += 1000; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE, - REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime); + REASON_MAIN_PREDICTED); assertBucket(STANDBY_BUCKET_ACTIVE); // CheckIdleStates should not change the prediction @@ -687,7 +693,7 @@ public class AppStandbyControllerTests { // Predict to FREQUENT mInjector.mElapsedRealtime = RARE_THRESHOLD; mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, - REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime); + REASON_MAIN_PREDICTED); assertBucket(STANDBY_BUCKET_FREQUENT); // Add a short timeout event diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java index eb45960bc82f..bd7d9ecf84d1 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java @@ -20,7 +20,6 @@ import static android.app.NotificationManager.IMPORTANCE_HIGH; import static android.app.NotificationManager.IMPORTANCE_LOW; import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertNull; import static org.mockito.Matchers.any; @@ -65,11 +64,10 @@ public class NotificationChannelExtractorTest extends UiServiceTestCase { NotificationChannel updatedChannel = new NotificationChannel("a", "", IMPORTANCE_HIGH); - when(mConfig.getNotificationChannel(any(), anyInt(), eq("a"), eq(false))) + when(mConfig.getNotificationChannel(any(), anyInt(), eq("a"), eq(null), eq(false))) .thenReturn(updatedChannel); assertNull(extractor.process(r)); assertEquals(updatedChannel, r.getChannel()); } - } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 95617b1162a8..62f52306ea49 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -112,6 +112,7 @@ import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.IBinder; +import android.os.Parcel; import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; @@ -133,6 +134,7 @@ import android.testing.TestableLooper.RunWithLooper; import android.testing.TestablePermissions; import android.testing.TestableResources; import android.text.Html; +import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; @@ -475,7 +477,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mPreferencesHelper.bubblesEnabled()).thenReturn(globalEnabled); when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(pkgEnabled); when(mPreferencesHelper.getNotificationChannel( - anyString(), anyInt(), anyString(), anyBoolean())).thenReturn( + anyString(), anyInt(), anyString(), eq(null), anyBoolean())).thenReturn( mTestNotificationChannel); when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn( mTestNotificationChannel.getImportance()); @@ -492,23 +494,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { return sbn; } - private NotificationRecord generateNotificationRecord(NotificationChannel channel, int id, String groupKey, boolean isSummary) { - return generateNotificationRecord(channel, id, groupKey, isSummary, false /* isBubble */); - } - - private NotificationRecord generateNotificationRecord(NotificationChannel channel, int id, - String groupKey, boolean isSummary, boolean isBubble) { Notification.Builder nb = new Notification.Builder(mContext, channel.getId()) .setContentTitle("foo") .setSmallIcon(android.R.drawable.sym_def_app_icon) .setGroup(groupKey) .setGroupSummary(isSummary); - if (isBubble) { - nb.setBubbleMetadata(getBasicBubbleMetadataBuilder().build()); - } - StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, id, "tag" + System.currentTimeMillis(), mUid, 0, nb.build(), new UserHandle(mUid), null, 0); @@ -521,11 +513,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { private NotificationRecord generateNotificationRecord(NotificationChannel channel, Notification.TvExtender extender) { - return generateNotificationRecord(channel, extender, false /* isBubble */); - } - - private NotificationRecord generateNotificationRecord(NotificationChannel channel, - Notification.TvExtender extender, boolean isBubble) { if (channel == null) { channel = mTestNotificationChannel; } @@ -535,9 +522,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { if (extender != null) { nb.extend(extender); } - if (isBubble) { - nb.setBubbleMetadata(getBasicBubbleMetadataBuilder().build()); - } StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0, nb.build(), new UserHandle(mUid), null, 0); return new NotificationRecord(mContext, sbn, channel); @@ -555,6 +539,26 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { return new NotificationRecord(mContext, sbn, channel); } + private NotificationRecord generateMessageBubbleNotifRecord(NotificationChannel channel, + String tag) { + return generateMessageBubbleNotifRecord(true, channel, 1, tag, null, false); + } + + private NotificationRecord generateMessageBubbleNotifRecord(boolean addMetadata, + NotificationChannel channel, int id, String tag, String groupKey, boolean isSummary) { + if (channel == null) { + channel = mTestNotificationChannel; + } + if (tag == null) { + tag = "tag"; + } + Notification.Builder nb = getMessageStyleNotifBuilder(addMetadata, groupKey, isSummary); + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, id, + tag, mUid, 0, + nb.build(), new UserHandle(mUid), null, 0); + return new NotificationRecord(mContext, sbn, channel); + } + private Map<String, Answer> getSignalExtractorSideEffects() { Map<String, Answer> answers = new ArrayMap<>(); @@ -610,23 +614,57 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { false); } - private Notification.BubbleMetadata.Builder getBasicBubbleMetadataBuilder() { + private Notification.BubbleMetadata.Builder getBubbleMetadataBuilder() { PendingIntent pi = PendingIntent.getActivity(mContext, 0, new Intent(), 0); return new Notification.BubbleMetadata.Builder() .setIntent(pi) .setIcon(Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon)); } + private Notification.Builder getMessageStyleNotifBuilder(boolean addBubbleMetadata, + String groupKey, boolean isSummary) { + // Give it a person + Person person = new Person.Builder() + .setName("bubblebot") + .build(); + // It needs remote input to be bubble-able + RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build(); + PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0); + Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon); + Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply", + inputIntent).addRemoteInput(remoteInput) + .build(); + // Make it messaging style + Notification.Builder nb = new Notification.Builder(mContext, + mTestNotificationChannel.getId()) + .setContentTitle("foo") + .setStyle(new Notification.MessagingStyle(person) + .setConversationTitle("Bubble Chat") + .addMessage("Hello?", + SystemClock.currentThreadTimeMillis() - 300000, person) + .addMessage("Is it me you're looking for?", + SystemClock.currentThreadTimeMillis(), person) + ) + .setActions(replyAction) + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setGroupSummary(isSummary); + if (groupKey != null) { + nb.setGroup(groupKey); + } + if (addBubbleMetadata) { + nb.setBubbleMetadata(getBubbleMetadataBuilder().build()); + } + return nb; + } + private NotificationRecord addGroupWithBubblesAndValidateAdded(boolean summaryAutoCancel) throws RemoteException { - // Notification that has bubble metadata - NotificationRecord nrBubble = generateNotificationRecord(mTestNotificationChannel, 1, - "BUBBLE_GROUP", false /* isSummary */, true /* isBubble */); + String groupKey = "BUBBLE_GROUP"; - // Make the package foreground so that we're allowed to be a bubble - when(mActivityManager.getPackageImportance(nrBubble.sbn.getPackageName())).thenReturn( - IMPORTANCE_FOREGROUND); + // Notification that has bubble metadata + NotificationRecord nrBubble = generateMessageBubbleNotifRecord(true /* addMetadata */, + mTestNotificationChannel, 1 /* id */, "tag", groupKey, false /* isSummary */); mBinderService.enqueueNotificationWithTag(PKG, PKG, nrBubble.sbn.getTag(), nrBubble.sbn.getId(), nrBubble.sbn.getNotification(), nrBubble.sbn.getUserId()); @@ -637,9 +675,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertEquals(1, notifsAfter.length); assertTrue((notifsAfter[0].getNotification().flags & FLAG_BUBBLE) != 0); - // Plain notification without bubble metadata - NotificationRecord nrPlain = generateNotificationRecord(mTestNotificationChannel, 2, - "BUBBLE_GROUP", false /* isSummary */, false /* isBubble */); + // Notification without bubble metadata + NotificationRecord nrPlain = generateMessageBubbleNotifRecord(false /* addMetadata */, + mTestNotificationChannel, 2 /* id */, "tag", groupKey, false /* isSummary */); + mBinderService.enqueueNotificationWithTag(PKG, PKG, nrPlain.sbn.getTag(), nrPlain.sbn.getId(), nrPlain.sbn.getNotification(), nrPlain.sbn.getUserId()); waitForIdle(); @@ -648,8 +687,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertEquals(2, notifsAfter.length); // Summary notification for both of those - NotificationRecord nrSummary = generateNotificationRecord(mTestNotificationChannel, 3, - "BUBBLE_GROUP", true /* isSummary */, false /* isBubble */); + NotificationRecord nrSummary = generateMessageBubbleNotifRecord(false /* addMetadata */, + mTestNotificationChannel, 3 /* id */, "tag", groupKey, true /* isSummary */); + if (summaryAutoCancel) { nrSummary.getNotification().flags |= FLAG_AUTO_CANCEL; } @@ -1724,7 +1764,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mBinderService.enqueueNotificationWithTag(PKG, PKG, "testTvExtenderChannelOverride_onTv", 0, generateNotificationRecord(null, tv).getNotification(), 0); verify(mPreferencesHelper, times(1)).getNotificationChannel( - anyString(), anyInt(), eq("foo"), anyBoolean()); + anyString(), anyInt(), eq("foo"), eq(null), anyBoolean()); } @Test @@ -1739,7 +1779,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mBinderService.enqueueNotificationWithTag(PKG, PKG, "testTvExtenderChannelOverride_notOnTv", 0, generateNotificationRecord(null, tv).getNotification(), 0); verify(mPreferencesHelper, times(1)).getNotificationChannel( - anyString(), anyInt(), eq(mTestNotificationChannel.getId()), anyBoolean()); + anyString(), anyInt(), eq(mTestNotificationChannel.getId()), eq(null), + anyBoolean()); } @Test @@ -3172,9 +3213,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { final NotificationVisibility nv = NotificationVisibility.obtain(r.getKey(), 1, 2, true); mService.mNotificationDelegate.onNotificationVisibilityChanged( new NotificationVisibility[] {nv}, new NotificationVisibility[]{}); + verify(mAssistants).notifyAssistantVisibilityChangedLocked(eq(r.sbn), eq(true)); assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasSeen()); mService.mNotificationDelegate.onNotificationVisibilityChanged( new NotificationVisibility[] {}, new NotificationVisibility[]{nv}); + verify(mAssistants).notifyAssistantVisibilityChangedLocked(eq(r.sbn), eq(false)); assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasSeen()); } @@ -4425,6 +4468,16 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testOnPanelRevealedAndHidden() { + int items = 5; + mService.mNotificationDelegate.onPanelRevealed(false, items); + verify(mAssistants, times(1)).onPanelRevealed(eq(items)); + + mService.mNotificationDelegate.onPanelHidden(); + verify(mAssistants, times(1)).onPanelHidden(); + } + + @Test public void testOnNotificationSmartReplySent() { final int replyIndex = 2; final String reply = "Hello"; @@ -4708,13 +4761,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // Bubbles are allowed! setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */); - // Notif with bubble metadata but not our other misc requirements - NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel, - null /* tvExtender */, true /* isBubble */); - - // Say we're foreground - when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn( - IMPORTANCE_FOREGROUND); + NotificationRecord nr = + generateMessageBubbleNotifRecord(mTestNotificationChannel, "testFlagBubble"); mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(), nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); @@ -4732,13 +4780,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // Bubbles are allowed! setUpPrefsForBubbles(true /* global */, false /* app */, true /* channel */); - // Notif with bubble metadata but not our other misc requirements - NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel, - null /* tvExtender */, true /* isBubble */); - - // Say we're foreground - when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn( - IMPORTANCE_FOREGROUND); + NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel, + "testFlagBubble_noFlag_appNotAllowed"); mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(), nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); @@ -4752,174 +4795,40 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testFlagBubbleNotifs_flag_appForeground() throws RemoteException { + public void testFlagBubbleNotifs_noFlag_whenAppForeground() throws RemoteException { // Bubbles are allowed! setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */); // Notif with bubble metadata but not our other misc requirements - NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel, - null /* tvExtender */, true /* isBubble */); + Notification.Builder nb = new Notification.Builder(mContext, + mTestNotificationChannel.getId()) + .setContentTitle("foo") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setBubbleMetadata(getBubbleMetadataBuilder().build()); + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, "tag", mUid, 0, + nb.build(), new UserHandle(mUid), null, 0); + NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); // Say we're foreground when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn( IMPORTANCE_FOREGROUND); - - mBinderService.enqueueNotificationWithTag(PKG, PKG, - nr.sbn.getTag(), nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); - waitForIdle(); - - // yes allowed, yes foreground, yes bubble - assertTrue(mService.getNotificationRecord( - nr.sbn.getKey()).getNotification().isBubbleNotification()); - } - - @Test - public void testFlagBubbleNotifs_noFlag_appNotForeground() throws RemoteException { - // Bubbles are allowed! - setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */); - - // Notif with bubble metadata but not our other misc requirements - NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel, - null /* tvExtender */, true /* isBubble */); - - // Make sure we're NOT foreground - when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn( - IMPORTANCE_VISIBLE); - mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(), nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); waitForIdle(); - // yes allowed but NOT foreground, no bubble + // if notif isn't configured properly it doesn't get to bubble just because app is + // foreground. assertFalse(mService.getNotificationRecord( nr.sbn.getKey()).getNotification().isBubbleNotification()); } @Test - public void testFlagBubbleNotifs_flag_previousForegroundFlag() throws RemoteException { - // Bubbles are allowed! - setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */); - - // Notif with bubble metadata but not our other misc requirements - NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel, - null /* tvExtender */, true /* isBubble */); - - // Send notif when we're foreground - when(mActivityManager.getPackageImportance(nr1.sbn.getPackageName())).thenReturn( - IMPORTANCE_FOREGROUND); - mBinderService.enqueueNotificationWithTag(PKG, PKG, nr1.sbn.getTag(), - nr1.sbn.getId(), nr1.sbn.getNotification(), nr1.sbn.getUserId()); - waitForIdle(); - - // yes allowed, yes foreground, yes bubble - assertTrue(mService.getNotificationRecord( - nr1.sbn.getKey()).getNotification().isBubbleNotification()); - - // Send a new update when we're not foreground - NotificationRecord nr2 = generateNotificationRecord(mTestNotificationChannel, - null /* tvExtender */, true /* isBubble */); - - when(mActivityManager.getPackageImportance(nr2.sbn.getPackageName())).thenReturn( - IMPORTANCE_VISIBLE); - mBinderService.enqueueNotificationWithTag(PKG, PKG, nr2.sbn.getTag(), - nr2.sbn.getId(), nr2.sbn.getNotification(), nr2.sbn.getUserId()); - waitForIdle(); - - // yes allowed, previously foreground / flagged, yes bubble - assertTrue(mService.getNotificationRecord( - nr2.sbn.getKey()).getNotification().isBubbleNotification()); - - StatusBarNotification[] notifs2 = mBinderService.getActiveNotifications(PKG); - assertEquals(1, notifs2.length); - assertEquals(1, mService.getNotificationRecordCount()); - } - - @Test - public void testFlagBubbleNotifs_noFlag_previousForegroundFlag_afterRemoval() - throws RemoteException { - // Bubbles are allowed! - setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */); - - // Notif with bubble metadata but not our other misc requirements - NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel, - null /* tvExtender */, true /* isBubble */); - - // Send notif when we're foreground - when(mActivityManager.getPackageImportance(nr1.sbn.getPackageName())).thenReturn( - IMPORTANCE_FOREGROUND); - mBinderService.enqueueNotificationWithTag(PKG, PKG, nr1.sbn.getTag(), - nr1.sbn.getId(), nr1.sbn.getNotification(), nr1.sbn.getUserId()); - waitForIdle(); - - // yes allowed, yes foreground, yes bubble - assertTrue(mService.getNotificationRecord( - nr1.sbn.getKey()).getNotification().isBubbleNotification()); - - // Remove the bubble - mBinderService.cancelNotificationWithTag(PKG, PKG, nr1.sbn.getTag(), nr1.sbn.getId(), - nr1.sbn.getUserId()); - waitForIdle(); - - StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG); - assertEquals(0, notifs.length); - assertEquals(0, mService.getNotificationRecordCount()); - - // Send a new update when we're not foreground - NotificationRecord nr2 = generateNotificationRecord(mTestNotificationChannel, - null /* tvExtender */, true /* isBubble */); - - when(mActivityManager.getPackageImportance(nr2.sbn.getPackageName())).thenReturn( - IMPORTANCE_VISIBLE); - mBinderService.enqueueNotificationWithTag(PKG, PKG, nr2.sbn.getTag(), - nr2.sbn.getId(), nr2.sbn.getNotification(), nr2.sbn.getUserId()); - waitForIdle(); - - // yes allowed, but was removed & no foreground, so no bubble - assertFalse(mService.getNotificationRecord( - nr2.sbn.getKey()).getNotification().isBubbleNotification()); - - StatusBarNotification[] notifs2 = mBinderService.getActiveNotifications(PKG); - assertEquals(1, notifs2.length); - assertEquals(1, mService.getNotificationRecordCount()); - } - - @Test public void testFlagBubbleNotifs_flag_messaging() throws RemoteException { // Bubbles are allowed! setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */); - // Give it bubble metadata - Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build(); - // Give it a person - Person person = new Person.Builder() - .setName("bubblebot") - .build(); - // It needs remote input to be bubble-able - RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build(); - PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0); - Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon); - Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply", - inputIntent).addRemoteInput(remoteInput) - .build(); - // Make it messaging style - Notification.Builder nb = new Notification.Builder(mContext, - mTestNotificationChannel.getId()) - .setContentTitle("foo") - .setBubbleMetadata(data) - .setStyle(new Notification.MessagingStyle(person) - .setConversationTitle("Bubble Chat") - .addMessage("Hello?", - SystemClock.currentThreadTimeMillis() - 300000, person) - .addMessage("Is it me you're looking for?", - SystemClock.currentThreadTimeMillis(), person) - ) - .setActions(replyAction) - .setSmallIcon(android.R.drawable.sym_def_app_icon); - - StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, - "testFlagBubbleNotifs_flag_messaging", mUid, 0, - nb.build(), new UserHandle(mUid), null, 0); - NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel, + "testFlagBubbleNotifs_flag_messaging"); mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(), nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); @@ -4927,7 +4836,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // yes allowed, yes messaging, yes bubble assertTrue(mService.getNotificationRecord( - sbn.getKey()).getNotification().isBubbleNotification()); + nr.sbn.getKey()).getNotification().isBubbleNotification()); } @Test @@ -4936,7 +4845,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */); // Give it bubble metadata - Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build(); + Notification.BubbleMetadata data = getBubbleMetadataBuilder().build(); // Give it a person Person person = new Person.Builder() .setName("bubblebot") @@ -4972,7 +4881,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */); // Give it bubble metadata - Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build(); + Notification.BubbleMetadata data = getBubbleMetadataBuilder().build(); // Give it a person Person person = new Person.Builder() .setName("bubblebot") @@ -5005,7 +4914,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */); // Give it bubble metadata - Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build(); + Notification.BubbleMetadata data = getBubbleMetadataBuilder().build(); // Make it a phone call Notification.Builder nb = new Notification.Builder(mContext, mTestNotificationChannel.getId()) @@ -5036,7 +4945,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */); // Give it bubble metadata - Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build(); + Notification.BubbleMetadata data = getBubbleMetadataBuilder().build(); // Give it a person Person person = new Person.Builder() .setName("bubblebot") @@ -5070,30 +4979,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // Bubbles are NOT allowed! setUpPrefsForBubbles(false /* global */, true /* app */, true /* channel */); - // Give it bubble metadata - Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build(); - // Give it a person - Person person = new Person.Builder() - .setName("bubblebot") - .build(); - // Make it messaging style - Notification.Builder nb = new Notification.Builder(mContext, - mTestNotificationChannel.getId()) - .setContentTitle("foo") - .setBubbleMetadata(data) - .setStyle(new Notification.MessagingStyle(person) - .setConversationTitle("Bubble Chat") - .addMessage("Hello?", - SystemClock.currentThreadTimeMillis() - 300000, person) - .addMessage("Is it me you're looking for?", - SystemClock.currentThreadTimeMillis(), person) - ) - .setSmallIcon(android.R.drawable.sym_def_app_icon); - - StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, - "testFlagBubbleNotifs_noFlag_messaging_appNotAllowed", mUid, 0, - nb.build(), new UserHandle(mUid), null, 0); - NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel, + "testFlagBubbleNotifs_noFlag_messaging_appNotAllowed"); // Post the notification mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(), @@ -5102,7 +4989,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // not allowed, no bubble assertFalse(mService.getNotificationRecord( - sbn.getKey()).getNotification().isBubbleNotification()); + nr.sbn.getKey()).getNotification().isBubbleNotification()); } @Test @@ -5110,8 +4997,14 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // Bubbles are allowed! setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */); - // Notif WITHOUT bubble metadata - NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel); + // Messaging notif WITHOUT bubble metadata + Notification.Builder nb = getMessageStyleNotifBuilder(false /* addBubbleMetadata */, + null /* groupKey */, false /* isSummary */); + + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, + "testFlagBubbleNotifs_noFlag_notBubble", mUid, 0, + nb.build(), new UserHandle(mUid), null, 0); + NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); // Post the notification mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(), @@ -5128,39 +5021,17 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // Bubbles are allowed except on this channel setUpPrefsForBubbles(true /* global */, true /* app */, false /* channel */); - // Give it bubble metadata - Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build(); - // Give it a person - Person person = new Person.Builder() - .setName("bubblebot") - .build(); - // Make it messaging style - Notification.Builder nb = new Notification.Builder(mContext, - mTestNotificationChannel.getId()) - .setContentTitle("foo") - .setBubbleMetadata(data) - .setStyle(new Notification.MessagingStyle(person) - .setConversationTitle("Bubble Chat") - .addMessage("Hello?", - SystemClock.currentThreadTimeMillis() - 300000, person) - .addMessage("Is it me you're looking for?", - SystemClock.currentThreadTimeMillis(), person) - ) - .setSmallIcon(android.R.drawable.sym_def_app_icon); - - StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, - "testFlagBubbleNotifs_noFlag_messaging_channelNotAllowed", mUid, 0, - nb.build(), new UserHandle(mUid), null, 0); - NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel, + "testFlagBubbleNotifs_noFlag_messaging_channelNotAllowed"); // Post the notification - mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(), + mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(), nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); waitForIdle(); // channel not allowed, no bubble assertFalse(mService.getNotificationRecord( - sbn.getKey()).getNotification().isBubbleNotification()); + nr.sbn.getKey()).getNotification().isBubbleNotification()); } @Test @@ -5169,7 +5040,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { setUpPrefsForBubbles(false /* global */, true /* app */, true /* channel */); // Give it bubble metadata - Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build(); + Notification.BubbleMetadata data = getBubbleMetadataBuilder().build(); // Give it a person Person person = new Person.Builder() .setName("bubblebot") @@ -5188,10 +5059,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { nb.build(), new UserHandle(mUid), null, 0); // Make sure it has foreground service sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE; - NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(), - nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); + sbn.getId(), sbn.getNotification(), sbn.getUserId()); waitForIdle(); // yes phone call, yes person, yes foreground service, but not allowed, no bubble @@ -5205,7 +5075,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { setUpPrefsForBubbles(true /* global */, true /* app */, false /* channel */); // Give it bubble metadata - Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build(); + Notification.BubbleMetadata data = getBubbleMetadataBuilder().build(); // Give it a person Person person = new Person.Builder() .setName("bubblebot") @@ -5412,13 +5282,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // Bubbles are allowed! setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */); - // Notif with bubble metadata but not our other misc requirements - NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel, - null /* tvExtender */, true /* isBubble */); - - // Say we're foreground - when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn( - IMPORTANCE_FOREGROUND); + // Notif with bubble metadata + NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel, + "testNotificationBubbleChanged_false"); mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(), nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); @@ -5447,9 +5313,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // Bubbles are allowed! setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */); - // Plain notification that has bubble metadata + // Notif that is not a bubble NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel, - null /* tvExtender */, true /* isBubble */); + 1, null, false); mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(), nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); waitForIdle(); @@ -5459,9 +5325,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertEquals(1, notifsBefore.length); assertEquals((notifsBefore[0].getNotification().flags & FLAG_BUBBLE), 0); - // Make the package foreground so that we're allowed to be a bubble - when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn( - IMPORTANCE_FOREGROUND); + // Update the notification to be message style / meet bubble requirements + NotificationRecord nr2 = generateMessageBubbleNotifRecord(mTestNotificationChannel, + nr.sbn.getTag()); + mBinderService.enqueueNotificationWithTag(PKG, PKG, nr2.sbn.getTag(), + nr2.sbn.getId(), nr2.sbn.getNotification(), nr2.sbn.getUserId()); + waitForIdle(); // Reset as this is called when the notif is first sent reset(mListeners); @@ -5482,8 +5351,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */); // Notif that is not a bubble - NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel, - null /* tvExtender */, true /* isBubble */); + NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel); mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(), nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); waitForIdle(); @@ -5681,26 +5549,17 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // Bubbles are allowed! setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */); - // Plain notification that has bubble metadata - NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel, - null /* tvExtender */, true /* isBubble */); + // And we are low ram + when(mActivityManager.isLowRamDevice()).thenReturn(true); + + // Notification that would typically bubble + NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel, + "testNotificationBubbles_disabled_lowRamDevice"); mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(), nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); waitForIdle(); - // Would be a normal notification because wouldn't have met requirements to bubble - StatusBarNotification[] notifsBefore = mBinderService.getActiveNotifications(PKG); - assertEquals(1, notifsBefore.length); - assertEquals((notifsBefore[0].getNotification().flags & FLAG_BUBBLE), 0); - - // Make the package foreground so that we're allowed to be a bubble - when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn( - IMPORTANCE_FOREGROUND); - - // And we are low ram - when(mActivityManager.isLowRamDevice()).thenReturn(true); - - // We wouldn't be a bubble because the notification didn't meet requirements (low ram) + // But we wouldn't be a bubble because the device is low ram & all bubbles are disabled. StatusBarNotification[] notifsAfter = mBinderService.getActiveNotifications(PKG); assertEquals(1, notifsAfter.length); assertEquals((notifsAfter[0].getNotification().flags & FLAG_BUBBLE), 0); @@ -5774,50 +5633,23 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // Bubbles are allowed! setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */); - // Give it bubble metadata - Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder() - .setSuppressNotification(true) - .setAutoExpandBubble(true).build(); - // Give it a person - Person person = new Person.Builder() - .setName("bubblebot") - .build(); - // It needs remote input to be bubble-able - RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build(); - PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0); - Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon); - Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply", - inputIntent).addRemoteInput(remoteInput) - .build(); - // Make it messaging style - Notification.Builder nb = new Notification.Builder(mContext, - mTestNotificationChannel.getId()) - .setContentTitle("foo") - .setBubbleMetadata(data) - .setStyle(new Notification.MessagingStyle(person) - .setConversationTitle("Bubble Chat") - .addMessage("Hello?", - SystemClock.currentThreadTimeMillis() - 300000, person) - .addMessage("Is it me you're looking for?", - SystemClock.currentThreadTimeMillis(), person) - ) - .setActions(replyAction) - .setSmallIcon(android.R.drawable.sym_def_app_icon); - - StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0, - nb.build(), new UserHandle(mUid), null, 0); - NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel, + "testNotificationBubbles_flagAutoExpandForeground_fails_notForeground"); + // Modify metadata flags + nr.sbn.getNotification().getBubbleMetadata().setFlags( + Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE + | Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION); // Ensure we're not foreground when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn( IMPORTANCE_VISIBLE); - mBinderService.enqueueNotificationWithTag(PKG, PKG, null, + mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(), nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); waitForIdle(); // yes allowed, yes messaging, yes bubble - Notification notif = mService.getNotificationRecord(sbn.getKey()).getNotification(); + Notification notif = mService.getNotificationRecord(nr.sbn.getKey()).getNotification(); assertTrue(notif.isBubbleNotification()); // Our flags should have failed since we're not foreground @@ -5831,53 +5663,26 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // Bubbles are allowed! setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */); - // Give it bubble metadata - Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder() - .setSuppressNotification(true) - .setAutoExpandBubble(true).build(); - // Give it a person - Person person = new Person.Builder() - .setName("bubblebot") - .build(); - // It needs remote input to be bubble-able - RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build(); - PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0); - Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon); - Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply", - inputIntent).addRemoteInput(remoteInput) - .build(); - // Make it messaging style - Notification.Builder nb = new Notification.Builder(mContext, - mTestNotificationChannel.getId()) - .setContentTitle("foo") - .setBubbleMetadata(data) - .setStyle(new Notification.MessagingStyle(person) - .setConversationTitle("Bubble Chat") - .addMessage("Hello?", - SystemClock.currentThreadTimeMillis() - 300000, person) - .addMessage("Is it me you're looking for?", - SystemClock.currentThreadTimeMillis(), person) - ) - .setActions(replyAction) - .setSmallIcon(android.R.drawable.sym_def_app_icon); - - StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0, - nb.build(), new UserHandle(mUid), null, 0); - NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel, + "testNotificationBubbles_flagAutoExpandForeground_succeeds_foreground"); + // Modify metadata flags + nr.sbn.getNotification().getBubbleMetadata().setFlags( + Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE + | Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION); // Ensure we are in the foreground when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn( IMPORTANCE_FOREGROUND); - mBinderService.enqueueNotificationWithTag(PKG, PKG, null, + mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(), nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); waitForIdle(); // yes allowed, yes messaging, yes bubble - Notification notif = mService.getNotificationRecord(sbn.getKey()).getNotification(); + Notification notif = mService.getNotificationRecord(nr.sbn.getKey()).getNotification(); assertTrue(notif.isBubbleNotification()); - // Our flags should have failed since we are foreground + // Our flags should have passed since we are foreground assertTrue(notif.getBubbleMetadata().getAutoExpandBubble()); assertTrue(notif.getBubbleMetadata().isNotificationSuppressed()); } @@ -6004,11 +5809,86 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testNotificationHistory_addNoisyNotification() throws Exception { NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel, - null /* tvExtender */, false); + null /* tvExtender */); mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(), nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId()); waitForIdle(); verify(mHistoryManager, times(1)).addNotification(any()); } + + @Test + public void createConversationNotificationChannel() throws Exception { + NotificationChannel original = new NotificationChannel("a", "a", IMPORTANCE_HIGH); + original.setAllowBubbles(!original.canBubble()); + original.setShowBadge(!original.canShowBadge()); + + Parcel parcel = Parcel.obtain(); + original.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + NotificationChannel orig = NotificationChannel.CREATOR.createFromParcel(parcel); + assertEquals(original, orig); + assertFalse(TextUtils.isEmpty(orig.getName())); + + mBinderService.createNotificationChannels(PKG, new ParceledListSlice(Arrays.asList( + orig))); + + mBinderService.createConversationNotificationChannelForPackage(PKG, mUid, orig, "friend"); + + NotificationChannel friendChannel = mBinderService.getConversationNotificationChannel( + PKG, 0, PKG, original.getId(), "friend"); + + assertEquals(original.getName(), friendChannel.getName()); + assertEquals(original.getId(), friendChannel.getParentChannelId()); + assertEquals("friend", friendChannel.getConversationId()); + assertEquals(null, original.getConversationId()); + assertEquals(original.canShowBadge(), friendChannel.canShowBadge()); + assertEquals(original.canBubble(), friendChannel.canBubble()); + assertFalse(original.getId().equals(friendChannel.getId())); + assertNotNull(friendChannel.getId()); + } + + @Test + public void deleteConversationNotificationChannels() throws Exception { + NotificationChannel messagesParent = + new NotificationChannel("messages", "messages", IMPORTANCE_HIGH); + Parcel msgParcel = Parcel.obtain(); + messagesParent.writeToParcel(msgParcel, 0); + msgParcel.setDataPosition(0); + + NotificationChannel callsParent = + new NotificationChannel("calls", "calls", IMPORTANCE_HIGH); + Parcel callParcel = Parcel.obtain(); + callsParent.writeToParcel(callParcel, 0); + callParcel.setDataPosition(0); + + mBinderService.createNotificationChannels(PKG, new ParceledListSlice(Arrays.asList( + messagesParent, callsParent))); + + String conversationId = "friend"; + + mBinderService.createConversationNotificationChannelForPackage( + PKG, mUid, NotificationChannel.CREATOR.createFromParcel(msgParcel), conversationId); + mBinderService.createConversationNotificationChannelForPackage( + PKG, mUid, NotificationChannel.CREATOR.createFromParcel(callParcel), + conversationId); + + NotificationChannel messagesChild = mBinderService.getConversationNotificationChannel( + PKG, 0, PKG, messagesParent.getId(), conversationId); + NotificationChannel callsChild = mBinderService.getConversationNotificationChannel( + PKG, 0, PKG, callsParent.getId(), conversationId); + + assertEquals(messagesParent.getId(), messagesChild.getParentChannelId()); + assertEquals(conversationId, messagesChild.getConversationId()); + + assertEquals(callsParent.getId(), callsChild.getParentChannelId()); + assertEquals(conversationId, callsChild.getConversationId()); + + mBinderService.deleteConversationNotificationChannels(PKG, mUid, conversationId); + + assertNull(mBinderService.getConversationNotificationChannel( + PKG, 0, PKG, messagesParent.getId(), conversationId)); + assertNull(mBinderService.getConversationNotificationChannel( + PKG, 0, PKG, callsParent.getId(), conversationId)); + } } 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 8961796ed617..7173e0cb625c 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -15,6 +15,7 @@ */ package com.android.server.notification; +import static android.app.NotificationChannel.CONVERSATION_CHANNEL_ID_FORMAT; import static android.app.NotificationManager.IMPORTANCE_DEFAULT; import static android.app.NotificationManager.IMPORTANCE_HIGH; import static android.app.NotificationManager.IMPORTANCE_LOW; @@ -224,6 +225,26 @@ public class PreferencesHelperTest extends UiServiceTestCase { assertEquals(expected.getGroup(), actual.getGroup()); assertEquals(expected.getAudioAttributes(), actual.getAudioAttributes()); assertEquals(expected.getLightColor(), actual.getLightColor()); + assertEquals(expected.getParentChannelId(), actual.getParentChannelId()); + assertEquals(expected.getConversationId(), actual.getConversationId()); + } + + private void compareChannelsParentChild(NotificationChannel parent, + NotificationChannel actual, String conversationId) { + assertEquals(parent.getName(), actual.getName()); + assertEquals(parent.getDescription(), actual.getDescription()); + assertEquals(parent.shouldVibrate(), actual.shouldVibrate()); + assertEquals(parent.shouldShowLights(), actual.shouldShowLights()); + assertEquals(parent.getImportance(), actual.getImportance()); + assertEquals(parent.getLockscreenVisibility(), actual.getLockscreenVisibility()); + assertEquals(parent.getSound(), actual.getSound()); + assertEquals(parent.canBypassDnd(), actual.canBypassDnd()); + assertTrue(Arrays.equals(parent.getVibrationPattern(), actual.getVibrationPattern())); + assertEquals(parent.getGroup(), actual.getGroup()); + assertEquals(parent.getAudioAttributes(), actual.getAudioAttributes()); + assertEquals(parent.getLightColor(), actual.getLightColor()); + assertEquals(parent.getId(), actual.getParentChannelId()); + assertEquals(conversationId, actual.getConversationId()); } private void compareGroups(NotificationChannelGroup expected, NotificationChannelGroup actual) { @@ -2786,4 +2807,40 @@ public class PreferencesHelperTest extends UiServiceTestCase { assertEquals(user10Importance, mHelper.getNotificationChannel( pkg, uidList10[0], channelId, false).getImportance()); } + + @Test + public void testGetConversationNotificationChannel() { + String conversationId = "friend"; + + NotificationChannel parent = + new NotificationChannel("parent", "messages", IMPORTANCE_DEFAULT); + mHelper.createNotificationChannel(PKG_O, UID_O, parent, true, false); + + NotificationChannel friend = new NotificationChannel(String.format( + CONVERSATION_CHANNEL_ID_FORMAT, parent.getId(), conversationId), + "messages", IMPORTANCE_DEFAULT); + friend.setConversationId(parent.getId(), conversationId); + mHelper.createNotificationChannel(PKG_O, UID_O, friend, true, false); + + compareChannelsParentChild(parent, mHelper.getNotificationChannel( + PKG_O, UID_O, parent.getId(), conversationId, false), conversationId); + } + + @Test + public void testConversationNotificationChannelsRequireParents() { + String parentId = "does not exist"; + String conversationId = "friend"; + + NotificationChannel friend = new NotificationChannel(String.format( + CONVERSATION_CHANNEL_ID_FORMAT, parentId, conversationId), + "messages", IMPORTANCE_DEFAULT); + friend.setConversationId(parentId, conversationId); + + try { + mHelper.createNotificationChannel(PKG_O, UID_O, friend, true, false); + fail("allowed creation of conversation channel without a parent"); + } catch (IllegalArgumentException e) { + // good + } + } } 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 73b2f6b2dc37..a3e94599cad3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -521,11 +521,12 @@ public class ActivityRecordTests extends ActivityTestsBase { } @Test - public void testShouldPauseWhenMakeClientVisible() { + public void testShouldStartWhenMakeClientActive() { ActivityRecord topActivity = new ActivityBuilder(mService).setTask(mTask).build(); topActivity.setOccludesParent(false); mActivity.setState(ActivityStack.ActivityState.STOPPED, "Testing"); - mActivity.makeClientVisible(); + mActivity.setVisibility(true); + mActivity.makeActiveIfNeeded(null /* activeActivity */); assertEquals(STARTED, mActivity.getState()); } diff --git a/services/tests/wmtests/src/com/android/server/wm/AnimatingActivityRegistryTest.java b/services/tests/wmtests/src/com/android/server/wm/AnimatingActivityRegistryTest.java index 574517a00f6f..71390dbbe4a3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AnimatingActivityRegistryTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AnimatingActivityRegistryTest.java @@ -40,7 +40,7 @@ import org.mockito.MockitoAnnotations; * Tests for the {@link ActivityStack} class. * * Build/Install/Run: - * atest FrameworksServicesTests:AnimatingActivityRegistryTest + * atest WmTests:AnimatingActivityRegistryTest */ @SmallTest @Presubmit diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java index 1311889d5605..f6213bd94ddd 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java @@ -42,7 +42,7 @@ import org.junit.runner.RunWith; /** * Build/Install/Run: - * atest FrameworksServicesTests:AppTransitionControllerTest + * atest WmTests:AppTransitionControllerTest */ @SmallTest @Presubmit diff --git a/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java index 0ad0f95f64c1..1dda535cfb95 100644 --- a/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java @@ -60,7 +60,7 @@ import org.junit.runner.RunWith; * appropriately. * * Build/Install/Run: - * atest FrameworksServicesTests:BoundsAnimationControllerTests + * atest WmTests:BoundsAnimationControllerTests */ @SmallTest @Presubmit diff --git a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java index 0aa6961d20b3..e8f7849ef96a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java @@ -40,7 +40,7 @@ import org.junit.runner.RunWith; /** * Build/Install/Run: - * atest FrameworksServicesTests:DimmerTests + * atest WmTests:DimmerTests */ @Presubmit @RunWith(WindowTestRunner.class) 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 73dd2df27e95..f2ba97c269d5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -57,6 +57,8 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.server.wm.WindowContainer.POSITION_TOP; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL; +import static com.google.common.truth.Truth.assertThat; + import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -86,6 +88,7 @@ import android.view.IWindowManager; import android.view.MotionEvent; import android.view.Surface; import android.view.ViewRootImpl; +import android.view.WindowManager; import android.view.test.InsetsModeSession; import androidx.test.filters.SmallTest; @@ -561,8 +564,7 @@ public class DisplayContentTests extends WindowTestsBase { final DisplayContent dc = createNewDisplay(); final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, dc, "w"); - dc.setLayoutNeeded(); - dc.performLayout(true /* initial */, false /* updateImeWindows */); + performLayout(dc); assertThat(win.mLayoutSeq, is(dc.mLayoutSeq)); } @@ -829,8 +831,7 @@ public class DisplayContentTests extends WindowTestsBase { win.getAttrs().flags |= FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR; win.setSystemGestureExclusion(Collections.singletonList(new Rect(10, 20, 30, 40))); - dc.setLayoutNeeded(); - dc.performLayout(true /* initial */, false /* updateImeWindows */); + performLayout(dc); win.setHasSurface(true); dc.updateSystemGestureExclusion(); @@ -866,8 +867,7 @@ public class DisplayContentTests extends WindowTestsBase { win2.getAttrs().flags |= FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR; win2.setSystemGestureExclusion(Collections.singletonList(new Rect(20, 30, 40, 50))); - dc.setLayoutNeeded(); - dc.performLayout(true /* initial */, false /* updateImeWindows */); + performLayout(dc); win.setHasSurface(true); win2.setHasSurface(true); @@ -898,8 +898,7 @@ public class DisplayContentTests extends WindowTestsBase { win2.getAttrs().height = 10; win2.setSystemGestureExclusion(Collections.emptyList()); - dc.setLayoutNeeded(); - dc.performLayout(true /* initial */, false /* updateImeWindows */); + performLayout(dc); win.setHasSurface(true); win2.setHasSurface(true); @@ -922,8 +921,7 @@ public class DisplayContentTests extends WindowTestsBase { | SYSTEM_UI_FLAG_IMMERSIVE_STICKY; win.mActivityRecord.mTargetSdk = P; - dc.setLayoutNeeded(); - dc.performLayout(true /* initial */, false /* updateImeWindows */); + performLayout(dc); win.setHasSurface(true); @@ -935,6 +933,22 @@ public class DisplayContentTests extends WindowTestsBase { } @Test + public void testRequestResizeForEmptyFrames() { + final WindowState win = mChildAppWindowAbove; + makeWindowVisible(win, win.getParentWindow()); + win.setRequestedSize(mDisplayContent.mBaseDisplayWidth, 0 /* height */); + win.mAttrs.width = win.mAttrs.height = WindowManager.LayoutParams.WRAP_CONTENT; + win.mAttrs.gravity = Gravity.CENTER; + performLayout(mDisplayContent); + + // The frame is empty because the requested height is zero. + assertTrue(win.getFrameLw().isEmpty()); + // The window should be scheduled to resize then the client may report a new non-empty size. + win.updateResizingWindowIfNeeded(); + assertThat(mWm.mResizingWindows).contains(win); + } + + @Test public void testOrientationChangeLogging() { MetricsLogger mockLogger = mock(MetricsLogger.class); Configuration oldConfig = new Configuration(); @@ -1011,6 +1025,11 @@ public class DisplayContentTests extends WindowTestsBase { mWm.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, false /* updateInputWindows */); } + private void performLayout(DisplayContent dc) { + dc.setLayoutNeeded(); + dc.performLayout(true /* initial */, false /* updateImeWindows */); + } + /** * Create DisplayContent that does not update display base/initial values from device to keep * the values set by test. diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java index 5aece4577c52..0527561880f2 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java @@ -594,6 +594,8 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { @Test public void layoutWindowLw_withForwardInset_SoftInputAdjustResize() { + assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_NONE); + mWindow.mAttrs.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; mWindow.mAttrs.softInputMode = SOFT_INPUT_ADJUST_RESIZE; diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java index 09ac9ce381c0..d819b1ada659 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java @@ -63,6 +63,26 @@ public class InsetsSourceProviderTest extends WindowTestsBase { assertEquals(Insets.of(0, 100, 0, 0), mProvider.getSource().calculateInsets(new Rect(0, 0, 500, 500), false /* ignoreVisibility */)); + assertEquals(Insets.of(0, 100, 0, 0), + mProvider.getSource().calculateVisibleInsets(new Rect(0, 0, 500, 500))); + } + + @Test + public void testPostLayout_givenInsets() { + final WindowState ime = createWindow(null, TYPE_APPLICATION, "ime"); + ime.getFrameLw().set(0, 0, 500, 100); + ime.getGivenContentInsetsLw().set(0, 0, 0, 60); + ime.getGivenVisibleInsetsLw().set(0, 0, 0, 75); + ime.mHasSurface = true; + mProvider.setWindow(ime, null); + mProvider.onPostLayout(); + assertEquals(new Rect(0, 0, 500, 40), mProvider.getSource().getFrame()); + assertEquals(new Rect(0, 0, 500, 25), mProvider.getSource().getVisibleFrame()); + assertEquals(Insets.of(0, 40, 0, 0), + mProvider.getSource().calculateInsets(new Rect(0, 0, 500, 500), + false /* ignoreVisibility */)); + assertEquals(Insets.of(0, 25, 0, 0), + mProvider.getSource().calculateVisibleInsets(new Rect(0, 0, 500, 500))); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java index 112479b3b9a0..1a575962b961 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java @@ -61,7 +61,7 @@ import org.mockito.MockitoAnnotations; /** * Build/Install/Run: - * atest FrameworksServicesTests:RemoteAnimationControllerTest + * atest WmTests:RemoteAnimationControllerTest */ @SmallTest @Presubmit diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java index e0112809b22b..bac2bcae30de 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java @@ -60,7 +60,7 @@ import java.util.concurrent.CountDownLatch; * Test class for {@link SurfaceAnimationRunner}. * * Build/Install/Run: - * atest FrameworksServicesTests:SurfaceAnimationRunnerTest + * atest WmTests:SurfaceAnimationRunnerTest */ @SmallTest @Presubmit diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java index 05d1c76fed8c..e50750841e20 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java +++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java @@ -408,6 +408,16 @@ public class SystemServicesTestRule implements TestRule { } } + /** + * Throws if caller doesn't hold the given lock. + * @param lock the lock + */ + static void checkHoldsLock(Object lock) { + if (!Thread.holdsLock(lock)) { + throw new IllegalStateException("Caller doesn't hold global lock."); + } + } + protected class TestActivityTaskManagerService extends ActivityTaskManagerService { // ActivityStackSupervisor may be created more than once while setting up AMS and ATMS. // We keep the reference in order to prevent creating it twice. diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java new file mode 100644 index 000000000000..8d2da1e6cb5b --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java @@ -0,0 +1,170 @@ +/* + * 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.wm; + +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 com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; + +import android.graphics.Point; +import android.graphics.Rect; +import android.platform.test.annotations.Presubmit; +import android.os.Binder; +import android.os.RemoteException; +import android.view.ITaskOrganizer; +import android.view.SurfaceControl; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; + +import androidx.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test class for {@link TaskOrganizer}. + * + * Build/Install/Run: + * atest WmTests:TaskOrganizerTests + */ +@SmallTest +@Presubmit +@RunWith(WindowTestRunner.class) +public class TaskOrganizerTests extends WindowTestsBase { + private ITaskOrganizer makeAndRegisterMockOrganizer() { + final ITaskOrganizer organizer = mock(ITaskOrganizer.class); + when(organizer.asBinder()).thenReturn(new Binder()); + + mWm.mAtmService.registerTaskOrganizer(organizer, WINDOWING_MODE_PINNED); + + return organizer; + } + + @Test + public void testAppearVanish() throws RemoteException { + final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent); + final Task task = createTaskInStack(stack, 0 /* userId */); + final ITaskOrganizer organizer = makeAndRegisterMockOrganizer(); + + task.setTaskOrganizer(organizer); + verify(organizer).taskAppeared(any(), any()); + assertTrue(task.isControlledByTaskOrganizer()); + + task.removeImmediately(); + verify(organizer).taskVanished(any()); + } + + @Test + public void testSwapOrganizer() throws RemoteException { + final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent); + final Task task = createTaskInStack(stack, 0 /* userId */); + final ITaskOrganizer organizer = makeAndRegisterMockOrganizer(); + final ITaskOrganizer organizer2 = makeAndRegisterMockOrganizer(); + + task.setTaskOrganizer(organizer); + verify(organizer).taskAppeared(any(), any()); + task.setTaskOrganizer(organizer2); + verify(organizer).taskVanished(any()); + verify(organizer2).taskAppeared(any(), any()); + } + + @Test + public void testClearOrganizer() throws RemoteException { + final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent); + final Task task = createTaskInStack(stack, 0 /* userId */); + final ITaskOrganizer organizer = makeAndRegisterMockOrganizer(); + + task.setTaskOrganizer(organizer); + verify(organizer).taskAppeared(any(), any()); + assertTrue(task.isControlledByTaskOrganizer()); + + task.setTaskOrganizer(null); + verify(organizer).taskVanished(any()); + assertFalse(task.isControlledByTaskOrganizer()); + } + + @Test + public void testTransferStackToOrganizer() throws RemoteException { + final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent); + final Task task = createTaskInStack(stack, 0 /* userId */); + final Task task2 = createTaskInStack(stack, 0 /* userId */); + final ITaskOrganizer organizer = makeAndRegisterMockOrganizer(); + + stack.transferToTaskOrganizer(organizer); + + verify(organizer, times(2)).taskAppeared(any(), any()); + assertTrue(task.isControlledByTaskOrganizer()); + assertTrue(task2.isControlledByTaskOrganizer()); + + stack.transferToTaskOrganizer(null); + + verify(organizer, times(2)).taskVanished(any()); + assertFalse(task.isControlledByTaskOrganizer()); + assertFalse(task2.isControlledByTaskOrganizer()); + } + + @Test + public void testRegisterTaskOrganizerTaskWindowingModeChanges() throws RemoteException { + final ITaskOrganizer organizer = makeAndRegisterMockOrganizer(); + + final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent); + final Task task = createTaskInStack(stack, 0 /* userId */); + task.setWindowingMode(WINDOWING_MODE_PINNED); + verify(organizer).taskAppeared(any(), any()); + assertTrue(task.isControlledByTaskOrganizer()); + + task.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + verify(organizer).taskVanished(any()); + assertFalse(task.isControlledByTaskOrganizer()); + } + + @Test + public void testRegisterTaskOrganizerStackWindowingModeChanges() throws RemoteException { + final ITaskOrganizer organizer = makeAndRegisterMockOrganizer(); + + final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent); + final Task task = createTaskInStack(stack, 0 /* userId */); + final Task task2 = createTaskInStack(stack, 0 /* userId */); + stack.setWindowingMode(WINDOWING_MODE_PINNED); + verify(organizer, times(2)).taskAppeared(any(), any()); + assertTrue(task.isControlledByTaskOrganizer()); + assertTrue(task2.isControlledByTaskOrganizer()); + + stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + verify(organizer, times(2)).taskVanished(any()); + assertFalse(task.isControlledByTaskOrganizer()); + assertFalse(task2.isControlledByTaskOrganizer()); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotCacheTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotCacheTest.java index 0274b7d788ff..e71247173930 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotCacheTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotCacheTest.java @@ -38,7 +38,7 @@ import org.mockito.MockitoAnnotations; * Test class for {@link TaskSnapshotCache}. * * Build/Install/Run: - * atest FrameworksServicesTests:TaskSnapshotCacheTest + * atest WmTests:TaskSnapshotCacheTest */ @SmallTest @Presubmit diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java index 8fe0cdbbd872..2e485dd1bec3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java @@ -53,7 +53,7 @@ import org.mockito.Mockito; * Test class for {@link TaskSnapshotController}. * * Build/Install/Run: - * * atest FrameworksServicesTests:TaskSnapshotControllerTest + * * atest WmTests:TaskSnapshotControllerTest */ @SmallTest @Presubmit diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java index b5a5790a3a8c..eb8eb98fd484 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java @@ -47,7 +47,7 @@ import java.util.function.Predicate; * Test class for {@link TaskSnapshotPersister} and {@link TaskSnapshotLoader} * * Build/Install/Run: - * atest FrameworksServicesTests:TaskSnapshotPersisterLoaderTest + * atest WmTests:TaskSnapshotPersisterLoaderTest */ @MediumTest @Presubmit diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java index 2d418ffffcf6..ed87f3a0c604 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java @@ -53,7 +53,7 @@ import org.junit.runner.RunWith; * Test class for {@link TaskSnapshotSurface}. * * Build/Install/Run: - * atest FrameworksServicesTests:TaskSnapshotSurfaceTest + * atest WmTests:TaskSnapshotSurfaceTest */ @SmallTest @Presubmit diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java index e5121b9297e6..77af5eec2796 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java +++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java @@ -72,7 +72,7 @@ class TestDisplayContent extends DisplayContent { private final DisplayInfo mInfo; private boolean mCanRotate = true; private int mWindowingMode = WINDOWING_MODE_FULLSCREEN; - private int mPosition = POSITION_TOP; + private int mPosition = POSITION_BOTTOM; private final ActivityTaskManagerService mService; private boolean mSystemDecorations = false; @@ -127,13 +127,13 @@ class TestDisplayContent extends DisplayContent { return this; } TestDisplayContent build() { + SystemServicesTestRule.checkHoldsLock(mService.mGlobalLock); + final int displayId = SystemServicesTestRule.sNextDisplayId++; final Display display = new Display(DisplayManagerGlobal.getInstance(), displayId, mInfo, DEFAULT_DISPLAY_ADJUSTMENTS); - final TestDisplayContent newDisplay; - synchronized (mService.mGlobalLock) { - newDisplay = new TestDisplayContent(mService.mStackSupervisor, display); - } + final TestDisplayContent newDisplay = + new TestDisplayContent(mService.mStackSupervisor, display); // disable the normal system decorations final DisplayPolicy displayPolicy = newDisplay.mDisplayContent.getDisplayPolicy(); spyOn(displayPolicy); @@ -153,6 +153,10 @@ class TestDisplayContent extends DisplayContent { doReturn(false).when(newDisplay.mDisplayContent) .handlesOrientationChangeFromDescendant(); } + // Please add stubbing before this line. Services will start using this display in other + // threads immediately after adding it to hierarchy. Calling doAnswer() type of stubbing + // reduces chance of races, but still doesn't eliminate race conditions. + mService.mRootWindowContainer.addChild(newDisplay, mPosition); return newDisplay; } } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java index 8936aadd5f92..3c0dd1e897f5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java @@ -39,7 +39,7 @@ import java.util.function.Consumer; * Tests for {@link WindowContainer#forAllWindows} and various implementations. * * Build/Install/Run: - * atest FrameworksServicesTests:WindowContainerTraversalTests + * atest WmTests:WindowContainerTraversalTests */ @SmallTest @Presubmit 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 e081ca374808..7e248f854dcf 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -92,7 +92,7 @@ import java.util.List; * Tests for the {@link WindowState} class. * * Build/Install/Run: - * atest FrameworksServicesTests:WindowStateTests + * atest WmTests:WindowStateTests */ @SmallTest @Presubmit 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 ea8831571639..31a7f2437536 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -44,6 +44,7 @@ import android.view.Display; import android.view.DisplayInfo; import android.view.IWindow; import android.view.SurfaceControl.Transaction; +import android.view.View; import android.view.WindowManager; import com.android.server.AttributeCache; @@ -312,6 +313,16 @@ class WindowTestsBase extends SystemServiceTestsBase { } } + static void makeWindowVisible(WindowState... windows) { + for (WindowState win : windows) { + win.mViewVisibility = View.VISIBLE; + win.mRelayoutCalled = true; + win.mHasSurface = true; + win.mHidden = false; + win.showLw(false /* doAnimation */, false /* requestAnim */); + } + } + /** Creates a {@link ActivityStack} and adds it to the specified {@link DisplayContent}. */ ActivityStack createTaskStackOnDisplay(DisplayContent dc) { return createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, dc); diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/RotationAnimationUtilsTest.java b/services/tests/wmtests/src/com/android/server/wm/utils/RotationAnimationUtilsTest.java new file mode 100644 index 000000000000..9cda08458640 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/utils/RotationAnimationUtilsTest.java @@ -0,0 +1,153 @@ +/* + * 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.wm.utils; + +import static android.graphics.Bitmap.Config.ARGB_8888; + +import static org.junit.Assert.assertEquals; + +import android.graphics.Bitmap; +import android.graphics.Color; +import android.graphics.ColorSpace; +import android.graphics.GraphicBuffer; +import android.graphics.Matrix; +import android.graphics.PointF; +import android.view.Surface; + +import org.junit.Before; +import org.junit.Test; + +public class RotationAnimationUtilsTest { + + private static final int BITMAP_HEIGHT = 100; + private static final int BITMAP_WIDTH = 100; + private static final int POINT_WIDTH = 1000; + private static final int POINT_HEIGHT = 2000; + + private ColorSpace mColorSpace = ColorSpace.get(ColorSpace.Named.DISPLAY_P3); + private Matrix mMatrix; + + @Before + public void setup() { + mMatrix = new Matrix(); + } + + @Test + public void blackLuma() { + Bitmap swBitmap = createBitmap(0); + GraphicBuffer gb = swBitmapToGraphicsBuffer(swBitmap); + float borderLuma = RotationAnimationUtils.getAvgBorderLuma(gb, mColorSpace); + assertEquals(0, borderLuma, 0); + } + + @Test + public void whiteLuma() { + Bitmap swBitmap = createBitmap(1); + GraphicBuffer gb = swBitmapToGraphicsBuffer(swBitmap); + float borderLuma = RotationAnimationUtils.getAvgBorderLuma(gb, mColorSpace); + assertEquals(1, borderLuma, 0); + } + + @Test + public void whiteImageBlackBorderLuma() { + Bitmap swBitmap = createBitmap(1); + setBorderLuma(swBitmap, 0); + GraphicBuffer gb = swBitmapToGraphicsBuffer(swBitmap); + float borderLuma = RotationAnimationUtils.getAvgBorderLuma(gb, mColorSpace); + assertEquals(0, borderLuma, 0); + } + + @Test + public void blackImageWhiteBorderLuma() { + Bitmap swBitmap = createBitmap(0); + setBorderLuma(swBitmap, 1); + GraphicBuffer gb = swBitmapToGraphicsBuffer(swBitmap); + float borderLuma = RotationAnimationUtils.getAvgBorderLuma(gb, mColorSpace); + assertEquals(1, borderLuma, 0); + } + + @Test + public void rotate_0_bottomRight() { + RotationAnimationUtils.createRotationMatrix(Surface.ROTATION_0, + POINT_WIDTH, POINT_HEIGHT, mMatrix); + PointF newPoints = checkMappedPoints(POINT_WIDTH, POINT_HEIGHT); + assertEquals(POINT_WIDTH, newPoints.x, 0); + assertEquals(POINT_HEIGHT, newPoints.y, 0); + } + + @Test + public void rotate_90_bottomRight() { + RotationAnimationUtils.createRotationMatrix(Surface.ROTATION_90, + POINT_WIDTH, POINT_HEIGHT, mMatrix); + PointF newPoints = checkMappedPoints(POINT_WIDTH, POINT_HEIGHT); + assertEquals(0, newPoints.x, 0); + assertEquals(POINT_WIDTH, newPoints.y, 0); + } + + @Test + public void rotate_180_bottomRight() { + RotationAnimationUtils.createRotationMatrix(Surface.ROTATION_180, + POINT_WIDTH, POINT_HEIGHT, mMatrix); + PointF newPoints = checkMappedPoints(POINT_WIDTH, POINT_HEIGHT); + assertEquals(0, newPoints.x, 0); + assertEquals(0, newPoints.y, 0); + } + + @Test + public void rotate_270_bottomRight() { + RotationAnimationUtils.createRotationMatrix(Surface.ROTATION_270, + POINT_WIDTH, POINT_HEIGHT, mMatrix); + PointF newPoints = checkMappedPoints(POINT_WIDTH, POINT_HEIGHT); + assertEquals(POINT_HEIGHT, newPoints.x, 0); + assertEquals(0, newPoints.y, 0); + } + + private PointF checkMappedPoints(int x, int y) { + final float[] fs = new float[] {x, y}; + mMatrix.mapPoints(fs); + return new PointF(fs[0], fs[1]); + } + + private Bitmap createBitmap(float luma) { + Bitmap bitmap = Bitmap.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, ARGB_8888); + for (int i = 0; i < BITMAP_WIDTH; i++) { + for (int j = 0; j < BITMAP_HEIGHT; j++) { + bitmap.setPixel(i, j, Color.argb(1, luma, luma, luma)); + } + } + return bitmap; + } + + private GraphicBuffer swBitmapToGraphicsBuffer(Bitmap swBitmap) { + Bitmap hwBitmap = swBitmap.copy(Bitmap.Config.HARDWARE, false); + return hwBitmap.createGraphicBufferHandle(); + } + + private void setBorderLuma(Bitmap swBitmap, float luma) { + int i; + int width = swBitmap.getWidth(); + int height = swBitmap.getHeight(); + for (i = 0; i < width; i++) { + swBitmap.setPixel(i, 0, Color.argb(1, luma, luma, luma)); + swBitmap.setPixel(i, height - 1, Color.argb(1, luma, luma, luma)); + } + for (i = 0; i < height; i++) { + swBitmap.setPixel(0, i, Color.argb(1, luma, luma, luma)); + swBitmap.setPixel(width - 1, i, Color.argb(1, luma, luma, luma)); + } + } +} diff --git a/services/usage/OWNERS b/services/usage/OWNERS new file mode 100644 index 000000000000..9daa0930e73a --- /dev/null +++ b/services/usage/OWNERS @@ -0,0 +1,4 @@ +mwachens@google.com +varunshah@google.com +huiyu@google.com +yamasani@google.com diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index c900f386b438..8397aa485595 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -1568,44 +1568,16 @@ public class UsageStatsService extends SystemService implements } @Override - public void setAppStandbyBucket(String packageName, - int bucket, int userId) { + public void setAppStandbyBucket(String packageName, int bucket, int userId) { getContext().enforceCallingPermission(Manifest.permission.CHANGE_APP_IDLE_STATE, "No permission to change app standby state"); - if (bucket < UsageStatsManager.STANDBY_BUCKET_ACTIVE - || bucket > UsageStatsManager.STANDBY_BUCKET_NEVER) { - throw new IllegalArgumentException("Cannot set the standby bucket to " + bucket); - } final int callingUid = Binder.getCallingUid(); - try { - userId = ActivityManager.getService().handleIncomingUser( - Binder.getCallingPid(), callingUid, userId, false, true, - "setAppStandbyBucket", null); - } catch (RemoteException re) { - throw re.rethrowFromSystemServer(); - } - final boolean shellCaller = callingUid == 0 || callingUid == Process.SHELL_UID; - final boolean systemCaller = UserHandle.isCore(callingUid); - final int reason = systemCaller - ? UsageStatsManager.REASON_MAIN_FORCED - : UsageStatsManager.REASON_MAIN_PREDICTED; + final int callingPid = Binder.getCallingPid(); final long token = Binder.clearCallingIdentity(); try { - final int packageUid = mPackageManagerInternal.getPackageUid(packageName, - PackageManager.MATCH_ANY_USER | PackageManager.MATCH_DIRECT_BOOT_UNAWARE - | PackageManager.MATCH_DIRECT_BOOT_AWARE, userId); - // Caller cannot set their own standby state - if (packageUid == callingUid) { - throw new IllegalArgumentException("Cannot set your own standby bucket"); - } - if (packageUid < 0) { - throw new IllegalArgumentException( - "Cannot set standby bucket for non existent package (" + packageName - + ")"); - } - mAppStandby.setAppStandbyBucket(packageName, userId, bucket, reason, - SystemClock.elapsedRealtime(), shellCaller); + mAppStandby.setAppStandbyBucket(packageName, bucket, userId, + callingUid, callingPid); } finally { Binder.restoreCallingIdentity(token); } @@ -1643,37 +1615,11 @@ public class UsageStatsService extends SystemService implements "No permission to change app standby state"); final int callingUid = Binder.getCallingUid(); - try { - userId = ActivityManager.getService().handleIncomingUser( - Binder.getCallingPid(), callingUid, userId, false, true, - "setAppStandbyBucket", null); - } catch (RemoteException re) { - throw re.rethrowFromSystemServer(); - } - final boolean shellCaller = callingUid == 0 || callingUid == Process.SHELL_UID; - final int reason = shellCaller - ? UsageStatsManager.REASON_MAIN_FORCED - : UsageStatsManager.REASON_MAIN_PREDICTED; + final int callingPid = Binder.getCallingPid(); final long token = Binder.clearCallingIdentity(); try { - final long elapsedRealtime = SystemClock.elapsedRealtime(); - List<AppStandbyInfo> bucketList = appBuckets.getList(); - for (AppStandbyInfo bucketInfo : bucketList) { - final String packageName = bucketInfo.mPackageName; - final int bucket = bucketInfo.mStandbyBucket; - if (bucket < UsageStatsManager.STANDBY_BUCKET_ACTIVE - || bucket > UsageStatsManager.STANDBY_BUCKET_NEVER) { - throw new IllegalArgumentException( - "Cannot set the standby bucket to " + bucket); - } - // Caller cannot set their own standby state - if (mPackageManagerInternal.getPackageUid(packageName, - PackageManager.MATCH_ANY_USER, userId) == callingUid) { - throw new IllegalArgumentException("Cannot set your own standby bucket"); - } - mAppStandby.setAppStandbyBucket(packageName, userId, bucket, reason, - elapsedRealtime, shellCaller); - } + mAppStandby.setAppStandbyBuckets(appBuckets.getList(), userId, + callingUid, callingPid); } finally { Binder.restoreCallingIdentity(token); } diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java index 198b4c31249a..bde2cfd52c0f 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java @@ -611,63 +611,89 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { int setParameter(UUID modelId, @ModelParams int modelParam, int value) { synchronized (mLock) { - MetricsLogger.count(mContext, "sth_set_parameter", 1); - if (modelId == null || mModule == null) { - return SoundTrigger.STATUS_ERROR; - } - ModelData modelData = mModelDataMap.get(modelId); - if (modelData == null) { - Slog.w(TAG, "SetParameter: Invalid model id:" + modelId); - return SoundTrigger.STATUS_BAD_VALUE; - } - if (!modelData.isModelLoaded()) { - Slog.i(TAG, "SetParameter: Given model is not loaded:" + modelId); - return SoundTrigger.STATUS_BAD_VALUE; - } + return setParameterLocked(mModelDataMap.get(modelId), modelParam, value); + } + } - return mModule.setParameter(modelData.getHandle(), modelParam, value); + int setKeyphraseParameter(int keyphraseId, @ModelParams int modelParam, int value) { + synchronized (mLock) { + return setParameterLocked(getKeyphraseModelDataLocked(keyphraseId), modelParam, value); } } - int getParameter(@NonNull UUID modelId, @ModelParams int modelParam) - throws UnsupportedOperationException, IllegalArgumentException { + private int setParameterLocked(@Nullable ModelData modelData, @ModelParams int modelParam, + int value) { + MetricsLogger.count(mContext, "sth_set_parameter", 1); + if (mModule == null) { + return SoundTrigger.STATUS_NO_INIT; + } + if (modelData == null || !modelData.isModelLoaded()) { + Slog.i(TAG, "SetParameter: Given model is not loaded:" + modelData); + return SoundTrigger.STATUS_BAD_VALUE; + } + + return mModule.setParameter(modelData.getHandle(), modelParam, value); + } + + int getParameter(@NonNull UUID modelId, @ModelParams int modelParam) { synchronized (mLock) { - MetricsLogger.count(mContext, "sth_get_parameter", 1); - if (mModule == null) { - throw new UnsupportedOperationException("SoundTriggerModule not initialized"); - } + return getParameterLocked(mModelDataMap.get(modelId), modelParam); + } + } - ModelData modelData = mModelDataMap.get(modelId); - if (modelData == null) { - throw new IllegalArgumentException("Invalid model id:" + modelId); - } - if (!modelData.isModelLoaded()) { - throw new UnsupportedOperationException("Given model is not loaded:" + modelId); - } + int getKeyphraseParameter(int keyphraseId, @ModelParams int modelParam) { + synchronized (mLock) { + return getParameterLocked(getKeyphraseModelDataLocked(keyphraseId), modelParam); + } + } - return mModule.getParameter(modelData.getHandle(), modelParam); + private int getParameterLocked(@Nullable ModelData modelData, @ModelParams int modelParam) { + MetricsLogger.count(mContext, "sth_get_parameter", 1); + if (mModule == null) { + throw new UnsupportedOperationException("SoundTriggerModule not initialized"); } + + if (modelData == null) { + throw new IllegalArgumentException("Invalid model id"); + } + if (!modelData.isModelLoaded()) { + throw new UnsupportedOperationException("Given model is not loaded:" + modelData); + } + + return mModule.getParameter(modelData.getHandle(), modelParam); } @Nullable ModelParamRange queryParameter(@NonNull UUID modelId, @ModelParams int modelParam) { synchronized (mLock) { - MetricsLogger.count(mContext, "sth_query_parameter", 1); - if (mModule == null) { - return null; - } - ModelData modelData = mModelDataMap.get(modelId); - if (modelData == null) { - Slog.w(TAG, "queryParameter: Invalid model id:" + modelId); - return null; - } - if (!modelData.isModelLoaded()) { - Slog.i(TAG, "queryParameter: Given model is not loaded:" + modelId); - return null; - } + return queryParameterLocked(mModelDataMap.get(modelId), modelParam); + } + } - return mModule.queryParameter(modelData.getHandle(), modelParam); + @Nullable + ModelParamRange queryKeyphraseParameter(int keyphraseId, @ModelParams int modelParam) { + synchronized (mLock) { + return queryParameterLocked(getKeyphraseModelDataLocked(keyphraseId), modelParam); + } + } + + @Nullable + private ModelParamRange queryParameterLocked(@Nullable ModelData modelData, + @ModelParams int modelParam) { + MetricsLogger.count(mContext, "sth_query_parameter", 1); + if (mModule == null) { + return null; + } + if (modelData == null) { + Slog.w(TAG, "queryParameter: Invalid model id"); + return null; } + if (!modelData.isModelLoaded()) { + Slog.i(TAG, "queryParameter: Given model is not loaded:" + modelData); + return null; + } + + return mModule.queryParameter(modelData.getHandle(), modelParam); } //---- SoundTrigger.StatusListener methods diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java index d05e044499ab..54dffdc4c13a 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java @@ -16,17 +16,17 @@ package com.android.server.soundtrigger; +import android.annotation.Nullable; import android.hardware.soundtrigger.IRecognitionStatusCallback; +import android.hardware.soundtrigger.ModelParams; import android.hardware.soundtrigger.SoundTrigger; import android.hardware.soundtrigger.SoundTrigger.Keyphrase; -import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionEvent; -import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra; import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel; +import android.hardware.soundtrigger.SoundTrigger.ModelParamRange; import android.hardware.soundtrigger.SoundTrigger.ModuleProperties; import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig; -import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent; -import android.hardware.soundtrigger.SoundTrigger.SoundModelEvent; -import android.hardware.soundtrigger.SoundTriggerModule; + +import com.android.server.voiceinteraction.VoiceInteractionManagerService; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -71,6 +71,58 @@ public abstract class SoundTriggerInternal { public abstract ModuleProperties getModuleProperties(); /** + * Set a model specific {@link ModelParams} with the given value. This + * parameter will keep its value for the duration the model is loaded regardless of starting and + * stopping recognition. Once the model is unloaded, the value will be lost. + * {@link SoundTriggerInternal#queryParameter} should be checked first before calling this + * method. + * + * @param keyphraseId The identifier of the keyphrase for which + * to modify model parameters + * @param modelParam {@link ModelParams} + * @param value Value to set + * @return - {@link SoundTrigger#STATUS_OK} in case of success + * - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached + * - {@link SoundTrigger#STATUS_BAD_VALUE} invalid input parameter + * - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or + * if API is not supported by HAL + */ + public abstract int setParameter(int keyphraseId, @ModelParams int modelParam, int value); + + /** + * Get a model specific {@link ModelParams}. This parameter will keep its value + * for the duration the model is loaded regardless of starting and stopping recognition. + * Once the model is unloaded, the value will be lost. If the value is not set, a default + * value is returned. See ModelParams for parameter default values. + * {@link SoundTriggerInternal#queryParameter} should be checked first before calling this + * method. + * + * @param keyphraseId The identifier of the keyphrase for which + * to modify model parameters + * @param modelParam {@link ModelParams} + * @return value of parameter + * @throws UnsupportedOperationException if hal or model do not support this API. + * queryParameter should be checked first. + * @throws IllegalArgumentException if invalid model handle or parameter is passed. + * queryParameter should be checked first. + */ + public abstract int getParameter(int keyphraseId, @ModelParams int modelParam); + + /** + * Determine if parameter control is supported for the given model handle. + * This method should be checked prior to calling {@link SoundTriggerInternal#setParameter} + * or {@link SoundTriggerInternal#getParameter}. + * + * @param keyphraseId The identifier of the keyphrase for which + * to modify model parameters + * @param modelParam {@link ModelParams} + * @return supported range of parameter, null if not supported + */ + @Nullable + public abstract ModelParamRange queryParameter(int keyphraseId, + @ModelParams int modelParam); + + /** * Unloads (and stops if running) the given keyphraseId */ public abstract int unloadKeyphraseModel(int keyphaseId); diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java index 68b16f39e149..e37755bddcaa 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java @@ -1446,26 +1446,45 @@ public class SoundTriggerService extends SystemService { @Override public int startRecognition(int keyphraseId, KeyphraseSoundModel soundModel, IRecognitionStatusCallback listener, RecognitionConfig recognitionConfig) { - if (!isInitialized()) return STATUS_ERROR; + if (!isInitialized()) throw new UnsupportedOperationException(); return mSoundTriggerHelper.startKeyphraseRecognition(keyphraseId, soundModel, listener, recognitionConfig); } @Override public synchronized int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) { - if (!isInitialized()) return STATUS_ERROR; + if (!isInitialized()) throw new UnsupportedOperationException(); return mSoundTriggerHelper.stopKeyphraseRecognition(keyphraseId, listener); } @Override public ModuleProperties getModuleProperties() { - if (!isInitialized()) return null; + if (!isInitialized()) throw new UnsupportedOperationException(); return mSoundTriggerHelper.getModuleProperties(); } @Override + public int setParameter(int keyphraseId, @ModelParams int modelParam, int value) { + if (!isInitialized()) throw new UnsupportedOperationException(); + return mSoundTriggerHelper.setKeyphraseParameter(keyphraseId, modelParam, value); + } + + @Override + public int getParameter(int keyphraseId, @ModelParams int modelParam) { + if (!isInitialized()) throw new UnsupportedOperationException(); + return mSoundTriggerHelper.getKeyphraseParameter(keyphraseId, modelParam); + } + + @Override + @Nullable + public ModelParamRange queryParameter(int keyphraseId, @ModelParams int modelParam) { + if (!isInitialized()) throw new UnsupportedOperationException(); + return mSoundTriggerHelper.queryKeyphraseParameter(keyphraseId, modelParam); + } + + @Override public int unloadKeyphraseModel(int keyphraseId) { - if (!isInitialized()) return STATUS_ERROR; + if (!isInitialized()) throw new UnsupportedOperationException(); return mSoundTriggerHelper.unloadKeyphraseSoundModel(keyphraseId); } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index e9db31ba3e25..06c807421d1a 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -41,7 +41,9 @@ import android.content.pm.UserInfo; import android.content.res.Resources; import android.database.ContentObserver; import android.hardware.soundtrigger.IRecognitionStatusCallback; +import android.hardware.soundtrigger.ModelParams; import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel; +import android.hardware.soundtrigger.SoundTrigger.ModelParamRange; import android.hardware.soundtrigger.SoundTrigger.ModuleProperties; import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig; import android.os.Binder; @@ -1084,6 +1086,55 @@ public class VoiceInteractionManagerService extends SystemService { } } + @Override + public int setParameter(IVoiceInteractionService service, int keyphraseId, + @ModelParams int modelParam, int value) { + // Allow the call if this is the current voice interaction service. + synchronized (this) { + enforceIsCurrentVoiceInteractionService(service); + } + + final long caller = Binder.clearCallingIdentity(); + try { + return mSoundTriggerInternal.setParameter(keyphraseId, modelParam, value); + } finally { + Binder.restoreCallingIdentity(caller); + } + } + + @Override + public int getParameter(IVoiceInteractionService service, int keyphraseId, + @ModelParams int modelParam) { + // Allow the call if this is the current voice interaction service. + synchronized (this) { + enforceIsCurrentVoiceInteractionService(service); + } + + final long caller = Binder.clearCallingIdentity(); + try { + return mSoundTriggerInternal.getParameter(keyphraseId, modelParam); + } finally { + Binder.restoreCallingIdentity(caller); + } + } + + @Override + @Nullable + public ModelParamRange queryParameter(IVoiceInteractionService service, + int keyphraseId, @ModelParams int modelParam) { + // Allow the call if this is the current voice interaction service. + synchronized (this) { + enforceIsCurrentVoiceInteractionService(service); + } + + final long caller = Binder.clearCallingIdentity(); + try { + return mSoundTriggerInternal.queryParameter(keyphraseId, modelParam); + } finally { + Binder.restoreCallingIdentity(caller); + } + } + private synchronized void unloadAllKeyphraseModels() { for (int i = 0; i < mLoadedKeyphraseIds.size(); i++) { final long caller = Binder.clearCallingIdentity(); diff --git a/startop/iorap/src/com/google/android/startop/iorap/EventSequenceValidator.java b/startop/iorap/src/com/google/android/startop/iorap/EventSequenceValidator.java index b75510b576f6..488ee78f6230 100644 --- a/startop/iorap/src/com/google/android/startop/iorap/EventSequenceValidator.java +++ b/startop/iorap/src/com/google/android/startop/iorap/EventSequenceValidator.java @@ -83,7 +83,7 @@ import com.android.server.wm.ActivityMetricsLaunchObserver; * could transition to INTENT_STARTED. * * <p> If any bad transition happened, the state becomse UNKNOWN. The UNKNOWN state - * could be * accumulated, because during the UNKNOWN state more IntentStarted may + * could be accumulated, because during the UNKNOWN state more IntentStarted may * be triggered. To recover from UNKNOWN to INIT, all the accumualted IntentStarted * should termniate. * @@ -100,7 +100,7 @@ public class EventSequenceValidator implements ActivityMetricsLaunchObserver { @Override public void onIntentStarted(@NonNull Intent intent, long timestampNs) { if (state == State.UNKNOWN) { - Log.e(TAG, "IntentStarted during UNKNOWN." + intent); + Log.wtf(TAG, "IntentStarted during UNKNOWN." + intent); incAccIntentStartedEvents(); return; } @@ -110,32 +110,32 @@ public class EventSequenceValidator implements ActivityMetricsLaunchObserver { state != State.ACTIVITY_CANCELLED && state != State.ACTIVITY_FINISHED && state != State.REPORT_FULLY_DRAWN) { - Log.e(TAG, + Log.wtf(TAG, String.format("Cannot transition from %s to %s", state, State.INTENT_STARTED)); incAccIntentStartedEvents(); incAccIntentStartedEvents(); return; } - Log.i(TAG, String.format("Tansition from %s to %s", state, State.INTENT_STARTED)); + Log.i(TAG, String.format("Transition from %s to %s", state, State.INTENT_STARTED)); state = State.INTENT_STARTED; } @Override public void onIntentFailed() { if (state == State.UNKNOWN) { - Log.e(TAG, "IntentFailed during UNKNOWN."); + Log.wtf(TAG, "IntentFailed during UNKNOWN."); decAccIntentStartedEvents(); return; } if (state != State.INTENT_STARTED) { - Log.e(TAG, + Log.wtf(TAG, String.format("Cannot transition from %s to %s", state, State.INTENT_FAILED)); incAccIntentStartedEvents(); return; } - Log.i(TAG, String.format("Tansition from %s to %s", state, State.INTENT_FAILED)); + Log.i(TAG, String.format("Transition from %s to %s", state, State.INTENT_FAILED)); state = State.INTENT_FAILED; } @@ -143,11 +143,11 @@ public class EventSequenceValidator implements ActivityMetricsLaunchObserver { public void onActivityLaunched(@NonNull @ActivityRecordProto byte[] activity, @Temperature int temperature) { if (state == State.UNKNOWN) { - Log.e(TAG, "onActivityLaunched during UNKNOWN."); + Log.wtf(TAG, "onActivityLaunched during UNKNOWN."); return; } if (state != State.INTENT_STARTED) { - Log.e(TAG, + Log.wtf(TAG, String.format("Cannot transition from %s to %s", state, State.ACTIVITY_LAUNCHED)); incAccIntentStartedEvents(); return; @@ -160,12 +160,12 @@ public class EventSequenceValidator implements ActivityMetricsLaunchObserver { @Override public void onActivityLaunchCancelled(@Nullable @ActivityRecordProto byte[] activity) { if (state == State.UNKNOWN) { - Log.e(TAG, "onActivityLaunchCancelled during UNKNOWN."); + Log.wtf(TAG, "onActivityLaunchCancelled during UNKNOWN."); decAccIntentStartedEvents(); return; } if (state != State.ACTIVITY_LAUNCHED) { - Log.e(TAG, + Log.wtf(TAG, String.format("Cannot transition from %s to %s", state, State.ACTIVITY_CANCELLED)); incAccIntentStartedEvents(); return; @@ -179,13 +179,13 @@ public class EventSequenceValidator implements ActivityMetricsLaunchObserver { public void onActivityLaunchFinished(@NonNull @ActivityRecordProto byte[] activity, long timestampNs) { if (state == State.UNKNOWN) { - Log.e(TAG, "onActivityLaunchFinished during UNKNOWN."); + Log.wtf(TAG, "onActivityLaunchFinished during UNKNOWN."); decAccIntentStartedEvents(); return; } if (state != State.ACTIVITY_LAUNCHED) { - Log.e(TAG, + Log.wtf(TAG, String.format("Cannot transition from %s to %s", state, State.ACTIVITY_FINISHED)); incAccIntentStartedEvents(); return; @@ -199,7 +199,7 @@ public class EventSequenceValidator implements ActivityMetricsLaunchObserver { public void onReportFullyDrawn(@NonNull @ActivityRecordProto byte[] activity, long timestampNs) { if (state == State.UNKNOWN) { - Log.e(TAG, "onReportFullyDrawn during UNKNOWN."); + Log.wtf(TAG, "onReportFullyDrawn during UNKNOWN."); return; } if (state == State.INIT) { @@ -207,7 +207,7 @@ public class EventSequenceValidator implements ActivityMetricsLaunchObserver { } if (state != State.ACTIVITY_FINISHED) { - Log.e(TAG, + Log.wtf(TAG, String.format("Cannot transition from %s to %s", state, State.REPORT_FULLY_DRAWN)); return; } @@ -230,7 +230,7 @@ public class EventSequenceValidator implements ActivityMetricsLaunchObserver { private void incAccIntentStartedEvents() { if (accIntentStartedEvents < 0) { throw new AssertionError( - String.format("The number of unknows cannot be negative")); + String.format("The number of unknowns cannot be negative")); } if (accIntentStartedEvents == 0) { state = State.UNKNOWN; @@ -243,7 +243,7 @@ public class EventSequenceValidator implements ActivityMetricsLaunchObserver { private void decAccIntentStartedEvents() { if (accIntentStartedEvents <= 0) { throw new AssertionError( - String.format("The number of unknows cannot be negative")); + String.format("The number of unknowns cannot be negative")); } if(accIntentStartedEvents == 1) { state = State.INIT; diff --git a/telecomm/java/android/telecom/AudioState.java b/telecomm/java/android/telecom/AudioState.java index 8b8c86be7b0a..ea641f866b98 100644 --- a/telecomm/java/android/telecom/AudioState.java +++ b/telecomm/java/android/telecom/AudioState.java @@ -19,7 +19,7 @@ package android.telecom; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java index 86ad795b9ea2..a8852a849604 100644 --- a/telecomm/java/android/telecom/Call.java +++ b/telecomm/java/android/telecom/Call.java @@ -20,12 +20,11 @@ import android.annotation.IntDef; import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.TestApi; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; -import android.os.IBinder; import android.os.ParcelFileDescriptor; import com.android.internal.telecom.IVideoProvider; diff --git a/telecomm/java/android/telecom/CallerInfo.java b/telecomm/java/android/telecom/CallerInfo.java index a5d25e2ce4bb..fb6f99405759 100644 --- a/telecomm/java/android/telecom/CallerInfo.java +++ b/telecomm/java/android/telecom/CallerInfo.java @@ -17,7 +17,7 @@ package android.telecom; import android.annotation.Nullable; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; @@ -41,8 +41,8 @@ import com.android.i18n.phonenumbers.NumberParseException; import com.android.i18n.phonenumbers.PhoneNumberUtil; import com.android.i18n.phonenumbers.Phonenumber.PhoneNumber; import com.android.i18n.phonenumbers.geocoding.PhoneNumberOfflineGeocoder; - import com.android.internal.annotations.VisibleForTesting; + import java.util.Locale; diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java index 8808339b1664..f205ec64f49b 100644 --- a/telecomm/java/android/telecom/Connection.java +++ b/telecomm/java/android/telecom/Connection.java @@ -21,9 +21,9 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.TestApi; -import android.annotation.UnsupportedAppUsage; import android.app.Notification; import android.bluetooth.BluetoothDevice; +import android.compat.annotation.UnsupportedAppUsage; import android.content.Intent; import android.hardware.camera2.CameraManager; import android.net.Uri; diff --git a/telecomm/java/android/telecom/Log.java b/telecomm/java/android/telecom/Log.java index 7d4ee7686512..4f6a9d6450f8 100644 --- a/telecomm/java/android/telecom/Log.java +++ b/telecomm/java/android/telecom/Log.java @@ -16,7 +16,7 @@ package android.telecom; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.net.Uri; import android.os.Build; diff --git a/telecomm/java/android/telecom/ParcelableCall.java b/telecomm/java/android/telecom/ParcelableCall.java index a234bb0af8fa..be4e2f4c65e1 100644 --- a/telecomm/java/android/telecom/ParcelableCall.java +++ b/telecomm/java/android/telecom/ParcelableCall.java @@ -16,22 +16,21 @@ package android.telecom; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.net.Uri; import android.os.Build; import android.os.Bundle; -import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; import android.telecom.Call.Details.CallDirection; +import com.android.internal.telecom.IVideoProvider; + import java.util.ArrayList; import java.util.Collections; import java.util.List; -import com.android.internal.telecom.IVideoProvider; - /** * Information about a call that is used between InCallService and Telecom. * @hide diff --git a/telecomm/java/android/telecom/Phone.java b/telecomm/java/android/telecom/Phone.java index 61a639a1a235..a427ed612b31 100644 --- a/telecomm/java/android/telecom/Phone.java +++ b/telecomm/java/android/telecom/Phone.java @@ -17,8 +17,8 @@ package android.telecom; import android.annotation.SystemApi; -import android.annotation.UnsupportedAppUsage; import android.bluetooth.BluetoothDevice; +import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; import android.os.Bundle; import android.util.ArrayMap; diff --git a/telecomm/java/android/telecom/PhoneAccountHandle.java b/telecomm/java/android/telecom/PhoneAccountHandle.java index eb568e04ebf3..e1bcb5fbdf00 100644 --- a/telecomm/java/android/telecom/PhoneAccountHandle.java +++ b/telecomm/java/android/telecom/PhoneAccountHandle.java @@ -18,7 +18,7 @@ package android.telecom; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.os.Build; import android.os.Parcel; diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index af3c55abf00c..c4c1e21e7c41 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -26,7 +26,7 @@ import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -49,6 +49,7 @@ import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.concurrent.Executor; /** @@ -882,7 +883,8 @@ public class TelecomManager { */ public TelecomManager(Context context, ITelecomService telecomServiceImpl) { Context appContext = context.getApplicationContext(); - if (appContext != null) { + if (appContext != null && Objects.equals(context.getFeatureId(), + appContext.getFeatureId())) { mContext = appContext; } else { mContext = context; @@ -916,7 +918,7 @@ public class TelecomManager { try { if (isServiceConnected()) { return getTelecomService().getDefaultOutgoingPhoneAccount(uriScheme, - mContext.getOpPackageName()); + mContext.getOpPackageName(), mContext.getFeatureId()); } } catch (RemoteException e) { Log.e(TAG, "Error calling ITelecomService#getDefaultOutgoingPhoneAccount", e); @@ -1113,7 +1115,8 @@ public class TelecomManager { public List<PhoneAccountHandle> getSelfManagedPhoneAccounts() { try { if (isServiceConnected()) { - return getTelecomService().getSelfManagedPhoneAccounts(mContext.getOpPackageName()); + return getTelecomService().getSelfManagedPhoneAccounts(mContext.getOpPackageName(), + mContext.getFeatureId()); } } catch (RemoteException e) { Log.e(TAG, "Error calling ITelecomService#getSelfManagedPhoneAccounts()", e); @@ -1138,8 +1141,8 @@ public class TelecomManager { boolean includeDisabledAccounts) { try { if (isServiceConnected()) { - return getTelecomService().getCallCapablePhoneAccounts( - includeDisabledAccounts, mContext.getOpPackageName()); + return getTelecomService().getCallCapablePhoneAccounts(includeDisabledAccounts, + mContext.getOpPackageName(), mContext.getFeatureId()); } } catch (RemoteException e) { Log.e(TAG, "Error calling ITelecomService#getCallCapablePhoneAccounts(" + @@ -1442,7 +1445,7 @@ public class TelecomManager { try { if (isServiceConnected()) { return getTelecomService().isVoiceMailNumber(accountHandle, number, - mContext.getOpPackageName()); + mContext.getOpPackageName(), mContext.getFeatureId()); } } catch (RemoteException e) { Log.e(TAG, "RemoteException calling ITelecomService#isVoiceMailNumber.", e); @@ -1464,7 +1467,7 @@ public class TelecomManager { try { if (isServiceConnected()) { return getTelecomService().getVoiceMailNumber(accountHandle, - mContext.getOpPackageName()); + mContext.getOpPackageName(), mContext.getFeatureId()); } } catch (RemoteException e) { Log.e(TAG, "RemoteException calling ITelecomService#hasVoiceMailNumber.", e); @@ -1485,7 +1488,7 @@ public class TelecomManager { try { if (isServiceConnected()) { return getTelecomService().getLine1Number(accountHandle, - mContext.getOpPackageName()); + mContext.getOpPackageName(), mContext.getFeatureId()); } } catch (RemoteException e) { Log.e(TAG, "RemoteException calling ITelecomService#getLine1Number.", e); @@ -1506,7 +1509,8 @@ public class TelecomManager { public boolean isInCall() { try { if (isServiceConnected()) { - return getTelecomService().isInCall(mContext.getOpPackageName()); + return getTelecomService().isInCall(mContext.getOpPackageName(), + mContext.getFeatureId()); } } catch (RemoteException e) { Log.e(TAG, "RemoteException calling isInCall().", e); @@ -1531,7 +1535,8 @@ public class TelecomManager { public boolean isInManagedCall() { try { if (isServiceConnected()) { - return getTelecomService().isInManagedCall(mContext.getOpPackageName()); + return getTelecomService().isInManagedCall(mContext.getOpPackageName(), + mContext.getFeatureId()); } } catch (RemoteException e) { Log.e(TAG, "RemoteException calling isInManagedCall().", e); @@ -1711,7 +1716,8 @@ public class TelecomManager { public boolean isTtySupported() { try { if (isServiceConnected()) { - return getTelecomService().isTtySupported(mContext.getOpPackageName()); + return getTelecomService().isTtySupported(mContext.getOpPackageName(), + mContext.getFeatureId()); } } catch (RemoteException e) { Log.e(TAG, "RemoteException attempting to get TTY supported state.", e); @@ -1735,7 +1741,8 @@ public class TelecomManager { public @TtyMode int getCurrentTtyMode() { try { if (isServiceConnected()) { - return getTelecomService().getCurrentTtyMode(mContext.getOpPackageName()); + return getTelecomService().getCurrentTtyMode(mContext.getOpPackageName(), + mContext.getFeatureId()); } } catch (RemoteException e) { Log.e(TAG, "RemoteException attempting to get the current TTY mode.", e); @@ -1925,7 +1932,8 @@ public class TelecomManager { ITelecomService service = getTelecomService(); if (service != null) { try { - service.showInCallScreen(showDialpad, mContext.getOpPackageName()); + service.showInCallScreen(showDialpad, mContext.getOpPackageName(), + mContext.getFeatureId()); } catch (RemoteException e) { Log.e(TAG, "Error calling ITelecomService#showCallScreen", e); } @@ -1988,7 +1996,7 @@ public class TelecomManager { } try { service.placeCall(address, extras == null ? new Bundle() : extras, - mContext.getOpPackageName()); + mContext.getOpPackageName(), mContext.getFeatureId()); } catch (RemoteException e) { Log.e(TAG, "Error calling ITelecomService#placeCall", e); } diff --git a/telecomm/java/android/telecom/VideoCallImpl.java b/telecomm/java/android/telecom/VideoCallImpl.java index 4a1aa0a8ffa4..109e7f829f2e 100644 --- a/telecomm/java/android/telecom/VideoCallImpl.java +++ b/telecomm/java/android/telecom/VideoCallImpl.java @@ -16,7 +16,7 @@ package android.telecom; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.net.Uri; import android.os.Build; import android.os.Handler; diff --git a/telecomm/java/android/telecom/VideoProfile.java b/telecomm/java/android/telecom/VideoProfile.java index 64e6ca3416e3..4197f3cfc6c3 100644 --- a/telecomm/java/android/telecom/VideoProfile.java +++ b/telecomm/java/android/telecom/VideoProfile.java @@ -19,7 +19,6 @@ package android.telecom; import android.annotation.FloatRange; import android.annotation.IntDef; import android.annotation.IntRange; -import android.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.os.Parcelable; diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl index 204c37e9aa38..c54da6b4d527 100644 --- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl +++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl @@ -35,12 +35,13 @@ interface ITelecomService { * * @param showDialpad if true, make the dialpad visible initially. */ - void showInCallScreen(boolean showDialpad, String callingPackage); + void showInCallScreen(boolean showDialpad, String callingPackage, String callingFeatureId); /** * @see TelecomServiceImpl#getDefaultOutgoingPhoneAccount */ - PhoneAccountHandle getDefaultOutgoingPhoneAccount(in String uriScheme, String callingPackage); + PhoneAccountHandle getDefaultOutgoingPhoneAccount(in String uriScheme, String callingPackage, + String callingFeatureId); /** * @see TelecomServiceImpl#getUserSelectedOutgoingPhoneAccount @@ -56,12 +57,13 @@ interface ITelecomService { * @see TelecomServiceImpl#getCallCapablePhoneAccounts */ List<PhoneAccountHandle> getCallCapablePhoneAccounts( - boolean includeDisabledAccounts, String callingPackage); + boolean includeDisabledAccounts, String callingPackage, String callingFeatureId); /** * @see TelecomServiceImpl#getSelfManagedPhoneAccounts */ - List<PhoneAccountHandle> getSelfManagedPhoneAccounts(String callingPackage); + List<PhoneAccountHandle> getSelfManagedPhoneAccounts(String callingPackage, + String callingFeatureId); /** * @see TelecomManager#getPhoneAccountsSupportingScheme @@ -123,17 +125,19 @@ interface ITelecomService { * @see TelecomServiceImpl#isVoiceMailNumber */ boolean isVoiceMailNumber(in PhoneAccountHandle accountHandle, String number, - String callingPackage); + String callingPackage, String callingFeatureId); /** * @see TelecomServiceImpl#getVoiceMailNumber */ - String getVoiceMailNumber(in PhoneAccountHandle accountHandle, String callingPackage); + String getVoiceMailNumber(in PhoneAccountHandle accountHandle, String callingPackage, + String callingFeatureId); /** * @see TelecomServiceImpl#getLine1Number */ - String getLine1Number(in PhoneAccountHandle accountHandle, String callingPackage); + String getLine1Number(in PhoneAccountHandle accountHandle, String callingPackage, + String callingFeatureId); /** * @see TelecomServiceImpl#getDefaultPhoneApp @@ -172,12 +176,12 @@ interface ITelecomService { /** * @see TelecomServiceImpl#isInCall */ - boolean isInCall(String callingPackage); + boolean isInCall(String callingPackage, String callingFeatureId); /** * @see TelecomServiceImpl#isInManagedCall */ - boolean isInManagedCall(String callingPackage); + boolean isInManagedCall(String callingPackage, String callingFeatureId); /** * @see TelecomServiceImpl#isRinging @@ -229,12 +233,12 @@ interface ITelecomService { /** * @see TelecomServiceImpl#isTtySupported */ - boolean isTtySupported(String callingPackage); + boolean isTtySupported(String callingPackage, String callingFeatureId); /** * @see TelecomServiceImpl#getCurrentTtyMode */ - int getCurrentTtyMode(String callingPackage); + int getCurrentTtyMode(String callingPackage, String callingFeatureId); /** * @see TelecomServiceImpl#addNewIncomingCall @@ -249,7 +253,7 @@ interface ITelecomService { /** * @see TelecomServiceImpl#placeCall */ - void placeCall(in Uri handle, in Bundle extras, String callingPackage); + void placeCall(in Uri handle, in Bundle extras, String callingPackage, String callingFeatureId); /** * @see TelecomServiceImpl#enablePhoneAccount diff --git a/telephony/OWNERS b/telephony/OWNERS index 2a6e8deeb13e..58a7ea08da3f 100644 --- a/telephony/OWNERS +++ b/telephony/OWNERS @@ -14,4 +14,5 @@ shuoq@google.com refuhoo@google.com paulye@google.com nazaninb@google.com -sarahchin@google.com
\ No newline at end of file +sarahchin@google.com +dbright@google.com diff --git a/telephony/common/android/telephony/LocationAccessPolicy.java b/telephony/common/android/telephony/LocationAccessPolicy.java index f39981fdf25d..aaafee29e24a 100644 --- a/telephony/common/android/telephony/LocationAccessPolicy.java +++ b/telephony/common/android/telephony/LocationAccessPolicy.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java index eb02ea6f5e40..97bcbc061f8a 100644 --- a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java +++ b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java @@ -18,14 +18,16 @@ package com.android.internal.telephony; import android.annotation.Nullable; import android.content.ContentResolver; +import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.res.Resources; import android.os.RemoteException; +import android.os.UserHandle; import android.permission.IPermissionManager; import android.provider.Settings; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.telephony.TelephonyManager; import android.util.ArrayMap; import android.util.ArraySet; @@ -76,7 +78,7 @@ public final class CarrierAppUtils { */ public static synchronized void disableCarrierAppsUntilPrivileged(String callingPackage, IPackageManager packageManager, IPermissionManager permissionManager, - TelephonyManager telephonyManager, ContentResolver contentResolver, int userId) { + TelephonyManager telephonyManager, int userId, Context context) { if (DEBUG) { Rlog.d(TAG, "disableCarrierAppsUntilPrivileged"); } @@ -85,6 +87,7 @@ public final class CarrierAppUtils { config.getDisabledUntilUsedPreinstalledCarrierApps(); ArrayMap<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed = config.getDisabledUntilUsedPreinstalledCarrierAssociatedApps(); + ContentResolver contentResolver = getContentResolverForUser(context, userId); disableCarrierAppsUntilPrivileged(callingPackage, packageManager, permissionManager, telephonyManager, contentResolver, userId, systemCarrierAppsDisabledUntilUsed, systemCarrierAssociatedAppsDisabledUntilUsed); @@ -102,8 +105,8 @@ public final class CarrierAppUtils { * Manager can kill it, and this can lead to crashes as the app is in an unexpected state. */ public static synchronized void disableCarrierAppsUntilPrivileged(String callingPackage, - IPackageManager packageManager, IPermissionManager permissionManager, - ContentResolver contentResolver, int userId) { + IPackageManager packageManager, IPermissionManager permissionManager, int userId, + Context context) { if (DEBUG) { Rlog.d(TAG, "disableCarrierAppsUntilPrivileged"); } @@ -114,15 +117,23 @@ public final class CarrierAppUtils { ArrayMap<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed = config.getDisabledUntilUsedPreinstalledCarrierAssociatedApps(); + ContentResolver contentResolver = getContentResolverForUser(context, userId); disableCarrierAppsUntilPrivileged(callingPackage, packageManager, permissionManager, null /* telephonyManager */, contentResolver, userId, systemCarrierAppsDisabledUntilUsed, systemCarrierAssociatedAppsDisabledUntilUsed); } + private static ContentResolver getContentResolverForUser(Context context, int userId) { + Context userContext = context.createContextAsUser(UserHandle.getUserHandleForUid(userId), + 0); + return userContext.getContentResolver(); + } + /** * Disable carrier apps until they are privileged * Must be public b/c framework unit tests can't access package-private methods. */ + // Must be public b/c framework unit tests can't access package-private methods. @VisibleForTesting public static void disableCarrierAppsUntilPrivileged(String callingPackage, IPackageManager packageManager, IPermissionManager permissionManager, @@ -142,9 +153,8 @@ public final class CarrierAppUtils { systemCarrierAssociatedAppsDisabledUntilUsed); List<String> enabledCarrierPackages = new ArrayList<>(); - - boolean hasRunOnce = Settings.Secure.getIntForUser( - contentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 0, userId) == 1; + boolean hasRunOnce = Settings.Secure.getInt(contentResolver, + Settings.Secure.CARRIER_APPS_HANDLED, 0) == 1; try { for (ApplicationInfo ai : candidates) { @@ -259,8 +269,7 @@ public final class CarrierAppUtils { // Mark the execution so we do not disable apps again. if (!hasRunOnce) { - Settings.Secure.putIntForUser( - contentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 1, userId); + Settings.Secure.putInt(contentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 1); } if (!enabledCarrierPackages.isEmpty()) { diff --git a/telephony/common/com/android/internal/telephony/GsmAlphabet.java b/telephony/common/com/android/internal/telephony/GsmAlphabet.java index 5fb4e90b9666..a36ff9341275 100644 --- a/telephony/common/com/android/internal/telephony/GsmAlphabet.java +++ b/telephony/common/com/android/internal/telephony/GsmAlphabet.java @@ -16,10 +16,10 @@ package com.android.internal.telephony; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.res.Resources; import android.os.Build; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.text.TextUtils; import android.util.SparseIntArray; diff --git a/telephony/common/com/android/internal/telephony/HbpcdUtils.java b/telephony/common/com/android/internal/telephony/HbpcdUtils.java index 2f3194214be6..45a563c09394 100644 --- a/telephony/common/com/android/internal/telephony/HbpcdUtils.java +++ b/telephony/common/com/android/internal/telephony/HbpcdUtils.java @@ -19,7 +19,7 @@ package com.android.internal.telephony; import android.content.ContentResolver; import android.content.Context; import android.database.Cursor; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import com.android.internal.telephony.HbpcdLookup.ArbitraryMccSidMatch; import com.android.internal.telephony.HbpcdLookup.MccIdd; diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java index b30258906368..afb9b6f52bdb 100644 --- a/telephony/common/com/android/internal/telephony/SmsApplication.java +++ b/telephony/common/com/android/internal/telephony/SmsApplication.java @@ -40,7 +40,7 @@ import android.os.UserHandle; import android.provider.Telephony; import android.provider.Telephony.Sms.Intents; import android.telephony.PackageChangeReceiver; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.telephony.TelephonyManager; import android.util.Log; diff --git a/telephony/common/com/android/internal/telephony/SmsConstants.java b/telephony/common/com/android/internal/telephony/SmsConstants.java index 19f52b0ef429..3aa8bbf607d1 100644 --- a/telephony/common/com/android/internal/telephony/SmsConstants.java +++ b/telephony/common/com/android/internal/telephony/SmsConstants.java @@ -15,7 +15,7 @@ */ package com.android.internal.telephony; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; /** * SMS Constants and must be the same as the corresponding diff --git a/telephony/common/com/android/internal/telephony/SmsNumberUtils.java b/telephony/common/com/android/internal/telephony/SmsNumberUtils.java index 06c08f56aa1f..06c37288a1a6 100644 --- a/telephony/common/com/android/internal/telephony/SmsNumberUtils.java +++ b/telephony/common/com/android/internal/telephony/SmsNumberUtils.java @@ -24,7 +24,7 @@ import android.os.PersistableBundle; import android.os.SystemProperties; import android.telephony.CarrierConfigManager; import android.telephony.PhoneNumberUtils; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.telephony.TelephonyManager; import android.text.TextUtils; diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java index 80a55b2a1147..4109ca6bd7d0 100644 --- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java +++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java @@ -28,7 +28,7 @@ import android.os.Binder; import android.os.Build; import android.os.Process; import android.os.UserHandle; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.util.Log; diff --git a/telephony/java/com/android/internal/telephony/util/TelephonyUtils.java b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java index 2abcc76fdccc..2abcc76fdccc 100644 --- a/telephony/java/com/android/internal/telephony/util/TelephonyUtils.java +++ b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java diff --git a/telephony/java/android/service/carrier/CarrierIdentifier.java b/telephony/java/android/service/carrier/CarrierIdentifier.java index af5bf7475f95..7957c258b54f 100644 --- a/telephony/java/android/service/carrier/CarrierIdentifier.java +++ b/telephony/java/android/service/carrier/CarrierIdentifier.java @@ -20,7 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.telephony.TelephonyManager; import com.android.internal.telephony.uicc.IccUtils; diff --git a/telephony/java/android/service/euicc/EuiccProfileInfo.java b/telephony/java/android/service/euicc/EuiccProfileInfo.java index 6c357ccdd03d..8450a9018634 100644 --- a/telephony/java/android/service/euicc/EuiccProfileInfo.java +++ b/telephony/java/android/service/euicc/EuiccProfileInfo.java @@ -19,7 +19,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.os.Parcelable; import android.service.carrier.CarrierIdentifier; diff --git a/telephony/java/android/service/euicc/GetDefaultDownloadableSubscriptionListResult.java b/telephony/java/android/service/euicc/GetDefaultDownloadableSubscriptionListResult.java index c7a985160730..2382f657c9ee 100644 --- a/telephony/java/android/service/euicc/GetDefaultDownloadableSubscriptionListResult.java +++ b/telephony/java/android/service/euicc/GetDefaultDownloadableSubscriptionListResult.java @@ -17,7 +17,7 @@ package android.service.euicc; import android.annotation.Nullable; import android.annotation.SystemApi; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.os.Parcelable; import android.telephony.euicc.DownloadableSubscription; diff --git a/telephony/java/android/service/euicc/GetDownloadableSubscriptionMetadataResult.java b/telephony/java/android/service/euicc/GetDownloadableSubscriptionMetadataResult.java index abd4065c754a..d0fb51180c1d 100644 --- a/telephony/java/android/service/euicc/GetDownloadableSubscriptionMetadataResult.java +++ b/telephony/java/android/service/euicc/GetDownloadableSubscriptionMetadataResult.java @@ -17,7 +17,7 @@ package android.service.euicc; import android.annotation.Nullable; import android.annotation.SystemApi; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.os.Parcelable; import android.telephony.euicc.DownloadableSubscription; diff --git a/telephony/java/android/telephony/AnomalyReporter.java b/telephony/java/android/telephony/AnomalyReporter.java index 9753d8be4065..097041f672ac 100644 --- a/telephony/java/android/telephony/AnomalyReporter.java +++ b/telephony/java/android/telephony/AnomalyReporter.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.content.Context; diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index fdf88497db02..6a622378dac7 100755 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; @@ -24,12 +26,11 @@ import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; import android.os.PersistableBundle; import android.os.RemoteException; -import android.os.ServiceManager; import android.service.carrier.CarrierService; import android.telecom.TelecomManager; import android.telephony.ims.ImsReasonInfo; @@ -3124,7 +3125,6 @@ public class CarrierConfigManager { * EAP-AKA: "0" * EAP-SIM: "1" * EAP-AKA_PRIME: "6" - * @hide */ public static final String ENABLE_EAP_METHOD_PREFIX_BOOL = "enable_eap_method_prefix_bool"; @@ -3399,12 +3399,17 @@ public class CarrierConfigManager { /** Prefix of all Ims.KEY_* constants. */ public static final String KEY_PREFIX = "ims."; - //TODO: Add configs related to IMS. + /** + * Delay in milliseconds to turn off wifi when IMS is registered over wifi. + */ + public static final String KEY_WIFI_OFF_DEFERRING_TIME_INT = + KEY_PREFIX + "wifi_off_deferring_time_int"; private Ims() {} private static PersistableBundle getDefaults() { PersistableBundle defaults = new PersistableBundle(); + defaults.putInt(KEY_WIFI_OFF_DEFERRING_TIME_INT, 0); return defaults; } } @@ -4205,8 +4210,11 @@ public class CarrierConfigManager { /** @hide */ @Nullable private ICarrierConfigLoader getICarrierConfigLoader() { - return ICarrierConfigLoader.Stub - .asInterface(ServiceManager.getService(Context.CARRIER_CONFIG_SERVICE)); + return ICarrierConfigLoader.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getCarrierConfigServiceRegisterer() + .get()); } /** diff --git a/telephony/java/android/telephony/CbGeoUtils.java b/telephony/java/android/telephony/CbGeoUtils.java index 84be4e8b9ba4..719ba8d98773 100644 --- a/telephony/java/android/telephony/CbGeoUtils.java +++ b/telephony/java/android/telephony/CbGeoUtils.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.NonNull; import android.annotation.SystemApi; import android.text.TextUtils; diff --git a/telephony/java/android/telephony/CellIdentity.java b/telephony/java/android/telephony/CellIdentity.java index ce32dce834db..8e703fee3126 100644 --- a/telephony/java/android/telephony/CellIdentity.java +++ b/telephony/java/android/telephony/CellIdentity.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.CallSuper; import android.annotation.NonNull; import android.annotation.Nullable; @@ -191,6 +193,7 @@ public abstract class CellIdentity implements Parcelable { * * @hide */ + @SystemApi public abstract @NonNull CellIdentity sanitizeLocationInfo(); @Override diff --git a/telephony/java/android/telephony/CellIdentityGsm.java b/telephony/java/android/telephony/CellIdentityGsm.java index 2ecdfce92825..49f425acead6 100644 --- a/telephony/java/android/telephony/CellIdentityGsm.java +++ b/telephony/java/android/telephony/CellIdentityGsm.java @@ -18,7 +18,7 @@ package android.telephony; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.telephony.gsm.GsmCellLocation; import android.text.TextUtils; diff --git a/telephony/java/android/telephony/CellIdentityLte.java b/telephony/java/android/telephony/CellIdentityLte.java index 15c91752badf..bc4655069dba 100644 --- a/telephony/java/android/telephony/CellIdentityLte.java +++ b/telephony/java/android/telephony/CellIdentityLte.java @@ -18,7 +18,7 @@ package android.telephony; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.UnsupportedAppUsage; +import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; import android.os.Parcel; import android.telephony.gsm.GsmCellLocation; diff --git a/telephony/java/android/telephony/CellInfoCdma.java b/telephony/java/android/telephony/CellInfoCdma.java index 2b1387c313ae..acb21f450243 100644 --- a/telephony/java/android/telephony/CellInfoCdma.java +++ b/telephony/java/android/telephony/CellInfoCdma.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; diff --git a/telephony/java/android/telephony/CellInfoGsm.java b/telephony/java/android/telephony/CellInfoGsm.java index 4f7c7a93d19b..79a9d44f36a2 100644 --- a/telephony/java/android/telephony/CellInfoGsm.java +++ b/telephony/java/android/telephony/CellInfoGsm.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; import android.os.Parcel; diff --git a/telephony/java/android/telephony/CellInfoLte.java b/telephony/java/android/telephony/CellInfoLte.java index 6d19261c8932..fed3ebf4af03 100644 --- a/telephony/java/android/telephony/CellInfoLte.java +++ b/telephony/java/android/telephony/CellInfoLte.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; diff --git a/telephony/java/android/telephony/CellInfoTdscdma.java b/telephony/java/android/telephony/CellInfoTdscdma.java index f1305f5ca768..58ff8c9558d9 100644 --- a/telephony/java/android/telephony/CellInfoTdscdma.java +++ b/telephony/java/android/telephony/CellInfoTdscdma.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.NonNull; import android.os.Parcel; import android.os.Parcelable; diff --git a/telephony/java/android/telephony/CellInfoWcdma.java b/telephony/java/android/telephony/CellInfoWcdma.java index ee5fec838d2d..33f6a555414c 100644 --- a/telephony/java/android/telephony/CellInfoWcdma.java +++ b/telephony/java/android/telephony/CellInfoWcdma.java @@ -18,7 +18,7 @@ package android.telephony; import android.os.Parcel; import android.os.Parcelable; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import java.util.Objects; diff --git a/telephony/java/android/telephony/CellLocation.java b/telephony/java/android/telephony/CellLocation.java index 64776e377fa4..2d0bd52f84ee 100644 --- a/telephony/java/android/telephony/CellLocation.java +++ b/telephony/java/android/telephony/CellLocation.java @@ -19,7 +19,6 @@ package android.telephony; import android.compat.annotation.UnsupportedAppUsage; import android.os.Bundle; import android.os.RemoteException; -import android.os.ServiceManager; import android.telephony.cdma.CdmaCellLocation; import android.telephony.gsm.GsmCellLocation; @@ -38,7 +37,11 @@ public abstract class CellLocation { */ public static void requestLocationUpdate() { try { - ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.getService("phone")); + ITelephony phone = ITelephony.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getTelephonyServiceRegisterer() + .get()); if (phone != null) { phone.updateServiceLocation(); } @@ -50,8 +53,7 @@ public abstract class CellLocation { /** * Create a new CellLocation from a intent notifier Bundle * - * This method is used by PhoneStateIntentReceiver and maybe by - * external applications. + * This method maybe used by external applications. * * @param bundle Bundle from intent notifier * @return newly created CellLocation diff --git a/telephony/java/android/telephony/CellSignalStrengthCdma.java b/telephony/java/android/telephony/CellSignalStrengthCdma.java index 199843905854..cab3b0cd3c47 100644 --- a/telephony/java/android/telephony/CellSignalStrengthCdma.java +++ b/telephony/java/android/telephony/CellSignalStrengthCdma.java @@ -20,7 +20,7 @@ import android.annotation.IntRange; import android.os.Parcel; import android.os.Parcelable; import android.os.PersistableBundle; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import java.util.Objects; diff --git a/telephony/java/android/telephony/CellSignalStrengthGsm.java b/telephony/java/android/telephony/CellSignalStrengthGsm.java index a9f3487a0880..28052aa93486 100644 --- a/telephony/java/android/telephony/CellSignalStrengthGsm.java +++ b/telephony/java/android/telephony/CellSignalStrengthGsm.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.IntRange; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; diff --git a/telephony/java/android/telephony/CellSignalStrengthLte.java b/telephony/java/android/telephony/CellSignalStrengthLte.java index a6ba9c279ae3..2ef2a52977ff 100644 --- a/telephony/java/android/telephony/CellSignalStrengthLte.java +++ b/telephony/java/android/telephony/CellSignalStrengthLte.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.IntRange; import android.compat.annotation.UnsupportedAppUsage; import android.os.Parcel; diff --git a/telephony/java/android/telephony/CellSignalStrengthNr.java b/telephony/java/android/telephony/CellSignalStrengthNr.java index f31fafe36508..4d67bcf536cf 100644 --- a/telephony/java/android/telephony/CellSignalStrengthNr.java +++ b/telephony/java/android/telephony/CellSignalStrengthNr.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.IntDef; import android.annotation.IntRange; import android.os.Parcel; @@ -155,7 +157,17 @@ public final class CellSignalStrengthNr extends CellSignalStrength implements Pa * @param ss signal strength from modem. */ public CellSignalStrengthNr(android.hardware.radio.V1_4.NrSignalStrength ss) { - this(ss.csiRsrp, ss.csiRsrq, ss.csiSinr, ss.ssRsrp, ss.ssRsrq, ss.ssSinr); + this(flip(ss.csiRsrp), flip(ss.csiRsrq), ss.csiSinr, flip(ss.ssRsrp), flip(ss.ssRsrq), + ss.ssSinr); + } + + /** + * Flip sign cell strength value when taking in the value from hal + * @param val cell strength value + * @return flipped value + */ + private static int flip(int val) { + return val != CellInfo.UNAVAILABLE ? -val : val; } /** diff --git a/telephony/java/android/telephony/CellSignalStrengthTdscdma.java b/telephony/java/android/telephony/CellSignalStrengthTdscdma.java index f4a3dbb37988..3bd9d5810136 100644 --- a/telephony/java/android/telephony/CellSignalStrengthTdscdma.java +++ b/telephony/java/android/telephony/CellSignalStrengthTdscdma.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.IntRange; import android.annotation.NonNull; import android.os.Parcel; diff --git a/telephony/java/android/telephony/CellSignalStrengthWcdma.java b/telephony/java/android/telephony/CellSignalStrengthWcdma.java index 34b13858f104..535e9520074d 100644 --- a/telephony/java/android/telephony/CellSignalStrengthWcdma.java +++ b/telephony/java/android/telephony/CellSignalStrengthWcdma.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.IntRange; import android.annotation.StringDef; import android.compat.annotation.UnsupportedAppUsage; diff --git a/telephony/java/android/telephony/DisconnectCause.java b/telephony/java/android/telephony/DisconnectCause.java index aff1391fc080..be85b30f272b 100644 --- a/telephony/java/android/telephony/DisconnectCause.java +++ b/telephony/java/android/telephony/DisconnectCause.java @@ -360,6 +360,12 @@ public final class DisconnectCause { */ public static final int OUTGOING_EMERGENCY_CALL_PLACED = 80; + /** + * Indicates that incoming call was rejected by the modem before the call went in ringing + */ + public static final int INCOMING_AUTO_REJECTED = 81; + + //********************************************************************************************* // When adding a disconnect type: // 1) Update toString() with the newly added disconnect type. @@ -536,6 +542,8 @@ public final class DisconnectCause { return "WFC_SERVICE_NOT_AVAILABLE_IN_THIS_LOCATION"; case OUTGOING_EMERGENCY_CALL_PLACED: return "OUTGOING_EMERGENCY_CALL_PLACED"; + case INCOMING_AUTO_REJECTED: + return "INCOMING_AUTO_REJECTED"; default: return "INVALID: " + cause; } diff --git a/telephony/java/android/telephony/NetworkScan.java b/telephony/java/android/telephony/NetworkScan.java index 202da6817cb5..a6dedf761636 100644 --- a/telephony/java/android/telephony/NetworkScan.java +++ b/telephony/java/android/telephony/NetworkScan.java @@ -16,10 +16,10 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.IntDef; -import android.content.Context; import android.os.RemoteException; -import android.os.ServiceManager; import com.android.internal.telephony.ITelephony; @@ -148,6 +148,9 @@ public class NetworkScan { private ITelephony getITelephony() { return ITelephony.Stub.asInterface( - ServiceManager.getService(Context.TELEPHONY_SERVICE)); + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getTelephonyServiceRegisterer() + .get()); } } diff --git a/telephony/java/android/telephony/NetworkService.java b/telephony/java/android/telephony/NetworkService.java index 8c5e10788b89..844289ce75d4 100644 --- a/telephony/java/android/telephony/NetworkService.java +++ b/telephony/java/android/telephony/NetworkService.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SdkConstant; diff --git a/telephony/java/android/telephony/NetworkServiceCallback.java b/telephony/java/android/telephony/NetworkServiceCallback.java index 89b96654451e..214ab41ae4f2 100644 --- a/telephony/java/android/telephony/NetworkServiceCallback.java +++ b/telephony/java/android/telephony/NetworkServiceCallback.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.IntDef; import android.annotation.Nullable; import android.annotation.SystemApi; diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java index 6e86a4211834..2f9e6ac0f9ff 100644 --- a/telephony/java/android/telephony/PhoneNumberUtils.java +++ b/telephony/java/android/telephony/PhoneNumberUtils.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java index 8139330cfc59..2c62d0667e19 100644 --- a/telephony/java/android/telephony/ServiceState.java +++ b/telephony/java/android/telephony/ServiceState.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -545,7 +547,7 @@ public class ServiceState implements Parcelable { * @see #STATE_EMERGENCY_ONLY * @see #STATE_POWER_OFF * - * @return current data registration state {@link RegState} + * @return current data registration state * * @hide */ @@ -562,7 +564,7 @@ public class ServiceState implements Parcelable { * @see #STATE_EMERGENCY_ONLY * @see #STATE_POWER_OFF * - * @return current data registration state {@link RegState} + * @return current data registration state * * @hide */ @@ -2044,4 +2046,27 @@ public class ServiceState implements Parcelable { public boolean isIwlanPreferred() { return mIsIwlanPreferred; } + /** + * @return {@code true}Returns True whenever the modem is searching for service. + * To check both CS and PS domain + */ + + public boolean isSearching() { + NetworkRegistrationInfo psRegState = getNetworkRegistrationInfo( + NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN); + + if (psRegState != null && psRegState.getRegistrationState() + == NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_SEARCHING) { + return true; + } + + NetworkRegistrationInfo csRegState = getNetworkRegistrationInfo( + NetworkRegistrationInfo.DOMAIN_CS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN); + + if (csRegState != null && csRegState.getRegistrationState() + == NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_SEARCHING) { + return true; + } + return false; + } } diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java index 3350a3371504..1c58f8faf7cf 100644 --- a/telephony/java/android/telephony/SignalStrength.java +++ b/telephony/java/android/telephony/SignalStrength.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.NonNull; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; @@ -85,8 +87,7 @@ public class SignalStrength implements Parcelable { /** * Create a new SignalStrength from a intent notifier Bundle * - * This method is used by PhoneStateIntentReceiver and maybe by - * external applications. + * This method may be used by external applications. * * @param m Bundle from intent notifier * @return newly created SignalStrength diff --git a/telephony/java/android/telephony/SmsCbMessage.java b/telephony/java/android/telephony/SmsCbMessage.java index 045d1ebb5640..3c6709415281 100644 --- a/telephony/java/android/telephony/SmsCbMessage.java +++ b/telephony/java/android/telephony/SmsCbMessage.java @@ -557,7 +557,7 @@ public final class SmsCbMessage implements Parcelable { public ContentValues getContentValues() { ContentValues cv = new ContentValues(16); cv.put(CellBroadcasts.SLOT_INDEX, mSlotIndex); - cv.put(CellBroadcasts.SUB_ID, mSubId); + cv.put(CellBroadcasts.SUBSCRIPTION_ID, mSubId); cv.put(CellBroadcasts.GEOGRAPHICAL_SCOPE, mGeographicalScope); if (mLocation.getPlmn() != null) { cv.put(CellBroadcasts.PLMN, mLocation.getPlmn()); @@ -621,7 +621,7 @@ public final class SmsCbMessage implements Parcelable { int format = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.MESSAGE_FORMAT)); int priority = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.MESSAGE_PRIORITY)); int slotIndex = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.SLOT_INDEX)); - int subId = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.SUB_ID)); + int subId = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.SUBSCRIPTION_ID)); String plmn; int plmnColumn = cursor.getColumnIndex(CellBroadcasts.PLMN); diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java index fbe355e5c78b..eb328a705e56 100644 --- a/telephony/java/android/telephony/SmsManager.java +++ b/telephony/java/android/telephony/SmsManager.java @@ -35,7 +35,6 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.RemoteException; -import android.os.ServiceManager; import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; @@ -745,7 +744,11 @@ public final class SmsManager { "Invalid pdu format. format must be either 3gpp or 3gpp2"); } try { - ISms iSms = ISms.Stub.asInterface(ServiceManager.getService("isms")); + ISms iSms = ISms.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSmsServiceRegisterer() + .get()); if (iSms != null) { iSms.injectSmsPduForSubscriber( getSubscriptionId(), pdu, format, receivedIntent); @@ -1535,7 +1538,10 @@ public final class SmsManager { private static ITelephony getITelephony() { ITelephony binder = ITelephony.Stub.asInterface( - ServiceManager.getService(Context.TELEPHONY_SERVICE)); + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getTelephonyServiceRegisterer() + .get()); if (binder == null) { throw new RuntimeException("Could not find Telephony Service."); } @@ -1573,7 +1579,11 @@ public final class SmsManager { } private static ISms getISmsService() { - return ISms.Stub.asInterface(ServiceManager.getService("isms")); + return ISms.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSmsServiceRegisterer() + .get()); } /** @@ -2007,7 +2017,11 @@ public final class SmsManager { public boolean isSMSPromptEnabled() { ISms iSms = null; try { - iSms = ISms.Stub.asInterface(ServiceManager.getService("isms")); + iSms = ISms.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSmsServiceRegisterer() + .get()); return iSms.isSMSPromptEnabled(); } catch (RemoteException ex) { return false; diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java index c0bc29d26c6c..c217b8b83c26 100644 --- a/telephony/java/android/telephony/SmsMessage.java +++ b/telephony/java/android/telephony/SmsMessage.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA; import android.annotation.Nullable; diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java index 2c0a1c94dc8d..c24eeb74f6cd 100644 --- a/telephony/java/android/telephony/SubscriptionInfo.java +++ b/telephony/java/android/telephony/SubscriptionInfo.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.Nullable; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 4c8a81b2e9a1..4510fede4e8e 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import static android.net.NetworkPolicyManager.OVERRIDE_CONGESTED; import static android.net.NetworkPolicyManager.OVERRIDE_UNMETERED; @@ -53,7 +55,6 @@ import android.os.Looper; import android.os.ParcelUuid; import android.os.Process; import android.os.RemoteException; -import android.os.ServiceManager; import android.provider.Telephony.SimInfo; import android.telephony.euicc.EuiccManager; import android.telephony.ims.ImsMmTelManager; @@ -265,7 +266,7 @@ public class SubscriptionManager { * <P>Type: TEXT (String)</P> */ /** @hide */ - public static final String UNIQUE_KEY_SUBSCRIPTION_ID = "_id"; + public static final String UNIQUE_KEY_SUBSCRIPTION_ID = SimInfo.UNIQUE_KEY_SUBSCRIPTION_ID; /** * TelephonyProvider column name for a unique identifier for the subscription within the @@ -274,18 +275,18 @@ public class SubscriptionManager { * <P>Type: TEXT (String)</P> */ /** @hide */ - public static final String ICC_ID = "icc_id"; + public static final String ICC_ID = SimInfo.ICC_ID; /** * TelephonyProvider column name for user SIM_SlOT_INDEX * <P>Type: INTEGER (int)</P> */ /** @hide */ - public static final String SIM_SLOT_INDEX = "sim_id"; + public static final String SIM_SLOT_INDEX = SimInfo.SIM_SLOT_INDEX; /** SIM is not inserted */ /** @hide */ - public static final int SIM_NOT_INSERTED = -1; + public static final int SIM_NOT_INSERTED = SimInfo.SIM_NOT_INSERTED; /** * The slot-index for Bluetooth Remote-SIM subscriptions @@ -300,23 +301,7 @@ public class SubscriptionManager { * Default value is 0. */ /** @hide */ - public static final String SUBSCRIPTION_TYPE = "subscription_type"; - - /** - * TelephonyProvider column name white_listed_apn_data. - * It's a bitmask of APN types that will be allowed on this subscription even if it's metered - * and mobile data is turned off by the user. - * <P>Type: INTEGER (int)</P> For example, if TYPE_MMS is is true, Telephony will allow MMS - * data connection to setup even if MMS is metered and mobile_data is turned off on that - * subscription. - * - * Default value is 0. - * - * @deprecated Replaced by {@link #DATA_ENABLED_OVERRIDE_RULES} - * @hide - */ - @Deprecated - public static final String WHITE_LISTED_APN_DATA = "white_listed_apn_data"; + public static final String SUBSCRIPTION_TYPE = SimInfo.SUBSCRIPTION_TYPE; /** * TelephonyProvider column name data_enabled_override_rules. @@ -329,7 +314,15 @@ public class SubscriptionManager { * * @hide */ - public static final String DATA_ENABLED_OVERRIDE_RULES = "data_enabled_override_rules"; + public static final String DATA_ENABLED_OVERRIDE_RULES = SimInfo.DATA_ENABLED_OVERRIDE_RULES; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"SUBSCRIPTION_TYPE_"}, + value = { + SUBSCRIPTION_TYPE_LOCAL_SIM, + SUBSCRIPTION_TYPE_REMOTE_SIM}) + public @interface SubscriptionType {} /** * This constant is to designate a subscription as a Local-SIM Subscription. @@ -337,7 +330,7 @@ public class SubscriptionManager { * device. * </p> */ - public static final int SUBSCRIPTION_TYPE_LOCAL_SIM = 0; + public static final int SUBSCRIPTION_TYPE_LOCAL_SIM = SimInfo.SUBSCRIPTION_TYPE_LOCAL_SIM; /** * This constant is to designate a subscription as a Remote-SIM Subscription. @@ -363,29 +356,21 @@ public class SubscriptionManager { * was never seen before. * </p> */ - public static final int SUBSCRIPTION_TYPE_REMOTE_SIM = 1; - - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(prefix = {"SUBSCRIPTION_TYPE_"}, - value = { - SUBSCRIPTION_TYPE_LOCAL_SIM, - SUBSCRIPTION_TYPE_REMOTE_SIM}) - public @interface SubscriptionType {} + public static final int SUBSCRIPTION_TYPE_REMOTE_SIM = SimInfo.SUBSCRIPTION_TYPE_REMOTE_SIM; /** * TelephonyProvider column name for user displayed name. * <P>Type: TEXT (String)</P> */ /** @hide */ - public static final String DISPLAY_NAME = "display_name"; + public static final String DISPLAY_NAME = SimInfo.DISPLAY_NAME; /** * TelephonyProvider column name for the service provider name for the SIM. * <P>Type: TEXT (String)</P> */ /** @hide */ - public static final String CARRIER_NAME = "carrier_name"; + public static final String CARRIER_NAME = SimInfo.CARRIER_NAME; /** * Default name resource @@ -399,44 +384,44 @@ public class SubscriptionManager { * * @hide */ - public static final String NAME_SOURCE = "name_source"; + public static final String NAME_SOURCE = SimInfo.NAME_SOURCE; /** * The name_source is the default, which is from the carrier id. * @hide */ - public static final int NAME_SOURCE_DEFAULT_SOURCE = 0; + public static final int NAME_SOURCE_DEFAULT = SimInfo.NAME_SOURCE_DEFAULT; /** * The name_source is from SIM EF_SPN. * @hide */ - public static final int NAME_SOURCE_SIM_SPN = 1; + public static final int NAME_SOURCE_SIM_SPN = SimInfo.NAME_SOURCE_SIM_SPN; /** * The name_source is from user input * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) - public static final int NAME_SOURCE_USER_INPUT = 2; + public static final int NAME_SOURCE_USER_INPUT = SimInfo.NAME_SOURCE_USER_INPUT; /** * The name_source is carrier (carrier app, carrier config, etc.) * @hide */ - public static final int NAME_SOURCE_CARRIER = 3; + public static final int NAME_SOURCE_CARRIER = SimInfo.NAME_SOURCE_CARRIER; /** * The name_source is from SIM EF_PNN. * @hide */ - public static final int NAME_SOURCE_SIM_PNN = 4; + public static final int NAME_SOURCE_SIM_PNN = SimInfo.NAME_SOURCE_SIM_PNN; /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = {"NAME_SOURCE_"}, value = { - NAME_SOURCE_DEFAULT_SOURCE, + NAME_SOURCE_DEFAULT, NAME_SOURCE_SIM_SPN, NAME_SOURCE_USER_INPUT, NAME_SOURCE_CARRIER, @@ -449,67 +434,30 @@ public class SubscriptionManager { * <P>Type: INTEGER (int)</P> */ /** @hide */ - public static final String COLOR = "color"; - - /** @hide */ - public static final int COLOR_1 = 0; - - /** @hide */ - public static final int COLOR_2 = 1; - - /** @hide */ - public static final int COLOR_3 = 2; - - /** @hide */ - public static final int COLOR_4 = 3; - - /** @hide */ - public static final int COLOR_DEFAULT = COLOR_1; + public static final String COLOR = SimInfo.COLOR; /** * TelephonyProvider column name for the phone number of a SIM. * <P>Type: TEXT (String)</P> */ /** @hide */ - public static final String NUMBER = "number"; - - /** - * TelephonyProvider column name for the number display format of a SIM. - * <P>Type: INTEGER (int)</P> - */ - /** @hide */ - public static final String DISPLAY_NUMBER_FORMAT = "display_number_format"; - - /** @hide */ - public static final int DISPLAY_NUMBER_NONE = 0; - - /** @hide */ - public static final int DISPLAY_NUMBER_FIRST = 1; - - /** @hide */ - public static final int DISPLAY_NUMBER_LAST = 2; - - /** @hide */ - public static final int DISPLAY_NUMBER_DEFAULT = DISPLAY_NUMBER_FIRST; + public static final String NUMBER = SimInfo.NUMBER; /** - * TelephonyProvider column name for permission for data roaming of a SIM. + * TelephonyProvider column name for whether data roaming is enabled. * <P>Type: INTEGER (int)</P> */ /** @hide */ - public static final String DATA_ROAMING = "data_roaming"; + public static final String DATA_ROAMING = SimInfo.DATA_ROAMING; /** Indicates that data roaming is enabled for a subscription */ - public static final int DATA_ROAMING_ENABLE = 1; + public static final int DATA_ROAMING_ENABLE = SimInfo.DATA_ROAMING_ENABLE; /** Indicates that data roaming is disabled for a subscription */ - public static final int DATA_ROAMING_DISABLE = 0; - - /** @hide */ - public static final int DATA_ROAMING_DEFAULT = DATA_ROAMING_DISABLE; + public static final int DATA_ROAMING_DISABLE = SimInfo.DATA_ROAMING_DISABLE; /** @hide */ - public static final int SIM_PROVISIONED = 0; + public static final int DATA_ROAMING_DEFAULT = SimInfo.DATA_ROAMING_DEFAULT; /** * TelephonyProvider column name for subscription carrier id. @@ -517,61 +465,61 @@ public class SubscriptionManager { * <p>Type: INTEGER (int) </p> * @hide */ - public static final String CARRIER_ID = "carrier_id"; + public static final String CARRIER_ID = SimInfo.CARRIER_ID; /** * @hide A comma-separated list of EHPLMNs associated with the subscription * <P>Type: TEXT (String)</P> */ - public static final String EHPLMNS = "ehplmns"; + public static final String EHPLMNS = SimInfo.EHPLMNS; /** * @hide A comma-separated list of HPLMNs associated with the subscription * <P>Type: TEXT (String)</P> */ - public static final String HPLMNS = "hplmns"; + public static final String HPLMNS = SimInfo.HPLMNS; /** * TelephonyProvider column name for the MCC associated with a SIM, stored as a string. * <P>Type: TEXT (String)</P> * @hide */ - public static final String MCC_STRING = "mcc_string"; + public static final String MCC_STRING = SimInfo.MCC_STRING; /** * TelephonyProvider column name for the MNC associated with a SIM, stored as a string. * <P>Type: TEXT (String)</P> * @hide */ - public static final String MNC_STRING = "mnc_string"; + public static final String MNC_STRING = SimInfo.MNC_STRING; /** * TelephonyProvider column name for the MCC associated with a SIM. * <P>Type: INTEGER (int)</P> * @hide */ - public static final String MCC = "mcc"; + public static final String MCC = SimInfo.MCC; /** * TelephonyProvider column name for the MNC associated with a SIM. * <P>Type: INTEGER (int)</P> * @hide */ - public static final String MNC = "mnc"; + public static final String MNC = SimInfo.MNC; /** * TelephonyProvider column name for the iso country code associated with a SIM. * <P>Type: TEXT (String)</P> * @hide */ - public static final String ISO_COUNTRY_CODE = "iso_country_code"; + public static final String ISO_COUNTRY_CODE = SimInfo.ISO_COUNTRY_CODE; /** * TelephonyProvider column name for the sim provisioning status associated with a SIM. * <P>Type: INTEGER (int)</P> * @hide */ - public static final String SIM_PROVISIONING_STATUS = "sim_provisioning_status"; + public static final String SIM_PROVISIONING_STATUS = SimInfo.SIM_PROVISIONING_STATUS; /** * TelephonyProvider column name for whether a subscription is embedded (that is, present on an @@ -579,7 +527,7 @@ public class SubscriptionManager { * <p>Type: INTEGER (int), 1 for embedded or 0 for non-embedded. * @hide */ - public static final String IS_EMBEDDED = "is_embedded"; + public static final String IS_EMBEDDED = SimInfo.IS_EMBEDDED; /** * TelephonyProvider column name for SIM card identifier. For UICC card it is the ICCID of the @@ -587,7 +535,7 @@ public class SubscriptionManager { * <P>Type: TEXT (String)</P> * @hide */ - public static final String CARD_ID = "card_id"; + public static final String CARD_ID = SimInfo.CARD_ID; /** * TelephonyProvider column name for the encoded {@link UiccAccessRule}s from @@ -595,7 +543,7 @@ public class SubscriptionManager { * <p>TYPE: BLOB * @hide */ - public static final String ACCESS_RULES = "access_rules"; + public static final String ACCESS_RULES = SimInfo.ACCESS_RULES; /** * TelephonyProvider column name for the encoded {@link UiccAccessRule}s from @@ -605,7 +553,7 @@ public class SubscriptionManager { * @hide */ public static final String ACCESS_RULES_FROM_CARRIER_CONFIGS = - "access_rules_from_carrier_configs"; + SimInfo.ACCESS_RULES_FROM_CARRIER_CONFIGS; /** * TelephonyProvider column name identifying whether an embedded subscription is on a removable @@ -615,79 +563,79 @@ public class SubscriptionManager { * <p>TYPE: INTEGER (int), 1 for removable or 0 for non-removable. * @hide */ - public static final String IS_REMOVABLE = "is_removable"; + public static final String IS_REMOVABLE = SimInfo.IS_REMOVABLE; /** * TelephonyProvider column name for extreme threat in CB settings * @hide */ - public static final String CB_EXTREME_THREAT_ALERT = "enable_cmas_extreme_threat_alerts"; + public static final String CB_EXTREME_THREAT_ALERT = SimInfo.CB_EXTREME_THREAT_ALERT; /** * TelephonyProvider column name for severe threat in CB settings *@hide */ - public static final String CB_SEVERE_THREAT_ALERT = "enable_cmas_severe_threat_alerts"; + public static final String CB_SEVERE_THREAT_ALERT = SimInfo.CB_SEVERE_THREAT_ALERT; /** * TelephonyProvider column name for amber alert in CB settings *@hide */ - public static final String CB_AMBER_ALERT = "enable_cmas_amber_alerts"; + public static final String CB_AMBER_ALERT = SimInfo.CB_AMBER_ALERT; /** * TelephonyProvider column name for emergency alert in CB settings *@hide */ - public static final String CB_EMERGENCY_ALERT = "enable_emergency_alerts"; + public static final String CB_EMERGENCY_ALERT = SimInfo.CB_EMERGENCY_ALERT; /** * TelephonyProvider column name for alert sound duration in CB settings *@hide */ - public static final String CB_ALERT_SOUND_DURATION = "alert_sound_duration"; + public static final String CB_ALERT_SOUND_DURATION = SimInfo.CB_ALERT_SOUND_DURATION; /** * TelephonyProvider column name for alert reminder interval in CB settings *@hide */ - public static final String CB_ALERT_REMINDER_INTERVAL = "alert_reminder_interval"; + public static final String CB_ALERT_REMINDER_INTERVAL = SimInfo.CB_ALERT_REMINDER_INTERVAL; /** * TelephonyProvider column name for enabling vibrate in CB settings *@hide */ - public static final String CB_ALERT_VIBRATE = "enable_alert_vibrate"; + public static final String CB_ALERT_VIBRATE = SimInfo.CB_ALERT_VIBRATE; /** * TelephonyProvider column name for enabling alert speech in CB settings *@hide */ - public static final String CB_ALERT_SPEECH = "enable_alert_speech"; + public static final String CB_ALERT_SPEECH = SimInfo.CB_ALERT_SPEECH; /** * TelephonyProvider column name for ETWS test alert in CB settings *@hide */ - public static final String CB_ETWS_TEST_ALERT = "enable_etws_test_alerts"; + public static final String CB_ETWS_TEST_ALERT = SimInfo.CB_ETWS_TEST_ALERT; /** * TelephonyProvider column name for enable channel50 alert in CB settings *@hide */ - public static final String CB_CHANNEL_50_ALERT = "enable_channel_50_alerts"; + public static final String CB_CHANNEL_50_ALERT = SimInfo.CB_CHANNEL_50_ALERT; /** * TelephonyProvider column name for CMAS test alert in CB settings *@hide */ - public static final String CB_CMAS_TEST_ALERT= "enable_cmas_test_alerts"; + public static final String CB_CMAS_TEST_ALERT = SimInfo.CB_CMAS_TEST_ALERT; /** * TelephonyProvider column name for Opt out dialog in CB settings *@hide */ - public static final String CB_OPT_OUT_DIALOG = "show_cmas_opt_out_dialog"; + public static final String CB_OPT_OUT_DIALOG = SimInfo.CB_OPT_OUT_DIALOG; /** * TelephonyProvider column name for enable Volte. @@ -696,37 +644,37 @@ public class SubscriptionManager { * {@link CarrierConfigManager#KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL}. *@hide */ - public static final String ENHANCED_4G_MODE_ENABLED = "volte_vt_enabled"; + public static final String ENHANCED_4G_MODE_ENABLED = SimInfo.ENHANCED_4G_MODE_ENABLED; /** * TelephonyProvider column name for enable VT (Video Telephony over IMS) *@hide */ - public static final String VT_IMS_ENABLED = "vt_ims_enabled"; + public static final String VT_IMS_ENABLED = SimInfo.VT_IMS_ENABLED; /** * TelephonyProvider column name for enable Wifi calling *@hide */ - public static final String WFC_IMS_ENABLED = "wfc_ims_enabled"; + public static final String WFC_IMS_ENABLED = SimInfo.WFC_IMS_ENABLED; /** * TelephonyProvider column name for Wifi calling mode *@hide */ - public static final String WFC_IMS_MODE = "wfc_ims_mode"; + public static final String WFC_IMS_MODE = SimInfo.WFC_IMS_MODE; /** * TelephonyProvider column name for Wifi calling mode in roaming *@hide */ - public static final String WFC_IMS_ROAMING_MODE = "wfc_ims_roaming_mode"; + public static final String WFC_IMS_ROAMING_MODE = SimInfo.WFC_IMS_ROAMING_MODE; /** * TelephonyProvider column name for enable Wifi calling in roaming *@hide */ - public static final String WFC_IMS_ROAMING_ENABLED = "wfc_ims_roaming_enabled"; + public static final String WFC_IMS_ROAMING_ENABLED = SimInfo.WFC_IMS_ROAMING_ENABLED; /** * TelephonyProvider column name for whether a subscription is opportunistic, that is, @@ -735,7 +683,7 @@ public class SubscriptionManager { * <p>Type: INTEGER (int), 1 for opportunistic or 0 for non-opportunistic. * @hide */ - public static final String IS_OPPORTUNISTIC = "is_opportunistic"; + public static final String IS_OPPORTUNISTIC = SimInfo.IS_OPPORTUNISTIC; /** * TelephonyProvider column name for group ID. Subscriptions with same group ID @@ -744,7 +692,7 @@ public class SubscriptionManager { * * @hide */ - public static final String GROUP_UUID = "group_uuid"; + public static final String GROUP_UUID = SimInfo.GROUP_UUID; /** * TelephonyProvider column name for group owner. It's the package name who created @@ -752,14 +700,7 @@ public class SubscriptionManager { * * @hide */ - public static final String GROUP_OWNER = "group_owner"; - - /** - * TelephonyProvider column name for whether a subscription is metered or not, that is, whether - * the network it connects to charges for subscription or not. For example, paid CBRS or unpaid. - * @hide - */ - public static final String IS_METERED = "is_metered"; + public static final String GROUP_OWNER = SimInfo.GROUP_OWNER; /** * TelephonyProvider column name for the profile class of a subscription @@ -767,7 +708,7 @@ public class SubscriptionManager { * <P>Type: INTEGER (int)</P> * @hide */ - public static final String PROFILE_CLASS = "profile_class"; + public static final String PROFILE_CLASS = SimInfo.PROFILE_CLASS; /** * Profile class of the subscription @@ -775,11 +716,11 @@ public class SubscriptionManager { */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = { "PROFILE_CLASS_" }, value = { - PROFILE_CLASS_TESTING, - PROFILE_CLASS_PROVISIONING, - PROFILE_CLASS_OPERATIONAL, - PROFILE_CLASS_UNSET, - PROFILE_CLASS_DEFAULT + SimInfo.PROFILE_CLASS_TESTING, + SimInfo.PROFILE_CLASS_PROVISIONING, + SimInfo.PROFILE_CLASS_OPERATIONAL, + SimInfo.PROFILE_CLASS_UNSET, + SimInfo.PROFILE_CLASS_DEFAULT }) public @interface ProfileClass {} @@ -791,7 +732,7 @@ public class SubscriptionManager { * @hide */ @SystemApi - public static final int PROFILE_CLASS_TESTING = 0; + public static final int PROFILE_CLASS_TESTING = SimInfo.PROFILE_CLASS_TESTING; /** * A provisioning profile is pre-loaded onto the eUICC and @@ -800,7 +741,7 @@ public class SubscriptionManager { * @hide */ @SystemApi - public static final int PROFILE_CLASS_PROVISIONING = 1; + public static final int PROFILE_CLASS_PROVISIONING = SimInfo.PROFILE_CLASS_PROVISIONING; /** * An operational profile can be pre-loaded or downloaded @@ -809,7 +750,7 @@ public class SubscriptionManager { * @hide */ @SystemApi - public static final int PROFILE_CLASS_OPERATIONAL = 2; + public static final int PROFILE_CLASS_OPERATIONAL = SimInfo.PROFILE_CLASS_OPERATIONAL; /** * The profile class is unset. This occurs when profile class @@ -818,14 +759,14 @@ public class SubscriptionManager { * @hide */ @SystemApi - public static final int PROFILE_CLASS_UNSET = -1; + public static final int PROFILE_CLASS_UNSET = SimInfo.PROFILE_CLASS_UNSET; /** * Default profile class * @hide */ @SystemApi - public static final int PROFILE_CLASS_DEFAULT = PROFILE_CLASS_UNSET; + public static final int PROFILE_CLASS_DEFAULT = SimInfo.PROFILE_CLASS_DEFAULT; /** * IMSI (International Mobile Subscriber Identity). @@ -833,13 +774,13 @@ public class SubscriptionManager { * @hide */ //TODO: add @SystemApi - public static final String IMSI = "imsi"; + public static final String IMSI = SimInfo.IMSI; /** * Whether uicc applications is set to be enabled or disabled. By default it's enabled. * @hide */ - public static final String UICC_APPLICATIONS_ENABLED = "uicc_applications_enabled"; + public static final String UICC_APPLICATIONS_ENABLED = SimInfo.UICC_APPLICATIONS_ENABLED; /** * Broadcast Action: The user has changed one of the default subs related to @@ -1023,8 +964,11 @@ public class SubscriptionManager { private final INetworkPolicyManager getNetworkPolicy() { if (mNetworkPolicy == null) { - mNetworkPolicy = INetworkPolicyManager.Stub - .asInterface(ServiceManager.getService(Context.NETWORK_POLICY_SERVICE)); + mNetworkPolicy = INetworkPolicyManager.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getNetworkPolicyServiceRegisterer() + .get()); } return mNetworkPolicy; } @@ -1041,6 +985,23 @@ public class SubscriptionManager { */ public void addOnSubscriptionsChangedListener(OnSubscriptionsChangedListener listener) { if (listener == null) return; + addOnSubscriptionsChangedListener(listener.mExecutor, listener); + } + + /** + * Register for changes to the list of active {@link SubscriptionInfo} records or to the + * individual records themselves. When a change occurs the onSubscriptionsChanged method of + * the listener will be invoked immediately if there has been a notification. The + * onSubscriptionChanged method will also be triggered once initially when calling this + * function. + * + * @param listener an instance of {@link OnSubscriptionsChangedListener} with + * onSubscriptionsChanged overridden. + * @param executor the executor that will execute callbacks. + */ + public void addOnSubscriptionsChangedListener( + @NonNull @CallbackExecutor Executor executor, + @NonNull OnSubscriptionsChangedListener listener) { String pkgName = mContext != null ? mContext.getOpPackageName() : "<unknown>"; if (DBG) { logd("register OnSubscriptionsChangedListener pkgName=" + pkgName @@ -1052,7 +1013,7 @@ public class SubscriptionManager { mContext.getSystemService(Context.TELEPHONY_REGISTRY_SERVICE); if (telephonyRegistryManager != null) { telephonyRegistryManager.addOnSubscriptionsChangedListener(listener, - listener.mExecutor); + executor); } } @@ -1185,7 +1146,11 @@ public class SubscriptionManager { SubscriptionInfo subInfo = null; try { - ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + ISub iSub = ISub.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSubscriptionServiceRegisterer() + .get()); if (iSub != null) { subInfo = iSub.getActiveSubscriptionInfo(subId, mContext.getOpPackageName(), mContext.getFeatureId()); @@ -1199,12 +1164,17 @@ public class SubscriptionManager { } /** - * Get the active SubscriptionInfo associated with the iccId + * Gets an active SubscriptionInfo {@link SubscriptionInfo} associated with the Sim IccId. + * * @param iccId the IccId of SIM card * @return SubscriptionInfo, maybe null if its not active + * * @hide */ - public SubscriptionInfo getActiveSubscriptionInfoForIccIndex(String iccId) { + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + @Nullable + @SystemApi + public SubscriptionInfo getActiveSubscriptionInfoForIcc(@NonNull String iccId) { if (VDBG) logd("[getActiveSubscriptionInfoForIccIndex]+ iccId=" + iccId); if (iccId == null) { logd("[getActiveSubscriptionInfoForIccIndex]- null iccid"); @@ -1214,7 +1184,11 @@ public class SubscriptionManager { SubscriptionInfo result = null; try { - ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + ISub iSub = ISub.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSubscriptionServiceRegisterer() + .get()); if (iSub != null) { result = iSub.getActiveSubscriptionInfoForIccId(iccId, mContext.getOpPackageName(), mContext.getFeatureId()); @@ -1248,7 +1222,11 @@ public class SubscriptionManager { SubscriptionInfo result = null; try { - ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + ISub iSub = ISub.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSubscriptionServiceRegisterer() + .get()); if (iSub != null) { result = iSub.getActiveSubscriptionInfoForSimSlotIndex(slotIndex, mContext.getOpPackageName(), mContext.getFeatureId()); @@ -1272,7 +1250,11 @@ public class SubscriptionManager { List<SubscriptionInfo> result = null; try { - ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + ISub iSub = ISub.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSubscriptionServiceRegisterer() + .get()); if (iSub != null) { result = iSub.getAllSubInfoList(mContext.getOpPackageName(), mContext.getFeatureId()); @@ -1353,7 +1335,11 @@ public class SubscriptionManager { List<SubscriptionInfo> activeList = null; try { - ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + ISub iSub = ISub.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSubscriptionServiceRegisterer() + .get()); if (iSub != null) { activeList = iSub.getActiveSubscriptionInfoList(mContext.getOpPackageName(), mContext.getFeatureId()); @@ -1404,7 +1390,11 @@ public class SubscriptionManager { List<SubscriptionInfo> result = null; try { - ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + ISub iSub = ISub.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSubscriptionServiceRegisterer() + .get()); if (iSub != null) { result = iSub.getAvailableSubscriptionInfoList(mContext.getOpPackageName(), mContext.getFeatureId()); @@ -1443,7 +1433,11 @@ public class SubscriptionManager { List<SubscriptionInfo> result = null; try { - ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + ISub iSub = ISub.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSubscriptionServiceRegisterer() + .get()); if (iSub != null) { result = iSub.getAccessibleSubscriptionInfoList(mContext.getOpPackageName()); } @@ -1472,7 +1466,11 @@ public class SubscriptionManager { public void requestEmbeddedSubscriptionInfoListRefresh() { int cardId = TelephonyManager.from(mContext).getCardIdForDefaultEuicc(); try { - ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + ISub iSub = ISub.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSubscriptionServiceRegisterer() + .get()); if (iSub != null) { iSub.requestEmbeddedSubscriptionInfoListRefresh(cardId); } @@ -1501,7 +1499,11 @@ public class SubscriptionManager { @SystemApi public void requestEmbeddedSubscriptionInfoListRefresh(int cardId) { try { - ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + ISub iSub = ISub.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSubscriptionServiceRegisterer() + .get()); if (iSub != null) { iSub.requestEmbeddedSubscriptionInfoListRefresh(cardId); } @@ -1522,7 +1524,11 @@ public class SubscriptionManager { int result = 0; try { - ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + ISub iSub = ISub.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSubscriptionServiceRegisterer() + .get()); if (iSub != null) { result = iSub.getAllSubInfoCount(mContext.getOpPackageName(), mContext.getFeatureId()); @@ -1551,7 +1557,11 @@ public class SubscriptionManager { int result = 0; try { - ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + ISub iSub = ISub.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSubscriptionServiceRegisterer() + .get()); if (iSub != null) { result = iSub.getActiveSubInfoCount(mContext.getOpPackageName(), mContext.getFeatureId()); @@ -1572,7 +1582,11 @@ public class SubscriptionManager { int result = 0; try { - ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + ISub iSub = ISub.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSubscriptionServiceRegisterer() + .get()); if (iSub != null) { result = iSub.getActiveSubInfoCountMax(); } @@ -1629,7 +1643,11 @@ public class SubscriptionManager { } try { - ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + ISub iSub = ISub.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSubscriptionServiceRegisterer() + .get()); if (iSub == null) { Log.e(LOG_TAG, "[addSubscriptionInfoRecord]- ISub service is null"); return; @@ -1663,7 +1681,11 @@ public class SubscriptionManager { } try { - ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + ISub iSub = ISub.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSubscriptionServiceRegisterer() + .get()); if (iSub == null) { Log.e(LOG_TAG, "[removeSubscriptionInfoRecord]- ISub service is null"); return; @@ -1766,7 +1788,11 @@ public class SubscriptionManager { int result = INVALID_SIM_SLOT_INDEX; try { - ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + ISub iSub = ISub.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSubscriptionServiceRegisterer() + .get()); if (iSub != null) { result = iSub.getSlotIndex(subscriptionId); } @@ -1800,7 +1826,11 @@ public class SubscriptionManager { int[] subId = null; try { - ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + ISub iSub = ISub.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSubscriptionServiceRegisterer() + .get()); if (iSub != null) { subId = iSub.getSubId(slotIndex); } @@ -1824,7 +1854,11 @@ public class SubscriptionManager { int result = INVALID_PHONE_INDEX; try { - ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + ISub iSub = ISub.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSubscriptionServiceRegisterer() + .get()); if (iSub != null) { result = iSub.getPhoneId(subId); } @@ -1858,7 +1892,11 @@ public class SubscriptionManager { int subId = INVALID_SUBSCRIPTION_ID; try { - ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + ISub iSub = ISub.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSubscriptionServiceRegisterer() + .get()); if (iSub != null) { subId = iSub.getDefaultSubId(); } @@ -1881,7 +1919,11 @@ public class SubscriptionManager { int subId = INVALID_SUBSCRIPTION_ID; try { - ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + ISub iSub = ISub.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSubscriptionServiceRegisterer() + .get()); if (iSub != null) { subId = iSub.getDefaultVoiceSubId(); } @@ -1911,7 +1953,11 @@ public class SubscriptionManager { public void setDefaultVoiceSubscriptionId(int subscriptionId) { if (VDBG) logd("setDefaultVoiceSubId sub id = " + subscriptionId); try { - ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + ISub iSub = ISub.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSubscriptionServiceRegisterer() + .get()); if (iSub != null) { iSub.setDefaultVoiceSubId(subscriptionId); } @@ -1959,7 +2005,11 @@ public class SubscriptionManager { int subId = INVALID_SUBSCRIPTION_ID; try { - ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + ISub iSub = ISub.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSubscriptionServiceRegisterer() + .get()); if (iSub != null) { subId = iSub.getDefaultSmsSubId(); } @@ -1985,7 +2035,11 @@ public class SubscriptionManager { public void setDefaultSmsSubId(int subscriptionId) { if (VDBG) logd("setDefaultSmsSubId sub id = " + subscriptionId); try { - ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + ISub iSub = ISub.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSubscriptionServiceRegisterer() + .get()); if (iSub != null) { iSub.setDefaultSmsSubId(subscriptionId); } @@ -2023,7 +2077,11 @@ public class SubscriptionManager { int subId = INVALID_SUBSCRIPTION_ID; try { - ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + ISub iSub = ISub.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSubscriptionServiceRegisterer() + .get()); if (iSub != null) { subId = iSub.getDefaultDataSubId(); } @@ -2049,7 +2107,11 @@ public class SubscriptionManager { public void setDefaultDataSubId(int subscriptionId) { if (VDBG) logd("setDataSubscription sub id = " + subscriptionId); try { - ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + ISub iSub = ISub.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSubscriptionServiceRegisterer() + .get()); if (iSub != null) { iSub.setDefaultDataSubId(subscriptionId); } @@ -2080,7 +2142,11 @@ public class SubscriptionManager { /** @hide */ public void clearSubscriptionInfo() { try { - ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + ISub iSub = ISub.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSubscriptionServiceRegisterer() + .get()); if (iSub != null) { iSub.clearSubInfo(); } @@ -2216,7 +2282,11 @@ public class SubscriptionManager { */ public @NonNull int[] getActiveSubscriptionIdList(boolean visibleOnly) { try { - ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + ISub iSub = ISub.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSubscriptionServiceRegisterer() + .get()); if (iSub != null) { int[] subId = iSub.getActiveSubIdList(visibleOnly); if (subId != null) return subId; @@ -2267,7 +2337,11 @@ public class SubscriptionManager { int simState = TelephonyManager.SIM_STATE_UNKNOWN; try { - ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + ISub iSub = ISub.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSubscriptionServiceRegisterer() + .get()); if (iSub != null) { simState = iSub.getSimStateForSlotIndex(slotIndex); } @@ -2286,7 +2360,11 @@ public class SubscriptionManager { */ public static void setSubscriptionProperty(int subId, String propKey, String propValue) { try { - ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + ISub iSub = ISub.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSubscriptionServiceRegisterer() + .get()); if (iSub != null) { iSub.setSubscriptionProperty(subId, propKey, propValue); } @@ -2306,7 +2384,11 @@ public class SubscriptionManager { Context context) { String resultValue = null; try { - ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + ISub iSub = ISub.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSubscriptionServiceRegisterer() + .get()); if (iSub != null) { resultValue = iSub.getSubscriptionProperty(subId, propKey, context.getOpPackageName(), context.getFeatureId()); @@ -2448,7 +2530,11 @@ public class SubscriptionManager { @UnsupportedAppUsage public boolean isActiveSubId(int subId) { try { - ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + ISub iSub = ISub.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSubscriptionServiceRegisterer() + .get()); if (iSub != null) { return iSub.isActiveSubId(subId, mContext.getOpPackageName(), mContext.getFeatureId()); @@ -2751,7 +2837,11 @@ public class SubscriptionManager { @TelephonyManager.SetOpportunisticSubscriptionResult Consumer<Integer> callback) { if (VDBG) logd("[setPreferredDataSubscriptionId]+ subId:" + subId); try { - ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + ISub iSub = ISub.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSubscriptionServiceRegisterer() + .get()); if (iSub == null) return; ISetOpportunisticDataCallback callbackStub = new ISetOpportunisticDataCallback.Stub() { @@ -2794,7 +2884,11 @@ public class SubscriptionManager { public int getPreferredDataSubscriptionId() { int preferredSubId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID; try { - ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + ISub iSub = ISub.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSubscriptionServiceRegisterer() + .get()); if (iSub != null) { preferredSubId = iSub.getPreferredDataSubscriptionId(); } @@ -2825,7 +2919,11 @@ public class SubscriptionManager { List<SubscriptionInfo> subInfoList = null; try { - ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + ISub iSub = ISub.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSubscriptionServiceRegisterer() + .get()); if (iSub != null) { subInfoList = iSub.getOpportunisticSubscriptions(contextPkg, contextFeature); } @@ -2926,7 +3024,11 @@ public class SubscriptionManager { ParcelUuid groupUuid = null; int[] subIdArray = subIdList.stream().mapToInt(i->i).toArray(); try { - ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + ISub iSub = ISub.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSubscriptionServiceRegisterer() + .get()); if (iSub != null) { groupUuid = iSub.createSubscriptionGroup(subIdArray, pkgForDebug); } else { @@ -2976,7 +3078,11 @@ public class SubscriptionManager { int[] subIdArray = subIdList.stream().mapToInt(i->i).toArray(); try { - ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + ISub iSub = ISub.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSubscriptionServiceRegisterer() + .get()); if (iSub != null) { iSub.addSubscriptionsIntoGroup(subIdArray, groupUuid, pkgForDebug); } else { @@ -3028,7 +3134,11 @@ public class SubscriptionManager { int[] subIdArray = subIdList.stream().mapToInt(i->i).toArray(); try { - ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + ISub iSub = ISub.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSubscriptionServiceRegisterer() + .get()); if (iSub != null) { iSub.removeSubscriptionsFromGroup(subIdArray, groupUuid, pkgForDebug); } else { @@ -3073,7 +3183,11 @@ public class SubscriptionManager { List<SubscriptionInfo> result = null; try { - ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + ISub iSub = ISub.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSubscriptionServiceRegisterer() + .get()); if (iSub != null) { result = iSub.getSubscriptionsInGroup(groupUuid, contextPkg, contextFeature); } else { @@ -3186,7 +3300,11 @@ public class SubscriptionManager { logd("setSubscriptionActivated subId= " + subscriptionId + " enable " + enable); } try { - ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + ISub iSub = ISub.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSubscriptionServiceRegisterer() + .get()); if (iSub != null) { return iSub.setSubscriptionEnabled(enable, subscriptionId); } @@ -3216,7 +3334,11 @@ public class SubscriptionManager { logd("setUiccApplicationsEnabled subId= " + subscriptionId + " enable " + enabled); } try { - ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + ISub iSub = ISub.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSubscriptionServiceRegisterer() + .get()); if (iSub != null) { iSub.setUiccApplicationsEnabled(enabled, subscriptionId); } @@ -3246,7 +3368,11 @@ public class SubscriptionManager { logd("canDisablePhysicalSubscription"); } try { - ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + ISub iSub = ISub.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSubscriptionServiceRegisterer() + .get()); if (iSub != null) { return iSub.canDisablePhysicalSubscription(); } @@ -3267,7 +3393,11 @@ public class SubscriptionManager { @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isSubscriptionEnabled(int subscriptionId) { try { - ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + ISub iSub = ISub.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSubscriptionServiceRegisterer() + .get()); if (iSub != null) { return iSub.isSubscriptionEnabled(subscriptionId); } @@ -3290,7 +3420,11 @@ public class SubscriptionManager { int subId = INVALID_SUBSCRIPTION_ID; try { - ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + ISub iSub = ISub.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSubscriptionServiceRegisterer() + .get()); if (iSub != null) { subId = iSub.getEnabledSubscriptionId(slotIndex); } @@ -3316,7 +3450,11 @@ public class SubscriptionManager { int result = 0; try { - ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + ISub iSub = ISub.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSubscriptionServiceRegisterer() + .get()); if (iSub != null) { result = helper.callMethod(iSub); } @@ -3339,7 +3477,11 @@ public class SubscriptionManager { */ public static int getActiveDataSubscriptionId() { try { - ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + ISub iSub = ISub.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getSubscriptionServiceRegisterer() + .get()); if (iSub != null) { return iSub.getActiveDataSubscriptionId(); } diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index df8c26da7af9..843c0656efc3 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -60,7 +60,6 @@ import android.os.PersistableBundle; import android.os.Process; import android.os.RemoteException; import android.os.ResultReceiver; -import android.os.ServiceManager; import android.os.SystemProperties; import android.os.WorkSource; import android.provider.Settings.SettingNotFoundException; @@ -107,6 +106,7 @@ import com.android.internal.telephony.OperatorInfo; import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.RILConstants; import com.android.internal.telephony.SmsApplication; +import com.android.telephony.Rlog; import dalvik.system.VMRuntime; @@ -785,30 +785,6 @@ public class TelephonyManager { public static final String EXTRA_PRECISE_DISCONNECT_CAUSE = "precise_disconnect_cause"; /** - * The lookup key used with the {@link #ACTION_PRECISE_DATA_CONNECTION_STATE_CHANGED} broadcast - * for an String containing the data APN type. - * - * <p class="note"> - * Retrieve with - * {@link android.content.Intent#getStringExtra(String name)}. - * - * @hide - */ - public static final String EXTRA_DATA_APN_TYPE = PhoneConstants.DATA_APN_TYPE_KEY; - - /** - * The lookup key used with the {@link #ACTION_PRECISE_DATA_CONNECTION_STATE_CHANGED} broadcast - * for an String containing the data APN. - * - * <p class="note"> - * Retrieve with - * {@link android.content.Intent#getStringExtra(String name)}. - * - * @hide - */ - public static final String EXTRA_DATA_APN = PhoneConstants.DATA_APN_KEY; - - /** * Broadcast intent action for letting the default dialer to know to show voicemail * notification. * @@ -5107,7 +5083,11 @@ public class TelephonyManager { @UnsupportedAppUsage private IPhoneSubInfo getSubscriberInfo() { // get it each time because that process crashes a lot - return IPhoneSubInfo.Stub.asInterface(ServiceManager.getService("iphonesubinfo")); + return IPhoneSubInfo.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getPhoneSubServiceRegisterer() + .get()); } /** @@ -5320,11 +5300,19 @@ public class TelephonyManager { } private ITelephonyRegistry getTelephonyRegistry() { - return ITelephonyRegistry.Stub.asInterface(ServiceManager.getService("telephony.registry")); + return ITelephonyRegistry.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getTelephonyRegistryServiceRegisterer() + .get()); } private IOns getIOns() { - return IOns.Stub.asInterface(ServiceManager.getService("ions")); + return IOns.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getOpportunisticNetworkServiceRegisterer() + .get()); } // @@ -5880,7 +5868,10 @@ public class TelephonyManager { * @param AID Application id. See ETSI 102.221 and 101.220. * @param p2 P2 parameter (described in ISO 7816-4). * @return an IccOpenLogicalChannelResponse object. + * @deprecated Use {@link android.se.omapi.SEService} APIs instead. */ + // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated. + @Deprecated public IccOpenLogicalChannelResponse iccOpenLogicalChannel(String AID, int p2) { return iccOpenLogicalChannel(getSubId(), AID, p2); } @@ -5911,7 +5902,10 @@ public class TelephonyManager { * @param p2 P2 parameter (described in ISO 7816-4). * @return an IccOpenLogicalChannelResponse object. * @hide + * @deprecated Use {@link android.se.omapi.SEService} APIs instead. */ + // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated. + @Deprecated public IccOpenLogicalChannelResponse iccOpenLogicalChannel(int subId, String AID, int p2) { try { ITelephony telephony = getITelephony(); @@ -5939,7 +5933,10 @@ public class TelephonyManager { * iccOpenLogicalChannel. * @return true if the channel was closed successfully. * @hide + * @deprecated Use {@link android.se.omapi.SEService} APIs instead. */ + // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated. + @Deprecated @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) @SystemApi public boolean iccCloseLogicalChannelBySlot(int slotIndex, int channel) { @@ -5966,7 +5963,10 @@ public class TelephonyManager { * @param channel is the channel id to be closed as returned by a successful * iccOpenLogicalChannel. * @return true if the channel was closed successfully. + * @deprecated Use {@link android.se.omapi.SEService} APIs instead. */ + // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated. + @Deprecated public boolean iccCloseLogicalChannel(int channel) { return iccCloseLogicalChannel(getSubId(), channel); } @@ -5985,7 +5985,10 @@ public class TelephonyManager { * iccOpenLogicalChannel. * @return true if the channel was closed successfully. * @hide + * @deprecated Use {@link android.se.omapi.SEService} APIs instead. */ + // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated. + @Deprecated public boolean iccCloseLogicalChannel(int subId, int channel) { try { ITelephony telephony = getITelephony(); @@ -6021,7 +6024,10 @@ public class TelephonyManager { * @return The APDU response from the ICC card with the status appended at the end, or null if * there is an issue connecting to the Telephony service. * @hide + * @deprecated Use {@link android.se.omapi.SEService} APIs instead. */ + // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated. + @Deprecated @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) @SystemApi @Nullable @@ -6059,7 +6065,10 @@ public class TelephonyManager { * @param data Data to be sent with the APDU. * @return The APDU response from the ICC card with the status appended at * the end. + * @deprecated Use {@link android.se.omapi.SEService} APIs instead. */ + // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated. + @Deprecated public String iccTransmitApduLogicalChannel(int channel, int cla, int instruction, int p1, int p2, int p3, String data) { return iccTransmitApduLogicalChannel(getSubId(), channel, cla, @@ -6088,7 +6097,10 @@ public class TelephonyManager { * @return The APDU response from the ICC card with the status appended at * the end. * @hide + * @deprecated Use {@link android.se.omapi.SEService} APIs instead. */ + // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated. + @Deprecated public String iccTransmitApduLogicalChannel(int subId, int channel, int cla, int instruction, int p1, int p2, int p3, String data) { try { @@ -6124,7 +6136,10 @@ public class TelephonyManager { * @return The APDU response from the ICC card with the status appended at * the end. * @hide + * @deprecated Use {@link android.se.omapi.SEService} APIs instead. */ + // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated. + @Deprecated @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) @SystemApi @NonNull @@ -6160,7 +6175,10 @@ public class TelephonyManager { * @param data Data to be sent with the APDU. * @return The APDU response from the ICC card with the status appended at * the end. + * @deprecated Use {@link android.se.omapi.SEService} APIs instead. */ + // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated. + @Deprecated public String iccTransmitApduBasicChannel(int cla, int instruction, int p1, int p2, int p3, String data) { return iccTransmitApduBasicChannel(getSubId(), cla, @@ -6187,7 +6205,10 @@ public class TelephonyManager { * @return The APDU response from the ICC card with the status appended at * the end. * @hide + * @deprecated Use {@link android.se.omapi.SEService} APIs instead. */ + // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated. + @Deprecated public String iccTransmitApduBasicChannel(int subId, int cla, int instruction, int p1, int p2, int p3, String data) { try { @@ -6215,7 +6236,10 @@ public class TelephonyManager { * @param p3 P3 value of the APDU command. * @param filePath * @return The APDU response. + * @deprecated Use {@link android.se.omapi.SEService} APIs instead. */ + // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated. + @Deprecated public byte[] iccExchangeSimIO(int fileID, int command, int p1, int p2, int p3, String filePath) { return iccExchangeSimIO(getSubId(), fileID, command, p1, p2, p3, filePath); @@ -6237,7 +6261,10 @@ public class TelephonyManager { * @param filePath * @return The APDU response. * @hide + * @deprecated Use {@link android.se.omapi.SEService} APIs instead. */ + // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated. + @Deprecated public byte[] iccExchangeSimIO(int subId, int fileID, int command, int p1, int p2, int p3, String filePath) { try { @@ -6263,7 +6290,10 @@ public class TelephonyManager { * @return The APDU response from the ICC card in hexadecimal format * with the last 4 bytes being the status word. If the command fails, * returns an empty string. + * @deprecated Use {@link android.se.omapi.SEService} APIs instead. */ + // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated. + @Deprecated public String sendEnvelopeWithStatus(String content) { return sendEnvelopeWithStatus(getSubId(), content); } @@ -6283,7 +6313,10 @@ public class TelephonyManager { * with the last 4 bytes being the status word. If the command fails, * returns an empty string. * @hide + * @deprecated Use {@link android.se.omapi.SEService} APIs instead. */ + // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated. + @Deprecated public String sendEnvelopeWithStatus(int subId, String content) { try { ITelephony telephony = getITelephony(); diff --git a/telephony/java/android/telephony/TelephonyScanManager.java b/telephony/java/android/telephony/TelephonyScanManager.java index 96b6db75b370..a1d40e85fb10 100644 --- a/telephony/java/android/telephony/TelephonyScanManager.java +++ b/telephony/java/android/telephony/TelephonyScanManager.java @@ -16,10 +16,11 @@ package android.telephony; +import com.android.telephony.Rlog; + import static com.android.internal.util.Preconditions.checkNotNull; import android.annotation.Nullable; -import android.content.Context; import android.os.Binder; import android.os.Bundle; import android.os.Handler; @@ -29,7 +30,6 @@ import android.os.Message; import android.os.Messenger; import android.os.Parcelable; import android.os.RemoteException; -import android.os.ServiceManager; import android.util.SparseArray; import com.android.internal.telephony.ITelephony; @@ -234,6 +234,9 @@ public final class TelephonyScanManager { private ITelephony getITelephony() { return ITelephony.Stub.asInterface( - ServiceManager.getService(Context.TELEPHONY_SERVICE)); + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getTelephonyServiceRegisterer() + .get()); } } diff --git a/telephony/java/android/telephony/UiccAccessRule.java b/telephony/java/android/telephony/UiccAccessRule.java index 93ccba1dd996..81a09c645070 100644 --- a/telephony/java/android/telephony/UiccAccessRule.java +++ b/telephony/java/android/telephony/UiccAccessRule.java @@ -15,6 +15,8 @@ */ package android.telephony; +import com.android.telephony.Rlog; + import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; diff --git a/telephony/java/android/telephony/VoLteServiceState.java b/telephony/java/android/telephony/VoLteServiceState.java index 414b9995fd58..d4a27d925208 100644 --- a/telephony/java/android/telephony/VoLteServiceState.java +++ b/telephony/java/android/telephony/VoLteServiceState.java @@ -16,6 +16,8 @@ package android.telephony; +import com.android.telephony.Rlog; + import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; import android.os.Bundle; @@ -51,8 +53,7 @@ public final class VoLteServiceState implements Parcelable { /** * Create a new VoLteServiceState from a intent notifier Bundle * - * This method is used by PhoneStateIntentReceiver and maybe by - * external applications. + * This method is maybe used by external applications. * * @param m Bundle from intent notifier * @return newly created VoLteServiceState diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java index 034fc220cbbe..fab1bf2215af 100644 --- a/telephony/java/android/telephony/data/ApnSetting.java +++ b/telephony/java/android/telephony/data/ApnSetting.java @@ -28,7 +28,7 @@ import android.provider.Telephony; import android.provider.Telephony.Carriers; import android.telephony.Annotation.ApnType; import android.telephony.Annotation.NetworkType; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.telephony.ServiceState; import android.telephony.TelephonyManager; import android.text.TextUtils; @@ -1056,6 +1056,11 @@ public class ApnSetting implements Parcelable { } /** @hide */ + public boolean isEmergencyApn() { + return hasApnType(TYPE_EMERGENCY); + } + + /** @hide */ public boolean canHandleType(@ApnType int type) { if (!mCarrierEnabled) { return false; diff --git a/telephony/java/android/telephony/data/DataService.java b/telephony/java/android/telephony/data/DataService.java index 372bdf1c0f81..bff12b624ae8 100644 --- a/telephony/java/android/telephony/data/DataService.java +++ b/telephony/java/android/telephony/data/DataService.java @@ -31,7 +31,7 @@ import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.telephony.AccessNetworkConstants; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; diff --git a/telephony/java/android/telephony/data/DataServiceCallback.java b/telephony/java/android/telephony/data/DataServiceCallback.java index 11dc78a611ff..d33d3f9a5eee 100644 --- a/telephony/java/android/telephony/data/DataServiceCallback.java +++ b/telephony/java/android/telephony/data/DataServiceCallback.java @@ -22,7 +22,7 @@ import android.annotation.Nullable; import android.annotation.SystemApi; import android.net.LinkProperties; import android.os.RemoteException; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.telephony.data.DataService.DataServiceProvider; import java.lang.annotation.Retention; diff --git a/telephony/java/android/telephony/data/QualifiedNetworksService.java b/telephony/java/android/telephony/data/QualifiedNetworksService.java index e793979a61c9..8220b16500de 100644 --- a/telephony/java/android/telephony/data/QualifiedNetworksService.java +++ b/telephony/java/android/telephony/data/QualifiedNetworksService.java @@ -28,7 +28,7 @@ import android.os.Message; import android.os.RemoteException; import android.telephony.AccessNetworkConstants.AccessNetworkType; import android.telephony.Annotation.ApnType; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; diff --git a/telephony/java/android/telephony/emergency/EmergencyNumber.java b/telephony/java/android/telephony/emergency/EmergencyNumber.java index 16662652847d..cd3fc953f9d2 100644 --- a/telephony/java/android/telephony/emergency/EmergencyNumber.java +++ b/telephony/java/android/telephony/emergency/EmergencyNumber.java @@ -25,7 +25,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.telephony.CarrierConfigManager; import android.telephony.PhoneNumberUtils; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; diff --git a/telephony/java/android/telephony/euicc/EuiccCardManager.java b/telephony/java/android/telephony/euicc/EuiccCardManager.java index 994c49cd2315..e16fffa69f8a 100644 --- a/telephony/java/android/telephony/euicc/EuiccCardManager.java +++ b/telephony/java/android/telephony/euicc/EuiccCardManager.java @@ -21,8 +21,8 @@ import android.annotation.Nullable; import android.annotation.SystemApi; import android.content.Context; import android.os.RemoteException; -import android.os.ServiceManager; import android.service.euicc.EuiccProfileInfo; +import android.telephony.TelephonyFrameworkInitializer; import android.util.Log; import com.android.internal.telephony.euicc.IAuthenticateServerCallback; @@ -148,7 +148,10 @@ public class EuiccCardManager { private IEuiccCardController getIEuiccCardController() { return IEuiccCardController.Stub.asInterface( - ServiceManager.getService("euicc_card_controller")); + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getEuiccCardControllerServiceRegisterer() + .get()); } /** diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java index cb66a9650f2f..d5a48df149f1 100644 --- a/telephony/java/android/telephony/euicc/EuiccManager.java +++ b/telephony/java/android/telephony/euicc/EuiccManager.java @@ -30,7 +30,7 @@ import android.content.IntentSender; import android.content.pm.PackageManager; import android.os.Bundle; import android.os.RemoteException; -import android.os.ServiceManager; +import android.telephony.TelephonyFrameworkInitializer; import android.telephony.TelephonyManager; import android.telephony.euicc.EuiccCardManager.ResetOption; @@ -968,6 +968,10 @@ public class EuiccManager { } private static IEuiccController getIEuiccController() { - return IEuiccController.Stub.asInterface(ServiceManager.getService("econtroller")); + return IEuiccController.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getEuiccControllerService() + .get()); } } diff --git a/telephony/java/android/telephony/ims/ImsCallProfile.java b/telephony/java/android/telephony/ims/ImsCallProfile.java index 998b39dfb436..bc60d81cdc47 100644 --- a/telephony/java/android/telephony/ims/ImsCallProfile.java +++ b/telephony/java/android/telephony/ims/ImsCallProfile.java @@ -279,6 +279,14 @@ public final class ImsCallProfile implements Parcelable { "android.telephony.ims.extra.ADDITIONAL_SIP_INVITE_FIELDS"; /** + * CallDisconnectCause: Specify call disconnect cause. This extra should be a code + * corresponding to ImsReasonInfo and should only be populated in the case that the + * call has already been missed + */ + public static final String EXTRA_CALL_DISCONNECT_CAUSE = + "android.telephony.ims.extra.CALL_DISCONNECT_CAUSE"; + + /** * Extra key which the RIL can use to indicate the radio technology used for a call. * Valid values are: * {@link android.telephony.ServiceState#RIL_RADIO_TECHNOLOGY_LTE}, diff --git a/telephony/java/android/telephony/ims/ImsConferenceState.java b/telephony/java/android/telephony/ims/ImsConferenceState.java index 8d2049b97138..abfee61930ed 100644 --- a/telephony/java/android/telephony/ims/ImsConferenceState.java +++ b/telephony/java/android/telephony/ims/ImsConferenceState.java @@ -24,7 +24,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.telecom.Call; import android.telecom.Connection; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.util.Log; import java.util.HashMap; diff --git a/telephony/java/android/telephony/ims/ImsExternalCallState.java b/telephony/java/android/telephony/ims/ImsExternalCallState.java index dcb9c9d5ec27..136a83e2eec9 100644 --- a/telephony/java/android/telephony/ims/ImsExternalCallState.java +++ b/telephony/java/android/telephony/ims/ImsExternalCallState.java @@ -24,7 +24,7 @@ import android.annotation.TestApi; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; diff --git a/telephony/java/android/telephony/ims/ImsMmTelManager.java b/telephony/java/android/telephony/ims/ImsMmTelManager.java index 057d22cd7eae..91514e9ebe28 100644 --- a/telephony/java/android/telephony/ims/ImsMmTelManager.java +++ b/telephony/java/android/telephony/ims/ImsMmTelManager.java @@ -25,14 +25,13 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.TestApi; -import android.content.Context; import android.os.Binder; import android.os.RemoteException; -import android.os.ServiceManager; import android.os.ServiceSpecificException; import android.telephony.AccessNetworkConstants; import android.telephony.CarrierConfigManager; import android.telephony.SubscriptionManager; +import android.telephony.TelephonyFrameworkInitializer; import android.telephony.ims.aidl.IImsCapabilityCallback; import android.telephony.ims.feature.ImsFeature; import android.telephony.ims.feature.MmTelFeature; @@ -1018,7 +1017,10 @@ public class ImsMmTelManager implements RegistrationManager { private static ITelephony getITelephony() { ITelephony binder = ITelephony.Stub.asInterface( - ServiceManager.getService(Context.TELEPHONY_SERVICE)); + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getTelephonyServiceRegisterer() + .get()); if (binder == null) { throw new RuntimeException("Could not find Telephony Service."); } diff --git a/telephony/java/android/telephony/ims/ImsRcsManager.java b/telephony/java/android/telephony/ims/ImsRcsManager.java index d3fb37f005cd..c96271432ea2 100644 --- a/telephony/java/android/telephony/ims/ImsRcsManager.java +++ b/telephony/java/android/telephony/ims/ImsRcsManager.java @@ -26,8 +26,8 @@ import android.content.Context; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; -import android.os.ServiceManager; import android.telephony.AccessNetworkConstants; +import android.telephony.TelephonyFrameworkInitializer; import android.telephony.ims.aidl.IImsCapabilityCallback; import android.telephony.ims.aidl.IImsRcsController; import android.telephony.ims.feature.ImsFeature; @@ -35,6 +35,8 @@ import android.telephony.ims.feature.RcsFeature; import android.telephony.ims.stub.ImsRegistrationImplBase; import android.util.Log; +import com.android.internal.telephony.IIntegerConsumer; + import java.util.concurrent.Executor; import java.util.function.Consumer; @@ -158,9 +160,20 @@ public class ImsRcsManager implements RegistrationManager { if (executor == null) { throw new IllegalArgumentException("Must include a non-null Executor."); } + + IImsRcsController imsRcsController = getIImsRcsController(); + if (imsRcsController == null) { + Log.e(TAG, "Register registration callback: IImsRcsController is null"); + throw new ImsException("Cannot find remote IMS service", + ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); + } + c.setExecutor(executor); - throw new UnsupportedOperationException("registerImsRegistrationCallback is not" - + "supported."); + try { + imsRcsController.registerImsRegistrationCallback(mSubId, c.getBinder()); + } catch (RemoteException | IllegalStateException e) { + throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); + } } /**{@inheritDoc}*/ @@ -171,8 +184,18 @@ public class ImsRcsManager implements RegistrationManager { if (c == null) { throw new IllegalArgumentException("Must include a non-null RegistrationCallback."); } - throw new UnsupportedOperationException("unregisterImsRegistrationCallback is not" - + "supported."); + + IImsRcsController imsRcsController = getIImsRcsController(); + if (imsRcsController == null) { + Log.e(TAG, "Unregister registration callback: IImsRcsController is null"); + throw new IllegalStateException("Cannot find remote IMS service"); + } + + try { + imsRcsController.unregisterImsRegistrationCallback(mSubId, c.getBinder()); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } } /**{@inheritDoc}*/ @@ -186,8 +209,23 @@ public class ImsRcsManager implements RegistrationManager { if (executor == null) { throw new IllegalArgumentException("Must include a non-null Executor."); } - throw new UnsupportedOperationException("getRegistrationState is not" - + "supported."); + + IImsRcsController imsRcsController = getIImsRcsController(); + if (imsRcsController == null) { + Log.e(TAG, "Get registration state error: IImsRcsController is null"); + throw new IllegalStateException("Cannot find remote IMS service"); + } + + try { + imsRcsController.getImsRcsRegistrationState(mSubId, new IIntegerConsumer.Stub() { + @Override + public void accept(int result) { + executor.execute(() -> stateCallback.accept(result)); + } + }); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } } /**{@inheritDoc}*/ @@ -202,10 +240,25 @@ public class ImsRcsManager implements RegistrationManager { if (executor == null) { throw new IllegalArgumentException("Must include a non-null Executor."); } - throw new UnsupportedOperationException("getRegistrationTransportType is not" - + "supported."); - } + IImsRcsController imsRcsController = getIImsRcsController(); + if (imsRcsController == null) { + Log.e(TAG, "Get registration transport type error: IImsRcsController is null"); + throw new IllegalStateException("Cannot find remote IMS service"); + } + + try { + imsRcsController.getImsRcsRegistrationTransportType(mSubId, + new IIntegerConsumer.Stub() { + @Override + public void accept(int result) { + executor.execute(() -> transportTypeCallback.accept(result)); + } + }); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } /** * Registers an {@link AvailabilityCallback} with the system, which will provide RCS @@ -362,7 +415,10 @@ public class ImsRcsManager implements RegistrationManager { } private IImsRcsController getIImsRcsController() { - IBinder binder = ServiceManager.getService(Context.TELEPHONY_IMS_SERVICE); + IBinder binder = TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getTelephonyImsServiceRegisterer() + .get(); return IImsRcsController.Stub.asInterface(binder); } } diff --git a/telephony/java/android/telephony/ims/ImsSsData.java b/telephony/java/android/telephony/ims/ImsSsData.java index 6b728599c7d3..2d2e63812fad 100644 --- a/telephony/java/android/telephony/ims/ImsSsData.java +++ b/telephony/java/android/telephony/ims/ImsSsData.java @@ -22,7 +22,7 @@ import android.annotation.SystemApi; import android.annotation.TestApi; import android.os.Parcel; import android.os.Parcelable; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java index e4d63355625d..f0521802a167 100644 --- a/telephony/java/android/telephony/ims/ProvisioningManager.java +++ b/telephony/java/android/telephony/ims/ProvisioningManager.java @@ -25,14 +25,13 @@ import android.annotation.StringDef; import android.annotation.SystemApi; import android.annotation.TestApi; import android.annotation.WorkerThread; -import android.content.Context; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.os.Binder; import android.os.RemoteException; -import android.os.ServiceManager; import android.telephony.CarrierConfigManager; import android.telephony.SubscriptionManager; +import android.telephony.TelephonyFrameworkInitializer; import android.telephony.ims.aidl.IImsConfigCallback; import android.telephony.ims.feature.MmTelFeature; import android.telephony.ims.stub.ImsConfigImplBase; @@ -415,7 +414,11 @@ public class ProvisioningManager { } private static boolean isImsAvailableOnDevice() { - IPackageManager pm = IPackageManager.Stub.asInterface(ServiceManager.getService("package")); + IPackageManager pm = IPackageManager.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getPackageManagerServiceRegisterer() + .get()); if (pm == null) { // For some reason package manger is not available.. This will fail internally anyways, // so do not throw error and allow. @@ -432,7 +435,10 @@ public class ProvisioningManager { private static ITelephony getITelephony() { ITelephony binder = ITelephony.Stub.asInterface( - ServiceManager.getService(Context.TELEPHONY_SERVICE)); + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getTelephonyServiceRegisterer() + .get()); if (binder == null) { throw new RuntimeException("Could not find Telephony Service."); } diff --git a/telephony/java/android/telephony/ims/RcsControllerCall.java b/telephony/java/android/telephony/ims/RcsControllerCall.java index ce03c3c799bb..1e93437a5a94 100644 --- a/telephony/java/android/telephony/ims/RcsControllerCall.java +++ b/telephony/java/android/telephony/ims/RcsControllerCall.java @@ -18,7 +18,7 @@ package android.telephony.ims; import android.content.Context; import android.os.RemoteException; -import android.os.ServiceManager; +import android.telephony.TelephonyFrameworkInitializer; import android.telephony.ims.aidl.IRcsMessage; /** @@ -35,8 +35,11 @@ class RcsControllerCall { } <R> R call(RcsServiceCall<R> serviceCall) throws RcsMessageStoreException { - IRcsMessage iRcsMessage = IRcsMessage.Stub.asInterface(ServiceManager.getService( - Context.TELEPHONY_RCS_MESSAGE_SERVICE)); + IRcsMessage iRcsMessage = IRcsMessage.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getTelephonyRcsMessageServiceRegisterer() + .get()); if (iRcsMessage == null) { throw new RcsMessageStoreException("Could not connect to RCS storage service"); } diff --git a/telephony/java/android/telephony/ims/RcsUceAdapter.java b/telephony/java/android/telephony/ims/RcsUceAdapter.java index 75e3f0a6393d..2e3f59a13670 100644 --- a/telephony/java/android/telephony/ims/RcsUceAdapter.java +++ b/telephony/java/android/telephony/ims/RcsUceAdapter.java @@ -21,12 +21,11 @@ import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.RequiresPermission; -import android.content.Context; import android.net.Uri; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; -import android.os.ServiceManager; +import android.telephony.TelephonyFrameworkInitializer; import android.telephony.ims.aidl.IImsRcsController; import android.telephony.ims.aidl.IRcsUceControllerCallback; import android.util.Log; @@ -365,7 +364,10 @@ public class RcsUceAdapter { } private IImsRcsController getIImsRcsController() { - IBinder binder = ServiceManager.getService(Context.TELEPHONY_IMS_SERVICE); + IBinder binder = TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getTelephonyImsServiceRegisterer() + .get(); return IImsRcsController.Stub.asInterface(binder); } } diff --git a/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl b/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl index e81bac0f6764..6f6aa44371fa 100644 --- a/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl +++ b/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl @@ -19,6 +19,9 @@ package android.telephony.ims.aidl; import android.net.Uri; import android.telephony.ims.aidl.IImsCapabilityCallback; import android.telephony.ims.aidl.IRcsUceControllerCallback; +import android.telephony.ims.aidl.IImsRegistrationCallback; + +import com.android.internal.telephony.IIntegerConsumer; /** * Interface used to interact with the Telephony IMS. @@ -26,6 +29,13 @@ import android.telephony.ims.aidl.IRcsUceControllerCallback; * {@hide} */ interface IImsRcsController { + // IMS RCS registration commands + void registerImsRegistrationCallback(int subId, IImsRegistrationCallback c); + void unregisterImsRegistrationCallback(int subId, IImsRegistrationCallback c); + void getImsRcsRegistrationState(int subId, IIntegerConsumer consumer); + void getImsRcsRegistrationTransportType(int subId, IIntegerConsumer consumer); + + // IMS RCS capability commands void registerRcsAvailabilityCallback(int subId, IImsCapabilityCallback c); void unregisterRcsAvailabilityCallback(int subId, IImsCapabilityCallback c); boolean isCapable(int subId, int capability, int radioTech); diff --git a/telephony/java/com/android/ims/ImsConfig.java b/telephony/java/com/android/ims/ImsConfig.java index cfc803ca3639..9116a3bf3bde 100644 --- a/telephony/java/com/android/ims/ImsConfig.java +++ b/telephony/java/com/android/ims/ImsConfig.java @@ -19,7 +19,7 @@ package com.android.ims; import android.os.Handler; import android.os.Looper; import android.os.RemoteException; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.telephony.ims.ImsReasonInfo; import android.telephony.ims.ProvisioningManager; import android.telephony.ims.aidl.IImsConfig; diff --git a/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java b/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java index 1d6ec2d82eb7..8e86ff788a08 100644 --- a/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java +++ b/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java @@ -19,7 +19,7 @@ package com.android.internal.telephony; import android.compat.annotation.UnsupportedAppUsage; import android.content.res.Resources; import android.content.res.XmlResourceParser; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.util.SparseIntArray; import com.android.internal.telephony.cdma.sms.UserData; diff --git a/telephony/java/com/android/internal/telephony/TelephonyIntents.java b/telephony/java/com/android/internal/telephony/TelephonyIntents.java index f78c65ffb3aa..54c07cfd3428 100644 --- a/telephony/java/com/android/internal/telephony/TelephonyIntents.java +++ b/telephony/java/com/android/internal/telephony/TelephonyIntents.java @@ -123,32 +123,6 @@ public class TelephonyIntents { public static final String ACTION_EMERGENCY_CALL_STATE_CHANGED = TelephonyManager.ACTION_EMERGENCY_CALL_STATE_CHANGED; - /** - * Broadcast Action: The phone's signal strength has changed. The intent will have the - * following extra values:</p> - * <ul> - * <li><em>phoneName</em> - A string version of the phone name.</li> - * <li><em>asu</em> - A numeric value for the signal strength. - * An ASU is 0-31 or -1 if unknown (for GSM, dBm = -113 - 2 * asu). - * The following special values are defined: - * <ul><li>0 means "-113 dBm or less".</li><li>31 means "-51 dBm or greater".</li></ul> - * </li> - * </ul> - * - * <p class="note"> - * You can <em>not</em> receive this through components declared - * in manifests, only by exlicitly registering for it with - * {@link android.content.Context#registerReceiver(android.content.BroadcastReceiver, - * android.content.IntentFilter) Context.registerReceiver()}. - * - * <p class="note"> - * Requires the READ_PHONE_STATE permission. - * - * <p class="note">This is a protected intent that can only be sent - * by the system. - */ - public static final String ACTION_SIGNAL_STRENGTH_CHANGED = "android.intent.action.SIG_STR"; - /** * Broadcast Action: The data connection state has changed for any one of the diff --git a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java index e75c5933f1df..832502cae37d 100644 --- a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java +++ b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java @@ -20,7 +20,7 @@ import android.compat.annotation.UnsupportedAppUsage; import android.content.res.Resources; import android.sysprop.TelephonyProperties; import android.telephony.PhoneNumberUtils; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.telephony.SmsCbLocation; import android.telephony.SmsCbMessage; import android.telephony.cdma.CdmaSmsCbProgramData; diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java index b5af6467a0ad..cbf0f5c297e1 100644 --- a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java +++ b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java @@ -18,7 +18,7 @@ package com.android.internal.telephony.cdma.sms; import android.compat.annotation.UnsupportedAppUsage; import android.content.res.Resources; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.telephony.SmsCbCmasInfo; import android.telephony.cdma.CdmaSmsCbProgramData; import android.telephony.cdma.CdmaSmsCbProgramResults; diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java index 0681dc11066e..417aafd765ea 100644 --- a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java +++ b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java @@ -28,7 +28,7 @@ import static com.android.internal.telephony.SmsConstants.MessageClass; import android.compat.annotation.UnsupportedAppUsage; import android.content.res.Resources; import android.telephony.PhoneNumberUtils; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import android.text.TextUtils; import com.android.internal.telephony.EncodeException; diff --git a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java index eed9a86cf448..0dc740194034 100644 --- a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java +++ b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java @@ -21,7 +21,7 @@ import android.content.res.Resources; import android.content.res.Resources.NotFoundException; import android.graphics.Bitmap; import android.graphics.Color; -import android.telephony.Rlog; +import com.android.telephony.Rlog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.GsmAlphabet; diff --git a/telephony/java/com/android/telephony/Rlog.java b/telephony/java/com/android/telephony/Rlog.java new file mode 100644 index 000000000000..9d6c930de8f5 --- /dev/null +++ b/telephony/java/com/android/telephony/Rlog.java @@ -0,0 +1,154 @@ +/* + * 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.telephony; + +import android.text.TextUtils; +import android.util.Base64; +import android.util.Log; + +import com.android.internal.telephony.util.TelephonyUtils; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * A copy of {@link android.telephony.Rlog} to be used within the telephony mainline module. + * + * @hide + */ +public final class Rlog { + + private static final boolean USER_BUILD = TelephonyUtils.IS_USER; + + private Rlog() { + } + + private static int log(int priority, String tag, String msg) { + return Log.logToRadioBuffer(priority, tag, msg); + } + + public static int v(String tag, String msg) { + return log(Log.VERBOSE, tag, msg); + } + + public static int v(String tag, String msg, Throwable tr) { + return log(Log.VERBOSE, tag, + msg + '\n' + Log.getStackTraceString(tr)); + } + + public static int d(String tag, String msg) { + return log(Log.DEBUG, tag, msg); + } + + public static int d(String tag, String msg, Throwable tr) { + return log(Log.DEBUG, tag, + msg + '\n' + Log.getStackTraceString(tr)); + } + + public static int i(String tag, String msg) { + return log(Log.INFO, tag, msg); + } + + public static int i(String tag, String msg, Throwable tr) { + return log(Log.INFO, tag, + msg + '\n' + Log.getStackTraceString(tr)); + } + + public static int w(String tag, String msg) { + return log(Log.WARN, tag, msg); + } + + public static int w(String tag, String msg, Throwable tr) { + return log(Log.WARN, tag, + msg + '\n' + Log.getStackTraceString(tr)); + } + + public static int w(String tag, Throwable tr) { + return log(Log.WARN, tag, Log.getStackTraceString(tr)); + } + + public static int e(String tag, String msg) { + return log(Log.ERROR, tag, msg); + } + + public static int e(String tag, String msg, Throwable tr) { + return log(Log.ERROR, tag, + msg + '\n' + Log.getStackTraceString(tr)); + } + + public static int println(int priority, String tag, String msg) { + return log(priority, tag, msg); + } + + public static boolean isLoggable(String tag, int level) { + return Log.isLoggable(tag, level); + } + + /** + * Redact personally identifiable information for production users. + * @param tag used to identify the source of a log message + * @param pii the personally identifiable information we want to apply secure hash on. + * @return If tag is loggable in verbose mode or pii is null, return the original input. + * otherwise return a secure Hash of input pii + */ + public static String pii(String tag, Object pii) { + String val = String.valueOf(pii); + if (pii == null || TextUtils.isEmpty(val) || isLoggable(tag, Log.VERBOSE)) { + return val; + } + return "[" + secureHash(val.getBytes()) + "]"; + } + + /** + * Redact personally identifiable information for production users. + * @param enablePiiLogging set when caller explicitly want to enable sensitive logging. + * @param pii the personally identifiable information we want to apply secure hash on. + * @return If enablePiiLogging is set to true or pii is null, return the original input. + * otherwise return a secure Hash of input pii + */ + public static String pii(boolean enablePiiLogging, Object pii) { + String val = String.valueOf(pii); + if (pii == null || TextUtils.isEmpty(val) || enablePiiLogging) { + return val; + } + return "[" + secureHash(val.getBytes()) + "]"; + } + + /** + * Returns a secure hash (using the SHA1 algorithm) of the provided input. + * + * @return "****" if the build type is user, otherwise the hash + * @param input the bytes for which the secure hash should be computed. + */ + private static String secureHash(byte[] input) { + // Refrain from logging user personal information in user build. + if (USER_BUILD) { + return "****"; + } + + MessageDigest messageDigest; + + try { + messageDigest = MessageDigest.getInstance("SHA-1"); + } catch (NoSuchAlgorithmException e) { + return "####"; + } + + byte[] result = messageDigest.digest(input); + return Base64.encodeToString( + result, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP); + } +} diff --git a/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java b/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java index 653282d0d365..1361df30e9d7 100644 --- a/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java +++ b/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java @@ -320,8 +320,9 @@ public class MemoryUsageTest extends InstrumentationTestCase { UserHandle.USER_CURRENT); } - mAtm.startActivityAndWait(null, null, mLaunchIntent, mimeType, - null, null, 0, mLaunchIntent.getFlags(), null, null, + mAtm.startActivityAndWait(null, + getInstrumentation().getContext().getBasePackageName(), mLaunchIntent, + mimeType, null, null, 0, mLaunchIntent.getFlags(), null, null, UserHandle.USER_CURRENT_OR_SELF); } catch (RemoteException e) { Log.w(TAG, "Error launching app", e); diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java index b4cafe41662e..656628eb39d5 100644 --- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java +++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java @@ -896,39 +896,78 @@ public class PackageWatchdogTest { assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_A); } - /** Test that observers execute correctly for different failure reasons */ + /** Test that observers execute correctly for failures reasons that go through thresholding. */ @Test - public void testFailureReasons() { + public void testNonImmediateFailureReasons() { PackageWatchdog watchdog = createWatchdog(); TestObserver observer1 = new TestObserver(OBSERVER_NAME_1); TestObserver observer2 = new TestObserver(OBSERVER_NAME_2); - TestObserver observer3 = new TestObserver(OBSERVER_NAME_3); - TestObserver observer4 = new TestObserver(OBSERVER_NAME_4); watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION); watchdog.startObservingHealth(observer2, Arrays.asList(APP_B), SHORT_DURATION); - watchdog.startObservingHealth(observer3, Arrays.asList(APP_C), SHORT_DURATION); - watchdog.startObservingHealth(observer4, Arrays.asList(APP_D), SHORT_DURATION); raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A, - VERSION_CODE)), PackageWatchdog.FAILURE_REASON_NATIVE_CRASH); - raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_B, - VERSION_CODE)), PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK); - raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_C, VERSION_CODE)), PackageWatchdog.FAILURE_REASON_APP_CRASH); - raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_D, + raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_B, VERSION_CODE)), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING); assertThat(observer1.getLastFailureReason()).isEqualTo( - PackageWatchdog.FAILURE_REASON_NATIVE_CRASH); - assertThat(observer2.getLastFailureReason()).isEqualTo( - PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK); - assertThat(observer3.getLastFailureReason()).isEqualTo( PackageWatchdog.FAILURE_REASON_APP_CRASH); - assertThat(observer4.getLastFailureReason()).isEqualTo( + assertThat(observer2.getLastFailureReason()).isEqualTo( PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING); } + /** Test that observers execute correctly for failures reasons that skip thresholding. */ + @Test + public void testImmediateFailures() { + PackageWatchdog watchdog = createWatchdog(); + TestObserver observer1 = new TestObserver(OBSERVER_NAME_1); + + watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION); + + raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A, + VERSION_CODE)), PackageWatchdog.FAILURE_REASON_NATIVE_CRASH); + raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_B, + VERSION_CODE)), PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK); + + assertThat(observer1.mMitigatedPackages).containsExactly(APP_A, APP_B); + } + + /** + * Test that a persistent observer will mitigate failures if it wishes to observe a package. + */ + @Test + public void testPersistentObserverWatchesPackage() { + PackageWatchdog watchdog = createWatchdog(); + TestObserver persistentObserver = new TestObserver(OBSERVER_NAME_1); + persistentObserver.setPersistent(true); + persistentObserver.setMayObservePackages(true); + + watchdog.startObservingHealth(persistentObserver, Arrays.asList(APP_B), SHORT_DURATION); + + raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A, + VERSION_CODE)), PackageWatchdog.FAILURE_REASON_UNKNOWN); + assertThat(persistentObserver.mHealthCheckFailedPackages).containsExactly(APP_A); + } + + /** + * Test that a persistent observer will not mitigate failures if it does not wish to observe + * a given package. + */ + @Test + public void testPersistentObserverDoesNotWatchPackage() { + PackageWatchdog watchdog = createWatchdog(); + TestObserver persistentObserver = new TestObserver(OBSERVER_NAME_1); + persistentObserver.setPersistent(true); + persistentObserver.setMayObservePackages(false); + + watchdog.startObservingHealth(persistentObserver, Arrays.asList(APP_B), SHORT_DURATION); + + raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A, + VERSION_CODE)), PackageWatchdog.FAILURE_REASON_UNKNOWN); + assertThat(persistentObserver.mHealthCheckFailedPackages).isEmpty(); + } + private void adoptShellPermissions(String... permissions) { InstrumentationRegistry .getInstrumentation() @@ -964,7 +1003,12 @@ public class PackageWatchdogTest { /** Trigger package failures above the threshold. */ private void raiseFatalFailureAndDispatch(PackageWatchdog watchdog, List<VersionedPackage> packages, int failureReason) { - for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) { + long triggerFailureCount = watchdog.getTriggerFailureCount(); + if (failureReason == PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK + || failureReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) { + triggerFailureCount = 1; + } + for (int i = 0; i < triggerFailureCount; i++) { watchdog.onPackageFailure(packages, failureReason); } mTestLooper.dispatchAll(); @@ -1000,6 +1044,8 @@ public class PackageWatchdogTest { private final String mName; private int mImpact; private int mLastFailureReason; + private boolean mIsPersistent = false; + private boolean mMayObservePackages = false; final List<String> mHealthCheckFailedPackages = new ArrayList<>(); final List<String> mMitigatedPackages = new ArrayList<>(); @@ -1028,9 +1074,25 @@ public class PackageWatchdogTest { return mName; } + public boolean isPersistent() { + return mIsPersistent; + } + + public boolean mayObservePackage(String packageName) { + return mMayObservePackages; + } + public int getLastFailureReason() { return mLastFailureReason; } + + public void setPersistent(boolean persistent) { + mIsPersistent = persistent; + } + + public void setMayObservePackages(boolean mayObservePackages) { + mMayObservePackages = mayObservePackages; + } } private static class TestController extends ExplicitHealthCheckController { diff --git a/tests/RollbackTest/Android.bp b/tests/RollbackTest/Android.bp index 2bc129ae4840..091edd4dc0d9 100644 --- a/tests/RollbackTest/Android.bp +++ b/tests/RollbackTest/Android.bp @@ -19,15 +19,17 @@ android_test { static_libs: ["androidx.test.rules", "cts-rollback-lib", "cts-install-lib"], test_suites: ["general-tests"], test_config: "RollbackTest.xml", + java_resources: [":com.android.apex.apkrollback.test_v2"], } java_test_host { name: "StagedRollbackTest", srcs: ["StagedRollbackTest/src/**/*.java"], libs: ["tradefed"], - static_libs: ["testng"], + static_libs: ["testng", "compatibility-tradefed"], test_suites: ["general-tests"], test_config: "StagedRollbackTest.xml", + data: [":com.android.apex.apkrollback.test_v1"], } java_test_host { @@ -37,3 +39,44 @@ java_test_host { test_suites: ["general-tests"], test_config: "MultiUserRollbackTest.xml", } + +genrule { + name: "com.android.apex.apkrollback.test.pem", + out: ["com.android.apex.apkrollback.test.pem"], + cmd: "openssl genrsa -out $(out) 4096", +} + +genrule { + name: "com.android.apex.apkrollback.test.pubkey", + srcs: [":com.android.apex.apkrollback.test.pem"], + out: ["com.android.apex.apkrollback.test.pubkey"], + tools: ["avbtool"], + cmd: "$(location avbtool) extract_public_key --key $(in) --output $(out)", +} + +apex_key { + name: "com.android.apex.apkrollback.test.key", + private_key: ":com.android.apex.apkrollback.test.pem", + public_key: ":com.android.apex.apkrollback.test.pubkey", + installable: false, +} + +apex { + name: "com.android.apex.apkrollback.test_v1", + manifest: "testdata/manifest_v1.json", + androidManifest: "testdata/AndroidManifest.xml", + file_contexts: ":apex.test-file_contexts", + key: "com.android.apex.apkrollback.test.key", + apps: ["TestAppAv1"], + installable: false, +} + +apex { + name: "com.android.apex.apkrollback.test_v2", + manifest: "testdata/manifest_v2.json", + androidManifest: "testdata/AndroidManifest.xml", + file_contexts: ":apex.test-file_contexts", + key: "com.android.apex.apkrollback.test.key", + apps: ["TestAppAv2"], + installable: false, +}
\ No newline at end of file diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java index 40169b8cdcd3..3877cc139a3e 100644 --- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java +++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java @@ -324,6 +324,7 @@ public class StagedRollbackTest { @Test public void testNetworkPassedDoesNotRollback_Phase1() throws Exception { + // Remove available rollbacks and uninstall NetworkStack on /data/ RollbackManager rm = RollbackUtils.getRollbackManager(); String networkStack = getNetworkStackPackageName(); @@ -332,6 +333,15 @@ public class StagedRollbackTest { assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), networkStack)).isNull(); + + // Reduce health check deadline, here unlike the network failed case, we use + // a longer deadline because joining a network can take a much longer time for + // reasons external to the device than 'not joining' + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK, + PROPERTY_WATCHDOG_REQUEST_TIMEOUT_MILLIS, + Integer.toString(300000), false); + // Simulate re-installation of new NetworkStack with rollbacks enabled + installNetworkStackPackage(); } @Test @@ -343,6 +353,9 @@ public class StagedRollbackTest { @Test public void testNetworkPassedDoesNotRollback_Phase3() throws Exception { + // Sleep for > health check deadline. We expect no rollback should happen during sleeping. + // If the device reboots for rollback, this device test will fail as well as the host test. + Thread.sleep(TimeUnit.SECONDS.toMillis(310)); RollbackManager rm = RollbackUtils.getRollbackManager(); assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(), getNetworkStackPackageName())).isNull(); @@ -380,11 +393,6 @@ public class StagedRollbackTest { } @Test - public void testRollbackWhitelistedApp_cleanUp() throws Exception { - RollbackUtils.getRollbackManager().expireRollbackForPackage(getModuleMetadataPackageName()); - } - - @Test public void testRollbackDataPolicy_Phase1() throws Exception { Uninstall.packages(TestApp.A, TestApp.B); Install.multi(TestApp.A1, TestApp.B1).commit(); @@ -422,6 +430,69 @@ public class StagedRollbackTest { assertThat(InstallUtils.getUserDataVersion(TestApp.B)).isEqualTo(1); } + @Test + public void testCleanUp() throws Exception { + // testNativeWatchdogTriggersRollback will fail if multiple staged sessions are + // committed on a device which doesn't support checkpoint. Let's clean up all rollbacks + // so there is only one rollback to commit when testing native crashes. + RollbackManager rm = RollbackUtils.getRollbackManager(); + rm.getAvailableRollbacks().stream().flatMap(info -> info.getPackages().stream()) + .map(info -> info.getPackageName()).forEach(rm::expireRollbackForPackage); + } + + private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test"; + private static final TestApp TEST_APEX_WITH_APK_V1 = new TestApp("TestApexWithApkV1", + APK_IN_APEX_TESTAPEX_NAME, 1, /*isApex*/true, APK_IN_APEX_TESTAPEX_NAME + "_v1.apex"); + private static final TestApp TEST_APEX_WITH_APK_V2 = new TestApp("TestApexWithApkV2", + APK_IN_APEX_TESTAPEX_NAME, 2, /*isApex*/true, APK_IN_APEX_TESTAPEX_NAME + "_v2.apex"); + private static final TestApp TEST_APP_A_V2_UNKNOWN = new TestApp("Av2Unknown", TestApp.A, 0, + /*isApex*/false, "TestAppAv2.apk"); + + @Test + public void testRollbackApexWithApk_Phase1() throws Exception { + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); + InstallUtils.processUserData(TestApp.A); + + int sessionId = Install.single(TEST_APEX_WITH_APK_V2).setStaged().setEnableRollback() + .commit(); + InstallUtils.waitForSessionReady(sessionId); + } + + @Test + public void testRollbackApexWithApk_Phase2() throws Exception { + assertThat(InstallUtils.getInstalledVersion(APK_IN_APEX_TESTAPEX_NAME)).isEqualTo(2); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); + InstallUtils.processUserData(TestApp.A); + + RollbackInfo available = RollbackUtils.getAvailableRollback(APK_IN_APEX_TESTAPEX_NAME); + assertThat(available).isStaged(); + assertThat(available).packagesContainsExactly( + Rollback.from(TEST_APEX_WITH_APK_V2).to(TEST_APEX_WITH_APK_V1), + Rollback.from(TEST_APP_A_V2_UNKNOWN).to(TestApp.A1)); + + RollbackUtils.rollback(available.getRollbackId(), TEST_APEX_WITH_APK_V2); + RollbackInfo committed = RollbackUtils.getCommittedRollbackById(available.getRollbackId()); + assertThat(committed).isNotNull(); + assertThat(committed).isStaged(); + assertThat(committed).packagesContainsExactly( + Rollback.from(TEST_APEX_WITH_APK_V2).to(TEST_APEX_WITH_APK_V1), + Rollback.from(TEST_APP_A_V2_UNKNOWN).to(TestApp.A1)); + assertThat(committed).causePackagesContainsExactly(TEST_APEX_WITH_APK_V2); + assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1); + + // Note: The app is not rolled back until after the rollback is staged + // and the device has been rebooted. + InstallUtils.waitForSessionReady(committed.getCommittedSessionId()); + assertThat(InstallUtils.getInstalledVersion(APK_IN_APEX_TESTAPEX_NAME)).isEqualTo(2); + } + + @Test + public void testRollbackApexWithApk_Phase3() throws Exception { + assertThat(InstallUtils.getInstalledVersion(APK_IN_APEX_TESTAPEX_NAME)).isEqualTo(1); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); + InstallUtils.processUserData(TestApp.A); + } + private static void runShellCommand(String cmd) { ParcelFileDescriptor pfd = InstrumentationRegistry.getInstrumentation().getUiAutomation() .executeShellCommand(cmd); diff --git a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java index 4644d8aee306..6daa6bc723c4 100644 --- a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java +++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java @@ -19,14 +19,16 @@ package com.android.tests.rollback.host; import static org.junit.Assert.assertTrue; import static org.testng.Assert.assertThrows; +import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper; import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; +import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; +import java.io.File; import java.util.concurrent.TimeUnit; /** @@ -48,8 +50,31 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { phase)); } + private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test"; + @Before public void setUp() throws Exception { + if (!getDevice().isAdbRoot()) { + getDevice().enableAdbRoot(); + } + getDevice().remountSystemWritable(); + getDevice().executeShellCommand( + "rm -f /system/apex/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex " + + "/data/apex/active/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex"); + getDevice().reboot(); + } + + @After + public void tearDown() throws Exception { + runPhase("testCleanUp"); + + if (!getDevice().isAdbRoot()) { + getDevice().enableAdbRoot(); + } + getDevice().remountSystemWritable(); + getDevice().executeShellCommand( + "rm -f /system/apex/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex " + + "/data/apex/active/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex"); getDevice().reboot(); } @@ -128,21 +153,8 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { * Tests passed network health check does not trigger watchdog staged rollbacks. */ @Test - @Ignore("b/143514090") public void testNetworkPassedDoesNotRollback() throws Exception { - // Remove available rollbacks and uninstall NetworkStack on /data/ runPhase("testNetworkPassedDoesNotRollback_Phase1"); - // Reduce health check deadline, here unlike the network failed case, we use - // a longer deadline because joining a network can take a much longer time for - // reasons external to the device than 'not joining' - getDevice().executeShellCommand("device_config put rollback " - + "watchdog_request_timeout_millis 300000"); - // Simulate re-installation of new NetworkStack with rollbacks enabled - getDevice().executeShellCommand("pm install -r --staged --enable-rollback " - + getNetworkStackPath()); - - // Sleep to allow writes to disk before reboot - Thread.sleep(5000); // Reboot device to activate staged package getDevice().reboot(); @@ -157,8 +169,6 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { // on mobile data getDevice().waitForDeviceAvailable(); - // Sleep for > health check deadline - Thread.sleep(310000); // Verify rollback was not executed after health check deadline runPhase("testNetworkPassedDoesNotRollback_Phase3"); } @@ -180,16 +190,9 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { */ @Test public void testRollbackWhitelistedApp() throws Exception { - try { - runPhase("testRollbackWhitelistedApp_Phase1"); - getDevice().reboot(); - runPhase("testRollbackWhitelistedApp_Phase2"); - } finally { - // testNativeWatchdogTriggersRollback will fail if multiple staged sessions are - // committed on a device which doesn't support checkpoint. Let's clean up the rollback - // so there is only one rollback to commit when testing native crashes. - runPhase("testRollbackWhitelistedApp_cleanUp"); - } + runPhase("testRollbackWhitelistedApp_Phase1"); + getDevice().reboot(); + runPhase("testRollbackWhitelistedApp_Phase2"); } @Test @@ -201,6 +204,28 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { runPhase("testRollbackDataPolicy_Phase3"); } + /** + * Tests that userdata of apk-in-apex is restored when apex is rolled back. + */ + @Test + public void testRollbackApexWithApk() throws Exception { + getDevice().uninstallPackage("com.android.cts.install.lib.testapp.A"); + CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild()); + final String fileName = APK_IN_APEX_TESTAPEX_NAME + "_v1.apex"; + final File apex = buildHelper.getTestFile(fileName); + if (!getDevice().isAdbRoot()) { + getDevice().enableAdbRoot(); + } + getDevice().remountSystemWritable(); + assertTrue(getDevice().pushFile(apex, "/system/apex/" + fileName)); + getDevice().reboot(); + runPhase("testRollbackApexWithApk_Phase1"); + getDevice().reboot(); + runPhase("testRollbackApexWithApk_Phase2"); + getDevice().reboot(); + runPhase("testRollbackApexWithApk_Phase3"); + } + private void crashProcess(String processName, int numberOfCrashes) throws Exception { String pid = ""; String lastPid = "invalid"; diff --git a/tests/RollbackTest/testdata/AndroidManifest.xml b/tests/RollbackTest/testdata/AndroidManifest.xml new file mode 100644 index 000000000000..f21ec899eb69 --- /dev/null +++ b/tests/RollbackTest/testdata/AndroidManifest.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.apex.apkrollback.test"> + <!-- APEX does not have classes.dex --> + <application android:hasCode="false" /> + <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="29"/> +</manifest> + diff --git a/tests/RollbackTest/testdata/manifest_v1.json b/tests/RollbackTest/testdata/manifest_v1.json new file mode 100644 index 000000000000..1762fc6764cf --- /dev/null +++ b/tests/RollbackTest/testdata/manifest_v1.json @@ -0,0 +1,4 @@ +{ + "name": "com.android.apex.apkrollback.test", + "version": 1 +} diff --git a/tests/RollbackTest/testdata/manifest_v2.json b/tests/RollbackTest/testdata/manifest_v2.json new file mode 100644 index 000000000000..c5127b9c3023 --- /dev/null +++ b/tests/RollbackTest/testdata/manifest_v2.json @@ -0,0 +1,4 @@ +{ + "name": "com.android.apex.apkrollback.test", + "version": 2 +} diff --git a/tests/TaskOrganizerTest/Android.bp b/tests/TaskOrganizerTest/Android.bp new file mode 100644 index 000000000000..8a13dbc52c66 --- /dev/null +++ b/tests/TaskOrganizerTest/Android.bp @@ -0,0 +1,22 @@ +// +// 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. +// + +android_test { + name: "TaskOrganizerTest", + srcs: ["**/*.java"], + platform_apis: true, + certificate: "platform", +} diff --git a/packages/SettingsProvider/res/values/overlayable.xml b/tests/TaskOrganizerTest/AndroidManifest.xml index dc41a77d0e2d..0cb6c10a7ff5 100644 --- a/packages/SettingsProvider/res/values/overlayable.xml +++ b/tests/TaskOrganizerTest/AndroidManifest.xml @@ -1,25 +1,23 @@ -<?xml version="1.0" encoding="utf-8" ?> +<?xml version="1.0" encoding="utf-8"?> <!-- 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. --> -<resources xmlns:android="http://schemas.android.com/apk/res/android"> - <overlayable name="SettingsToNotRestore"> - <policy type="product|system|vendor"> - <item type="array" name="restore_blocked_device_specific_settings" /> - <item type="array" name="restore_blocked_global_settings" /> - <item type="array" name="restore_blocked_secure_settings" /> - <item type="array" name="restore_blocked_system_settings" /> - </policy> - </overlayable> -</resources> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.test.taskembed"> + <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" /> + <uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" /> + <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> + <application> + <service android:name=".TaskOrganizerPipTest" + android:exported="true"> + </service> + </application> +</manifest> diff --git a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java new file mode 100644 index 000000000000..6ffa19d4ec98 --- /dev/null +++ b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java @@ -0,0 +1,127 @@ +/* + * 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.test.taskembed; + +import android.app.ActivityManager; +import android.app.ActivityTaskManager; +import android.app.Service; +import android.app.WindowConfiguration; +import android.content.Context; +import android.content.Intent; +import android.graphics.Rect; +import android.os.IBinder; +import android.view.ITaskOrganizer; +import android.view.IWindowContainer; +import android.view.WindowContainerTransaction; +import android.view.SurfaceControl; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.FrameLayout; + +public class TaskOrganizerPipTest extends Service { + static final int PIP_WIDTH = 640; + static final int PIP_HEIGHT = 360; + + class PipOrgView extends SurfaceView implements SurfaceHolder.Callback { + PipOrgView(Context c) { + super(c); + getHolder().addCallback(this); + setZOrderOnTop(true); + } + @Override + public void surfaceCreated(SurfaceHolder holder) { + try { + ActivityTaskManager.getService().registerTaskOrganizer(mOrganizer, + WindowConfiguration.WINDOWING_MODE_PINNED); + } catch (Exception e) { + } + } + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + } + + void reparentTask(IWindowContainer wc) { + SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + SurfaceControl leash = null; + try { + leash = wc.getLeash(); + } catch (Exception e) { + // System server died.. oh well + } + t.reparent(leash, getSurfaceControl()) + .setPosition(leash, 0, 0) + .apply(); + } + } + + PipOrgView mPipView; + + class Organizer extends ITaskOrganizer.Stub { + public void taskAppeared(IWindowContainer wc, ActivityManager.RunningTaskInfo ti) { + mPipView.reparentTask(wc); + + final WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.scheduleFinishEnterPip(wc, new Rect(0, 0, PIP_WIDTH, PIP_HEIGHT)); + try { + ActivityTaskManager.getService().applyContainerTransaction(wct); + } catch (Exception e) { + } + } + public void taskVanished(IWindowContainer wc) { + } + public void transactionReady(int id, SurfaceControl.Transaction t) { + } + } + + Organizer mOrganizer = new Organizer(); + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + public void onCreate() { + super.onCreate(); + + final WindowManager.LayoutParams wlp = new WindowManager.LayoutParams(); + wlp.setTitle("TaskOrganizerPipTest"); + wlp.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; + wlp.width = wlp.height = ViewGroup.LayoutParams.WRAP_CONTENT; + + FrameLayout layout = new FrameLayout(this); + ViewGroup.LayoutParams lp = + new ViewGroup.LayoutParams(PIP_WIDTH, PIP_HEIGHT); + mPipView = new PipOrgView(this); + layout.addView(mPipView, lp); + + WindowManager wm = getSystemService(WindowManager.class); + wm.addView(layout, wlp); + } + + @Override + public void onDestroy() { + super.onDestroy(); + } +} diff --git a/tests/net/java/android/net/NetworkStatsTest.java b/tests/net/java/android/net/NetworkStatsTest.java index c16a0f446651..33d77d288e15 100644 --- a/tests/net/java/android/net/NetworkStatsTest.java +++ b/tests/net/java/android/net/NetworkStatsTest.java @@ -64,15 +64,15 @@ public class NetworkStatsTest { @Test public void testFindIndex() throws Exception { final NetworkStats stats = new NetworkStats(TEST_START, 5) - .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 1024L, 8L, 0L, 0L, 10) - .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 0L, 0L, 1024L, 8L, 11) - .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, + .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_YES, 0L, 0L, 1024L, 8L, 11) - .addValues(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + .addEntry(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1024L, 8L, 1024L, 8L, 12) - .addValues(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES, + .addEntry(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES, DEFAULT_NETWORK_YES, 1024L, 8L, 1024L, 8L, 12); assertEquals(4, stats.findIndex(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_YES, @@ -94,21 +94,21 @@ public class NetworkStatsTest { @Test public void testFindIndexHinted() { final NetworkStats stats = new NetworkStats(TEST_START, 3) - .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 1024L, 8L, 0L, 0L, 10) - .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 0L, 0L, 1024L, 8L, 11) - .addValues(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + .addEntry(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 1024L, 8L, 1024L, 8L, 12) - .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, + .addEntry(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1024L, 8L, 0L, 0L, 10) - .addValues(TEST_IFACE2, 101, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, + .addEntry(TEST_IFACE2, 101, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 0L, 0L, 1024L, 8L, 11) - .addValues(TEST_IFACE2, 101, SET_DEFAULT, 0xF00D, METERED_YES, ROAMING_NO, + .addEntry(TEST_IFACE2, 101, SET_DEFAULT, 0xF00D, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_NO, 0L, 0L, 1024L, 8L, 11) - .addValues(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + .addEntry(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 1024L, 8L, 1024L, 8L, 12) - .addValues(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES, + .addEntry(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES, DEFAULT_NETWORK_NO, 1024L, 8L, 1024L, 8L, 12); // verify that we correctly find across regardless of hinting @@ -143,27 +143,27 @@ public class NetworkStatsTest { assertEquals(0, stats.size()); assertEquals(4, stats.internalSize()); - stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + stats.addEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 1L, 1L, 2L, 2L, 3); - stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + stats.addEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 2L, 2L, 2L, 2L, 4); - stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES, + stats.addEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES, DEFAULT_NETWORK_YES, 3L, 3L, 2L, 2L, 5); - stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES, + stats.addEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES, DEFAULT_NETWORK_NO, 3L, 3L, 2L, 2L, 5); assertEquals(4, stats.size()); assertEquals(4, stats.internalSize()); - stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + stats.addEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 4L, 40L, 4L, 40L, 7); - stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + stats.addEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 5L, 50L, 4L, 40L, 8); - stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + stats.addEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 6L, 60L, 5L, 50L, 10); - stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES, + stats.addEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES, DEFAULT_NETWORK_YES, 7L, 70L, 5L, 50L, 11); - stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES, + stats.addEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES, DEFAULT_NETWORK_NO, 7L, 70L, 5L, 50L, 11); assertEquals(9, stats.size()); @@ -193,8 +193,8 @@ public class NetworkStatsTest { public void testCombineExisting() throws Exception { final NetworkStats stats = new NetworkStats(TEST_START, 10); - stats.addValues(TEST_IFACE, 1001, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 10); - stats.addValues(TEST_IFACE, 1001, SET_DEFAULT, 0xff, 128L, 1L, 128L, 1L, 2); + stats.addEntry(TEST_IFACE, 1001, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 10); + stats.addEntry(TEST_IFACE, 1001, SET_DEFAULT, 0xff, 128L, 1L, 128L, 1L, 2); stats.combineValues(TEST_IFACE, 1001, SET_DEFAULT, TAG_NONE, -128L, -1L, -128L, -1L, -1); @@ -215,12 +215,12 @@ public class NetworkStatsTest { @Test public void testSubtractIdenticalData() throws Exception { final NetworkStats before = new NetworkStats(TEST_START, 2) - .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11) - .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12); + .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11) + .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12); final NetworkStats after = new NetworkStats(TEST_START, 2) - .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11) - .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12); + .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11) + .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12); final NetworkStats result = after.subtract(before); @@ -234,12 +234,12 @@ public class NetworkStatsTest { @Test public void testSubtractIdenticalRows() throws Exception { final NetworkStats before = new NetworkStats(TEST_START, 2) - .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11) - .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12); + .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11) + .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12); final NetworkStats after = new NetworkStats(TEST_START, 2) - .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1025L, 9L, 2L, 1L, 15) - .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 3L, 1L, 1028L, 9L, 20); + .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1025L, 9L, 2L, 1L, 15) + .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 3L, 1L, 1028L, 9L, 20); final NetworkStats result = after.subtract(before); @@ -253,13 +253,13 @@ public class NetworkStatsTest { @Test public void testSubtractNewRows() throws Exception { final NetworkStats before = new NetworkStats(TEST_START, 2) - .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11) - .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12); + .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11) + .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12); final NetworkStats after = new NetworkStats(TEST_START, 3) - .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11) - .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12) - .addValues(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 20); + .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11) + .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12) + .addEntry(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 20); final NetworkStats result = after.subtract(before); @@ -275,11 +275,11 @@ public class NetworkStatsTest { @Test public void testSubtractMissingRows() throws Exception { final NetworkStats before = new NetworkStats(TEST_START, 2) - .addValues(TEST_IFACE, UID_ALL, SET_DEFAULT, TAG_NONE, 1024L, 0L, 0L, 0L, 0) - .addValues(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 2048L, 0L, 0L, 0L, 0); + .addEntry(TEST_IFACE, UID_ALL, SET_DEFAULT, TAG_NONE, 1024L, 0L, 0L, 0L, 0) + .addEntry(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 2048L, 0L, 0L, 0L, 0); final NetworkStats after = new NetworkStats(TEST_START, 1) - .addValues(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 2049L, 2L, 3L, 4L, 0); + .addEntry(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 2049L, 2L, 3L, 4L, 0); final NetworkStats result = after.subtract(before); @@ -293,40 +293,40 @@ public class NetworkStatsTest { @Test public void testTotalBytes() throws Exception { final NetworkStats iface = new NetworkStats(TEST_START, 2) - .addValues(TEST_IFACE, UID_ALL, SET_DEFAULT, TAG_NONE, 128L, 0L, 0L, 0L, 0L) - .addValues(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 256L, 0L, 0L, 0L, 0L); + .addEntry(TEST_IFACE, UID_ALL, SET_DEFAULT, TAG_NONE, 128L, 0L, 0L, 0L, 0L) + .addEntry(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 256L, 0L, 0L, 0L, 0L); assertEquals(384L, iface.getTotalBytes()); final NetworkStats uidSet = new NetworkStats(TEST_START, 3) - .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 32L, 0L, 0L, 0L, 0L) - .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 32L, 0L, 0L, 0L, 0L) - .addValues(TEST_IFACE, 101, SET_FOREGROUND, TAG_NONE, 32L, 0L, 0L, 0L, 0L); + .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 32L, 0L, 0L, 0L, 0L) + .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 32L, 0L, 0L, 0L, 0L) + .addEntry(TEST_IFACE, 101, SET_FOREGROUND, TAG_NONE, 32L, 0L, 0L, 0L, 0L); assertEquals(96L, uidSet.getTotalBytes()); final NetworkStats uidTag = new NetworkStats(TEST_START, 6) - .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 16L, 0L, 0L, 0L, 0L) - .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 16L, 0L, 0L, 0L, 0L) - .addValues(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, 8L, 0L, 0L, 0L, 0L) - .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, 16L, 0L, 0L, 0L, 0L) - .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 16L, 0L, 0L, 0L, 0L) - .addValues(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 8L, 0L, 0L, 0L, 0L); + .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 16L, 0L, 0L, 0L, 0L) + .addEntry(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 16L, 0L, 0L, 0L, 0L) + .addEntry(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, 8L, 0L, 0L, 0L, 0L) + .addEntry(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, 16L, 0L, 0L, 0L, 0L) + .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 16L, 0L, 0L, 0L, 0L) + .addEntry(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 8L, 0L, 0L, 0L, 0L); assertEquals(64L, uidTag.getTotalBytes()); final NetworkStats uidMetered = new NetworkStats(TEST_START, 3) - .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L) - .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, + .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_NO, 32L, 0L, 0L, 0L, 0L) - .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, + .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L); assertEquals(96L, uidMetered.getTotalBytes()); final NetworkStats uidRoaming = new NetworkStats(TEST_START, 3) - .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L) - .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, + .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_NO, 32L, 0L, 0L, 0L, 0L) - .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES, + .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES, DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L); assertEquals(96L, uidRoaming.getTotalBytes()); } @@ -343,11 +343,11 @@ public class NetworkStatsTest { @Test public void testGroupedByIfaceAll() throws Exception { final NetworkStats uidStats = new NetworkStats(TEST_START, 3) - .addValues(IFACE_ALL, 100, SET_ALL, TAG_NONE, METERED_NO, ROAMING_NO, + .addEntry(IFACE_ALL, 100, SET_ALL, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 128L, 8L, 0L, 2L, 20L) - .addValues(IFACE_ALL, 101, SET_FOREGROUND, TAG_NONE, METERED_YES, ROAMING_NO, + .addEntry(IFACE_ALL, 101, SET_FOREGROUND, TAG_NONE, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_NO, 128L, 8L, 0L, 2L, 20L) - .addValues(IFACE_ALL, 101, SET_ALL, TAG_NONE, METERED_NO, ROAMING_YES, + .addEntry(IFACE_ALL, 101, SET_ALL, TAG_NONE, METERED_NO, ROAMING_YES, DEFAULT_NETWORK_YES, 128L, 8L, 0L, 2L, 20L); final NetworkStats grouped = uidStats.groupedByIface(); @@ -361,19 +361,19 @@ public class NetworkStatsTest { @Test public void testGroupedByIface() throws Exception { final NetworkStats uidStats = new NetworkStats(TEST_START, 7) - .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 128L, 8L, 0L, 2L, 20L) - .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + .addEntry(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 512L, 32L, 0L, 0L, 0L) - .addValues(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, + .addEntry(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 64L, 4L, 0L, 0L, 0L) - .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, + .addEntry(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 512L, 32L, 0L, 0L, 0L) - .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 128L, 8L, 0L, 0L, 0L) - .addValues(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, METERED_YES, ROAMING_NO, + .addEntry(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_NO, 128L, 8L, 0L, 0L, 0L) - .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES, + .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES, DEFAULT_NETWORK_YES, 128L, 8L, 0L, 0L, 0L); final NetworkStats grouped = uidStats.groupedByIface(); @@ -390,19 +390,19 @@ public class NetworkStatsTest { @Test public void testAddAllValues() { final NetworkStats first = new NetworkStats(TEST_START, 5) - .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, + .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L) - .addValues(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, + .addEntry(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 32L, 0L, 0L, 0L, 0L) - .addValues(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, METERED_YES, ROAMING_YES, + .addEntry(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, METERED_YES, ROAMING_YES, DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L); final NetworkStats second = new NetworkStats(TEST_START, 2) - .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, + .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L) - .addValues(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + .addEntry(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 32L, 0L, 0L, 0L, 0L) - .addValues(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, METERED_YES, ROAMING_YES, + .addEntry(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, METERED_YES, ROAMING_YES, DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L); first.combineAllValues(second); @@ -421,19 +421,19 @@ public class NetworkStatsTest { @Test public void testGetTotal() { final NetworkStats stats = new NetworkStats(TEST_START, 7) - .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 128L, 8L, 0L, 2L, 20L) - .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + .addEntry(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 512L, 32L, 0L, 0L, 0L) - .addValues(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, + .addEntry(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 64L, 4L, 0L, 0L, 0L) - .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, + .addEntry(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 512L,32L, 0L, 0L, 0L) - .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, + .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_YES, 128L, 8L, 0L, 0L, 0L) - .addValues(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, + .addEntry(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 128L, 8L, 0L, 0L, 0L) - .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES, + .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES, DEFAULT_NETWORK_NO, 128L, 8L, 0L, 0L, 0L); assertValues(stats.getTotal(null), 1408L, 88L, 0L, 2L, 20L); @@ -459,7 +459,7 @@ public class NetworkStatsTest { assertEquals(0, after.size()); // Test 1 item stats. - before.addValues(TEST_IFACE, 99, SET_DEFAULT, TAG_NONE, 1L, 128L, 0L, 2L, 20L); + before.addEntry(TEST_IFACE, 99, SET_DEFAULT, TAG_NONE, 1L, 128L, 0L, 2L, 20L); after = before.clone(); after.removeUids(new int[0]); assertEquals(1, after.size()); @@ -469,12 +469,12 @@ public class NetworkStatsTest { assertEquals(0, after.size()); // Append remaining test items. - before.addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 2L, 64L, 0L, 2L, 20L) - .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 4L, 32L, 0L, 0L, 0L) - .addValues(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, 8L, 16L, 0L, 0L, 0L) - .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, 16L, 8L, 0L, 0L, 0L) - .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 32L, 4L, 0L, 0L, 0L) - .addValues(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 64L, 2L, 0L, 0L, 0L); + before.addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 2L, 64L, 0L, 2L, 20L) + .addEntry(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 4L, 32L, 0L, 0L, 0L) + .addEntry(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, 8L, 16L, 0L, 0L, 0L) + .addEntry(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, 16L, 8L, 0L, 0L, 0L) + .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 32L, 4L, 0L, 0L, 0L) + .addEntry(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 64L, 2L, 0L, 0L, 0L); assertEquals(7, before.size()); // Test remove with empty uid list. @@ -505,12 +505,12 @@ public class NetworkStatsTest { @Test public void testClone() throws Exception { final NetworkStats original = new NetworkStats(TEST_START, 5) - .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 2L, 20L) - .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 512L, 32L, 0L, 0L, 0L); + .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 2L, 20L) + .addEntry(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 512L, 32L, 0L, 0L, 0L); // make clone and mutate original final NetworkStats clone = original.clone(); - original.addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 0L, 0L); + original.addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 0L, 0L); assertEquals(3, original.size()); assertEquals(2, clone.size()); @@ -523,8 +523,8 @@ public class NetworkStatsTest { public void testAddWhenEmpty() throws Exception { final NetworkStats red = new NetworkStats(TEST_START, -1); final NetworkStats blue = new NetworkStats(TEST_START, 5) - .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 2L, 20L) - .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 512L, 32L, 0L, 0L, 0L); + .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 2L, 20L) + .addEntry(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 512L, 32L, 0L, 0L, 0L); // We're mostly checking that we don't crash red.combineAllValues(blue); @@ -537,39 +537,39 @@ public class NetworkStatsTest { final String underlyingIface = "wlan0"; final int testTag1 = 8888; NetworkStats delta = new NetworkStats(TEST_START, 17) - .addValues(tunIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, - DEFAULT_NETWORK_NO, 39605L, 46L, 12259L, 55L, 0L) - .addValues(tunIface, 10100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, - DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L) - .addValues(tunIface, 10120, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, - DEFAULT_NETWORK_NO, 72667L, 197L, 43909L, 241L, 0L) - .addValues(tunIface, 10120, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, - DEFAULT_NETWORK_NO, 9297L, 17L, 4128L, 21L, 0L) - // VPN package also uses some traffic through unprotected network. - .addValues(tunIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, - DEFAULT_NETWORK_NO, 4983L, 10L, 1801L, 12L, 0L) - .addValues(tunIface, tunUid, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, - DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L) - // Tag entries - .addValues(tunIface, 10120, SET_DEFAULT, testTag1, METERED_NO, ROAMING_NO, - DEFAULT_NETWORK_NO, 21691L, 41L, 13820L, 51L, 0L) - .addValues(tunIface, 10120, SET_FOREGROUND, testTag1, METERED_NO, ROAMING_NO, - DEFAULT_NETWORK_NO, 1281L, 2L, 665L, 2L, 0L) - // Irrelevant entries - .addValues(TEST_IFACE, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, - DEFAULT_NETWORK_NO, 1685L, 5L, 2070L, 6L, 0L) - // Underlying Iface entries - .addValues(underlyingIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, - DEFAULT_NETWORK_NO, 5178L, 8L, 2139L, 11L, 0L) - .addValues(underlyingIface, 10100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, - DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L) - .addValues(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, - DEFAULT_NETWORK_NO, 149873L, 287L, 59217L /* smaller than sum(tun0) */, - 299L /* smaller than sum(tun0) */, 0L) - .addValues(underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, - DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L); - - delta.migrateTun(tunUid, tunIface, new String[] {underlyingIface}); + .addEntry(tunIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 39605L, 46L, 12259L, 55L, 0L) + .addEntry(tunIface, 10100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L) + .addEntry(tunIface, 10120, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 72667L, 197L, 43909L, 241L, 0L) + .addEntry(tunIface, 10120, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 9297L, 17L, 4128L, 21L, 0L) + // VPN package also uses some traffic through unprotected network. + .addEntry(tunIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 4983L, 10L, 1801L, 12L, 0L) + .addEntry(tunIface, tunUid, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L) + // Tag entries + .addEntry(tunIface, 10120, SET_DEFAULT, testTag1, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 21691L, 41L, 13820L, 51L, 0L) + .addEntry(tunIface, 10120, SET_FOREGROUND, testTag1, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 1281L, 2L, 665L, 2L, 0L) + // Irrelevant entries + .addEntry(TEST_IFACE, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 1685L, 5L, 2070L, 6L, 0L) + // Underlying Iface entries + .addEntry(underlyingIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 5178L, 8L, 2139L, 11L, 0L) + .addEntry(underlyingIface, 10100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L) + .addEntry(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 149873L, 287L, 59217L /* smaller than sum(tun0) */, + 299L /* smaller than sum(tun0) */, 0L) + .addEntry(underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L); + + delta.migrateTun(tunUid, tunIface, new String[]{underlyingIface}); assertEquals(20, delta.size()); // tunIface and TEST_IFACE entries are not changed. @@ -634,21 +634,21 @@ public class NetworkStatsTest { final String tunIface = "tun0"; final String underlyingIface = "wlan0"; NetworkStats delta = new NetworkStats(TEST_START, 9) - // 2 different apps sent/receive data via tun0. - .addValues(tunIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, - DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L) - .addValues(tunIface, 20100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, - DEFAULT_NETWORK_NO, 500L, 2L, 200L, 5L, 0L) - // VPN package resends data through the tunnel (with exaggerated overhead) - .addValues(tunIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, - DEFAULT_NETWORK_NO, 240000, 100L, 120000L, 60L, 0L) - // 1 app already has some traffic on the underlying interface, the other doesn't yet - .addValues(underlyingIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, - DEFAULT_NETWORK_NO, 1000L, 10L, 2000L, 20L, 0L) - // Traffic through the underlying interface via the vpn app. - // This test should redistribute this data correctly. - .addValues(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, - DEFAULT_NETWORK_NO, 75500L, 37L, 130000L, 70L, 0L); + // 2 different apps sent/receive data via tun0. + .addEntry(tunIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L) + .addEntry(tunIface, 20100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 500L, 2L, 200L, 5L, 0L) + // VPN package resends data through the tunnel (with exaggerated overhead) + .addEntry(tunIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 240000, 100L, 120000L, 60L, 0L) + // 1 app already has some traffic on the underlying interface, the other doesn't yet + .addEntry(underlyingIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 1000L, 10L, 2000L, 20L, 0L) + // Traffic through the underlying interface via the vpn app. + // This test should redistribute this data correctly. + .addEntry(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + DEFAULT_NETWORK_NO, 75500L, 37L, 130000L, 70L, 0L); delta.migrateTun(tunUid, tunIface, new String[]{underlyingIface}); assertEquals(9, delta.size()); @@ -697,9 +697,9 @@ public class NetworkStatsTest { DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L); NetworkStats stats = new NetworkStats(TEST_START, 3) - .addValues(entry1) - .addValues(entry2) - .addValues(entry3); + .addEntry(entry1) + .addEntry(entry2) + .addEntry(entry3); stats.filter(UID_ALL, INTERFACES_ALL, TAG_ALL); assertEquals(3, stats.size()); @@ -724,9 +724,9 @@ public class NetworkStatsTest { DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L); NetworkStats stats = new NetworkStats(TEST_START, 3) - .addValues(entry1) - .addValues(entry2) - .addValues(entry3); + .addEntry(entry1) + .addEntry(entry2) + .addEntry(entry3); stats.filter(testUid, INTERFACES_ALL, TAG_ALL); assertEquals(2, stats.size()); @@ -755,10 +755,10 @@ public class NetworkStatsTest { DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L); NetworkStats stats = new NetworkStats(TEST_START, 4) - .addValues(entry1) - .addValues(entry2) - .addValues(entry3) - .addValues(entry4); + .addEntry(entry1) + .addEntry(entry2) + .addEntry(entry3) + .addEntry(entry4); stats.filter(UID_ALL, new String[] { testIf1, testIf2 }, TAG_ALL); assertEquals(3, stats.size()); @@ -778,8 +778,8 @@ public class NetworkStatsTest { DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L); NetworkStats stats = new NetworkStats(TEST_START, 3) - .addValues(entry1) - .addValues(entry2); + .addEntry(entry1) + .addEntry(entry2); stats.filter(UID_ALL, new String[] { }, TAG_ALL); assertEquals(0, stats.size()); @@ -802,9 +802,9 @@ public class NetworkStatsTest { DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L); NetworkStats stats = new NetworkStats(TEST_START, 3) - .addValues(entry1) - .addValues(entry2) - .addValues(entry3); + .addEntry(entry1) + .addEntry(entry2) + .addEntry(entry3); stats.filter(UID_ALL, INTERFACES_ALL, testTag); assertEquals(2, stats.size()); @@ -831,10 +831,10 @@ public class NetworkStatsTest { DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L); NetworkStats stats = new NetworkStats(TEST_START, 4) - .addValues(entry1) - .addValues(entry2) - .addValues(entry3) - .addValues(entry4); + .addEntry(entry1) + .addEntry(entry2) + .addEntry(entry3) + .addEntry(entry4); stats.filterDebugEntries(); @@ -891,14 +891,14 @@ public class NetworkStatsTest { 0 /* operations */); final NetworkStats statsXt = new NetworkStats(TEST_START, 3) - .addValues(appEntry) - .addValues(xtRootUidEntry) - .addValues(otherEntry); + .addEntry(appEntry) + .addEntry(xtRootUidEntry) + .addEntry(otherEntry); final NetworkStats statsEbpf = new NetworkStats(TEST_START, 3) - .addValues(appEntry) - .addValues(ebpfRootUidEntry) - .addValues(otherEntry); + .addEntry(appEntry) + .addEntry(ebpfRootUidEntry) + .addEntry(otherEntry); statsXt.apply464xlatAdjustments(stackedIface, false); statsEbpf.apply464xlatAdjustments(stackedIface, true); @@ -945,8 +945,8 @@ public class NetworkStatsTest { 0 /* operations */); NetworkStats stats = new NetworkStats(TEST_START, 2) - .addValues(firstEntry) - .addValues(secondEntry); + .addEntry(firstEntry) + .addEntry(secondEntry); // Empty map: no adjustment stats.apply464xlatAdjustments(new ArrayMap<>(), false); diff --git a/tests/net/java/com/android/server/net/NetworkStatsObserversTest.java b/tests/net/java/com/android/server/net/NetworkStatsObserversTest.java index c0f9dc14869f..f0e5774a5dea 100644 --- a/tests/net/java/com/android/server/net/NetworkStatsObserversTest.java +++ b/tests/net/java/com/android/server/net/NetworkStatsObserversTest.java @@ -326,14 +326,14 @@ public class NetworkStatsObserversTest { // Baseline NetworkStats xtSnapshot = null; NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, BASE_BYTES, 2L, BASE_BYTES, 2L, 0L); mStatsObservers.updateStats( xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START); // Delta uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, BASE_BYTES + THRESHOLD_BYTES, 2L, BASE_BYTES + THRESHOLD_BYTES, 2L, 0L); mStatsObservers.updateStats( @@ -359,14 +359,14 @@ public class NetworkStatsObserversTest { // Baseline NetworkStats xtSnapshot = null; NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, BASE_BYTES, 2L, BASE_BYTES, 2L, 0L); mStatsObservers.updateStats( xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START); // Delta uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, BASE_BYTES + THRESHOLD_BYTES, 2L, BASE_BYTES + THRESHOLD_BYTES, 2L, 0L); mStatsObservers.updateStats( @@ -391,14 +391,14 @@ public class NetworkStatsObserversTest { // Baseline NetworkStats xtSnapshot = null; NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, BASE_BYTES, 2L, BASE_BYTES, 2L, 0L); mStatsObservers.updateStats( xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START); // Delta uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, BASE_BYTES + THRESHOLD_BYTES, 2L, BASE_BYTES + THRESHOLD_BYTES, 2L, 0L); mStatsObservers.updateStats( @@ -424,14 +424,14 @@ public class NetworkStatsObserversTest { // Baseline NetworkStats xtSnapshot = null; NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */) - .addValues(TEST_IFACE, UID_ANOTHER_USER, SET_DEFAULT, TAG_NONE, METERED_NO, + .addEntry(TEST_IFACE, UID_ANOTHER_USER, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, BASE_BYTES, 2L, BASE_BYTES, 2L, 0L); mStatsObservers.updateStats( xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START); // Delta uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */) - .addValues(TEST_IFACE, UID_ANOTHER_USER, SET_DEFAULT, TAG_NONE, METERED_NO, + .addEntry(TEST_IFACE, UID_ANOTHER_USER, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, BASE_BYTES + THRESHOLD_BYTES, 2L, BASE_BYTES + THRESHOLD_BYTES, 2L, 0L); mStatsObservers.updateStats( diff --git a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java index 4d42a612030d..6de068e48a38 100644 --- a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java +++ b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java @@ -298,11 +298,11 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1) .addIfaceValues(TEST_IFACE, 1024L, 8L, 2048L, 16L)); expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 2) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 0L) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 256L, 2L, 128L, 1L, 0L) - .addValues(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE, 512L, 4L, 256L, 2L, 0L) - .addValues(TEST_IFACE, UID_RED, SET_FOREGROUND, 0xFAAD, 256L, 2L, 128L, 1L, 0L) - .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 128L, 1L, 128L, 1L, 0L)); + .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 0L) + .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 256L, 2L, 128L, 1L, 0L) + .addEntry(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE, 512L, 4L, 256L, 2L, 0L) + .addEntry(TEST_IFACE, UID_RED, SET_FOREGROUND, 0xFAAD, 256L, 2L, 128L, 1L, 0L) + .addEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 128L, 1L, 128L, 1L, 0L)); mService.setUidForeground(UID_RED, false); mService.incrementOperationCount(UID_RED, 0xFAAD, 4); mService.setUidForeground(UID_RED, true); @@ -407,9 +407,9 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1) .addIfaceValues(TEST_IFACE, 2048L, 16L, 512L, 4L)); expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L) - .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 512L, 4L, 0L, 0L, 0L)); + .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L) + .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L) + .addEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 512L, 4L, 0L, 0L, 0L)); mService.incrementOperationCount(UID_RED, 0xF00D, 10); forcePollAndWaitForIdle(); @@ -429,9 +429,9 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1) .addIfaceValues(TEST_IFACE, 2048L, 16L, 512L, 4L)); expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L) - .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 512L, 4L, 0L, 0L, 0L)); + .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L) + .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L) + .addEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 512L, 4L, 0L, 0L, 0L)); mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states), new VpnInfo[0]); forcePollAndWaitForIdle(); @@ -443,10 +443,10 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1) .addIfaceValues(TEST_IFACE, 2176L, 17L, 1536L, 12L)); expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L) - .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 640L, 5L, 1024L, 8L, 0L) - .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, 0xFAAD, 128L, 1L, 1024L, 8L, 0L)); + .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L) + .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L) + .addEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 640L, 5L, 1024L, 8L, 0L) + .addEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, 0xFAAD, 128L, 1L, 1024L, 8L, 0L)); mService.incrementOperationCount(UID_BLUE, 0xFAAD, 10); forcePollAndWaitForIdle(); @@ -480,10 +480,10 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1) .addIfaceValues(TEST_IFACE, 4128L, 258L, 544L, 34L)); expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 16L, 1L, 16L, 1L, 0L) - .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 4096L, 258L, 512L, 32L, 0L) - .addValues(TEST_IFACE, UID_GREEN, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L)); + .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L) + .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 16L, 1L, 16L, 1L, 0L) + .addEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 4096L, 258L, 512L, 32L, 0L) + .addEntry(TEST_IFACE, UID_GREEN, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L)); mService.incrementOperationCount(UID_RED, 0xFAAD, 10); forcePollAndWaitForIdle(); @@ -501,10 +501,10 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1) .addIfaceValues(TEST_IFACE, 4128L, 258L, 544L, 34L)); expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 16L, 1L, 16L, 1L, 0L) - .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 4096L, 258L, 512L, 32L, 0L) - .addValues(TEST_IFACE, UID_GREEN, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L)); + .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L) + .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 16L, 1L, 16L, 1L, 0L) + .addEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 4096L, 258L, 512L, 32L, 0L) + .addEntry(TEST_IFACE, UID_GREEN, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L)); final Intent intent = new Intent(ACTION_UID_REMOVED); intent.putExtra(EXTRA_UID, UID_BLUE); mServiceContext.sendBroadcast(intent); @@ -536,8 +536,8 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { expectDefaultSettings(); expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)); + .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L) + .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)); mService.incrementOperationCount(UID_RED, 0xF00D, 5); forcePollAndWaitForIdle(); @@ -552,8 +552,8 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { states = new NetworkState[] {buildMobile4gState(TEST_IFACE2)}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)); + .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L) + .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)); mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states), new VpnInfo[0]); forcePollAndWaitForIdle(); @@ -564,10 +564,10 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { expectDefaultSettings(); expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L) - .addValues(TEST_IFACE2, UID_RED, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 0L) - .addValues(TEST_IFACE2, UID_RED, SET_DEFAULT, 0xFAAD, 512L, 4L, 256L, 2L, 0L)); + .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L) + .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L) + .addEntry(TEST_IFACE2, UID_RED, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 0L) + .addEntry(TEST_IFACE2, UID_RED, SET_DEFAULT, 0xFAAD, 512L, 4L, 256L, 2L, 0L)); mService.incrementOperationCount(UID_RED, 0xFAAD, 5); forcePollAndWaitForIdle(); @@ -591,9 +591,9 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { expectDefaultSettings(); expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 50L, 5L, 50L, 5L, 0L) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 10L, 1L, 10L, 1L, 0L) - .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 1024L, 8L, 512L, 4L, 0L)); + .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 50L, 5L, 50L, 5L, 0L) + .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 10L, 1L, 10L, 1L, 0L) + .addEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 1024L, 8L, 512L, 4L, 0L)); mService.incrementOperationCount(UID_RED, 0xF00D, 1); forcePollAndWaitForIdle(); @@ -608,9 +608,9 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { expectDefaultSettings(); expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 50L, 5L, 50L, 5L, 0L) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 10L, 1L, 10L, 1L, 0L) - .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 2048L, 16L, 1024L, 8L, 0L)); + .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 50L, 5L, 50L, 5L, 0L) + .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 10L, 1L, 10L, 1L, 0L) + .addEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 2048L, 16L, 1024L, 8L, 0L)); forcePollAndWaitForIdle(); // first verify entire history present @@ -654,9 +654,9 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { expectDefaultSettings(); expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3) - .addValues(entry1) - .addValues(entry2) - .addValues(entry3)); + .addEntry(entry1) + .addEntry(entry2) + .addEntry(entry3)); mService.incrementOperationCount(UID_RED, 0xF00D, 1); NetworkStats stats = mService.getDetailedUidStats(INTERFACES_ALL); @@ -704,11 +704,11 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { .thenReturn(augmentedIfaceFilter); when(mStatsFactory.readNetworkStatsDetail(eq(UID_ALL), any(), eq(TAG_ALL))) .thenReturn(new NetworkStats(getElapsedRealtime(), 1) - .addValues(uidStats)); + .addEntry(uidStats)); when(mNetManager.getNetworkStatsTethering(STATS_PER_UID)) .thenReturn(new NetworkStats(getElapsedRealtime(), 2) - .addValues(tetheredStats1) - .addValues(tetheredStats2)); + .addEntry(tetheredStats1) + .addEntry(tetheredStats2)); NetworkStats stats = mService.getDetailedUidStats(ifaceFilter); @@ -745,8 +745,8 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { expectDefaultSettings(); expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 64L, 1L, 64L, 1L, 0L)); + .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L) + .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 64L, 1L, 64L, 1L, 0L)); mService.incrementOperationCount(UID_RED, 0xF00D, 1); forcePollAndWaitForIdle(); @@ -760,10 +760,10 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { expectDefaultSettings(); expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 64L, 1L, 64L, 1L, 0L) - .addValues(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE, 32L, 2L, 32L, 2L, 0L) - .addValues(TEST_IFACE, UID_RED, SET_FOREGROUND, 0xFAAD, 1L, 1L, 1L, 1L, 0L)); + .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L) + .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 64L, 1L, 64L, 1L, 0L) + .addEntry(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE, 32L, 2L, 32L, 2L, 0L) + .addEntry(TEST_IFACE, UID_RED, SET_FOREGROUND, 0xFAAD, 1L, 1L, 1L, 1L, 0L)); mService.setUidForeground(UID_RED, true); mService.incrementOperationCount(UID_RED, 0xFAAD, 1); @@ -804,9 +804,9 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { // and DEFAULT_NETWORK_YES, because these three properties aren't tracked at that layer. // We layer them on top by inspecting the iface properties. expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, + .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 128L, 2L, 128L, 2L, 0L) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, + .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 64L, 1L, 64L, 1L, 0L)); mService.incrementOperationCount(UID_RED, 0xF00D, 1); @@ -843,9 +843,9 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { // ROAMING_NO, because metered and roaming isn't tracked at that layer. We layer it // on top by inspecting the iface properties. expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_ALL, ROAMING_NO, + .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_ALL, ROAMING_NO, DEFAULT_NETWORK_YES, 128L, 2L, 128L, 2L, 0L) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, METERED_ALL, ROAMING_NO, + .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, METERED_ALL, ROAMING_NO, DEFAULT_NETWORK_YES, 64L, 1L, 64L, 1L, 0L)); forcePollAndWaitForIdle(); @@ -885,10 +885,10 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { // Traffic for UID_RED. final NetworkStats uidStats = new NetworkStats(getElapsedRealtime(), 1) - .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L); + .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L); // All tethering traffic, both hardware and software. final NetworkStats tetherStats = new NetworkStats(getElapsedRealtime(), 1) - .addValues(TEST_IFACE, UID_TETHERING, SET_DEFAULT, TAG_NONE, 1920L, 14L, 384L, 2L, + .addEntry(TEST_IFACE, UID_TETHERING, SET_DEFAULT, TAG_NONE, 1920L, 14L, 384L, 2L, 0L); expectNetworkStatsSummary(ifaceStats, tetherStatsHardware); diff --git a/tests/utils/testutils/Android.bp b/tests/utils/testutils/Android.bp index f71be7b0b7d3..a6625ab9c17f 100644 --- a/tests/utils/testutils/Android.bp +++ b/tests/utils/testutils/Android.bp @@ -22,6 +22,7 @@ java_library { static_libs: [ "junit", "hamcrest-library", + "androidx.test.runner", ], libs: [ diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MessageCapturingHandler.java b/tests/utils/testutils/java/com/android/server/accessibility/test/MessageCapturingHandler.java index e2b517f875db..bce2ab5c5a7f 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/MessageCapturingHandler.java +++ b/tests/utils/testutils/java/com/android/server/accessibility/test/MessageCapturingHandler.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.accessibility; +package com.android.server.accessibility.test; import android.os.Handler; import android.os.Looper; @@ -31,7 +31,7 @@ import java.util.List; * at their target. */ public class MessageCapturingHandler extends Handler { - List<Pair<Message, Long>> timedMessages = new ArrayList<>(); + public List<Pair<Message, Long>> timedMessages = new ArrayList<>(); Handler.Callback mCallback; diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp index c55765663204..74e2a0987c3f 100644 --- a/tools/aapt2/ResourceParser.cpp +++ b/tools/aapt2/ResourceParser.cpp @@ -1641,8 +1641,14 @@ bool ResourceParser::ParseDeclareStyleable(xml::XmlPullParser* parser, ParsedResource* out_resource) { out_resource->name.type = ResourceType::kStyleable; - // Declare-styleable is kPrivate by default, because it technically only exists in R.java. - out_resource->visibility_level = Visibility::Level::kPublic; + if (!options_.preserve_visibility_of_styleables) { + // This was added in change Idd21b5de4d20be06c6f8c8eb5a22ccd68afc4927 to mimic aapt1, but no one + // knows exactly what for. + // + // FWIW, styleables only appear in generated R classes. For custom views these should always be + // package-private (to be used only by the view class); themes are a different story. + out_resource->visibility_level = Visibility::Level::kPublic; + } // Declare-styleable only ends up in default config; if (out_resource->config != ConfigDescription::DefaultConfig()) { diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h index 06bb0c9cf264..9d3ecc866c5d 100644 --- a/tools/aapt2/ResourceParser.h +++ b/tools/aapt2/ResourceParser.h @@ -46,6 +46,12 @@ struct ResourceParserOptions { */ bool error_on_positional_arguments = true; + /** + * If true, apply the same visibility rules for styleables as are used for + * all other resources. Otherwise, all styleables will be made public. + */ + bool preserve_visibility_of_styleables = false; + // If visibility was forced, we need to use it when creating a new resource and also error if we // try to parse the <public>, <public-group>, <java-symbol> or <symbol> tags. Maybe<Visibility::Level> visibility; diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp index 42374690d135..24531bc16445 100644 --- a/tools/aapt2/ResourceParser_test.cpp +++ b/tools/aapt2/ResourceParser_test.cpp @@ -614,6 +614,32 @@ TEST_F(ResourceParserTest, ParseAttributesDeclareStyleable) { EXPECT_THAT(styleable->entries[2].name, Eq(make_value(test::ParseNameOrDie("attr/baz")))); } +TEST_F(ResourceParserTest, ParseDeclareStyleablePreservingVisibility) { + StringInputStream input(R"( + <resources> + <declare-styleable name="foo"> + <attr name="myattr" /> + </declare-styleable> + <declare-styleable name="bar"> + <attr name="myattr" /> + </declare-styleable> + <public type="styleable" name="bar" /> + </resources>)"); + ResourceParser parser(context_->GetDiagnostics(), &table_, Source{"test"}, + ConfigDescription::DefaultConfig(), + ResourceParserOptions{.preserve_visibility_of_styleables = true}); + + xml::XmlPullParser xml_parser(&input); + ASSERT_TRUE(parser.Parse(&xml_parser)); + + EXPECT_EQ( + table_.FindResource(test::ParseNameOrDie("styleable/foo")).value().entry->visibility.level, + Visibility::Level::kUndefined); + EXPECT_EQ( + table_.FindResource(test::ParseNameOrDie("styleable/bar")).value().entry->visibility.level, + Visibility::Level::kPublic); +} + TEST_F(ResourceParserTest, ParsePrivateAttributesDeclareStyleable) { std::string input = R"( <declare-styleable xmlns:privAndroid="http://schemas.android.com/apk/prv/res/android" diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp index d50b1de335fb..32686538c10d 100644 --- a/tools/aapt2/cmd/Compile.cpp +++ b/tools/aapt2/cmd/Compile.cpp @@ -159,6 +159,7 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options, ResourceParserOptions parser_options; parser_options.error_on_positional_arguments = !options.legacy_mode; + parser_options.preserve_visibility_of_styleables = options.preserve_visibility_of_styleables; parser_options.translatable = translatable_file; // If visibility was forced, we need to use it when creating a new resource and also error if diff --git a/tools/aapt2/cmd/Compile.h b/tools/aapt2/cmd/Compile.h index d3456b25da9a..1752a1adac24 100644 --- a/tools/aapt2/cmd/Compile.h +++ b/tools/aapt2/cmd/Compile.h @@ -35,6 +35,8 @@ struct CompileOptions { bool pseudolocalize = false; bool no_png_crunch = false; bool legacy_mode = false; + // See comments on aapt::ResourceParserOptions. + bool preserve_visibility_of_styleables = false; bool verbose = false; }; @@ -56,6 +58,11 @@ class CompileCommand : public Command { AddOptionalSwitch("--no-crunch", "Disables PNG processing", &options_.no_png_crunch); AddOptionalSwitch("--legacy", "Treat errors that used to be valid in AAPT as warnings", &options_.legacy_mode); + AddOptionalSwitch("--preserve-visibility-of-styleables", + "If specified, apply the same visibility rules for\n" + "styleables as are used for all other resources.\n" + "Otherwise, all stylesables will be made public.", + &options_.preserve_visibility_of_styleables); AddOptionalFlag("--visibility", "Sets the visibility of the compiled resources to the specified\n" "level. Accepted levels: public, private, default", &visibility_); diff --git a/tools/hiddenapi/generate_hiddenapi_lists.py b/tools/hiddenapi/generate_hiddenapi_lists.py index 46105f4d66b0..0b2077d9bba0 100755 --- a/tools/hiddenapi/generate_hiddenapi_lists.py +++ b/tools/hiddenapi/generate_hiddenapi_lists.py @@ -149,7 +149,12 @@ def extract_package(signature): The package name of the class containing the field/method. """ full_class_name = signature.split(";->")[0] - package_name = full_class_name[1:full_class_name.rindex("/")] + # Example: Landroid/hardware/radio/V1_2/IRadio$Proxy + if (full_class_name[0] != "L"): + raise ValueError("Expected to start with 'L': %s" % full_class_name) + full_class_name = full_class_name[1:] + # If full_class_name doesn't contain '/', then package_name will be ''. + package_name = full_class_name.rpartition("/")[0] return package_name.replace('/', '.') class FlagsDict: diff --git a/tools/lock_agent/Android.bp b/tools/lock_agent/Android.bp index 79dce4a8ce09..7b2ca9a65242 100644 --- a/tools/lock_agent/Android.bp +++ b/tools/lock_agent/Android.bp @@ -25,6 +25,7 @@ cc_binary_host { srcs: ["agent.cpp"], static_libs: [ "libbase", + "liblog", "libz", "slicer", ], diff --git a/tools/stats_log_api_gen/java_writer.cpp b/tools/stats_log_api_gen/java_writer.cpp index 7f0872c02613..7fa47f696b50 100644 --- a/tools/stats_log_api_gen/java_writer.cpp +++ b/tools/stats_log_api_gen/java_writer.cpp @@ -85,8 +85,9 @@ static int write_java_methods( string indent(""); if (supportQ) { // TODO(b/146235828): Use just SDK_INT check once it is incremented from Q. - fprintf(out, " if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q ||\n"); - fprintf(out, " Build.VERSION.CODENAME.equals(\"R\")) {\n"); + fprintf(out, " if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q\n"); + fprintf(out, " || (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q\n"); + fprintf(out, " && Build.VERSION.PREVIEW_SDK_INT > 0)) {\n"); indent = " "; } diff --git a/wifi/Android.bp b/wifi/Android.bp index 3934af10eddd..180368cbd9f7 100644 --- a/wifi/Android.bp +++ b/wifi/Android.bp @@ -48,6 +48,7 @@ test_access_hidden_api_whitelist = [ "//frameworks/opt/net/wifi/tests/wifitests:__subpackages__", "//frameworks/opt/net/wifi/libs/WifiTrackerLib/tests", + "//external/robolectric-shadows:__subpackages__", ] java_library { @@ -66,10 +67,15 @@ java_library { optimize: { enabled: false }, + hostdex: true, // for hiddenapi check visibility: [ "//frameworks/base", // TODO(b/140299412) remove once all dependencies are fixed "//frameworks/opt/net/wifi/service:__subpackages__", ] + test_access_hidden_api_whitelist, + apex_available: [ + "com.android.wifi", + "test_com.android.wifi", + ], plugins: ["java_api_finder"], } @@ -105,8 +111,10 @@ java_defaults { name: "framework-wifi-test-defaults", sdk_version: "core_platform", // tests can use @CorePlatformApi's libs: [ + // order matters: classes in framework-wifi are resolved before framework, meaning + // @hide APIs in framework-wifi are resolved before @SystemApi stubs in framework "framework-wifi", - "framework-minus-apex", + "framework", // if sdk_version="" this gets automatically included, but here we need to add manually. "framework-res", diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl index 1678d5a4776b..f490766559de 100644 --- a/wifi/java/android/net/wifi/IWifiManager.aidl +++ b/wifi/java/android/net/wifi/IWifiManager.aidl @@ -90,6 +90,8 @@ interface IWifiManager void allowAutojoin(int netId, boolean choice); + void allowAutojoinPasspoint(String fqdn, boolean enableAutoJoin); + boolean startScan(String packageName, String featureId); List<ScanResult> getScanResults(String callingPackage, String callingFeatureId); @@ -248,4 +250,6 @@ interface IWifiManager void unregisterSuggestionConnectionStatusListener(int listenerIdentifier, String packageName); int calculateSignalLevel(int rssi); + + List<WifiConfiguration> getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(in List<ScanResult> scanResults); } diff --git a/wifi/java/android/net/wifi/SoftApCapability.java b/wifi/java/android/net/wifi/SoftApCapability.java index c4474e2bc9cc..2bbe7d2aa4ec 100644 --- a/wifi/java/android/net/wifi/SoftApCapability.java +++ b/wifi/java/android/net/wifi/SoftApCapability.java @@ -61,11 +61,20 @@ public final class SoftApCapability implements Parcelable { */ public static final int SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT = 1 << 1; + + /** + * Support for WPA3 Simultaneous Authentication of Equals (WPA3-SAE). + * + * flag when {@link config_wifi_softap_sae_supported)} is true. + */ + public static final int SOFTAP_FEATURE_WPA3_SAE = 1 << 2; + /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, prefix = { "SOFTAP_FEATURE_" }, value = { SOFTAP_FEATURE_ACS_OFFLOAD, SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT, + SOFTAP_FEATURE_WPA3_SAE, }) public @interface HotspotFeatures {} diff --git a/wifi/java/android/net/wifi/SoftApConfiguration.java b/wifi/java/android/net/wifi/SoftApConfiguration.java index 05e245b8eabc..2b7f8af728d7 100644 --- a/wifi/java/android/net/wifi/SoftApConfiguration.java +++ b/wifi/java/android/net/wifi/SoftApConfiguration.java @@ -25,6 +25,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; @@ -55,6 +56,11 @@ import java.util.concurrent.Executor; @SystemApi public final class SoftApConfiguration implements Parcelable { + @VisibleForTesting + static final int PSK_MIN_LEN = 8; + @VisibleForTesting + static final int PSK_MAX_LEN = 63; + /** * 2GHz band. * @hide @@ -142,9 +148,10 @@ public final class SoftApConfiguration implements Parcelable { private final @Nullable MacAddress mBssid; /** - * Pre-shared key for WPA2-PSK encryption (non-null enables WPA2-PSK). + * Pre-shared key for WPA2-PSK or WPA3-SAE-Transition or WPA3-SAE encryption which depends on + * the security type. */ - private final @Nullable String mWpa2Passphrase; + private final @Nullable String mPassphrase; /** * This is a network that does not broadcast its SSID, so an @@ -175,6 +182,12 @@ public final class SoftApConfiguration implements Parcelable { private final @SecurityType int mSecurityType; /** + * Delay in milliseconds before shutting down soft AP when + * there are no connected devices. + */ + private final int mShutdownTimeoutMillis; + + /** * Security types we support. */ /** @hide */ @@ -186,25 +199,36 @@ public final class SoftApConfiguration implements Parcelable { public static final int SECURITY_TYPE_WPA2_PSK = 1; /** @hide */ + @SystemApi + public static final int SECURITY_TYPE_WPA3_SAE_TRANSITION = 2; + + /** @hide */ + @SystemApi + public static final int SECURITY_TYPE_WPA3_SAE = 3; + + /** @hide */ @Retention(RetentionPolicy.SOURCE) - @IntDef(prefix = { "SECURITY_TYPE" }, value = { + @IntDef(prefix = { "SECURITY_TYPE_" }, value = { SECURITY_TYPE_OPEN, SECURITY_TYPE_WPA2_PSK, + SECURITY_TYPE_WPA3_SAE_TRANSITION, + SECURITY_TYPE_WPA3_SAE, }) public @interface SecurityType {} /** Private constructor for Builder and Parcelable implementation. */ private SoftApConfiguration(@Nullable String ssid, @Nullable MacAddress bssid, - @Nullable String wpa2Passphrase, boolean hiddenSsid, @BandType int band, int channel, - @SecurityType int securityType, int maxNumberOfClients) { + @Nullable String passphrase, boolean hiddenSsid, @BandType int band, int channel, + @SecurityType int securityType, int maxNumberOfClients, int shutdownTimeoutMillis) { mSsid = ssid; mBssid = bssid; - mWpa2Passphrase = wpa2Passphrase; + mPassphrase = passphrase; mHiddenSsid = hiddenSsid; mBand = band; mChannel = channel; mSecurityType = securityType; mMaxNumberOfClients = maxNumberOfClients; + mShutdownTimeoutMillis = shutdownTimeoutMillis; } @Override @@ -218,18 +242,19 @@ public final class SoftApConfiguration implements Parcelable { SoftApConfiguration other = (SoftApConfiguration) otherObj; return Objects.equals(mSsid, other.mSsid) && Objects.equals(mBssid, other.mBssid) - && Objects.equals(mWpa2Passphrase, other.mWpa2Passphrase) + && Objects.equals(mPassphrase, other.mPassphrase) && mHiddenSsid == other.mHiddenSsid && mBand == other.mBand && mChannel == other.mChannel && mSecurityType == other.mSecurityType - && mMaxNumberOfClients == other.mMaxNumberOfClients; + && mMaxNumberOfClients == other.mMaxNumberOfClients + && mShutdownTimeoutMillis == other.mShutdownTimeoutMillis; } @Override public int hashCode() { - return Objects.hash(mSsid, mBssid, mWpa2Passphrase, mHiddenSsid, - mBand, mChannel, mSecurityType, mMaxNumberOfClients); + return Objects.hash(mSsid, mBssid, mPassphrase, mHiddenSsid, + mBand, mChannel, mSecurityType, mMaxNumberOfClients, mShutdownTimeoutMillis); } @Override @@ -237,13 +262,14 @@ public final class SoftApConfiguration implements Parcelable { StringBuilder sbuf = new StringBuilder(); sbuf.append("ssid=").append(mSsid); if (mBssid != null) sbuf.append(" \n bssid=").append(mBssid.toString()); - sbuf.append(" \n Wpa2Passphrase =").append( - TextUtils.isEmpty(mWpa2Passphrase) ? "<empty>" : "<non-empty>"); + sbuf.append(" \n Passphrase =").append( + TextUtils.isEmpty(mPassphrase) ? "<empty>" : "<non-empty>"); sbuf.append(" \n HiddenSsid =").append(mHiddenSsid); sbuf.append(" \n Band =").append(mBand); sbuf.append(" \n Channel =").append(mChannel); sbuf.append(" \n SecurityType=").append(getSecurityType()); sbuf.append(" \n MaxClient=").append(mMaxNumberOfClients); + sbuf.append(" \n ShutdownTimeoutMillis=").append(mShutdownTimeoutMillis); return sbuf.toString(); } @@ -251,12 +277,13 @@ public final class SoftApConfiguration implements Parcelable { public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeString(mSsid); dest.writeParcelable(mBssid, flags); - dest.writeString(mWpa2Passphrase); + dest.writeString(mPassphrase); dest.writeBoolean(mHiddenSsid); dest.writeInt(mBand); dest.writeInt(mChannel); dest.writeInt(mSecurityType); dest.writeInt(mMaxNumberOfClients); + dest.writeInt(mShutdownTimeoutMillis); } @Override @@ -272,7 +299,7 @@ public final class SoftApConfiguration implements Parcelable { in.readString(), in.readParcelable(MacAddress.class.getClassLoader()), in.readString(), in.readBoolean(), in.readInt(), in.readInt(), in.readInt(), - in.readInt()); + in.readInt(), in.readInt()); } @Override @@ -300,12 +327,12 @@ public final class SoftApConfiguration implements Parcelable { } /** - * Returns String set to be passphrase for the WPA2-PSK AP. - * {@link Builder#setWpa2Passphrase(String)}. + * Returns String set to be passphrase for current AP. + * {@link #setPassphrase(String, @SecurityType int)}. */ @Nullable - public String getWpa2Passphrase() { - return mWpa2Passphrase; + public String getPassphrase() { + return mPassphrase; } /** @@ -351,6 +378,15 @@ public final class SoftApConfiguration implements Parcelable { } /** + * Returns the shutdown timeout in milliseconds. + * The Soft AP will shutdown when there are no devices associated to it for + * the timeout duration. See {@link Builder#setShutdownTimeoutMillis(int)}. + */ + public int getShutdownTimeoutMillis() { + return mShutdownTimeoutMillis; + } + + /** * Builds a {@link SoftApConfiguration}, which allows an app to configure various aspects of a * Soft AP. * @@ -360,23 +396,13 @@ public final class SoftApConfiguration implements Parcelable { public static final class Builder { private String mSsid; private MacAddress mBssid; - private String mWpa2Passphrase; + private String mPassphrase; private boolean mHiddenSsid; private int mBand; private int mChannel; private int mMaxNumberOfClients; - - private int setSecurityType() { - int securityType = SECURITY_TYPE_OPEN; - if (!TextUtils.isEmpty(mWpa2Passphrase)) { // WPA2-PSK network. - securityType = SECURITY_TYPE_WPA2_PSK; - } - return securityType; - } - - private void clearAllPassphrase() { - mWpa2Passphrase = null; - } + private int mSecurityType; + private int mShutdownTimeoutMillis; /** * Constructs a Builder with default values (see {@link Builder}). @@ -384,11 +410,13 @@ public final class SoftApConfiguration implements Parcelable { public Builder() { mSsid = null; mBssid = null; - mWpa2Passphrase = null; + mPassphrase = null; mHiddenSsid = false; mBand = BAND_2GHZ; mChannel = 0; mMaxNumberOfClients = 0; + mSecurityType = SECURITY_TYPE_OPEN; + mShutdownTimeoutMillis = 0; } /** @@ -399,11 +427,13 @@ public final class SoftApConfiguration implements Parcelable { mSsid = other.mSsid; mBssid = other.mBssid; - mWpa2Passphrase = other.mWpa2Passphrase; + mPassphrase = other.mPassphrase; mHiddenSsid = other.mHiddenSsid; mBand = other.mBand; mChannel = other.mChannel; mMaxNumberOfClients = other.mMaxNumberOfClients; + mSecurityType = other.mSecurityType; + mShutdownTimeoutMillis = other.mShutdownTimeoutMillis; } /** @@ -413,8 +443,9 @@ public final class SoftApConfiguration implements Parcelable { */ @NonNull public SoftApConfiguration build() { - return new SoftApConfiguration(mSsid, mBssid, mWpa2Passphrase, - mHiddenSsid, mBand, mChannel, setSecurityType(), mMaxNumberOfClients); + return new SoftApConfiguration(mSsid, mBssid, mPassphrase, + mHiddenSsid, mBand, mChannel, mSecurityType, mMaxNumberOfClients, + mShutdownTimeoutMillis); } /** @@ -462,26 +493,43 @@ public final class SoftApConfiguration implements Parcelable { } /** - * Specifies that this AP should use WPA2-PSK with the given ASCII WPA2 passphrase. - * When set to null, an open network is created. - * <p> + * Specifies that this AP should use specific security type with the given ASCII passphrase. + * + * @param securityType one of the security types from {@link @SecurityType}. + * @param passphrase The passphrase to use for sepcific {@link @SecurityType} configuration + * or null with {@link @SecurityType#SECURITY_TYPE_OPEN}. * - * @param passphrase The passphrase to use, or null to unset a previously-set WPA2-PSK - * configuration. * @return Builder for chaining. - * @throws IllegalArgumentException when the passphrase is the empty string + * @throws IllegalArgumentException when the passphrase length is invalid and + * {@code securityType} is not {@link @SecurityType#SECURITY_TYPE_OPEN} + * or non-null passphrase and {@code securityType} is + * {@link @SecurityType#SECURITY_TYPE_OPEN}. */ @NonNull - public Builder setWpa2Passphrase(@Nullable String passphrase) { - if (passphrase != null) { + public Builder setPassphrase(@Nullable String passphrase, @SecurityType int securityType) { + if (securityType == SECURITY_TYPE_OPEN) { + if (passphrase != null) { + throw new IllegalArgumentException( + "passphrase should be null when security type is open"); + } + } else { + Preconditions.checkStringNotEmpty(passphrase); final CharsetEncoder asciiEncoder = StandardCharsets.US_ASCII.newEncoder(); if (!asciiEncoder.canEncode(passphrase)) { throw new IllegalArgumentException("passphrase not ASCII encodable"); } - Preconditions.checkStringNotEmpty(passphrase); + if (securityType == SECURITY_TYPE_WPA2_PSK + || securityType == SECURITY_TYPE_WPA3_SAE_TRANSITION) { + if (passphrase.length() < PSK_MIN_LEN || passphrase.length() > PSK_MAX_LEN) { + throw new IllegalArgumentException( + "Password size must be at least " + PSK_MIN_LEN + + " and no more than " + PSK_MAX_LEN + + " for WPA2_PSK and WPA3_SAE_TRANSITION Mode"); + } + } } - clearAllPassphrase(); - mWpa2Passphrase = passphrase; + mSecurityType = securityType; + mPassphrase = passphrase; return this; } @@ -589,5 +637,30 @@ public final class SoftApConfiguration implements Parcelable { mMaxNumberOfClients = maxNumberOfClients; return this; } + + /** + * Specifies the shutdown timeout in milliseconds. + * The Soft AP will shut down when there are no devices connected to it for + * the timeout duration. + * + * Specify a value of 0 to have the framework automatically use default timeout + * setting which defined in {@link R.integer.config_wifi_framework_soft_ap_timeout_delay} + * + * <p> + * <li>If not set, defaults to 0</li> + * <li>The shut down timout will apply when + * {@link Settings.Global.SOFT_AP_TIMEOUT_ENABLED} is true</li> + * + * @param timeoutMillis milliseconds of the timeout delay. + * @return Builder for chaining. + */ + @NonNull + public Builder setShutdownTimeoutMillis(int timeoutMillis) { + if (timeoutMillis < 0) { + throw new IllegalArgumentException("Invalid timeout value"); + } + mShutdownTimeoutMillis = timeoutMillis; + return this; + } } } diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java index a379c75360ac..f4c5b9168cd0 100644 --- a/wifi/java/android/net/wifi/WifiConfiguration.java +++ b/wifi/java/android/net/wifi/WifiConfiguration.java @@ -253,9 +253,12 @@ public class WifiConfiguration implements Parcelable { /** LEAP/Network EAP (only used with LEAP) */ public static final int LEAP = 2; + /** SAE (Used only for WPA3-Personal) */ + public static final int SAE = 3; + public static final String varName = "auth_alg"; - public static final String[] strings = { "OPEN", "SHARED", "LEAP" }; + public static final String[] strings = { "OPEN", "SHARED", "LEAP", "SAE" }; } /** @@ -468,10 +471,13 @@ public class WifiConfiguration implements Parcelable { break; case SECURITY_TYPE_SAE: allowedKeyManagement.set(WifiConfiguration.KeyMgmt.SAE); + allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP); + allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP); requirePMF = true; break; case SECURITY_TYPE_EAP_SUITE_B: allowedKeyManagement.set(WifiConfiguration.KeyMgmt.SUITE_B_192); + allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.GCMP_256); allowedGroupCiphers.set(WifiConfiguration.GroupCipher.GCMP_256); allowedGroupManagementCiphers.set(WifiConfiguration.GroupMgmtCipher.BIP_GMAC_256); // Note: allowedSuiteBCiphers bitset will be set by the service once the @@ -480,6 +486,8 @@ public class WifiConfiguration implements Parcelable { break; case SECURITY_TYPE_OWE: allowedKeyManagement.set(WifiConfiguration.KeyMgmt.OWE); + allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP); + allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP); requirePMF = true; break; case SECURITY_TYPE_WAPI_PSK: diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java index 148e8a410aac..41f7c6e2bb0d 100644 --- a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java +++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java @@ -1336,20 +1336,18 @@ public class WifiEnterpriseConfig implements Parcelable { } /** - * If the current authentication method needs SIM card. - * @return true if the credential information require SIM card for current authentication + * Utility method to determine whether the configuration's authentication method is SIM-based. + * + * @return true if the credential information requires SIM card for current authentication * method, otherwise it returns false. - * @hide */ - public boolean requireSimCredential() { + public boolean isAuthenticationSimBased() { if (mEapMethod == Eap.SIM || mEapMethod == Eap.AKA || mEapMethod == Eap.AKA_PRIME) { return true; } if (mEapMethod == Eap.PEAP) { - if (mPhase2Method == Phase2.SIM || mPhase2Method == Phase2.AKA - || mPhase2Method == Phase2.AKA_PRIME) { - return true; - } + return mPhase2Method == Phase2.SIM || mPhase2Method == Phase2.AKA + || mPhase2Method == Phase2.AKA_PRIME; } return false; } diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index 378c67ba4fcb..1baab12b8ab5 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -545,15 +545,22 @@ public class WifiManager { public static final String EXTRA_WIFI_AP_STATE = "wifi_state"; /** - * The look up key for an int that indicates why softAP started failed - * currently support general and no_channel - * @see #SAP_START_FAILURE_GENERAL - * @see #SAP_START_FAILURE_NO_CHANNEL - * @see #SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION + * An extra containing the int error code for Soft AP start failure. + * Can be obtained from the {@link #WIFI_AP_STATE_CHANGED_ACTION} using + * {@link android.content.Intent#getIntExtra}. + * This extra will only be attached if {@link #EXTRA_WIFI_AP_STATE} is + * attached and is equal to {@link #WIFI_AP_STATE_FAILED}. + * + * The error code will be one of: + * {@link #SAP_START_FAILURE_GENERAL}, + * {@link #SAP_START_FAILURE_NO_CHANNEL}, + * {@link #SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION} * * @hide */ - public static final String EXTRA_WIFI_AP_FAILURE_REASON = "wifi_ap_error_code"; + @SystemApi + public static final String EXTRA_WIFI_AP_FAILURE_REASON = + "android.net.wifi.extra.WIFI_AP_FAILURE_REASON"; /** * The previous Wi-Fi state. * @@ -1359,6 +1366,36 @@ public class WifiManager { } /** + * Retrieve a list of {@link WifiConfiguration} for available {@link WifiNetworkSuggestion} + * matching the given list of {@link ScanResult}. + * + * An available {@link WifiNetworkSuggestion} must satisfy: + * <ul> + * <li> Matching one of the {@link ScanResult} from the given list. + * <li> and {@link WifiNetworkSuggestion.Builder#setIsUserAllowedToManuallyConnect(boolean)} set + * to true. + * </ul> + * + * @param scanResults a list of scanResult. + * @return a list of @link WifiConfiguration} for available {@link WifiNetworkSuggestion} + * @hide + */ + @SystemApi + @RequiresPermission(anyOf = { + android.Manifest.permission.NETWORK_SETTINGS, + android.Manifest.permission.NETWORK_SETUP_WIZARD + }) + @NonNull + public List<WifiConfiguration> getWifiConfigForMatchedNetworkSuggestionsSharedWithUser( + @NonNull List<ScanResult> scanResults) { + try { + return mService.getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(scanResults); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** * Returns a list of unique Hotspot 2.0 OSU (Online Sign-Up) providers associated with a given * list of ScanResult. * @@ -4103,6 +4140,23 @@ public class WifiManager { } /** + * Configure auto-join settings for a Passpoint profile. + * + * @param fqdn the FQDN (fully qualified domain name) of the passpoint profile. + * @param enableAutoJoin true to enable autojoin, false to disable autojoin. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) + public void allowAutojoinPasspoint(@NonNull String fqdn, boolean enableAutoJoin) { + try { + mService.allowAutojoinPasspoint(fqdn, enableAutoJoin); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Disable an ephemeral network. * * @param ssid in the format of WifiConfiguration's SSID. diff --git a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java index 5befb54ce50a..1822e84fdd57 100644 --- a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java +++ b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java @@ -17,6 +17,7 @@ package android.net.wifi.hotspot2; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.net.wifi.hotspot2.pps.Credential; import android.net.wifi.hotspot2.pps.HomeSp; import android.net.wifi.hotspot2.pps.Policy; @@ -423,6 +424,41 @@ public final class PasspointConfiguration implements Parcelable { } /** + * The auto-join configuration specifies whether or not the Passpoint Configuration is + * considered for auto-connection. If true then yes, if false then it isn't considered as part + * of auto-connection - but can still be manually connected to. + */ + private boolean mIsAutoJoinEnabled = true; + + /** + * Configures the auto-association status of this Passpoint configuration. A value of true + * indicates that the configuration will be considered for auto-connection, a value of false + * indicates that only manual connection will work - the framework will not auto-associate to + * this Passpoint network. + * + * @param autoJoinEnabled true to be considered for framework auto-connection, false otherwise. + * @hide + */ + public void setAutoJoinEnabled(boolean autoJoinEnabled) { + mIsAutoJoinEnabled = autoJoinEnabled; + } + + /** + * Indicates whether the Passpoint configuration may be auto-connected to by the framework. A + * value of true indicates that auto-connection can happen, a value of false indicates that it + * cannot. However, even when auto-connection is not possible manual connection by the user is + * possible. + * + * @return the auto-join configuration: true for auto-connection (or join) enabled, false + * otherwise. + * @hide + */ + @SystemApi + public boolean isAutoJoinEnabled() { + return mIsAutoJoinEnabled; + } + + /** * Constructor for creating PasspointConfiguration with default values. */ public PasspointConfiguration() {} @@ -464,6 +500,7 @@ public final class PasspointConfiguration implements Parcelable { mServiceFriendlyNames = source.mServiceFriendlyNames; mAaaServerTrustedNames = source.mAaaServerTrustedNames; mCarrierId = source.mCarrierId; + mIsAutoJoinEnabled = source.mIsAutoJoinEnabled; } @Override @@ -493,6 +530,7 @@ public final class PasspointConfiguration implements Parcelable { (HashMap<String, String>) mServiceFriendlyNames); dest.writeBundle(bundle); dest.writeInt(mCarrierId); + dest.writeBoolean(mIsAutoJoinEnabled); } @Override @@ -523,6 +561,7 @@ public final class PasspointConfiguration implements Parcelable { && mUsageLimitDataLimit == that.mUsageLimitDataLimit && mUsageLimitTimeLimitInMinutes == that.mUsageLimitTimeLimitInMinutes && mCarrierId == that.mCarrierId + && mIsAutoJoinEnabled == that.mIsAutoJoinEnabled && (mServiceFriendlyNames == null ? that.mServiceFriendlyNames == null : mServiceFriendlyNames.equals(that.mServiceFriendlyNames)); } @@ -533,7 +572,7 @@ public final class PasspointConfiguration implements Parcelable { mUpdateIdentifier, mCredentialPriority, mSubscriptionCreationTimeInMillis, mSubscriptionExpirationTimeInMillis, mUsageLimitUsageTimePeriodInMinutes, mUsageLimitStartTimeInMillis, mUsageLimitDataLimit, mUsageLimitTimeLimitInMinutes, - mServiceFriendlyNames, mCarrierId); + mServiceFriendlyNames, mCarrierId, mIsAutoJoinEnabled); } @Override @@ -587,6 +626,7 @@ public final class PasspointConfiguration implements Parcelable { builder.append("ServiceFriendlyNames: ").append(mServiceFriendlyNames); } builder.append("CarrierId:" + mCarrierId); + builder.append("IsAutoJoinEnabled:" + mIsAutoJoinEnabled); return builder.toString(); } @@ -692,6 +732,7 @@ public final class PasspointConfiguration implements Parcelable { "serviceFriendlyNames"); config.setServiceFriendlyNames(friendlyNamesMap); config.mCarrierId = in.readInt(); + config.mIsAutoJoinEnabled = in.readBoolean(); return config; } diff --git a/wifi/java/com/android/server/wifi/BaseWifiService.java b/wifi/java/com/android/server/wifi/BaseWifiService.java index d58083c92df6..3c13562d6952 100644 --- a/wifi/java/com/android/server/wifi/BaseWifiService.java +++ b/wifi/java/com/android/server/wifi/BaseWifiService.java @@ -182,6 +182,11 @@ public class BaseWifiService extends IWifiManager.Stub { } @Override + public void allowAutojoinPasspoint(String fqdn, boolean enableAutoJoin) { + throw new UnsupportedOperationException(); + } + + @Override public boolean startScan(String packageName, String featureId) { throw new UnsupportedOperationException(); } @@ -584,4 +589,10 @@ public class BaseWifiService extends IWifiManager.Stub { public int calculateSignalLevel(int rssi) { throw new UnsupportedOperationException(); } + + @Override + public List<WifiConfiguration> getWifiConfigForMatchedNetworkSuggestionsSharedWithUser( + List<ScanResult> scanResults) { + throw new UnsupportedOperationException(); + } } diff --git a/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java index 1f601036a718..eeea7e2a6cd8 100644 --- a/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java +++ b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java @@ -25,8 +25,12 @@ import androidx.test.filters.SmallTest; import org.junit.Test; +import java.util.Random; + @SmallTest public class SoftApConfigurationTest { + private static final String TEST_CHAR_SET_AS_STRING = "abcdefghijklmnopqrstuvwxyz0123456789"; + private SoftApConfiguration parcelUnparcel(SoftApConfiguration configIn) { Parcel parcel = Parcel.obtain(); parcel.writeParcelable(configIn, 0); @@ -37,6 +41,25 @@ public class SoftApConfigurationTest { return configOut; } + /** + * Helper method to generate random string. + * + * Note: this method has limited use as a random string generator. + * The characters used in this method do no not cover all valid inputs. + * @param length number of characters to generate for the string + * @return String generated string of random characters + */ + private String generateRandomString(int length) { + Random random = new Random(); + StringBuilder stringBuilder = new StringBuilder(length); + int index = -1; + while (stringBuilder.length() < length) { + index = random.nextInt(TEST_CHAR_SET_AS_STRING.length()); + stringBuilder.append(TEST_CHAR_SET_AS_STRING.charAt(index)); + } + return stringBuilder.toString(); + } + @Test public void testBasicSettings() { SoftApConfiguration original = new SoftApConfiguration.Builder() @@ -45,7 +68,7 @@ public class SoftApConfigurationTest { .build(); assertThat(original.getSsid()).isEqualTo("ssid"); assertThat(original.getBssid()).isEqualTo(MacAddress.fromString("11:22:33:44:55:66")); - assertThat(original.getWpa2Passphrase()).isNull(); + assertThat(original.getPassphrase()).isNull(); assertThat(original.getSecurityType()).isEqualTo(SoftApConfiguration.SECURITY_TYPE_OPEN); assertThat(original.getBand()).isEqualTo(SoftApConfiguration.BAND_2GHZ); assertThat(original.getChannel()).isEqualTo(0); @@ -66,9 +89,9 @@ public class SoftApConfigurationTest { @Test public void testWpa2() { SoftApConfiguration original = new SoftApConfiguration.Builder() - .setWpa2Passphrase("secretsecret") + .setPassphrase("secretsecret", SoftApConfiguration.SECURITY_TYPE_WPA2_PSK) .build(); - assertThat(original.getWpa2Passphrase()).isEqualTo("secretsecret"); + assertThat(original.getPassphrase()).isEqualTo("secretsecret"); assertThat(original.getSecurityType()).isEqualTo( SoftApConfiguration.SECURITY_TYPE_WPA2_PSK); assertThat(original.getBand()).isEqualTo(SoftApConfiguration.BAND_2GHZ); @@ -90,19 +113,73 @@ public class SoftApConfigurationTest { @Test public void testWpa2WithAllFieldCustomized() { SoftApConfiguration original = new SoftApConfiguration.Builder() - .setWpa2Passphrase("secretsecret") - .setBand(SoftApConfiguration.BAND_ANY) + .setPassphrase("secretsecret", SoftApConfiguration.SECURITY_TYPE_WPA2_PSK) .setChannel(149, SoftApConfiguration.BAND_5GHZ) .setHiddenSsid(true) .setMaxNumberOfClients(10) + .setShutdownTimeoutMillis(500000) .build(); - assertThat(original.getWpa2Passphrase()).isEqualTo("secretsecret"); + assertThat(original.getPassphrase()).isEqualTo("secretsecret"); assertThat(original.getSecurityType()).isEqualTo( SoftApConfiguration.SECURITY_TYPE_WPA2_PSK); assertThat(original.getBand()).isEqualTo(SoftApConfiguration.BAND_5GHZ); assertThat(original.getChannel()).isEqualTo(149); assertThat(original.isHiddenSsid()).isEqualTo(true); assertThat(original.getMaxNumberOfClients()).isEqualTo(10); + assertThat(original.getShutdownTimeoutMillis()).isEqualTo(500000); + + SoftApConfiguration unparceled = parcelUnparcel(original); + assertThat(unparceled).isNotSameAs(original); + assertThat(unparceled).isEqualTo(original); + assertThat(unparceled.hashCode()).isEqualTo(original.hashCode()); + + SoftApConfiguration copy = new SoftApConfiguration.Builder(original).build(); + assertThat(copy).isNotSameAs(original); + assertThat(copy).isEqualTo(original); + assertThat(copy.hashCode()).isEqualTo(original.hashCode()); + } + + @Test + public void testWpa3Sae() { + SoftApConfiguration original = new SoftApConfiguration.Builder() + .setPassphrase("secretsecret", SoftApConfiguration.SECURITY_TYPE_WPA3_SAE) + .setChannel(149, SoftApConfiguration.BAND_5GHZ) + .setHiddenSsid(true) + .build(); + assertThat(original.getPassphrase()).isEqualTo("secretsecret"); + assertThat(original.getSecurityType()).isEqualTo( + SoftApConfiguration.SECURITY_TYPE_WPA3_SAE); + assertThat(original.getBand()).isEqualTo(SoftApConfiguration.BAND_5GHZ); + assertThat(original.getChannel()).isEqualTo(149); + assertThat(original.isHiddenSsid()).isEqualTo(true); + + + SoftApConfiguration unparceled = parcelUnparcel(original); + assertThat(unparceled).isNotSameAs(original); + assertThat(unparceled).isEqualTo(original); + assertThat(unparceled.hashCode()).isEqualTo(original.hashCode()); + + SoftApConfiguration copy = new SoftApConfiguration.Builder(original).build(); + assertThat(copy).isNotSameAs(original); + assertThat(copy).isEqualTo(original); + assertThat(copy.hashCode()).isEqualTo(original.hashCode()); + } + + @Test + public void testWpa3SaeTransition() { + SoftApConfiguration original = new SoftApConfiguration.Builder() + .setPassphrase("secretsecret", + SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION) + .setChannel(149, SoftApConfiguration.BAND_5GHZ) + .setHiddenSsid(true) + .build(); + assertThat(original.getSecurityType()).isEqualTo( + SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION); + assertThat(original.getPassphrase()).isEqualTo("secretsecret"); + assertThat(original.getBand()).isEqualTo(SoftApConfiguration.BAND_5GHZ); + assertThat(original.getChannel()).isEqualTo(149); + assertThat(original.isHiddenSsid()).isEqualTo(true); + SoftApConfiguration unparceled = parcelUnparcel(original); assertThat(unparceled).isNotSameAs(original); @@ -114,4 +191,51 @@ public class SoftApConfigurationTest { assertThat(copy).isEqualTo(original); assertThat(copy.hashCode()).isEqualTo(original.hashCode()); } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidShortPasswordLengthForWpa2() { + SoftApConfiguration original = new SoftApConfiguration.Builder() + .setPassphrase(generateRandomString(SoftApConfiguration.PSK_MIN_LEN - 1), + SoftApConfiguration.SECURITY_TYPE_WPA2_PSK) + .setChannel(149, SoftApConfiguration.BAND_5GHZ) + .setHiddenSsid(true) + .build(); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidLongPasswordLengthForWpa2() { + SoftApConfiguration original = new SoftApConfiguration.Builder() + .setPassphrase(generateRandomString(SoftApConfiguration.PSK_MAX_LEN + 1), + SoftApConfiguration.SECURITY_TYPE_WPA2_PSK) + .setChannel(149, SoftApConfiguration.BAND_5GHZ) + .setHiddenSsid(true) + .build(); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidShortPasswordLengthForWpa3SaeTransition() { + SoftApConfiguration original = new SoftApConfiguration.Builder() + .setPassphrase(generateRandomString(SoftApConfiguration.PSK_MIN_LEN - 1), + SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION) + .setChannel(149, SoftApConfiguration.BAND_5GHZ) + .setHiddenSsid(true) + .build(); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidLongPasswordLengthForWpa3SaeTransition() { + SoftApConfiguration original = new SoftApConfiguration.Builder() + .setPassphrase(generateRandomString(SoftApConfiguration.PSK_MAX_LEN + 1), + SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION) + .setChannel(149, SoftApConfiguration.BAND_5GHZ) + .setHiddenSsid(true) + .build(); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalieShutdownTimeoutMillis() { + SoftApConfiguration original = new SoftApConfiguration.Builder() + .setShutdownTimeoutMillis(-1) + .build(); + } } diff --git a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java index 909cfefba941..8689a38c6b17 100644 --- a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java +++ b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java @@ -16,6 +16,10 @@ package android.net.wifi; +import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_EAP_SUITE_B; +import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_OWE; +import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_SAE; + import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -328,4 +332,57 @@ public class WifiConfigurationTest { assertNotNull(NetworkSelectionStatus.DISABLE_REASON_INFOS.get(i)); } } + + /** + * Ensure that {@link WifiConfiguration#setSecurityParams(int)} sets up the + * {@link WifiConfiguration} object correctly for SAE security type. + * @throws Exception + */ + @Test + public void testSetSecurityParamsForSae() throws Exception { + WifiConfiguration config = new WifiConfiguration(); + + config.setSecurityParams(SECURITY_TYPE_SAE); + + assertTrue(config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SAE)); + assertTrue(config.allowedPairwiseCiphers.get(WifiConfiguration.PairwiseCipher.CCMP)); + assertTrue(config.allowedGroupCiphers.get(WifiConfiguration.GroupCipher.CCMP)); + assertTrue(config.requirePMF); + } + + /** + * Ensure that {@link WifiConfiguration#setSecurityParams(int)} sets up the + * {@link WifiConfiguration} object correctly for OWE security type. + * @throws Exception + */ + @Test + public void testSetSecurityParamsForOwe() throws Exception { + WifiConfiguration config = new WifiConfiguration(); + + config.setSecurityParams(SECURITY_TYPE_OWE); + + assertTrue(config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE)); + assertTrue(config.allowedPairwiseCiphers.get(WifiConfiguration.PairwiseCipher.CCMP)); + assertTrue(config.allowedGroupCiphers.get(WifiConfiguration.GroupCipher.CCMP)); + assertTrue(config.requirePMF); + } + + /** + * Ensure that {@link WifiConfiguration#setSecurityParams(int)} sets up the + * {@link WifiConfiguration} object correctly for Suite-B security type. + * @throws Exception + */ + @Test + public void testSetSecurityParamsForSuiteB() throws Exception { + WifiConfiguration config = new WifiConfiguration(); + + config.setSecurityParams(SECURITY_TYPE_EAP_SUITE_B); + + assertTrue(config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SUITE_B_192)); + assertTrue(config.allowedPairwiseCiphers.get(WifiConfiguration.PairwiseCipher.GCMP_256)); + assertTrue(config.allowedGroupCiphers.get(WifiConfiguration.GroupCipher.GCMP_256)); + assertTrue(config.allowedGroupManagementCiphers + .get(WifiConfiguration.GroupMgmtCipher.BIP_GMAC_256)); + assertTrue(config.requirePMF); + } } diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java index f9bd31d57ffc..4b837184dc9a 100644 --- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java +++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java @@ -1672,10 +1672,22 @@ public class WifiManagerTest { @Test public void testAllowAutojoin() throws Exception { mWifiManager.allowAutojoin(1, true); - verify(mWifiService).allowAutojoin(eq(1), eq(true)); + verify(mWifiService).allowAutojoin(1, true); } /** + * Test behavior of {@link WifiManager#allowAutojoinPasspoint(String, boolean)} + * @throws Exception + */ + @Test + public void testAllowAutojoinPasspoint() throws Exception { + final String fqdn = "FullyQualifiedDomainName"; + mWifiManager.allowAutojoinPasspoint(fqdn, true); + verify(mWifiService).allowAutojoinPasspoint(fqdn, true); + } + + + /** * Test behavior of {@link WifiManager#disconnect()} */ @Test @@ -2173,4 +2185,18 @@ public class WifiManagerTest { result = WifiManager.parseDppChannelList(channelList); assertEquals(result.size(), 0); } + + /** + * Test getWifiConfigsForMatchedNetworkSuggestions for given scanResults. + */ + @Test + public void testGetWifiConfigsForMatchedNetworkSuggestions() throws Exception { + List<WifiConfiguration> testResults = new ArrayList<>(); + testResults.add(new WifiConfiguration()); + + when(mWifiService.getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(any(List.class))) + .thenReturn(testResults); + assertEquals(testResults, mWifiManager + .getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(new ArrayList<>())); + } } diff --git a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java index f501b16d2c79..94054fdde8da 100644 --- a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java +++ b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java @@ -171,6 +171,7 @@ public class PasspointConfigurationTest { assertFalse(config.validate()); assertFalse(config.validateForR2()); + assertTrue(config.isAutoJoinEnabled()); } /** |