diff options
512 files changed, 16941 insertions, 8036 deletions
diff --git a/Android.bp b/Android.bp index e4c5c37ab576..908280e5d7f3 100644 --- a/Android.bp +++ b/Android.bp @@ -371,7 +371,6 @@ filegroup { srcs: [ // Java/AIDL sources under frameworks/base ":framework-blobstore-sources", - ":framework-connectivity-sources", // framework-connectivity is not yet a module ":framework-core-sources", ":framework-drm-sources", ":framework-graphics-nonupdatable-sources", @@ -437,6 +436,7 @@ filegroup { name: "framework-updatable-sources", srcs: [ ":framework-appsearch-sources", + ":framework-connectivity-sources", ":framework-graphics-srcs", ":framework-mediaprovider-sources", ":framework-permission-sources", @@ -639,6 +639,7 @@ java_defaults { defaults: ["framework-aidl-export-defaults"], srcs: [ ":framework-non-updatable-sources", + ":framework-connectivity-sources", "core/java/**/*.logtags", ], // See comment on framework-atb-backward-compatibility module below @@ -700,6 +701,8 @@ java_library { apex_available: ["//apex_available:platform"], visibility: [ "//frameworks/base", + // TODO: remove when framework-connectivity can build against API + "//frameworks/base/packages/Connectivity/framework", // TODO(b/147128803) remove the below lines "//frameworks/base/apex/appsearch/framework", "//frameworks/base/apex/blobstore/framework", @@ -1452,6 +1455,7 @@ java_library { ], libs: [ "framework-annotations-lib", + "framework-connectivity", "unsupportedappusage", ], visibility: [ diff --git a/StubLibraries.bp b/StubLibraries.bp index 4bd524f229ca..bff222e7d42f 100644 --- a/StubLibraries.bp +++ b/StubLibraries.bp @@ -58,6 +58,9 @@ stubs_defaults { local_include_dirs: [ "apex/media/aidl/stable", "media/aidl", + // TODO: move to include-dirs for packages/modules/Connectivity when this moves out of + // frameworks/base + "packages/Connectivity/framework/aidl-export", "telephony/java", ], include_dirs: ["frameworks/av/aidl"], @@ -310,6 +313,7 @@ java_library_static { "art.module.public.api.stubs", "conscrypt.module.public.api.stubs", "framework-appsearch.stubs", + "framework-connectivity.stubs", "framework-graphics.stubs", "framework-media.stubs", "framework-mediaprovider.stubs", @@ -334,6 +338,7 @@ java_library_static { "art.module.public.api.stubs", "conscrypt.module.public.api.stubs", "framework-appsearch.stubs.system", + "framework-connectivity.stubs.system", "framework-graphics.stubs.system", "framework-media.stubs.system", "framework-mediaprovider.stubs.system", @@ -374,6 +379,7 @@ java_library_static { "art.module.public.api.stubs", "conscrypt.module.public.api.stubs", "framework-appsearch.stubs.system", + "framework-connectivity.stubs.system", "framework-graphics.stubs.system", "framework-media.stubs.system", "framework-mediaprovider.stubs.system", diff --git a/apex/appsearch/framework/api/current.txt b/apex/appsearch/framework/api/current.txt index cc79f6bf9682..168c7c2f13cd 100644 --- a/apex/appsearch/framework/api/current.txt +++ b/apex/appsearch/framework/api/current.txt @@ -148,7 +148,7 @@ package android.app.appsearch { method public void remove(@NonNull android.app.appsearch.RemoveByUriRequest, @NonNull java.util.concurrent.Executor, @NonNull android.app.appsearch.BatchResultCallback<java.lang.String,java.lang.Void>); method public void remove(@NonNull String, @NonNull android.app.appsearch.SearchSpec, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<java.lang.Void>>); method public void reportUsage(@NonNull android.app.appsearch.ReportUsageRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<java.lang.Void>>); - method @NonNull public android.app.appsearch.SearchResults search(@NonNull String, @NonNull android.app.appsearch.SearchSpec, @NonNull java.util.concurrent.Executor); + method @NonNull public android.app.appsearch.SearchResults search(@NonNull String, @NonNull android.app.appsearch.SearchSpec); method public void setSchema(@NonNull android.app.appsearch.SetSchemaRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<android.app.appsearch.SetSchemaResponse>>); } @@ -217,7 +217,7 @@ package android.app.appsearch { public class GlobalSearchSession implements java.io.Closeable { method public void close(); - method @NonNull public android.app.appsearch.SearchResults search(@NonNull String, @NonNull android.app.appsearch.SearchSpec, @NonNull java.util.concurrent.Executor); + method @NonNull public android.app.appsearch.SearchResults search(@NonNull String, @NonNull android.app.appsearch.SearchSpec); } public class PackageIdentifier { @@ -287,7 +287,7 @@ package android.app.appsearch { public class SearchResults implements java.io.Closeable { method public void close(); - method public void getNextPage(@NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<java.util.List<android.app.appsearch.SearchResult>>>); + method public void getNextPage(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<java.util.List<android.app.appsearch.SearchResult>>>); } public final class SearchSpec { diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java index 54f33509072b..24cc60e5eef5 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java +++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java @@ -395,21 +395,15 @@ public final class AppSearchSession implements Closeable { * @param queryExpression query string to search. * @param searchSpec spec for setting document filters, adding projection, setting term match * type, etc. - * @param executor Executor on which to invoke the callback of the following request - * {@link SearchResults#getNextPage}. * @return a {@link SearchResults} object for retrieved matched documents. */ @NonNull - public SearchResults search( - @NonNull String queryExpression, - @NonNull SearchSpec searchSpec, - @NonNull @CallbackExecutor Executor executor) { + public SearchResults search(@NonNull String queryExpression, @NonNull SearchSpec searchSpec) { Objects.requireNonNull(queryExpression); Objects.requireNonNull(searchSpec); - Objects.requireNonNull(executor); Preconditions.checkState(!mIsClosed, "AppSearchSession has already been closed"); return new SearchResults(mService, mPackageName, mDatabaseName, queryExpression, - searchSpec, mUserId, executor); + searchSpec, mUserId); } /** diff --git a/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java b/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java index 93b102b864f4..8dd9dc1be312 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java +++ b/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java @@ -106,21 +106,15 @@ public class GlobalSearchSession implements Closeable { * @param queryExpression query string to search. * @param searchSpec spec for setting document filters, adding projection, setting term match * type, etc. - * @param executor Executor on which to invoke the callback of the following request - * {@link SearchResults#getNextPage}. * @return a {@link SearchResults} object for retrieved matched documents. */ @NonNull - public SearchResults search( - @NonNull String queryExpression, - @NonNull SearchSpec searchSpec, - @NonNull @CallbackExecutor Executor executor) { + public SearchResults search(@NonNull String queryExpression, @NonNull SearchSpec searchSpec) { Objects.requireNonNull(queryExpression); Objects.requireNonNull(searchSpec); - Objects.requireNonNull(executor); Preconditions.checkState(!mIsClosed, "GlobalSearchSession has already been closed"); return new SearchResults(mService, mPackageName, /*databaseName=*/null, queryExpression, - searchSpec, mUserId, executor); + searchSpec, mUserId); } /** Closes the {@link GlobalSearchSession}. */ diff --git a/apex/appsearch/framework/java/android/app/appsearch/SearchResults.java b/apex/appsearch/framework/java/android/app/appsearch/SearchResults.java index e9e978eea943..531c98425288 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/SearchResults.java +++ b/apex/appsearch/framework/java/android/app/appsearch/SearchResults.java @@ -65,8 +65,6 @@ public class SearchResults implements Closeable { @UserIdInt private final int mUserId; - private final Executor mExecutor; - private long mNextPageToken; private boolean mIsFirstLoad = true; @@ -79,15 +77,13 @@ public class SearchResults implements Closeable { @Nullable String databaseName, @NonNull String queryExpression, @NonNull SearchSpec searchSpec, - @UserIdInt int userId, - @NonNull @CallbackExecutor Executor executor) { + @UserIdInt int userId) { mService = Objects.requireNonNull(service); mPackageName = packageName; mDatabaseName = databaseName; mQueryExpression = Objects.requireNonNull(queryExpression); mSearchSpec = Objects.requireNonNull(searchSpec); mUserId = userId; - mExecutor = Objects.requireNonNull(executor); } /** @@ -98,9 +94,14 @@ public class SearchResults implements Closeable { * <p>Continue calling this method to access results until it returns an empty list, signifying * there are no more results. * + * @param executor Executor on which to invoke the callback. * @param callback Callback to receive the pending result of performing this operation. */ - public void getNextPage(@NonNull Consumer<AppSearchResult<List<SearchResult>>> callback) { + public void getNextPage( + @NonNull @CallbackExecutor Executor executor, + @NonNull Consumer<AppSearchResult<List<SearchResult>>> callback) { + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); Preconditions.checkState(!mIsClosed, "SearchResults has already been closed"); try { if (mIsFirstLoad) { @@ -108,14 +109,14 @@ public class SearchResults implements Closeable { if (mDatabaseName == null) { // Global query, there's no one package-database combination to check. mService.globalQuery(mPackageName, mQueryExpression, - mSearchSpec.getBundle(), mUserId, wrapCallback(callback)); + mSearchSpec.getBundle(), mUserId, wrapCallback(executor, callback)); } else { // Normal local query, pass in specified database. mService.query(mPackageName, mDatabaseName, mQueryExpression, - mSearchSpec.getBundle(), mUserId, wrapCallback(callback)); + mSearchSpec.getBundle(), mUserId, wrapCallback(executor, callback)); } } else { - mService.getNextPage(mNextPageToken, mUserId, wrapCallback(callback)); + mService.getNextPage(mNextPageToken, mUserId, wrapCallback(executor, callback)); } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -135,10 +136,11 @@ public class SearchResults implements Closeable { } private IAppSearchResultCallback wrapCallback( + @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<AppSearchResult<List<SearchResult>>> callback) { return new IAppSearchResultCallback.Stub() { public void onResult(AppSearchResult result) { - mExecutor.execute(() -> invokeCallback(result, callback)); + executor.execute(() -> invokeCallback(result, callback)); } }; } diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java index afa633a48b2b..9ef6e0b2dee8 100644 --- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java +++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java @@ -124,8 +124,7 @@ public class AppSearchSessionShimImpl implements AppSearchSessionShim { @NonNull public SearchResultsShim search( @NonNull String queryExpression, @NonNull SearchSpec searchSpec) { - SearchResults searchResults = - mAppSearchSession.search(queryExpression, searchSpec, mExecutor); + SearchResults searchResults = mAppSearchSession.search(queryExpression, searchSpec); return new SearchResultsShimImpl(searchResults, mExecutor); } diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java index 6595d8d4abba..69a4c18c4028 100644 --- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java +++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java @@ -75,8 +75,7 @@ public class GlobalSearchSessionShimImpl implements GlobalSearchSessionShim { @Override public SearchResultsShim search( @NonNull String queryExpression, @NonNull SearchSpec searchSpec) { - SearchResults searchResults = - mGlobalSearchSession.search(queryExpression, searchSpec, mExecutor); + SearchResults searchResults = mGlobalSearchSession.search(queryExpression, searchSpec); return new SearchResultsShimImpl(searchResults, mExecutor); } diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/SearchResultsShimImpl.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/SearchResultsShimImpl.java index 75add81c8d64..5f26e8cba585 100644 --- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/SearchResultsShimImpl.java +++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/SearchResultsShimImpl.java @@ -47,7 +47,7 @@ public class SearchResultsShimImpl implements SearchResultsShim { @NonNull public ListenableFuture<List<SearchResult>> getNextPage() { SettableFuture<AppSearchResult<List<SearchResult>>> future = SettableFuture.create(); - mSearchResults.getNextPage(future::set); + mSearchResults.getNextPage(mExecutor, future::set); return Futures.transform(future, AppSearchResult::getResultValue, mExecutor); } diff --git a/apex/jobscheduler/framework/java/android/app/AlarmManager.java b/apex/jobscheduler/framework/java/android/app/AlarmManager.java index 7c7b21001c3b..77146e0d1282 100644 --- a/apex/jobscheduler/framework/java/android/app/AlarmManager.java +++ b/apex/jobscheduler/framework/java/android/app/AlarmManager.java @@ -23,7 +23,7 @@ import android.annotation.SdkConstant; import android.annotation.SystemApi; import android.annotation.SystemService; import android.compat.annotation.ChangeId; -import android.compat.annotation.Disabled; +import android.compat.annotation.EnabledSince; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.Intent; @@ -202,7 +202,7 @@ public class AlarmManager { * @hide */ @ChangeId - @Disabled // TODO (b/171306433): Enable starting S. + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.S) public static final long REQUIRE_EXACT_ALARM_PERMISSION = 171306433L; @UnsupportedAppUsage diff --git a/apex/jobscheduler/framework/java/android/os/IDeviceIdleController.aidl b/apex/jobscheduler/framework/java/android/os/IDeviceIdleController.aidl index 5693abe4d4e1..43d4873a3540 100644 --- a/apex/jobscheduler/framework/java/android/os/IDeviceIdleController.aidl +++ b/apex/jobscheduler/framework/java/android/os/IDeviceIdleController.aidl @@ -43,10 +43,10 @@ interface IDeviceIdleController { boolean isPowerSaveWhitelistApp(String name); @UnsupportedAppUsage(maxTargetSdk = 30, publicAlternatives = "Use SystemApi {@code PowerWhitelistManager#whitelistAppTemporarily(String, int, String)}.") - void addPowerSaveTempWhitelistApp(String name, long duration, int userId, String reason); - long addPowerSaveTempWhitelistAppForMms(String name, int userId, String reason); - long addPowerSaveTempWhitelistAppForSms(String name, int userId, String reason); - long whitelistAppTemporarily(String name, int userId, String reason); + void addPowerSaveTempWhitelistApp(String name, long duration, int userId, int reasonCode, String reason); + long addPowerSaveTempWhitelistAppForMms(String name, int userId, int reasonCode, String reason); + long addPowerSaveTempWhitelistAppForSms(String name, int userId, int reasonCode, String reason); + long whitelistAppTemporarily(String name, int userId, int reasonCode, String reason); void exitIdle(String reason); int setPreIdleTimeoutMode(int Mode); void resetPreIdleTimeoutMode(); diff --git a/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java b/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java index df0e157abc6a..df690d00a322 100644 --- a/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java +++ b/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java @@ -16,8 +16,16 @@ package android.os; +import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; +import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP; +import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; +import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT; +import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT_UI; +import static android.app.ActivityManager.PROCESS_STATE_TOP; + import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; @@ -94,6 +102,239 @@ public class PowerWhitelistManager { @Retention(RetentionPolicy.SOURCE) public @interface TempAllowListType {} + /* Reason code for BG-FGS-launch. */ + /** + * BG-FGS-launch is denied. + * @hide + */ + public static final int REASON_DENIED = -1; + /** + * The default reason code if reason is unknown. + */ + public static final int REASON_UNKNOWN = 0; + /** + * Use REASON_OTHER if there is no better choice. + */ + public static final int REASON_OTHER = 1; + /** @hide */ + public static final int REASON_PROC_STATE_PERSISTENT = 10; + /** @hide */ + public static final int REASON_PROC_STATE_PERSISTENT_UI = 11; + /** @hide */ + public static final int REASON_PROC_STATE_TOP = 12; + /** @hide */ + public static final int REASON_PROC_STATE_BTOP = 13; + /** @hide */ + public static final int REASON_PROC_STATE_FGS = 14; + /** @hide */ + public static final int REASON_PROC_STATE_BFGS = 15; + /** @hide */ + public static final int REASON_UID_VISIBLE = 50; + /** @hide */ + public static final int REASON_SYSTEM_UID = 51; + /** @hide */ + public static final int REASON_ACTIVITY_STARTER = 52; + /** @hide */ + public static final int REASON_START_ACTIVITY_FLAG = 53; + /** @hide */ + public static final int REASON_FGS_BINDING = 54; + /** @hide */ + public static final int REASON_DEVICE_OWNER = 55; + /** @hide */ + public static final int REASON_PROFILE_OWNER = 56; + /** @hide */ + public static final int REASON_COMPANION_DEVICE_MANAGER = 57; + /** + * START_ACTIVITIES_FROM_BACKGROUND permission. + * @hide + */ + public static final int REASON_BACKGROUND_ACTIVITY_PERMISSION = 58; + /** + * START_FOREGROUND_SERVICES_FROM_BACKGROUND permission. + * @hide + */ + public static final int REASON_BACKGROUND_FGS_PERMISSION = 59; + /** @hide */ + public static final int REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION = 60; + /** @hide */ + public static final int REASON_INSTR_BACKGROUND_FGS_PERMISSION = 61; + /** @hide */ + public static final int REASON_SYSTEM_ALERT_WINDOW_PERMISSION = 62; + /** @hide */ + public static final int REASON_DEVICE_DEMO_MODE = 63; + /** @hide */ + public static final int REASON_EXEMPTED_PACKAGE = 64; + /** @hide */ + public static final int REASON_ALLOWLISTED_PACKAGE = 65; + /** + * If it's because of a role, + * @hide + */ + public static final int REASON_APPOP = 66; + + /* BG-FGS-launch is allowed by temp-allowlist or system-allowlist. + Reason code for temp and system allowlist starts here. + */ + public static final int REASON_GEOFENCING = 100; + public static final int REASON_PUSH_MESSAGING = 101; + public static final int REASON_ACTIVITY_RECOGNITION = 102; + + /** + * Broadcast ACTION_BOOT_COMPLETED. + * @hide + */ + public static final int REASON_BOOT_COMPLETED = 103; + /** + * Broadcast ACTION_PRE_BOOT_COMPLETED. + * @hide + */ + public static final int REASON_PRE_BOOT_COMPLETED = 104; + + /** + * Broadcast ACTION_LOCKED_BOOT_COMPLETED. + * @hide + */ + public static final int REASON_LOCKED_BOOT_COMPLETED = 105; + /** + * Device idle system allowlist, including EXCEPT-IDLE + * @hide + */ + public static final int REASON_SYSTEM_ALLOW_LISTED = 106; + /** @hide */ + public static final int REASON_ALARM_MANAGER_ALARM_CLOCK = 107; + /** + * AlarmManagerService. + * @hide + */ + public static final int REASON_ALARM_MANAGER_WHILE_IDLE = 108; + /** + * ActiveServices. + * @hide + */ + public static final int REASON_SERVICE_LAUNCH = 109; + /** + * KeyChainSystemService. + * @hide + */ + public static final int REASON_KEY_CHAIN = 110; + /** + * PackageManagerService. + * @hide + */ + public static final int REASON_PACKAGE_VERIFIER = 111; + /** + * SyncManager. + * @hide + */ + public static final int REASON_SYNC_MANAGER = 112; + /** + * DomainVerificationProxyV1. + * @hide + */ + public static final int REASON_DOMAIN_VERIFICATION_V1 = 113; + /** + * DomainVerificationProxyV2. + * @hide + */ + public static final int REASON_DOMAIN_VERIFICATION_V2 = 114; + /** @hide */ + public static final int REASON_VPN = 115; + /** + * NotificationManagerService. + * @hide + */ + public static final int REASON_NOTIFICATION_SERVICE = 116; + /** + * Broadcast ACTION_MY_PACKAGE_REPLACED. + * @hide + */ + public static final int REASON_PACKAGE_REPLACED = 117; + /** + * LocationProviderManager. + * @hide + */ + public static final int REASON_LOCATION_PROVIDER = 118; + /** + * MediaButtonReceiver. + * @hide + */ + public static final int REASON_MEDIA_BUTTON = 119; + /** + * InboundSmsHandler. + * @hide + */ + public static final int REASON_EVENT_SMS = 120; + /** + * InboundSmsHandler. + * @hide + */ + public static final int REASON_EVENT_MMS = 121; + /** + * Shell app. + * @hide + */ + public static final int REASON_SHELL = 122; + + /** + * The list of BG-FGS-Launch and temp-allowlist reason code. + * @hide + */ + @IntDef(flag = true, prefix = { "REASON_" }, value = { + // BG-FGS-Launch reasons. + REASON_DENIED, + REASON_UNKNOWN, + REASON_OTHER, + REASON_PROC_STATE_PERSISTENT, + REASON_PROC_STATE_PERSISTENT_UI, + REASON_PROC_STATE_TOP, + REASON_PROC_STATE_BTOP, + REASON_PROC_STATE_FGS, + REASON_PROC_STATE_BFGS, + REASON_UID_VISIBLE, + REASON_SYSTEM_UID, + REASON_ACTIVITY_STARTER, + REASON_START_ACTIVITY_FLAG, + REASON_FGS_BINDING, + REASON_DEVICE_OWNER, + REASON_PROFILE_OWNER, + REASON_COMPANION_DEVICE_MANAGER, + REASON_BACKGROUND_ACTIVITY_PERMISSION, + REASON_BACKGROUND_FGS_PERMISSION, + REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION, + REASON_INSTR_BACKGROUND_FGS_PERMISSION, + REASON_SYSTEM_ALERT_WINDOW_PERMISSION, + REASON_DEVICE_DEMO_MODE, + REASON_EXEMPTED_PACKAGE, + REASON_ALLOWLISTED_PACKAGE, + REASON_APPOP, + // temp and system allowlist reasons. + REASON_GEOFENCING, + REASON_PUSH_MESSAGING, + REASON_ACTIVITY_RECOGNITION, + REASON_BOOT_COMPLETED, + REASON_PRE_BOOT_COMPLETED, + REASON_LOCKED_BOOT_COMPLETED, + REASON_SYSTEM_ALLOW_LISTED, + REASON_ALARM_MANAGER_ALARM_CLOCK, + REASON_ALARM_MANAGER_WHILE_IDLE, + REASON_SERVICE_LAUNCH, + REASON_KEY_CHAIN, + REASON_PACKAGE_VERIFIER, + REASON_SYNC_MANAGER, + REASON_DOMAIN_VERIFICATION_V1, + REASON_DOMAIN_VERIFICATION_V2, + REASON_VPN, + REASON_NOTIFICATION_SERVICE, + REASON_PACKAGE_REPLACED, + REASON_LOCATION_PROVIDER, + REASON_MEDIA_BUTTON, + REASON_EVENT_SMS, + REASON_EVENT_MMS, + REASON_SHELL, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ReasonCode {} + /** * @hide */ @@ -184,19 +425,34 @@ public class PowerWhitelistManager { * * @param packageName The package to add to the temp whitelist * @param durationMs How long to keep the app on the temp whitelist for (in milliseconds) + * @param reasonCode one of {@link ReasonCode}, use {@link #REASON_UNKNOWN} if not sure. + * @param reason a optional human readable reason string, could be null or empty string. */ @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) - public void whitelistAppTemporarily(@NonNull String packageName, long durationMs) { - String reason = "from:" + UserHandle.formatUid(Binder.getCallingUid()); + public void whitelistAppTemporarily(@NonNull String packageName, long durationMs, + @ReasonCode int reasonCode, @Nullable String reason) { try { mService.addPowerSaveTempWhitelistApp(packageName, durationMs, mContext.getUserId(), - reason); + reasonCode, reason); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** + * Add an app to the temporary whitelist for a short amount of time. + * + * @param packageName The package to add to the temp whitelist + * @param durationMs How long to keep the app on the temp whitelist for (in milliseconds) + * @deprecated Use {@link #whitelistAppTemporarily(String, long, int, String)} instead + */ + @Deprecated + @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) + public void whitelistAppTemporarily(@NonNull String packageName, long durationMs) { + whitelistAppTemporarily(packageName, durationMs, REASON_UNKNOWN, packageName); + } + + /** * Add an app to the temporary whitelist for a short amount of time for a specific reason. The * temporary whitelist is kept separately from the permanent whitelist and apps are * automatically removed from the temporary whitelist after a predetermined amount of time. @@ -204,27 +460,179 @@ public class PowerWhitelistManager { * @param packageName The package to add to the temp whitelist * @param event The reason to add the app to the temp whitelist * @param reason A human-readable reason explaining why the app is temp whitelisted. Only - * used for logging purposes + * used for logging purposes. Could be null or empty string. * @return The duration (in milliseconds) that the app is whitelisted for + * @deprecated Use {@link #whitelistAppTemporarilyForEvent(String, int, int, String)} instead */ + @Deprecated @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public long whitelistAppTemporarilyForEvent(@NonNull String packageName, - @WhitelistEvent int event, @NonNull String reason) { + @WhitelistEvent int event, @Nullable String reason) { + return whitelistAppTemporarilyForEvent(packageName, event, REASON_UNKNOWN, reason); + } + + /** + * Add an app to the temporary whitelist for a short amount of time for a specific reason. The + * temporary whitelist is kept separately from the permanent whitelist and apps are + * automatically removed from the temporary whitelist after a predetermined amount of time. + * + * @param packageName The package to add to the temp whitelist + * @param event The reason to add the app to the temp whitelist + * @param reasonCode one of {@link ReasonCode}, use {@link #REASON_UNKNOWN} if not sure. + * @param reason A human-readable reason explaining why the app is temp whitelisted. Only + * used for logging purposes. Could be null or empty string. + * @return The duration (in milliseconds) that the app is whitelisted for + */ + @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) + public long whitelistAppTemporarilyForEvent(@NonNull String packageName, + @WhitelistEvent int event, @ReasonCode int reasonCode, @Nullable String reason) { try { switch (event) { case EVENT_MMS: return mService.addPowerSaveTempWhitelistAppForMms( - packageName, mContext.getUserId(), reason); + packageName, mContext.getUserId(), reasonCode, reason); case EVENT_SMS: return mService.addPowerSaveTempWhitelistAppForSms( - packageName, mContext.getUserId(), reason); + packageName, mContext.getUserId(), reasonCode, reason); case EVENT_UNSPECIFIED: default: return mService.whitelistAppTemporarily( - packageName, mContext.getUserId(), reason); + packageName, mContext.getUserId(), reasonCode, reason); } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } + + /** + * @hide + */ + public static @ReasonCode int getReasonCodeFromProcState(int procState) { + if (procState <= PROCESS_STATE_PERSISTENT) { + return REASON_PROC_STATE_PERSISTENT; + } else if (procState <= PROCESS_STATE_PERSISTENT_UI) { + return REASON_PROC_STATE_PERSISTENT_UI; + } else if (procState <= PROCESS_STATE_TOP) { + return REASON_PROC_STATE_TOP; + } else if (procState <= PROCESS_STATE_BOUND_TOP) { + return REASON_PROC_STATE_BTOP; + } else if (procState <= PROCESS_STATE_FOREGROUND_SERVICE) { + return REASON_PROC_STATE_FGS; + } else if (procState <= PROCESS_STATE_BOUND_FOREGROUND_SERVICE) { + return REASON_PROC_STATE_BFGS; + } else { + return REASON_DENIED; + } + } + + /** + * Return string name of the integer reason code. + * @hide + * @param reasonCode + * @return string name of the reason code. + */ + public static String reasonCodeToString(@ReasonCode int reasonCode) { + switch (reasonCode) { + case REASON_DENIED: + return "DENIED"; + case REASON_UNKNOWN: + return "UNKNOWN"; + case REASON_OTHER: + return "OTHER"; + case REASON_PROC_STATE_PERSISTENT: + return "PROC_STATE_PERSISTENT"; + case REASON_PROC_STATE_PERSISTENT_UI: + return "PROC_STATE_PERSISTENT_UI"; + case REASON_PROC_STATE_TOP: + return "PROC_STATE_TOP"; + case REASON_PROC_STATE_BTOP: + return "PROC_STATE_BTOP"; + case REASON_PROC_STATE_FGS: + return "PROC_STATE_FGS"; + case REASON_PROC_STATE_BFGS: + return "PROC_STATE_BFGS"; + case REASON_UID_VISIBLE: + return "UID_VISIBLE"; + case REASON_SYSTEM_UID: + return "SYSTEM_UID"; + case REASON_ACTIVITY_STARTER: + return "ACTIVITY_STARTER"; + case REASON_START_ACTIVITY_FLAG: + return "START_ACTIVITY_FLAG"; + case REASON_FGS_BINDING: + return "FGS_BINDING"; + case REASON_DEVICE_OWNER: + return "DEVICE_OWNER"; + case REASON_PROFILE_OWNER: + return "PROFILE_OWNER"; + case REASON_COMPANION_DEVICE_MANAGER: + return "COMPANION_DEVICE_MANAGER"; + case REASON_BACKGROUND_ACTIVITY_PERMISSION: + return "BACKGROUND_ACTIVITY_PERMISSION"; + case REASON_BACKGROUND_FGS_PERMISSION: + return "BACKGROUND_FGS_PERMISSION"; + case REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION: + return "INSTR_BACKGROUND_ACTIVITY_PERMISSION"; + case REASON_INSTR_BACKGROUND_FGS_PERMISSION: + return "INSTR_BACKGROUND_FGS_PERMISSION"; + case REASON_SYSTEM_ALERT_WINDOW_PERMISSION: + return "SYSTEM_ALERT_WINDOW_PERMISSION"; + case REASON_DEVICE_DEMO_MODE: + return "DEVICE_DEMO_MODE"; + case REASON_EXEMPTED_PACKAGE: + return "EXEMPTED_PACKAGE"; + case REASON_ALLOWLISTED_PACKAGE: + return "ALLOWLISTED_PACKAGE"; + case REASON_APPOP: + return "APPOP"; + case REASON_GEOFENCING: + return "GEOFENCING"; + case REASON_PUSH_MESSAGING: + return "PUSH_MESSAGING"; + case REASON_ACTIVITY_RECOGNITION: + return "ACTIVITY_RECOGNITION"; + case REASON_BOOT_COMPLETED: + return "BOOT_COMPLETED"; + case REASON_PRE_BOOT_COMPLETED: + return "PRE_BOOT_COMPLETED"; + case REASON_LOCKED_BOOT_COMPLETED: + return "LOCKED_BOOT_COMPLETED"; + case REASON_SYSTEM_ALLOW_LISTED: + return "SYSTEM_ALLOW_LISTED"; + case REASON_ALARM_MANAGER_ALARM_CLOCK: + return "ALARM_MANAGER_ALARM_CLOCK"; + case REASON_ALARM_MANAGER_WHILE_IDLE: + return "ALARM_MANAGER_WHILE_IDLE"; + case REASON_SERVICE_LAUNCH: + return "SERVICE_LAUNCH"; + case REASON_KEY_CHAIN: + return "KEY_CHAIN"; + case REASON_PACKAGE_VERIFIER: + return "PACKAGE_VERIFIER"; + case REASON_SYNC_MANAGER: + return "SYNC_MANAGER"; + case REASON_DOMAIN_VERIFICATION_V1: + return "DOMAIN_VERIFICATION_V1"; + case REASON_DOMAIN_VERIFICATION_V2: + return "DOMAIN_VERIFICATION_V2"; + case REASON_VPN: + return "VPN"; + case REASON_NOTIFICATION_SERVICE: + return "NOTIFICATION_SERVICE"; + case REASON_PACKAGE_REPLACED: + return "PACKAGE_REPLACED"; + case REASON_LOCATION_PROVIDER: + return "LOCATION_PROVIDER"; + case REASON_MEDIA_BUTTON: + return "MEDIA_BUTTON"; + case REASON_EVENT_SMS: + return "EVENT_SMS"; + case REASON_EVENT_MMS: + return "EVENT_MMS"; + case REASON_SHELL: + return "SHELL"; + default: + return "(unknown:" + reasonCode + ")"; + } + } } diff --git a/apex/jobscheduler/framework/java/com/android/server/DeviceIdleInternal.java b/apex/jobscheduler/framework/java/com/android/server/DeviceIdleInternal.java index e045b0fa3a6b..5e5717d11432 100644 --- a/apex/jobscheduler/framework/java/com/android/server/DeviceIdleInternal.java +++ b/apex/jobscheduler/framework/java/com/android/server/DeviceIdleInternal.java @@ -16,6 +16,8 @@ package com.android.server; +import android.annotation.Nullable; +import android.os.PowerWhitelistManager.ReasonCode; import android.os.PowerWhitelistManager.TempAllowListType; import com.android.server.deviceidle.IDeviceIdleConstraint; @@ -34,6 +36,10 @@ public interface DeviceIdleInternal { void addPowerSaveTempWhitelistApp(int callingUid, String packageName, long duration, int userId, boolean sync, String reason); + void addPowerSaveTempWhitelistApp(int callingUid, String packageName, + long duration, int userId, boolean sync, @ReasonCode int reasonCode, + @Nullable String reason); + /** * Called by ActivityManagerService to directly add UID to DeviceIdleController's temp * allowlist. @@ -41,11 +47,12 @@ public interface DeviceIdleInternal { * @param duration duration in milliseconds * @param type temp allowlist type defined at {@link TempAllowListType} * @param sync + * @param reasonCode one of {@link ReasonCode} * @param reason */ void addPowerSaveTempWhitelistAppDirect(int uid, long duration, - @TempAllowListType int type, boolean sync, - String reason); + @TempAllowListType int type, boolean sync, @ReasonCode int reasonCode, + @Nullable String reason); // duration in milliseconds long getNotificationAllowlistDuration(); diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java index 8f7f705163ba..ac28e828eb2e 100644 --- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java +++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java @@ -16,12 +16,17 @@ package com.android.server; +import static android.os.PowerWhitelistManager.REASON_SHELL; +import static android.os.PowerWhitelistManager.REASON_UNKNOWN; +import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED; +import static android.os.Process.INVALID_UID; + import android.Manifest; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.AlarmManager; -import android.app.BroadcastOptions; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -56,6 +61,7 @@ import android.os.Message; import android.os.PowerManager; import android.os.PowerManager.ServiceType; import android.os.PowerManagerInternal; +import android.os.PowerWhitelistManager.ReasonCode; import android.os.PowerWhitelistManager.TempAllowListType; import android.os.Process; import android.os.RemoteException; @@ -1838,31 +1844,34 @@ public class DeviceIdleController extends SystemService } @Override - public long whitelistAppTemporarily(String packageName, int userId, String reason) - throws RemoteException { + public long whitelistAppTemporarily(String packageName, int userId, + @ReasonCode int reasonCode, @Nullable String reason) throws RemoteException { // At least 10 seconds. long durationMs = Math.max(10_000L, mConstants.MAX_TEMP_APP_ALLOWLIST_DURATION_MS / 2); - addPowerSaveTempAllowlistAppChecked(packageName, durationMs, userId, reason); + addPowerSaveTempAllowlistAppChecked(packageName, durationMs, userId, reasonCode, + reason); return durationMs; } @Override - public void addPowerSaveTempWhitelistApp(String packageName, long duration, - int userId, String reason) throws RemoteException { - addPowerSaveTempAllowlistAppChecked(packageName, duration, userId, reason); + public void addPowerSaveTempWhitelistApp(String packageName, long duration, int userId, + @ReasonCode int reasonCode, @Nullable String reason) throws RemoteException { + addPowerSaveTempAllowlistAppChecked(packageName, duration, userId, reasonCode, reason); } - @Override public long addPowerSaveTempWhitelistAppForMms(String packageName, - int userId, String reason) throws RemoteException { + @Override public long addPowerSaveTempWhitelistAppForMms(String packageName, int userId, + @ReasonCode int reasonCode, @Nullable String reason) throws RemoteException { long durationMs = mConstants.MMS_TEMP_APP_ALLOWLIST_DURATION_MS; - addPowerSaveTempAllowlistAppChecked(packageName, durationMs, userId, reason); + addPowerSaveTempAllowlistAppChecked(packageName, durationMs, userId, reasonCode, + reason); return durationMs; } - @Override public long addPowerSaveTempWhitelistAppForSms(String packageName, - int userId, String reason) throws RemoteException { + @Override public long addPowerSaveTempWhitelistAppForSms(String packageName, int userId, + @ReasonCode int reasonCode, @Nullable String reason) throws RemoteException { long durationMs = mConstants.SMS_TEMP_APP_ALLOWLIST_DURATION_MS; - addPowerSaveTempAllowlistAppChecked(packageName, durationMs, userId, reason); + addPowerSaveTempAllowlistAppChecked(packageName, durationMs, userId, reasonCode, + reason); return durationMs; } @@ -1934,18 +1943,29 @@ public class DeviceIdleController extends SystemService } // duration in milliseconds + @Deprecated @Override public void addPowerSaveTempWhitelistApp(int callingUid, String packageName, - long duration, int userId, boolean sync, String reason) { + long duration, int userId, boolean sync, @Nullable String reason) { addPowerSaveTempAllowlistAppInternal(callingUid, packageName, duration, - userId, sync, reason); + userId, sync, REASON_UNKNOWN, reason); + } + + @Override + public void addPowerSaveTempWhitelistApp(int callingUid, String packageName, + long duration, int userId, boolean sync, @ReasonCode int reasonCode, + @Nullable String reason) { + addPowerSaveTempAllowlistAppInternal(callingUid, packageName, duration, + userId, sync, reasonCode, reason); } // duration in milliseconds @Override public void addPowerSaveTempWhitelistAppDirect(int uid, long duration, - @TempAllowListType int type, boolean sync, String reason) { - addPowerSaveTempWhitelistAppDirectInternal(0, uid, duration, type, sync, reason); + @TempAllowListType int type, boolean sync, @ReasonCode int reasonCode, + @Nullable String reason) { + addPowerSaveTempWhitelistAppDirectInternal(0, uid, duration, type, sync, + reasonCode, reason); } // duration in milliseconds @@ -2293,7 +2313,7 @@ public class DeviceIdleController extends SystemService filter.addAction(Intent.ACTION_SCREEN_ON); getContext().registerReceiver(mInteractivityReceiver, filter); - mLocalActivityManager.setDeviceIdleWhitelist( + mLocalActivityManager.setDeviceIdleAllowlist( mPowerSaveWhitelistAllAppIdArray, mPowerSaveWhitelistExceptIdleAppIdArray); mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray); @@ -2671,7 +2691,8 @@ public class DeviceIdleController extends SystemService } void addPowerSaveTempAllowlistAppChecked(String packageName, long duration, - int userId, String reason) throws RemoteException { + int userId, @ReasonCode int reasonCode, @Nullable String reason) + throws RemoteException { getContext().enforceCallingPermission( Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, "No permission to change device idle whitelist"); @@ -2686,7 +2707,7 @@ public class DeviceIdleController extends SystemService final long token = Binder.clearCallingIdentity(); try { addPowerSaveTempAllowlistAppInternal(callingUid, - packageName, duration, userId, true, reason); + packageName, duration, userId, true, reasonCode, reason); } finally { Binder.restoreCallingIdentity(token); } @@ -2718,12 +2739,12 @@ public class DeviceIdleController extends SystemService * app an exemption to access network and acquire wakelocks. */ void addPowerSaveTempAllowlistAppInternal(int callingUid, String packageName, - long duration, int userId, boolean sync, String reason) { + long duration, int userId, boolean sync, @ReasonCode int reasonCode, + @Nullable String reason) { try { int uid = getContext().getPackageManager().getPackageUidAsUser(packageName, userId); addPowerSaveTempWhitelistAppDirectInternal(callingUid, uid, duration, - BroadcastOptions.TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_ALLOWED, sync, - reason); + TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, sync, reasonCode, reason); } catch (NameNotFoundException e) { } } @@ -2733,7 +2754,8 @@ public class DeviceIdleController extends SystemService * app an exemption to access network and acquire wakelocks. */ void addPowerSaveTempWhitelistAppDirectInternal(int callingUid, int uid, - long duration, @TempAllowListType int type, boolean sync, String reason) { + long duration, @TempAllowListType int type, boolean sync, @ReasonCode int reasonCode, + @Nullable String reason) { final long timeNow = SystemClock.elapsedRealtime(); boolean informWhitelistChanged = false; int appId = UserHandle.getAppId(uid); @@ -2765,7 +2787,8 @@ public class DeviceIdleController extends SystemService } catch (RemoteException e) { } postTempActiveTimeoutMessage(uid, duration); - updateTempWhitelistAppIdsLocked(uid, true, duration, type); + updateTempWhitelistAppIdsLocked(uid, true, duration, type, reasonCode, + reason, callingUid); if (sync) { informWhitelistChanged = true; } else { @@ -2844,12 +2867,13 @@ public class DeviceIdleController extends SystemService } @GuardedBy("this") - private void onAppRemovedFromTempWhitelistLocked(int uid, String reason) { + private void onAppRemovedFromTempWhitelistLocked(int uid, @Nullable String reason) { if (DEBUG) { Slog.d(TAG, "Removing uid " + uid + " from temp whitelist"); } final int appId = UserHandle.getAppId(uid); - updateTempWhitelistAppIdsLocked(uid, false, 0, 0); + updateTempWhitelistAppIdsLocked(uid, false, 0, 0, REASON_UNKNOWN, + reason, INVALID_UID); mHandler.obtainMessage(MSG_REPORT_TEMP_APP_WHITELIST_CHANGED_TO_NPMS, appId, 0) .sendToTarget(); reportTempWhitelistChangedLocked(uid, false); @@ -3860,7 +3884,7 @@ public class DeviceIdleController extends SystemService mPowerSaveWhitelistUserAppIdArray = buildAppIdArray(null, mPowerSaveWhitelistUserApps, mPowerSaveWhitelistUserAppIds); if (mLocalActivityManager != null) { - mLocalActivityManager.setDeviceIdleWhitelist( + mLocalActivityManager.setDeviceIdleAllowlist( mPowerSaveWhitelistAllAppIdArray, mPowerSaveWhitelistExceptIdleAppIdArray); } if (mLocalPowerManager != null) { @@ -3880,9 +3904,14 @@ public class DeviceIdleController extends SystemService * @param durationMs duration in milliseconds to add to temp allowlist, only valid when * param adding is true. * @param type temp allowlist type defined at {@link TempAllowListType} + * @prama reasonCode one of {@Link ReasonCode} + * @param reason A human-readable reason for logging purposes. + * @param callingUid the callingUid that setup this temp-allowlist, only valid when param adding + * is true. */ private void updateTempWhitelistAppIdsLocked(int uid, boolean adding, long durationMs, - @TempAllowListType int type) { + @TempAllowListType int type, @ReasonCode int reasonCode, @Nullable String reason, + int callingUid) { final int size = mTempWhitelistAppIdEndTimes.size(); if (mTempWhitelistAppIdArray.length != size) { mTempWhitelistAppIdArray = new int[size]; @@ -3895,8 +3924,8 @@ public class DeviceIdleController extends SystemService Slog.d(TAG, "Setting activity manager temp whitelist to " + Arrays.toString(mTempWhitelistAppIdArray)); } - mLocalActivityManager.updateDeviceIdleTempWhitelist(mTempWhitelistAppIdArray, uid, - adding, durationMs, type); + mLocalActivityManager.updateDeviceIdleTempAllowlist(mTempWhitelistAppIdArray, uid, + adding, durationMs, type, reasonCode, reason, callingUid); } if (mLocalPowerManager != null) { if (DEBUG) { @@ -4428,7 +4457,8 @@ public class DeviceIdleController extends SystemService if (removePkg) { removePowerSaveTempAllowlistAppChecked(arg, shell.userId); } else { - addPowerSaveTempAllowlistAppChecked(arg, duration, shell.userId, "shell"); + addPowerSaveTempAllowlistAppChecked(arg, duration, shell.userId, + REASON_SHELL, "shell"); } } catch (Exception e) { pw.println("Failed: " + e); diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java index 559a43491c7f..ea733696e1f7 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -423,6 +423,8 @@ public class AlarmManagerService extends SystemService { static final String KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA = "allow_while_idle_compat_quota"; private static final String KEY_ALLOW_WHILE_IDLE_WINDOW = "allow_while_idle_window"; + private static final String KEY_CRASH_NON_CLOCK_APPS = "crash_non_clock_apps"; + private static final long DEFAULT_MIN_FUTURITY = 5 * 1000; private static final long DEFAULT_MIN_INTERVAL = 60 * 1000; private static final long DEFAULT_MAX_INTERVAL = 365 * DateUtils.DAY_IN_MILLIS; @@ -454,6 +456,8 @@ public class AlarmManagerService extends SystemService { private static final int DEFAULT_ALLOW_WHILE_IDLE_QUOTA = 72; private static final long DEFAULT_ALLOW_WHILE_IDLE_WINDOW = 60 * 60 * 1000; // 1 hour. + // TODO (b/171306433): Change to true by default. + private static final boolean DEFAULT_CRASH_NON_CLOCK_APPS = false; // Minimum futurity of a new alarm public long MIN_FUTURITY = DEFAULT_MIN_FUTURITY; @@ -495,6 +499,13 @@ public class AlarmManagerService extends SystemService { */ public long ALLOW_WHILE_IDLE_WINDOW = DEFAULT_ALLOW_WHILE_IDLE_WINDOW; + /** + * Whether or not to crash callers that use setExactAndAllowWhileIdle or setAlarmClock + * but don't hold the required permission. This is useful to catch broken + * apps and reverting to a softer failure in case of broken apps. + */ + public boolean CRASH_NON_CLOCK_APPS = DEFAULT_CRASH_NON_CLOCK_APPS; + private long mLastAllowWhileIdleWhitelistDuration = -1; Constants() { @@ -607,6 +618,10 @@ public class AlarmManagerService extends SystemService { KEY_TIME_TICK_ALLOWED_WHILE_IDLE, DEFAULT_TIME_TICK_ALLOWED_WHILE_IDLE); break; + case KEY_CRASH_NON_CLOCK_APPS: + CRASH_NON_CLOCK_APPS = properties.getBoolean(KEY_CRASH_NON_CLOCK_APPS, + DEFAULT_CRASH_NON_CLOCK_APPS); + break; default: if (name.startsWith(KEY_PREFIX_STANDBY_QUOTA) && !standbyQuotaUpdated) { // The quotas need to be updated in order, so we can't just rely @@ -741,6 +756,9 @@ public class AlarmManagerService extends SystemService { pw.print(KEY_TIME_TICK_ALLOWED_WHILE_IDLE, TIME_TICK_ALLOWED_WHILE_IDLE); pw.println(); + pw.print(KEY_CRASH_NON_CLOCK_APPS, CRASH_NON_CLOCK_APPS); + pw.println(); + pw.decreaseIndent(); } @@ -1914,11 +1932,12 @@ public class AlarmManagerService extends SystemService { } /** - * Returns true if the given uid is on the system or user's power save exclusion list. + * Returns true if the given uid does not require SCHEDULE_EXACT_ALARM to set exact, + * allow-while-idle alarms. */ - boolean isWhitelisted(int uid) { - return (mLocalDeviceIdleController == null || mLocalDeviceIdleController.isAppOnWhitelist( - UserHandle.getAppId(uid))); + boolean isExemptFromPermission(int uid) { + return (UserHandle.isSameApp(mSystemUiUid, uid) || mLocalDeviceIdleController == null + || mLocalDeviceIdleController.isAppOnWhitelist(UserHandle.getAppId(uid))); } /** @@ -1949,7 +1968,7 @@ public class AlarmManagerService extends SystemService { if (windowLength != AlarmManager.WINDOW_EXACT) { needsPermission = false; lowQuota = true; - idleOptions = isWhitelisted(callingUid) ? mOptsWithFgs.toBundle() + idleOptions = isExemptFromPermission(callingUid) ? mOptsWithFgs.toBundle() : mOptsWithoutFgs.toBundle(); } else if (alarmClock != null) { needsPermission = true; @@ -1966,16 +1985,22 @@ public class AlarmManagerService extends SystemService { idleOptions = allowWhileIdle ? mOptsWithFgs.toBundle() : null; } if (needsPermission && !canScheduleExactAlarms()) { - if (alarmClock == null && isWhitelisted(callingUid)) { + if (alarmClock == null && isExemptFromPermission(callingUid)) { // If the app is on the full system allow-list (not except-idle), we still // allow the alarms, but with a lower quota to keep pre-S compatibility. lowQuota = true; } else { - final String errorMessage = "Caller needs to hold " + final String errorMessage = "Caller " + callingPackage + " needs to hold " + Manifest.permission.SCHEDULE_EXACT_ALARM + " to set " + ((allowWhileIdle) ? "exact, allow-while-idle" : "alarm-clock") + " alarms."; - throw new SecurityException(errorMessage); + if (mConstants.CRASH_NON_CLOCK_APPS) { + throw new SecurityException(errorMessage); + } else { + Slog.wtf(TAG, errorMessage); + idleOptions = mOptsWithoutFgs.toBundle(); + lowQuota = allowWhileIdle; + } } } if (lowQuota) { @@ -2933,7 +2958,7 @@ public class AlarmManagerService extends SystemService { if (UserHandle.isCore(uid) || uid == mSystemUiUid) { return; } - if (isWhitelisted(uid)) { + if (isExemptFromPermission(uid)) { return; } if (!CompatChanges.isChangeEnabled( diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index fc2a409be2c2..040a1164fc73 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -887,8 +887,10 @@ public class JobSchedulerService extends com.android.server.SystemService } @Override - public void onUserStarting(@NonNull TargetUser user) { + public void onUserUnlocked(@NonNull TargetUser user) { synchronized (mLock) { + // Note that the user has started after its unlocked instead of when the user + // actually starts because the storage won't be decrypted until unlock. mStartedUsers = ArrayUtils.appendInt(mStartedUsers, user.getUserIdentifier()); } // Let's kick any outstanding jobs for this user. @@ -896,12 +898,6 @@ public class JobSchedulerService extends com.android.server.SystemService } @Override - public void onUserUnlocking(@NonNull TargetUser user) { - // Let's kick any outstanding jobs for this user. - mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget(); - } - - @Override public void onUserStopping(@NonNull TargetUser user) { synchronized (mLock) { mStartedUsers = ArrayUtils.removeInt(mStartedUsers, user.getUserIdentifier()); diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java index be91947b0445..2a23d60d8af6 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java @@ -271,7 +271,9 @@ public final class JobServiceContext implements ServiceConnection { if (job.shouldTreatAsExpeditedJob()) { // TODO(171305774): The job should run on the little cores. We'll probably need // another binding flag for that. - bindFlags = Context.BIND_AUTO_CREATE; + bindFlags = Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND + | Context.BIND_ALMOST_PERCEPTIBLE + | Context.BIND_ALLOW_NETWORK_ACCESS; } else { bindFlags = Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND | Context.BIND_NOT_PERCEPTIBLE; diff --git a/api/Android.bp b/api/Android.bp index 15c1dfcfe153..1fdf1771bb13 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -40,6 +40,7 @@ genrule { ":art.module.public.api{.public.api.txt}", ":conscrypt.module.public.api{.public.api.txt}", ":framework-appsearch{.public.api.txt}", + ":framework-connectivity{.public.api.txt}", ":framework-graphics{.public.api.txt}", ":framework-media{.public.api.txt}", ":framework-mediaprovider{.public.api.txt}", @@ -95,6 +96,7 @@ genrule { ":art.module.public.api{.public.stubs.source}", ":conscrypt.module.public.api{.public.stubs.source}", ":framework-appsearch{.public.stubs.source}", + ":framework-connectivity{.public.stubs.source}", ":framework-graphics{.public.stubs.source}", ":framework-media{.public.stubs.source}", ":framework-mediaprovider{.public.stubs.source}", @@ -120,6 +122,7 @@ genrule { ":art.module.public.api{.public.removed-api.txt}", ":conscrypt.module.public.api{.public.removed-api.txt}", ":framework-appsearch{.public.removed-api.txt}", + ":framework-connectivity{.public.removed-api.txt}", ":framework-graphics{.public.removed-api.txt}", ":framework-media{.public.removed-api.txt}", ":framework-mediaprovider{.public.removed-api.txt}", @@ -155,6 +158,7 @@ genrule { srcs: [ ":android.net.ipsec.ike{.system.api.txt}", ":framework-appsearch{.system.api.txt}", + ":framework-connectivity{.system.api.txt}", ":framework-graphics{.system.api.txt}", ":framework-media{.system.api.txt}", ":framework-mediaprovider{.system.api.txt}", @@ -208,6 +212,7 @@ genrule { srcs: [ ":android.net.ipsec.ike{.system.removed-api.txt}", ":framework-appsearch{.system.removed-api.txt}", + ":framework-connectivity{.system.removed-api.txt}", ":framework-graphics{.system.removed-api.txt}", ":framework-media{.system.removed-api.txt}", ":framework-mediaprovider{.system.removed-api.txt}", @@ -243,6 +248,7 @@ genrule { srcs: [ ":android.net.ipsec.ike{.module-lib.api.txt}", ":framework-appsearch{.module-lib.api.txt}", + ":framework-connectivity{.module-lib.api.txt}", ":framework-graphics{.module-lib.api.txt}", ":framework-media{.module-lib.api.txt}", ":framework-mediaprovider{.module-lib.api.txt}", @@ -298,6 +304,7 @@ genrule { srcs: [ ":android.net.ipsec.ike{.module-lib.removed-api.txt}", ":framework-appsearch{.module-lib.removed-api.txt}", + ":framework-connectivity{.module-lib.removed-api.txt}", ":framework-graphics{.module-lib.removed-api.txt}", ":framework-media{.module-lib.removed-api.txt}", ":framework-mediaprovider{.module-lib.removed-api.txt}", @@ -340,3 +347,49 @@ genrule { out: ["combined-removed-dex.txt"], cmd: "$(location gen_combined_removed_dex.sh) $(location metalava) $(genDir) $(in) > $(out)", } + +genrule { + name: "services-system-server-current.txt", + srcs: [ + ":service-permission{.system-server.api.txt}", + ":non-updatable-system-server-current.txt", + ], + out: ["system-server-current.txt"], + tools: ["metalava"], + cmd: "$(location metalava) --no-banner --format=v2 $(in) --api $(out)", + dists: [ + { + targets: ["droidcore"], + dir: "api", + dest: "system-server-current.txt", + }, + { + targets: ["sdk", "win_sdk"], + dir: "apistubs/android/system-server/api", + dest: "merge-android.txt", + }, + ], +} + +genrule { + name: "services-system-server-removed.txt", + srcs: [ + ":service-permission{.system-server.removed-api.txt}", + ":non-updatable-system-server-removed.txt", + ], + out: ["system-server-removed.txt"], + tools: ["metalava"], + cmd: "$(location metalava) --no-banner --format=v2 $(in) --api $(out)", + dists: [ + { + targets: ["droidcore"], + dir: "api", + dest: "system-server-removed.txt", + }, + { + targets: ["sdk", "win_sdk"], + dir: "apistubs/android/system-server/api", + dest: "merge-removed.txt", + }, + ], +} diff --git a/core/api/current.txt b/core/api/current.txt index 8a543de40b18..3e2676dcec05 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -21,7 +21,6 @@ package android { field public static final String ACTIVITY_RECOGNITION = "android.permission.ACTIVITY_RECOGNITION"; field public static final String ADD_VOICEMAIL = "com.android.voicemail.permission.ADD_VOICEMAIL"; field public static final String ANSWER_PHONE_CALLS = "android.permission.ANSWER_PHONE_CALLS"; - field public static final String BACKGROUND_CAMERA = "android.permission.BACKGROUND_CAMERA"; field public static final String BATTERY_STATS = "android.permission.BATTERY_STATS"; field public static final String BIND_ACCESSIBILITY_SERVICE = "android.permission.BIND_ACCESSIBILITY_SERVICE"; field public static final String BIND_APPWIDGET = "android.permission.BIND_APPWIDGET"; @@ -134,7 +133,6 @@ package android { field public static final String RECEIVE_SMS = "android.permission.RECEIVE_SMS"; field public static final String RECEIVE_WAP_PUSH = "android.permission.RECEIVE_WAP_PUSH"; field public static final String RECORD_AUDIO = "android.permission.RECORD_AUDIO"; - field public static final String RECORD_BACKGROUND_AUDIO = "android.permission.RECORD_BACKGROUND_AUDIO"; field public static final String REORDER_TASKS = "android.permission.REORDER_TASKS"; field public static final String REQUEST_COMPANION_PROFILE_WATCH = "android.permission.REQUEST_COMPANION_PROFILE_WATCH"; field public static final String REQUEST_COMPANION_RUN_IN_BACKGROUND = "android.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND"; @@ -6270,14 +6268,14 @@ package android.app { method public static android.app.PendingIntent getActivities(android.content.Context, int, @NonNull android.content.Intent[], int, @Nullable android.os.Bundle); method public static android.app.PendingIntent getActivity(android.content.Context, int, android.content.Intent, int); method public static android.app.PendingIntent getActivity(android.content.Context, int, @NonNull android.content.Intent, int, @Nullable android.os.Bundle); - method public static android.app.PendingIntent getBroadcast(android.content.Context, int, android.content.Intent, int); - method @Nullable public String getCreatorPackage(); + method public static android.app.PendingIntent getBroadcast(android.content.Context, int, @NonNull android.content.Intent, int); + method @NonNull public String getCreatorPackage(); method public int getCreatorUid(); - method @Nullable public android.os.UserHandle getCreatorUserHandle(); + method @NonNull public android.os.UserHandle getCreatorUserHandle(); method public static android.app.PendingIntent getForegroundService(android.content.Context, int, @NonNull android.content.Intent, int); - method public android.content.IntentSender getIntentSender(); + method @NonNull public android.content.IntentSender getIntentSender(); method public static android.app.PendingIntent getService(android.content.Context, int, @NonNull android.content.Intent, int); - method @Deprecated public String getTargetPackage(); + method @Deprecated @NonNull public String getTargetPackage(); method public boolean isActivity(); method public boolean isBroadcast(); method public boolean isForegroundService(); @@ -9741,7 +9739,7 @@ package android.companion { public interface DeviceFilter<D extends android.os.Parcelable> extends android.os.Parcelable { } - public class DeviceNotAssociatedException extends java.lang.Exception { + public class DeviceNotAssociatedException extends java.lang.RuntimeException { } public final class WifiDeviceFilter implements android.companion.DeviceFilter<android.net.wifi.ScanResult> { @@ -10389,7 +10387,7 @@ package android.content { method public abstract void grantUriPermission(String, android.net.Uri, int); method public abstract boolean isDeviceProtectedStorage(); method public boolean isRestricted(); - method public static boolean isUiContext(@NonNull android.content.Context); + method public boolean isUiContext(); method public abstract boolean moveDatabaseFrom(android.content.Context, String); method public abstract boolean moveSharedPreferencesFrom(android.content.Context, String); method @NonNull public final android.content.res.TypedArray obtainStyledAttributes(@NonNull @StyleableRes int[]); @@ -12563,7 +12561,6 @@ package android.content.pm { field public static final String FEATURE_WIFI_DIRECT = "android.hardware.wifi.direct"; field public static final String FEATURE_WIFI_PASSPOINT = "android.hardware.wifi.passpoint"; field public static final String FEATURE_WIFI_RTT = "android.hardware.wifi.rtt"; - field public static final int FLAG_PERMISSION_ALLOWLIST_ROLE = 8; // 0x8 field public static final int FLAG_PERMISSION_WHITELIST_INSTALLER = 2; // 0x2 field public static final int FLAG_PERMISSION_WHITELIST_SYSTEM = 1; // 0x1 field public static final int FLAG_PERMISSION_WHITELIST_UPGRADE = 4; // 0x4 @@ -12702,7 +12699,6 @@ package android.content.pm { field public static final int FLAG_HARD_RESTRICTED = 4; // 0x4 field public static final int FLAG_IMMUTABLY_RESTRICTED = 16; // 0x10 field public static final int FLAG_INSTALLED = 1073741824; // 0x40000000 - field public static final int FLAG_INSTALLER_EXEMPT_IGNORED = 32; // 0x20 field public static final int FLAG_SOFT_RESTRICTED = 8; // 0x8 field public static final int PROTECTION_DANGEROUS = 1; // 0x1 field public static final int PROTECTION_FLAG_APPOP = 64; // 0x40 @@ -18896,6 +18892,7 @@ package android.inputmethodservice { public abstract class AbstractInputMethodService extends android.app.Service implements android.view.KeyEvent.Callback { ctor public AbstractInputMethodService(); method public android.view.KeyEvent.DispatcherState getKeyDispatcherState(); + method public final boolean isUiContext(); method public final android.os.IBinder onBind(android.content.Intent); method public abstract android.inputmethodservice.AbstractInputMethodService.AbstractInputMethodImpl onCreateInputMethodInterface(); method public abstract android.inputmethodservice.AbstractInputMethodService.AbstractInputMethodSessionImpl onCreateInputMethodSessionInterface(); @@ -19661,6 +19658,7 @@ package android.location { method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void addProximityAlert(double, double, float, long, @NonNull android.app.PendingIntent); method public void addTestProvider(@NonNull String, boolean, boolean, boolean, boolean, boolean, boolean, boolean, int, int); method public void addTestProvider(@NonNull String, @NonNull android.location.provider.ProviderProperties); + method public void addTestProvider(@NonNull String, @NonNull android.location.provider.ProviderProperties, @NonNull java.util.Set<java.lang.String>); method @Deprecated public void clearTestProviderEnabled(@NonNull String); method @Deprecated public void clearTestProviderLocation(@NonNull String); method @Deprecated public void clearTestProviderStatus(@NonNull String); @@ -22149,6 +22147,14 @@ package android.media { field public static final String KEY_TILE_HEIGHT = "tile-height"; field public static final String KEY_TILE_WIDTH = "tile-width"; field public static final String KEY_TRACK_ID = "track-id"; + field public static final String KEY_VIDEO_QP_B_MAX = "video-qp-b-max"; + field public static final String KEY_VIDEO_QP_B_MIN = "video-qp-b-min"; + field public static final String KEY_VIDEO_QP_I_MAX = "video-qp-i-max"; + field public static final String KEY_VIDEO_QP_I_MIN = "video-qp-i-min"; + field public static final String KEY_VIDEO_QP_MAX = "video-qp-max"; + field public static final String KEY_VIDEO_QP_MIN = "video-qp-min"; + field public static final String KEY_VIDEO_QP_P_MAX = "video-qp-p-max"; + field public static final String KEY_VIDEO_QP_P_MIN = "video-qp-p-min"; field public static final String KEY_WIDTH = "width"; field public static final String MIMETYPE_AUDIO_AAC = "audio/mp4a-latm"; field public static final String MIMETYPE_AUDIO_AC3 = "audio/ac3"; @@ -25817,161 +25823,6 @@ package android.mtp { package android.net { - public class CaptivePortal implements android.os.Parcelable { - method public int describeContents(); - method public void ignoreNetwork(); - method public void reportCaptivePortalDismissed(); - method public void writeToParcel(android.os.Parcel, int); - 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); - } - - public abstract static class ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback { - ctor public ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback(); - method public void onConnectivityReportAvailable(@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 final class ConnectivityDiagnosticsManager.ConnectivityReport implements android.os.Parcelable { - ctor public ConnectivityDiagnosticsManager.ConnectivityReport(@NonNull android.net.Network, long, @NonNull android.net.LinkProperties, @NonNull android.net.NetworkCapabilities, @NonNull android.os.PersistableBundle); - method public int describeContents(); - method @NonNull public android.os.PersistableBundle getAdditionalInfo(); - method @NonNull public android.net.LinkProperties getLinkProperties(); - method @NonNull public android.net.Network getNetwork(); - method @NonNull public android.net.NetworkCapabilities getNetworkCapabilities(); - method public long getReportTimestamp(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.ConnectivityDiagnosticsManager.ConnectivityReport> CREATOR; - field public static final String KEY_NETWORK_PROBES_ATTEMPTED_BITMASK = "networkProbesAttempted"; - field public static final String KEY_NETWORK_PROBES_SUCCEEDED_BITMASK = "networkProbesSucceeded"; - field public static final String KEY_NETWORK_VALIDATION_RESULT = "networkValidationResult"; - field public static final int NETWORK_PROBE_DNS = 4; // 0x4 - field public static final int NETWORK_PROBE_FALLBACK = 32; // 0x20 - field public static final int NETWORK_PROBE_HTTP = 8; // 0x8 - field public static final int NETWORK_PROBE_HTTPS = 16; // 0x10 - field public static final int NETWORK_PROBE_PRIVATE_DNS = 64; // 0x40 - field public static final int NETWORK_VALIDATION_RESULT_INVALID = 0; // 0x0 - field public static final int NETWORK_VALIDATION_RESULT_PARTIALLY_VALID = 2; // 0x2 - field public static final int NETWORK_VALIDATION_RESULT_SKIPPED = 3; // 0x3 - field public static final int NETWORK_VALIDATION_RESULT_VALID = 1; // 0x1 - } - - public static final class ConnectivityDiagnosticsManager.DataStallReport implements android.os.Parcelable { - ctor public ConnectivityDiagnosticsManager.DataStallReport(@NonNull android.net.Network, long, int, @NonNull android.net.LinkProperties, @NonNull android.net.NetworkCapabilities, @NonNull android.os.PersistableBundle); - method public int describeContents(); - method public int getDetectionMethod(); - method @NonNull public android.net.LinkProperties getLinkProperties(); - method @NonNull public android.net.Network getNetwork(); - method @NonNull public android.net.NetworkCapabilities getNetworkCapabilities(); - method public long getReportTimestamp(); - method @NonNull public android.os.PersistableBundle getStallDetails(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.ConnectivityDiagnosticsManager.DataStallReport> CREATOR; - field public static final int DETECTION_METHOD_DNS_EVENTS = 1; // 0x1 - field public static final int DETECTION_METHOD_TCP_METRICS = 2; // 0x2 - field public static final String KEY_DNS_CONSECUTIVE_TIMEOUTS = "dnsConsecutiveTimeouts"; - field public static final String KEY_TCP_METRICS_COLLECTION_PERIOD_MILLIS = "tcpMetricsCollectionPeriodMillis"; - field public static final String KEY_TCP_PACKET_FAIL_RATE = "tcpPacketFailRate"; - } - - public class ConnectivityManager { - method public void addDefaultNetworkActiveListener(android.net.ConnectivityManager.OnNetworkActiveListener); - method public boolean bindProcessToNetwork(@Nullable android.net.Network); - method @NonNull public android.net.SocketKeepalive createSocketKeepalive(@NonNull android.net.Network, @NonNull android.net.IpSecManager.UdpEncapsulationSocket, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback); - method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.Network getActiveNetwork(); - method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo getActiveNetworkInfo(); - method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo[] getAllNetworkInfo(); - method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.Network[] getAllNetworks(); - method @Deprecated public boolean getBackgroundDataSetting(); - method @Nullable public android.net.Network getBoundNetworkForProcess(); - method public int getConnectionOwnerUid(int, @NonNull java.net.InetSocketAddress, @NonNull java.net.InetSocketAddress); - method @Nullable public android.net.ProxyInfo getDefaultProxy(); - method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.LinkProperties getLinkProperties(@Nullable android.net.Network); - method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public int getMultipathPreference(@Nullable android.net.Network); - method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkCapabilities getNetworkCapabilities(@Nullable android.net.Network); - method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo getNetworkInfo(int); - method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo getNetworkInfo(@Nullable android.net.Network); - method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public int getNetworkPreference(); - method @Nullable public byte[] getNetworkWatchlistConfigHash(); - method @Deprecated @Nullable public static android.net.Network getProcessDefaultNetwork(); - method public int getRestrictBackgroundStatus(); - method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public boolean isActiveNetworkMetered(); - method public boolean isDefaultNetworkActive(); - method @Deprecated public static boolean isNetworkTypeValid(int); - method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback); - method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); - method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerNetworkCallback(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback); - method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerNetworkCallback(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); - method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerNetworkCallback(@NonNull android.net.NetworkRequest, @NonNull android.app.PendingIntent); - method public void releaseNetworkRequest(@NonNull android.app.PendingIntent); - method public void removeDefaultNetworkActiveListener(@NonNull android.net.ConnectivityManager.OnNetworkActiveListener); - method @Deprecated public void reportBadNetwork(@Nullable android.net.Network); - method public void reportNetworkConnectivity(@Nullable android.net.Network, boolean); - method public boolean requestBandwidthUpdate(@NonNull android.net.Network); - method public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback); - method public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); - method public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, int); - method public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler, int); - method public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.app.PendingIntent); - method @Deprecated public void setNetworkPreference(int); - method @Deprecated public static boolean setProcessDefaultNetwork(@Nullable android.net.Network); - method public void unregisterNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback); - method public void unregisterNetworkCallback(@NonNull android.app.PendingIntent); - field @Deprecated public static final String ACTION_BACKGROUND_DATA_SETTING_CHANGED = "android.net.conn.BACKGROUND_DATA_SETTING_CHANGED"; - field public static final String ACTION_CAPTIVE_PORTAL_SIGN_IN = "android.net.conn.CAPTIVE_PORTAL"; - field public static final String ACTION_RESTRICT_BACKGROUND_CHANGED = "android.net.conn.RESTRICT_BACKGROUND_CHANGED"; - field @Deprecated public static final String CONNECTIVITY_ACTION = "android.net.conn.CONNECTIVITY_CHANGE"; - field @Deprecated public static final int DEFAULT_NETWORK_PREFERENCE = 1; // 0x1 - field public static final String EXTRA_CAPTIVE_PORTAL = "android.net.extra.CAPTIVE_PORTAL"; - field public static final String EXTRA_CAPTIVE_PORTAL_URL = "android.net.extra.CAPTIVE_PORTAL_URL"; - field @Deprecated public static final String EXTRA_EXTRA_INFO = "extraInfo"; - field @Deprecated public static final String EXTRA_IS_FAILOVER = "isFailover"; - field public static final String EXTRA_NETWORK = "android.net.extra.NETWORK"; - field @Deprecated public static final String EXTRA_NETWORK_INFO = "networkInfo"; - field public static final String EXTRA_NETWORK_REQUEST = "android.net.extra.NETWORK_REQUEST"; - field @Deprecated public static final String EXTRA_NETWORK_TYPE = "networkType"; - field public static final String EXTRA_NO_CONNECTIVITY = "noConnectivity"; - field @Deprecated public static final String EXTRA_OTHER_NETWORK_INFO = "otherNetwork"; - field public static final String EXTRA_REASON = "reason"; - field public static final int MULTIPATH_PREFERENCE_HANDOVER = 1; // 0x1 - field public static final int MULTIPATH_PREFERENCE_PERFORMANCE = 4; // 0x4 - field public static final int MULTIPATH_PREFERENCE_RELIABILITY = 2; // 0x2 - field public static final int RESTRICT_BACKGROUND_STATUS_DISABLED = 1; // 0x1 - field public static final int RESTRICT_BACKGROUND_STATUS_ENABLED = 3; // 0x3 - field public static final int RESTRICT_BACKGROUND_STATUS_WHITELISTED = 2; // 0x2 - field @Deprecated public static final int TYPE_BLUETOOTH = 7; // 0x7 - field @Deprecated public static final int TYPE_DUMMY = 8; // 0x8 - field @Deprecated public static final int TYPE_ETHERNET = 9; // 0x9 - field @Deprecated public static final int TYPE_MOBILE = 0; // 0x0 - field @Deprecated public static final int TYPE_MOBILE_DUN = 4; // 0x4 - field @Deprecated public static final int TYPE_MOBILE_HIPRI = 5; // 0x5 - field @Deprecated public static final int TYPE_MOBILE_MMS = 2; // 0x2 - field @Deprecated public static final int TYPE_MOBILE_SUPL = 3; // 0x3 - field @Deprecated public static final int TYPE_VPN = 17; // 0x11 - field @Deprecated public static final int TYPE_WIFI = 1; // 0x1 - field @Deprecated public static final int TYPE_WIMAX = 6; // 0x6 - } - - public static class ConnectivityManager.NetworkCallback { - ctor public ConnectivityManager.NetworkCallback(); - method public void onAvailable(@NonNull android.net.Network); - method public void onBlockedStatusChanged(@NonNull android.net.Network, boolean); - method public void onCapabilitiesChanged(@NonNull android.net.Network, @NonNull android.net.NetworkCapabilities); - method public void onLinkPropertiesChanged(@NonNull android.net.Network, @NonNull android.net.LinkProperties); - method public void onLosing(@NonNull android.net.Network, int); - method public void onLost(@NonNull android.net.Network); - method public void onUnavailable(); - } - - public static interface ConnectivityManager.OnNetworkActiveListener { - method public void onNetworkActive(); - } - public class Credentials { ctor public Credentials(int, int, int); method public int getGid(); @@ -25979,46 +25830,6 @@ package android.net { method public int getUid(); } - public class DhcpInfo implements android.os.Parcelable { - 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; - field public int ipAddress; - field public int leaseDuration; - field public int netmask; - field public int serverAddress; - } - - public final class DnsResolver { - method @NonNull public static android.net.DnsResolver getInstance(); - method public void query(@Nullable android.net.Network, @NonNull String, int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.Callback<? super java.util.List<java.net.InetAddress>>); - method public void query(@Nullable android.net.Network, @NonNull String, int, int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.Callback<? super java.util.List<java.net.InetAddress>>); - method public void rawQuery(@Nullable android.net.Network, @NonNull byte[], int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.Callback<? super byte[]>); - method public void rawQuery(@Nullable android.net.Network, @NonNull String, int, int, int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.Callback<? super byte[]>); - field public static final int CLASS_IN = 1; // 0x1 - field public static final int ERROR_PARSE = 0; // 0x0 - field public static final int ERROR_SYSTEM = 1; // 0x1 - field public static final int FLAG_EMPTY = 0; // 0x0 - field public static final int FLAG_NO_CACHE_LOOKUP = 4; // 0x4 - field public static final int FLAG_NO_CACHE_STORE = 2; // 0x2 - field public static final int FLAG_NO_RETRY = 1; // 0x1 - field public static final int TYPE_A = 1; // 0x1 - field public static final int TYPE_AAAA = 28; // 0x1c - } - - public static interface DnsResolver.Callback<T> { - method public void onAnswer(@NonNull T, int); - method public void onError(@NonNull android.net.DnsResolver.DnsException); - } - - public static class DnsResolver.DnsException extends java.lang.Exception { - field public final int code; - } - public final class Ikev2VpnProfile extends android.net.PlatformVpnProfile { method @NonNull public java.util.List<java.lang.String> getAllowedAlgorithms(); method public int getMaxMtu(); @@ -26048,21 +25859,6 @@ package android.net { method @NonNull public android.net.Ikev2VpnProfile.Builder setProxy(@Nullable android.net.ProxyInfo); } - public class InetAddresses { - method public static boolean isNumericAddress(@NonNull String); - method @NonNull public static java.net.InetAddress parseNumericAddress(@NonNull String); - } - - public final class IpPrefix implements android.os.Parcelable { - method public boolean contains(@NonNull java.net.InetAddress); - method public int describeContents(); - method @NonNull public java.net.InetAddress getAddress(); - method @IntRange(from=0, to=128) public int getPrefixLength(); - method @NonNull public byte[] getRawAddress(); - method public void writeToParcel(android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.IpPrefix> CREATOR; - } - public final class IpSecAlgorithm implements android.os.Parcelable { ctor public IpSecAlgorithm(@NonNull String, @NonNull byte[]); ctor public IpSecAlgorithm(@NonNull String, @NonNull byte[], int); @@ -26131,45 +25927,6 @@ package android.net { method @NonNull public android.net.IpSecTransform.Builder setIpv4Encapsulation(@NonNull android.net.IpSecManager.UdpEncapsulationSocket, int); } - public class LinkAddress implements android.os.Parcelable { - method public int describeContents(); - method public java.net.InetAddress getAddress(); - method public int getFlags(); - method @IntRange(from=0, to=128) public int getPrefixLength(); - method public int getScope(); - method public void writeToParcel(android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.LinkAddress> CREATOR; - } - - public final class LinkProperties implements android.os.Parcelable { - ctor public LinkProperties(); - method public boolean addRoute(@NonNull android.net.RouteInfo); - method public void clear(); - method public int describeContents(); - method @Nullable public java.net.Inet4Address getDhcpServerAddress(); - method @NonNull public java.util.List<java.net.InetAddress> getDnsServers(); - method @Nullable public String getDomains(); - method @Nullable public android.net.ProxyInfo getHttpProxy(); - method @Nullable public String getInterfaceName(); - method @NonNull public java.util.List<android.net.LinkAddress> getLinkAddresses(); - method public int getMtu(); - method @Nullable public android.net.IpPrefix getNat64Prefix(); - method @Nullable public String getPrivateDnsServerName(); - method @NonNull public java.util.List<android.net.RouteInfo> getRoutes(); - method public boolean isPrivateDnsActive(); - method public boolean isWakeOnLanSupported(); - method public void setDhcpServerAddress(@Nullable java.net.Inet4Address); - method public void setDnsServers(@NonNull java.util.Collection<java.net.InetAddress>); - method public void setDomains(@Nullable String); - method public void setHttpProxy(@Nullable android.net.ProxyInfo); - method public void setInterfaceName(@Nullable String); - method public void setLinkAddresses(@NonNull java.util.Collection<android.net.LinkAddress>); - method public void setMtu(int); - method public void setNat64Prefix(@Nullable android.net.IpPrefix); - method public void writeToParcel(android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.LinkProperties> CREATOR; - } - public class LocalServerSocket implements java.io.Closeable { ctor public LocalServerSocket(String) throws java.io.IOException; ctor public LocalServerSocket(java.io.FileDescriptor) throws java.io.IOException; @@ -26225,24 +25982,6 @@ package android.net { enum_constant public static final android.net.LocalSocketAddress.Namespace RESERVED; } - public final class MacAddress implements android.os.Parcelable { - method public int describeContents(); - method @NonNull public static android.net.MacAddress fromBytes(@NonNull byte[]); - method @NonNull public static android.net.MacAddress fromString(@NonNull String); - method public int getAddressType(); - method @Nullable public java.net.Inet6Address getLinkLocalIpv6FromEui48Mac(); - method public boolean isLocallyAssigned(); - method public boolean matches(@NonNull android.net.MacAddress, @NonNull android.net.MacAddress); - method @NonNull public byte[] toByteArray(); - method @NonNull public String toOuiString(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.net.MacAddress BROADCAST_ADDRESS; - field @NonNull public static final android.os.Parcelable.Creator<android.net.MacAddress> CREATOR; - field public static final int TYPE_BROADCAST = 3; // 0x3 - field public static final int TYPE_MULTICAST = 2; // 0x2 - field public static final int TYPE_UNICAST = 1; // 0x1 - } - public class MailTo { method public String getBody(); method public String getCc(); @@ -26254,139 +25993,6 @@ package android.net { field public static final String MAILTO_SCHEME = "mailto:"; } - public class Network implements android.os.Parcelable { - method public void bindSocket(java.net.DatagramSocket) throws java.io.IOException; - method public void bindSocket(java.net.Socket) throws java.io.IOException; - method public void bindSocket(java.io.FileDescriptor) throws java.io.IOException; - method public int describeContents(); - method public static android.net.Network fromNetworkHandle(long); - method public java.net.InetAddress[] getAllByName(String) throws java.net.UnknownHostException; - method public java.net.InetAddress getByName(String) throws java.net.UnknownHostException; - method public long getNetworkHandle(); - method public javax.net.SocketFactory getSocketFactory(); - method public java.net.URLConnection openConnection(java.net.URL) throws java.io.IOException; - method public java.net.URLConnection openConnection(java.net.URL, java.net.Proxy) throws java.io.IOException; - method public void writeToParcel(android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.Network> CREATOR; - } - - public final class NetworkCapabilities implements android.os.Parcelable { - ctor public NetworkCapabilities(); - ctor public NetworkCapabilities(android.net.NetworkCapabilities); - method public int describeContents(); - method public int getLinkDownstreamBandwidthKbps(); - method public int getLinkUpstreamBandwidthKbps(); - method @Nullable public android.net.NetworkSpecifier getNetworkSpecifier(); - method public int getOwnerUid(); - method public int getSignalStrength(); - method @Nullable public android.net.TransportInfo getTransportInfo(); - method public boolean hasCapability(int); - method public boolean hasTransport(int); - method public void writeToParcel(android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkCapabilities> CREATOR; - field public static final int NET_CAPABILITY_CAPTIVE_PORTAL = 17; // 0x11 - field public static final int NET_CAPABILITY_CBS = 5; // 0x5 - field public static final int NET_CAPABILITY_DUN = 2; // 0x2 - field public static final int NET_CAPABILITY_EIMS = 10; // 0xa - field public static final int NET_CAPABILITY_ENTERPRISE = 29; // 0x1d - field public static final int NET_CAPABILITY_FOREGROUND = 19; // 0x13 - field public static final int NET_CAPABILITY_FOTA = 3; // 0x3 - field public static final int NET_CAPABILITY_IA = 7; // 0x7 - field public static final int NET_CAPABILITY_IMS = 4; // 0x4 - field public static final int NET_CAPABILITY_INTERNET = 12; // 0xc - field public static final int NET_CAPABILITY_MCX = 23; // 0x17 - field public static final int NET_CAPABILITY_MMS = 0; // 0x0 - field public static final int NET_CAPABILITY_NOT_CONGESTED = 20; // 0x14 - field public static final int NET_CAPABILITY_NOT_METERED = 11; // 0xb - field public static final int NET_CAPABILITY_NOT_RESTRICTED = 13; // 0xd - field public static final int NET_CAPABILITY_NOT_ROAMING = 18; // 0x12 - field public static final int NET_CAPABILITY_NOT_SUSPENDED = 21; // 0x15 - field public static final int NET_CAPABILITY_NOT_VPN = 15; // 0xf - field public static final int NET_CAPABILITY_RCS = 8; // 0x8 - field public static final int NET_CAPABILITY_SUPL = 1; // 0x1 - field public static final int NET_CAPABILITY_TEMPORARILY_NOT_METERED = 25; // 0x19 - field public static final int NET_CAPABILITY_TRUSTED = 14; // 0xe - field public static final int NET_CAPABILITY_VALIDATED = 16; // 0x10 - field public static final int NET_CAPABILITY_WIFI_P2P = 6; // 0x6 - field public static final int NET_CAPABILITY_XCAP = 9; // 0x9 - field public static final int SIGNAL_STRENGTH_UNSPECIFIED = -2147483648; // 0x80000000 - field public static final int TRANSPORT_BLUETOOTH = 2; // 0x2 - field public static final int TRANSPORT_CELLULAR = 0; // 0x0 - field public static final int TRANSPORT_ETHERNET = 3; // 0x3 - field public static final int TRANSPORT_LOWPAN = 6; // 0x6 - field public static final int TRANSPORT_VPN = 4; // 0x4 - field public static final int TRANSPORT_WIFI = 1; // 0x1 - field public static final int TRANSPORT_WIFI_AWARE = 5; // 0x5 - } - - @Deprecated public class NetworkInfo implements android.os.Parcelable { - ctor @Deprecated public NetworkInfo(int, int, @Nullable String, @Nullable String); - method @Deprecated public int describeContents(); - method @Deprecated @NonNull public android.net.NetworkInfo.DetailedState getDetailedState(); - method @Deprecated public String getExtraInfo(); - method @Deprecated public String getReason(); - method @Deprecated public android.net.NetworkInfo.State getState(); - method @Deprecated public int getSubtype(); - method @Deprecated public String getSubtypeName(); - method @Deprecated public int getType(); - method @Deprecated public String getTypeName(); - method @Deprecated public boolean isAvailable(); - method @Deprecated public boolean isConnected(); - method @Deprecated public boolean isConnectedOrConnecting(); - method @Deprecated public boolean isFailover(); - method @Deprecated public boolean isRoaming(); - method @Deprecated public void setDetailedState(@NonNull android.net.NetworkInfo.DetailedState, @Nullable String, @Nullable String); - method @Deprecated public void writeToParcel(android.os.Parcel, int); - field @Deprecated @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkInfo> CREATOR; - } - - @Deprecated public enum NetworkInfo.DetailedState { - enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState AUTHENTICATING; - enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState BLOCKED; - enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState CAPTIVE_PORTAL_CHECK; - enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState CONNECTED; - enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState CONNECTING; - enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState DISCONNECTED; - enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState DISCONNECTING; - enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState FAILED; - enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState IDLE; - enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState OBTAINING_IPADDR; - enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState SCANNING; - enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState SUSPENDED; - enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState VERIFYING_POOR_LINK; - } - - @Deprecated public enum NetworkInfo.State { - enum_constant @Deprecated public static final android.net.NetworkInfo.State CONNECTED; - enum_constant @Deprecated public static final android.net.NetworkInfo.State CONNECTING; - enum_constant @Deprecated public static final android.net.NetworkInfo.State DISCONNECTED; - enum_constant @Deprecated public static final android.net.NetworkInfo.State DISCONNECTING; - enum_constant @Deprecated public static final android.net.NetworkInfo.State SUSPENDED; - enum_constant @Deprecated public static final android.net.NetworkInfo.State UNKNOWN; - } - - public class NetworkRequest implements android.os.Parcelable { - method public boolean canBeSatisfiedBy(@Nullable android.net.NetworkCapabilities); - method public int describeContents(); - method @Nullable public android.net.NetworkSpecifier getNetworkSpecifier(); - method public boolean hasCapability(int); - method public boolean hasTransport(int); - method public void writeToParcel(android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkRequest> CREATOR; - } - - public static class NetworkRequest.Builder { - ctor public NetworkRequest.Builder(); - 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 @Deprecated public android.net.NetworkRequest.Builder setNetworkSpecifier(String); - method public android.net.NetworkRequest.Builder setNetworkSpecifier(android.net.NetworkSpecifier); - } - public abstract class NetworkSpecifier { ctor public NetworkSpecifier(); } @@ -26403,44 +26009,6 @@ package android.net { field public static final int TYPE_IKEV2_IPSEC_USER_PASS = 6; // 0x6 } - public final class Proxy { - ctor public Proxy(); - method @Deprecated public static String getDefaultHost(); - method @Deprecated public static int getDefaultPort(); - method @Deprecated public static String getHost(android.content.Context); - method @Deprecated public static int getPort(android.content.Context); - field @Deprecated public static final String EXTRA_PROXY_INFO = "android.intent.extra.PROXY_INFO"; - field public static final String PROXY_CHANGE_ACTION = "android.intent.action.PROXY_CHANGE"; - } - - public class ProxyInfo implements android.os.Parcelable { - ctor public ProxyInfo(@Nullable android.net.ProxyInfo); - method public static android.net.ProxyInfo buildDirectProxy(String, int); - method public static android.net.ProxyInfo buildDirectProxy(String, int, java.util.List<java.lang.String>); - method public static android.net.ProxyInfo buildPacProxy(android.net.Uri); - method @NonNull public static android.net.ProxyInfo buildPacProxy(@NonNull android.net.Uri, int); - method public int describeContents(); - method public String[] getExclusionList(); - method public String getHost(); - method public android.net.Uri getPacFileUrl(); - method public int getPort(); - method public boolean isValid(); - method public void writeToParcel(android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.ProxyInfo> CREATOR; - } - - public final class RouteInfo implements android.os.Parcelable { - method public int describeContents(); - method @NonNull public android.net.IpPrefix getDestination(); - method @Nullable public java.net.InetAddress getGateway(); - method @Nullable public String getInterface(); - method public boolean hasGateway(); - method public boolean isDefaultRoute(); - method public boolean matches(java.net.InetAddress); - method public void writeToParcel(android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.RouteInfo> CREATOR; - } - @Deprecated public class SSLCertificateSocketFactory extends javax.net.ssl.SSLSocketFactory { ctor @Deprecated public SSLCertificateSocketFactory(int); method @Deprecated public java.net.Socket createSocket(java.net.Socket, String, int, boolean) throws java.io.IOException; @@ -26466,30 +26034,6 @@ package android.net { ctor public SSLSessionCache(android.content.Context); } - public abstract class SocketKeepalive implements java.lang.AutoCloseable { - method public final void close(); - method public final void start(@IntRange(from=0xa, to=0xe10) int); - method public final void stop(); - field public static final int ERROR_HARDWARE_ERROR = -31; // 0xffffffe1 - field public static final int ERROR_INSUFFICIENT_RESOURCES = -32; // 0xffffffe0 - field public static final int ERROR_INVALID_INTERVAL = -24; // 0xffffffe8 - field public static final int ERROR_INVALID_IP_ADDRESS = -21; // 0xffffffeb - field public static final int ERROR_INVALID_LENGTH = -23; // 0xffffffe9 - field public static final int ERROR_INVALID_NETWORK = -20; // 0xffffffec - field public static final int ERROR_INVALID_PORT = -22; // 0xffffffea - field public static final int ERROR_INVALID_SOCKET = -25; // 0xffffffe7 - field public static final int ERROR_SOCKET_NOT_IDLE = -26; // 0xffffffe6 - field public static final int ERROR_UNSUPPORTED = -30; // 0xffffffe2 - } - - public static class SocketKeepalive.Callback { - ctor public SocketKeepalive.Callback(); - method public void onDataReceived(); - method public void onError(int); - method public void onStarted(); - method public void onStopped(); - } - public final class TelephonyNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable { method public int describeContents(); method public int getSubscriptionId(); @@ -26547,9 +26091,6 @@ package android.net { field public static final int UNSUPPORTED = -1; // 0xffffffff } - public interface TransportInfo { - } - public abstract class Uri implements java.lang.Comparable<android.net.Uri> android.os.Parcelable { method public abstract android.net.Uri.Builder buildUpon(); method public int compareTo(android.net.Uri); @@ -31922,10 +31463,10 @@ package android.os { method public static android.content.Intent createUserCreationIntent(@Nullable String, @Nullable String, @Nullable String, @Nullable android.os.PersistableBundle); method @WorkerThread public android.os.Bundle getApplicationRestrictions(String); method public long getSerialNumberForUser(android.os.UserHandle); - method @RequiresPermission("android.permission.MANAGE_USERS") public int getUserCount(); + method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.CREATE_USERS"}) public int getUserCount(); method public long getUserCreationTime(android.os.UserHandle); method public android.os.UserHandle getUserForSerialNumber(long); - method @NonNull @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}, conditional=true) public String getUserName(); + method @NonNull @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED, "android.permission.CREATE_USERS"}, conditional=true) public String getUserName(); method public java.util.List<android.os.UserHandle> getUserProfiles(); method public android.os.Bundle getUserRestrictions(); method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public android.os.Bundle getUserRestrictions(android.os.UserHandle); @@ -37164,8 +36705,10 @@ package android.security { method @NonNull public static android.content.Intent createInstallIntent(); method @NonNull public static android.content.Intent createManageCredentialsIntent(@NonNull android.security.AppUriAuthenticationPolicy); method @Nullable @WorkerThread public static java.security.cert.X509Certificate[] getCertificateChain(@NonNull android.content.Context, @NonNull String) throws java.lang.InterruptedException, android.security.KeyChainException; + method @NonNull public static android.security.AppUriAuthenticationPolicy getCredentialManagementAppPolicy(@NonNull android.content.Context) throws java.lang.SecurityException; method @Nullable @WorkerThread public static java.security.PrivateKey getPrivateKey(@NonNull android.content.Context, @NonNull String) throws java.lang.InterruptedException, android.security.KeyChainException; method @Deprecated public static boolean isBoundKeyAlgorithm(@NonNull String); + method public static boolean isCredentialManagementApp(@NonNull android.content.Context); method public static boolean isKeyAlgorithmSupported(@NonNull String); field public static final String ACTION_KEYCHAIN_CHANGED = "android.security.action.KEYCHAIN_CHANGED"; field public static final String ACTION_KEY_ACCESS_CHANGED = "android.security.action.KEY_ACCESS_CHANGED"; @@ -40245,7 +39788,7 @@ package android.telecom { method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getVoiceMailNumber(android.telecom.PhoneAccountHandle); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean handleMmi(String); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean handleMmi(String, android.telecom.PhoneAccountHandle); - method public boolean hasCompanionInCallServiceAccess(); + method public boolean hasManageOngoingCallsPermission(); method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isInCall(); method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isInManagedCall(); method public boolean isIncomingCallPermitted(android.telecom.PhoneAccountHandle); @@ -40872,6 +40415,7 @@ package android.telephony { } public static final class CarrierConfigManager.Apn { + field @Deprecated public static final String KEY_PREFIX = "apn."; field public static final String KEY_SETTINGS_DEFAULT_PROTOCOL_STRING = "apn.settings_default_protocol_string"; field public static final String KEY_SETTINGS_DEFAULT_ROAMING_PROTOCOL_STRING = "apn.settings_default_roaming_protocol_string"; field public static final String PROTOCOL_IPV4 = "IP"; @@ -40896,7 +40440,7 @@ package android.telephony { } public static final class CarrierConfigManager.ImsServiceEntitlement { - field public static final String KEY_AES_URL_STRING = "imsserviceentitlement.aes_url_string"; + field public static final String KEY_ENTITLEMENT_SERVER_URL_STRING = "imsserviceentitlement.entitlement_server_url_string"; field public static final String KEY_PREFIX = "imsserviceentitlement."; } @@ -42124,7 +41668,6 @@ package android.telephony { public final class SignalStrengthUpdateRequest implements android.os.Parcelable { method public int describeContents(); - method @NonNull public android.os.IBinder getLiveToken(); method @NonNull public java.util.Collection<android.telephony.SignalThresholdInfo> getSignalThresholdInfos(); method public boolean isReportingRequestedWhileIdle(); method public void writeToParcel(@NonNull android.os.Parcel, int); @@ -48025,16 +47568,47 @@ package android.view { method public void onScaleEnd(android.view.ScaleGestureDetector); } + @UiThread public interface ScrollCaptureCallback { + method public void onScrollCaptureEnd(@NonNull Runnable); + method public void onScrollCaptureImageRequest(@NonNull android.view.ScrollCaptureSession, @NonNull android.os.CancellationSignal, @NonNull android.graphics.Rect, @NonNull java.util.function.Consumer<android.graphics.Rect>); + method public void onScrollCaptureSearch(@NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.graphics.Rect>); + method public void onScrollCaptureStart(@NonNull android.view.ScrollCaptureSession, @NonNull android.os.CancellationSignal, @NonNull Runnable); + } + + public class ScrollCaptureSession { + ctor public ScrollCaptureSession(@NonNull android.view.Surface, @NonNull android.graphics.Rect, @NonNull android.graphics.Point); + method @NonNull public android.graphics.Point getPositionInWindow(); + method @NonNull public android.graphics.Rect getScrollBounds(); + method @NonNull public android.view.Surface getSurface(); + } + + public final class ScrollCaptureTarget { + ctor public ScrollCaptureTarget(@NonNull android.view.View, @NonNull android.graphics.Rect, @NonNull android.graphics.Point, @NonNull android.view.ScrollCaptureCallback); + method @NonNull public android.view.ScrollCaptureCallback getCallback(); + method @NonNull public android.view.View getContainingView(); + method public int getHint(); + method @NonNull public android.graphics.Rect getLocalVisibleRect(); + method @NonNull public android.graphics.Point getPositionInWindow(); + method @Nullable public android.graphics.Rect getScrollBounds(); + method public void setScrollBounds(@Nullable android.graphics.Rect); + method @UiThread public void updatePositionInWindow(); + } + public class SearchEvent { ctor public SearchEvent(android.view.InputDevice); method public android.view.InputDevice getInputDevice(); } public class SoundEffectConstants { + method public static int getConstantForFocusDirection(int, boolean); method public static int getContantForFocusDirection(int); field public static final int CLICK = 0; // 0x0 field public static final int NAVIGATION_DOWN = 4; // 0x4 field public static final int NAVIGATION_LEFT = 1; // 0x1 + field public static final int NAVIGATION_REPEAT_DOWN = 8; // 0x8 + field public static final int NAVIGATION_REPEAT_LEFT = 5; // 0x5 + field public static final int NAVIGATION_REPEAT_RIGHT = 7; // 0x7 + field public static final int NAVIGATION_REPEAT_UP = 6; // 0x6 field public static final int NAVIGATION_RIGHT = 3; // 0x3 field public static final int NAVIGATION_UP = 2; // 0x2 } @@ -48346,6 +47920,7 @@ package android.view { method public void dispatchProvideStructure(android.view.ViewStructure); method protected void dispatchRestoreInstanceState(android.util.SparseArray<android.os.Parcelable>); method protected void dispatchSaveInstanceState(android.util.SparseArray<android.os.Parcelable>); + method public void dispatchScrollCaptureSearch(@NonNull android.graphics.Rect, @NonNull android.graphics.Point, @NonNull java.util.function.Consumer<android.view.ScrollCaptureTarget>); method protected void dispatchSetActivated(boolean); method protected void dispatchSetPressed(boolean); method protected void dispatchSetSelected(boolean); @@ -48505,6 +48080,7 @@ package android.view { method public int getScrollBarFadeDuration(); method public int getScrollBarSize(); method public int getScrollBarStyle(); + method public int getScrollCaptureHint(); method public int getScrollIndicators(); method public final int getScrollX(); method public final int getScrollY(); @@ -48673,6 +48249,7 @@ package android.view { method public void onRtlPropertiesChanged(int); method @CallSuper @Nullable protected android.os.Parcelable onSaveInstanceState(); method public void onScreenStateChanged(int); + method public void onScrollCaptureSearch(@NonNull android.graphics.Rect, @NonNull android.graphics.Point, @NonNull java.util.function.Consumer<android.view.ScrollCaptureTarget>); method protected void onScrollChanged(int, int, int, int); method protected boolean onSetAlpha(int); method protected void onSizeChanged(int, int, int, int); @@ -48855,6 +48432,8 @@ package android.view { method public void setScrollBarFadeDuration(int); method public void setScrollBarSize(int); method public void setScrollBarStyle(int); + method public final void setScrollCaptureCallback(@Nullable android.view.ScrollCaptureCallback); + method public void setScrollCaptureHint(int); method public void setScrollContainer(boolean); method public void setScrollIndicators(int); method public void setScrollIndicators(int, int); @@ -49033,6 +48612,10 @@ package android.view { field public static final int SCROLL_AXIS_HORIZONTAL = 1; // 0x1 field public static final int SCROLL_AXIS_NONE = 0; // 0x0 field public static final int SCROLL_AXIS_VERTICAL = 2; // 0x2 + field public static final int SCROLL_CAPTURE_HINT_AUTO = 0; // 0x0 + field public static final int SCROLL_CAPTURE_HINT_EXCLUDE = 1; // 0x1 + field public static final int SCROLL_CAPTURE_HINT_EXCLUDE_DESCENDANTS = 4; // 0x4 + field public static final int SCROLL_CAPTURE_HINT_INCLUDE = 2; // 0x2 field public static final int SCROLL_INDICATOR_BOTTOM = 2; // 0x2 field public static final int SCROLL_INDICATOR_END = 32; // 0x20 field public static final int SCROLL_INDICATOR_LEFT = 4; // 0x4 @@ -49817,6 +49400,7 @@ package android.view { method public abstract boolean performContextMenuIdentifierAction(int, int); method public abstract boolean performPanelIdentifierAction(int, int, int); method public abstract boolean performPanelShortcut(int, int, android.view.KeyEvent, int); + method public void registerScrollCaptureCallback(@NonNull android.view.ScrollCaptureCallback); method public final void removeOnFrameMetricsAvailableListener(android.view.Window.OnFrameMetricsAvailableListener); method public boolean requestFeature(int); method @NonNull public final <T extends android.view.View> T requireViewById(@IdRes int); @@ -49896,6 +49480,7 @@ package android.view { method public abstract void takeKeyEvents(boolean); method public abstract void takeSurface(android.view.SurfaceHolder.Callback2); method public abstract void togglePanel(int, android.view.KeyEvent); + method public void unregisterScrollCaptureCallback(@NonNull android.view.ScrollCaptureCallback); field public static final int DECOR_CAPTION_SHADE_AUTO = 0; // 0x0 field public static final int DECOR_CAPTION_SHADE_DARK = 2; // 0x2 field public static final int DECOR_CAPTION_SHADE_LIGHT = 1; // 0x1 diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index dd9582fddd4a..e6f0e4804655 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -163,67 +163,14 @@ package android.media.session { package android.net { - public final class ConnectivityFrameworkInitializer { - method public static void registerServiceWrappers(); - } - - public class ConnectivityManager { - method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); - method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @Nullable android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback); - method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle); - } - public static final class IpSecManager.UdpEncapsulationSocket implements java.lang.AutoCloseable { method public int getResourceId(); } - public final class NetworkAgentConfig implements android.os.Parcelable { - method @Nullable public String getSubscriberId(); - } - - public static final class NetworkAgentConfig.Builder { - method @NonNull public android.net.NetworkAgentConfig.Builder setSubscriberId(@Nullable String); - } - - public final class NetworkCapabilities implements android.os.Parcelable { - field public static final int TRANSPORT_TEST = 7; // 0x7 - } - public class NetworkWatchlistManager { method @Nullable public byte[] getWatchlistConfigHash(); } - public final class Proxy { - method public static void setHttpProxyConfiguration(@Nullable android.net.ProxyInfo); - } - - public final class TcpRepairWindow { - ctor public TcpRepairWindow(int, int, int, int, int, int); - field public final int maxWindow; - field public final int rcvWnd; - field public final int rcvWndScale; - field public final int rcvWup; - field public final int sndWl1; - field public final int sndWnd; - } - - public final class TestNetworkInterface implements android.os.Parcelable { - ctor public TestNetworkInterface(@NonNull android.os.ParcelFileDescriptor, @NonNull String); - method public int describeContents(); - method @NonNull public android.os.ParcelFileDescriptor getFileDescriptor(); - method @NonNull public String getInterfaceName(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.TestNetworkInterface> CREATOR; - } - - public class TestNetworkManager { - method @NonNull public android.net.TestNetworkInterface createTapInterface(); - method @NonNull public android.net.TestNetworkInterface createTunInterface(@NonNull java.util.Collection<android.net.LinkAddress>); - method public void setupTestNetwork(@NonNull String, @NonNull android.os.IBinder); - method public void teardownTestNetwork(@NonNull android.net.Network); - field public static final String TEST_TAP_PREFIX = "testtap"; - } - public final class UnderlyingNetworkInfo implements android.os.Parcelable { ctor public UnderlyingNetworkInfo(int, @NonNull String, @NonNull java.util.List<java.lang.String>); method public int describeContents(); @@ -234,14 +181,6 @@ package android.net { field @NonNull public final java.util.List<java.lang.String> underlyingIfaces; } - public final class VpnTransportInfo implements android.os.Parcelable android.net.TransportInfo { - ctor public VpnTransportInfo(int); - method public int describeContents(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.VpnTransportInfo> CREATOR; - field public final int type; - } - } package android.os { diff --git a/core/api/removed.txt b/core/api/removed.txt index 990388a54c85..a99178d51d77 100644 --- a/core/api/removed.txt +++ b/core/api/removed.txt @@ -241,12 +241,6 @@ package android.media.tv { package android.net { - public class ConnectivityManager { - method @Deprecated public boolean requestRouteToHost(int, int); - method @Deprecated public int startUsingNetworkFeature(int, String); - method @Deprecated public int stopUsingNetworkFeature(int, String); - } - @Deprecated public class NetworkBadging { method @NonNull public static android.graphics.drawable.Drawable getWifiIcon(@IntRange(from=0, to=4) int, int, @Nullable android.content.res.Resources.Theme); field public static final int BADGING_4K = 30; // 0x1e diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 17783b7ee0d0..c3d5d5a4eda7 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -27,6 +27,7 @@ package android { field public static final String ALLOW_ANY_CODEC_FOR_PLAYBACK = "android.permission.ALLOW_ANY_CODEC_FOR_PLAYBACK"; field public static final String AMBIENT_WALLPAPER = "android.permission.AMBIENT_WALLPAPER"; field public static final String APPROVE_INCIDENT_REPORTS = "android.permission.APPROVE_INCIDENT_REPORTS"; + field public static final String BACKGROUND_CAMERA = "android.permission.BACKGROUND_CAMERA"; field public static final String BACKUP = "android.permission.BACKUP"; field public static final String BATTERY_PREDICTION = "android.permission.BATTERY_PREDICTION"; field public static final String BIND_ATTENTION_SERVICE = "android.permission.BIND_ATTENTION_SERVICE"; @@ -176,6 +177,7 @@ package android { field public static final String OBSERVE_APP_USAGE = "android.permission.OBSERVE_APP_USAGE"; field public static final String OBSERVE_NETWORK_POLICY = "android.permission.OBSERVE_NETWORK_POLICY"; field public static final String OBSERVE_ROLE_HOLDERS = "android.permission.OBSERVE_ROLE_HOLDERS"; + field public static final String OBSERVE_SENSOR_PRIVACY = "android.permission.OBSERVE_SENSOR_PRIVACY"; field public static final String OPEN_ACCESSIBILITY_DETAILS_SETTINGS = "android.permission.OPEN_ACCESSIBILITY_DETAILS_SETTINGS"; field public static final String OVERRIDE_WIFI_CONFIG = "android.permission.OVERRIDE_WIFI_CONFIG"; field public static final String PACKAGE_VERIFICATION_AGENT = "android.permission.PACKAGE_VERIFICATION_AGENT"; @@ -214,6 +216,7 @@ package android { field public static final String RECEIVE_DEVICE_CUSTOMIZATION_READY = "android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY"; field public static final String RECEIVE_EMERGENCY_BROADCAST = "android.permission.RECEIVE_EMERGENCY_BROADCAST"; field public static final String RECEIVE_WIFI_CREDENTIAL_CHANGE = "android.permission.RECEIVE_WIFI_CREDENTIAL_CHANGE"; + field public static final String RECORD_BACKGROUND_AUDIO = "android.permission.RECORD_BACKGROUND_AUDIO"; field public static final String RECOVERY = "android.permission.RECOVERY"; field public static final String RECOVER_KEYSTORE = "android.permission.RECOVER_KEYSTORE"; field public static final String REGISTER_CALL_PROVIDER = "android.permission.REGISTER_CALL_PROVIDER"; @@ -352,6 +355,7 @@ package android { field public static final int config_systemContacts = 17039403; // 0x104002b field public static final int config_systemGallery = 17039399; // 0x1040027 field public static final int config_systemShell = 17039402; // 0x104002a + field public static final int config_systemSpeechRecognizer = 17039406; // 0x104002e } public static final class R.style { @@ -641,8 +645,9 @@ package android.app { method public static android.app.BroadcastOptions makeBasic(); method @RequiresPermission(android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND) public void setBackgroundActivityStartsAllowed(boolean); method public void setDontSendToRestrictedApps(boolean); - method @RequiresPermission(anyOf={android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}) public void setTemporaryAppWhitelistDuration(long); - method @RequiresPermission(anyOf={android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}) public void setTemporaryAppWhitelistDuration(int, long); + method @RequiresPermission(anyOf={android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}) public void setTemporaryAppAllowlist(long, int, int, @Nullable String); + method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}) public void setTemporaryAppWhitelistDuration(long); + method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}) public void setTemporaryAppWhitelistDuration(int, long); method public android.os.Bundle toBundle(); } @@ -884,7 +889,7 @@ package android.app.admin { public class DevicePolicyManager { method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public boolean getBluetoothContactSharingDisabled(@NonNull android.os.UserHandle); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getDeviceOwner(); - method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public android.content.ComponentName getDeviceOwnerComponentOnAnyUser(); + method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, "android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS"}) public android.content.ComponentName getDeviceOwnerComponentOnAnyUser(); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getDeviceOwnerNameOnAnyUser(); method @Nullable public CharSequence getDeviceOwnerOrganizationName(); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public android.os.UserHandle getDeviceOwnerUser(); @@ -2461,10 +2466,10 @@ package android.content.pm { } public static class PackageInstaller.Session implements java.io.Closeable { - method @RequiresPermission("com.android.permission.USE_INSTALLER_V2") public void addFile(int, @NonNull String, long, @NonNull byte[], @Nullable byte[]); + method public void addFile(int, @NonNull String, long, @NonNull byte[], @Nullable byte[]); method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void commitTransferred(@NonNull android.content.IntentSender); - method @Nullable @RequiresPermission("com.android.permission.USE_INSTALLER_V2") public android.content.pm.DataLoaderParams getDataLoaderParams(); - method @RequiresPermission("com.android.permission.USE_INSTALLER_V2") public void removeFile(int, @NonNull String); + method @Nullable public android.content.pm.DataLoaderParams getDataLoaderParams(); + method public void removeFile(int, @NonNull String); } public static class PackageInstaller.SessionInfo implements android.os.Parcelable { @@ -2485,7 +2490,7 @@ package android.content.pm { public static class PackageInstaller.SessionParams implements android.os.Parcelable { method @RequiresPermission(android.Manifest.permission.ALLOCATE_AGGRESSIVE) public void setAllocateAggressive(boolean); method @Deprecated public void setAllowDowngrade(boolean); - method @RequiresPermission(allOf={android.Manifest.permission.INSTALL_PACKAGES, "com.android.permission.USE_INSTALLER_V2"}) public void setDataLoaderParams(@NonNull android.content.pm.DataLoaderParams); + method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setDataLoaderParams(@NonNull android.content.pm.DataLoaderParams); method public void setDontKillApp(boolean); method public void setEnableRollback(boolean); method public void setEnableRollback(boolean, int); @@ -2550,10 +2555,12 @@ 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_CAMERA_TOGGLE = "android.hardware.camera.toggle"; field public static final String FEATURE_CONTEXT_HUB = "android.hardware.context_hub"; field public static final String FEATURE_CROSS_LAYER_BLUR = "android.software.cross_layer_blur"; field @Deprecated public static final String FEATURE_INCREMENTAL_DELIVERY = "android.software.incremental_delivery"; field public static final String FEATURE_INCREMENTAL_DELIVERY_VERSION = "android.software.incremental_delivery_version"; + field public static final String FEATURE_MICROPHONE_TOGGLE = "android.hardware.microphone.toggle"; 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 String FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION = "android.hardware.telephony.ims.singlereg"; @@ -2565,7 +2572,6 @@ package android.content.pm { field public static final int FLAG_PERMISSION_ONE_TIME = 65536; // 0x10000 field public static final int FLAG_PERMISSION_POLICY_FIXED = 4; // 0x4 field public static final int FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT = 2048; // 0x800 - field public static final int FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT = 262144; // 0x40000 field public static final int FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT = 4096; // 0x1000 field public static final int FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT = 8192; // 0x2000 field public static final int FLAG_PERMISSION_REVIEW_REQUIRED = 64; // 0x40 @@ -2751,6 +2757,15 @@ package android.content.pm.permission { package android.content.pm.verify.domain { + public final class DomainOwner implements android.os.Parcelable { + ctor public DomainOwner(@NonNull String, boolean); + method public int describeContents(); + method @NonNull public String getPackageName(); + method public boolean isOverrideable(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.verify.domain.DomainOwner> CREATOR; + } + public final class DomainVerificationInfo implements android.os.Parcelable { method public int describeContents(); method @NonNull public java.util.Map<java.lang.String,java.lang.Integer> getHostToStateMap(); @@ -2763,6 +2778,7 @@ package android.content.pm.verify.domain { public interface DomainVerificationManager { method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.DOMAIN_VERIFICATION_AGENT, android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION}) public android.content.pm.verify.domain.DomainVerificationInfo getDomainVerificationInfo(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; method @Nullable @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public android.content.pm.verify.domain.DomainVerificationUserSelection getDomainVerificationUserSelection(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; + method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public java.util.List<android.content.pm.verify.domain.DomainOwner> getOwnersForDomain(@NonNull String); method @NonNull @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) public java.util.List<java.lang.String> getValidVerificationPackageNames(); method public static boolean isStateModifiable(int); method public static boolean isStateVerified(int); @@ -2784,13 +2800,16 @@ package android.content.pm.verify.domain { public final class DomainVerificationUserSelection implements android.os.Parcelable { method public int describeContents(); - method @NonNull public java.util.Map<java.lang.String,java.lang.Boolean> getHostToUserSelectionMap(); + method @NonNull public java.util.Map<java.lang.String,java.lang.Integer> getHostToStateMap(); method @NonNull public java.util.UUID getIdentifier(); method @NonNull public String getPackageName(); method @NonNull public android.os.UserHandle getUser(); method @NonNull public boolean isLinkHandlingAllowed(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.verify.domain.DomainVerificationUserSelection> CREATOR; + field public static final int DOMAIN_STATE_NONE = 0; // 0x0 + field public static final int DOMAIN_STATE_SELECTED = 1; // 0x1 + field public static final int DOMAIN_STATE_VERIFIED = 2; // 0x2 } } @@ -2874,7 +2893,7 @@ package android.graphics.fonts { } public class FontManager { - method @Nullable public android.text.FontConfig getFontConfig(); + method @NonNull public android.text.FontConfig getFontConfig(); method @RequiresPermission(android.Manifest.permission.UPDATE_FONTS) public int updateFontFamily(@NonNull android.graphics.fonts.FontFamilyUpdateRequest, @IntRange(from=0) int); method @RequiresPermission(android.Manifest.permission.UPDATE_FONTS) public int updateFontFile(@NonNull android.graphics.fonts.FontFileUpdateRequest, @IntRange(from=0) int); method @Deprecated @RequiresPermission(android.Manifest.permission.UPDATE_FONTS) public int updateFontFile(@NonNull android.os.ParcelFileDescriptor, @NonNull byte[], @IntRange(from=0) int); @@ -2908,6 +2927,22 @@ package android.hardware { method public boolean injectSensorData(android.hardware.Sensor, float[], int, long); } + public final class SensorPrivacyManager { + method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void addSensorPrivacyListener(int, @NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener); + method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void addSensorPrivacyListener(int, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener); + method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean isSensorPrivacyEnabled(int); + method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void removeSensorPrivacyListener(@NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener); + } + + public static interface SensorPrivacyManager.OnSensorPrivacyChangedListener { + method public void onSensorPrivacyChanged(boolean); + } + + public static class SensorPrivacyManager.Sensors { + field public static final int CAMERA = 2; // 0x2 + field public static final int MICROPHONE = 1; // 0x1 + } + } package android.hardware.biometrics { @@ -7063,102 +7098,6 @@ package android.metrics { package android.net { - public class CaptivePortal implements android.os.Parcelable { - method public void logEvent(int, @NonNull String); - method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void reevaluateNetwork(); - method public void useNetwork(); - field public static final int APP_REQUEST_REEVALUATION_REQUIRED = 100; // 0x64 - field public static final int APP_RETURN_DISMISSED = 0; // 0x0 - field public static final int APP_RETURN_UNWANTED = 1; // 0x1 - field public static final int APP_RETURN_WANTED_AS_IS = 2; // 0x2 - } - - public final class CaptivePortalData implements android.os.Parcelable { - method public int describeContents(); - method public long getByteLimit(); - method public long getExpiryTimeMillis(); - method public long getRefreshTimeMillis(); - method @Nullable public android.net.Uri getUserPortalUrl(); - method public int getUserPortalUrlSource(); - method @Nullable public String getVenueFriendlyName(); - method @Nullable public android.net.Uri getVenueInfoUrl(); - method public int getVenueInfoUrlSource(); - method public boolean isCaptive(); - method public boolean isSessionExtendable(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field public static final int CAPTIVE_PORTAL_DATA_SOURCE_OTHER = 0; // 0x0 - field public static final int CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT = 1; // 0x1 - field @NonNull public static final android.os.Parcelable.Creator<android.net.CaptivePortalData> CREATOR; - } - - public static class CaptivePortalData.Builder { - ctor public CaptivePortalData.Builder(); - ctor public CaptivePortalData.Builder(@Nullable android.net.CaptivePortalData); - method @NonNull public android.net.CaptivePortalData build(); - method @NonNull public android.net.CaptivePortalData.Builder setBytesRemaining(long); - method @NonNull public android.net.CaptivePortalData.Builder setCaptive(boolean); - method @NonNull public android.net.CaptivePortalData.Builder setExpiryTime(long); - method @NonNull public android.net.CaptivePortalData.Builder setRefreshTime(long); - method @NonNull public android.net.CaptivePortalData.Builder setSessionExtendable(boolean); - method @NonNull public android.net.CaptivePortalData.Builder setUserPortalUrl(@Nullable android.net.Uri); - method @NonNull public android.net.CaptivePortalData.Builder setUserPortalUrl(@Nullable android.net.Uri, int); - method @NonNull public android.net.CaptivePortalData.Builder setVenueFriendlyName(@Nullable String); - method @NonNull public android.net.CaptivePortalData.Builder setVenueInfoUrl(@Nullable android.net.Uri); - method @NonNull public android.net.CaptivePortalData.Builder setVenueInfoUrl(@Nullable android.net.Uri, int); - } - - public class ConnectivityManager { - method @NonNull @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD) public android.net.SocketKeepalive createNattKeepalive(@NonNull android.net.Network, @NonNull android.os.ParcelFileDescriptor, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback); - method @NonNull @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD) public android.net.SocketKeepalive createSocketKeepalive(@NonNull android.net.Network, @NonNull java.net.Socket, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback); - method @Deprecated @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public String getCaptivePortalServerUrl(); - method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void getLatestTetheringEntitlementResult(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEntitlementResultListener); - method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public boolean isTetheringSupported(); - method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_FACTORY}) public int registerNetworkProvider(@NonNull android.net.NetworkProvider); - method public void registerQosCallback(@NonNull android.net.QosSocketInfo, @NonNull android.net.QosCallback, @NonNull java.util.concurrent.Executor); - method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEventCallback); - method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void requestNetwork(@NonNull android.net.NetworkRequest, int, int, @NonNull android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback); - 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(android.Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE) public void setOemNetworkPreference(@NonNull android.net.OemNetworkPreferences, @Nullable java.util.concurrent.Executor, @Nullable android.net.ConnectivityManager.OnSetOemNetworkPreferenceListener); - method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public boolean shouldAvoidBadWifi(); - method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void startCaptivePortalApp(@NonNull android.net.Network, @NonNull android.os.Bundle); - method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback); - method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback, android.os.Handler); - method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void stopTethering(int); - method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_FACTORY}) public void unregisterNetworkProvider(@NonNull android.net.NetworkProvider); - method public void unregisterQosCallback(@NonNull android.net.QosCallback); - method @Deprecated @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"; - field public static final int TETHERING_BLUETOOTH = 2; // 0x2 - field public static final int TETHERING_USB = 1; // 0x1 - field public static final int TETHERING_WIFI = 0; // 0x0 - field @Deprecated public static final int TETHER_ERROR_ENTITLEMENT_UNKONWN = 13; // 0xd - field @Deprecated public static final int TETHER_ERROR_NO_ERROR = 0; // 0x0 - field @Deprecated public static final int TETHER_ERROR_PROVISION_FAILED = 11; // 0xb - field public static final int TYPE_NONE = -1; // 0xffffffff - field @Deprecated public static final int TYPE_PROXY = 16; // 0x10 - field @Deprecated public static final int TYPE_WIFI_P2P = 13; // 0xd - } - - public static interface ConnectivityManager.OnSetOemNetworkPreferenceListener { - method public void onComplete(); - } - - @Deprecated public abstract static class ConnectivityManager.OnStartTetheringCallback { - ctor @Deprecated public ConnectivityManager.OnStartTetheringCallback(); - method @Deprecated public void onTetheringFailed(); - method @Deprecated public void onTetheringStarted(); - } - - @Deprecated public static interface ConnectivityManager.OnTetheringEntitlementResultListener { - method @Deprecated public void onTetheringEntitlementResult(int); - } - - @Deprecated public abstract static class ConnectivityManager.OnTetheringEventCallback { - ctor @Deprecated public ConnectivityManager.OnTetheringEventCallback(); - method @Deprecated public void onUpstreamChanged(@Nullable android.net.Network); - } - public class DnsResolverServiceManager { method @NonNull @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public static android.os.IBinder getService(@NonNull android.content.Context); } @@ -7176,48 +7115,6 @@ package android.net { method public void release(); } - public final class InvalidPacketException extends java.lang.Exception { - ctor public InvalidPacketException(int); - method public int getError(); - field public static final int ERROR_INVALID_IP_ADDRESS = -21; // 0xffffffeb - field public static final int ERROR_INVALID_LENGTH = -23; // 0xffffffe9 - field public static final int ERROR_INVALID_PORT = -22; // 0xffffffea - } - - public final class IpConfiguration implements android.os.Parcelable { - ctor public IpConfiguration(); - ctor public IpConfiguration(@NonNull android.net.IpConfiguration); - method public int describeContents(); - method @Nullable public android.net.ProxyInfo getHttpProxy(); - method @NonNull public android.net.IpConfiguration.IpAssignment getIpAssignment(); - method @NonNull public android.net.IpConfiguration.ProxySettings getProxySettings(); - method @Nullable public android.net.StaticIpConfiguration getStaticIpConfiguration(); - method public void setHttpProxy(@Nullable android.net.ProxyInfo); - method public void setIpAssignment(@NonNull android.net.IpConfiguration.IpAssignment); - method public void setProxySettings(@NonNull android.net.IpConfiguration.ProxySettings); - method public void setStaticIpConfiguration(@Nullable android.net.StaticIpConfiguration); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.IpConfiguration> CREATOR; - } - - public enum IpConfiguration.IpAssignment { - enum_constant public static final android.net.IpConfiguration.IpAssignment DHCP; - enum_constant public static final android.net.IpConfiguration.IpAssignment STATIC; - enum_constant public static final android.net.IpConfiguration.IpAssignment UNASSIGNED; - } - - public enum IpConfiguration.ProxySettings { - enum_constant public static final android.net.IpConfiguration.ProxySettings NONE; - enum_constant public static final android.net.IpConfiguration.ProxySettings PAC; - enum_constant public static final android.net.IpConfiguration.ProxySettings STATIC; - enum_constant public static final android.net.IpConfiguration.ProxySettings UNASSIGNED; - } - - public final class IpPrefix implements android.os.Parcelable { - ctor public IpPrefix(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int); - ctor public IpPrefix(@NonNull String); - } - public final class IpSecManager { method @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public void applyTunnelModeTransform(@NonNull android.net.IpSecManager.IpSecTunnelInterface, int, @NonNull android.net.IpSecTransform) throws java.io.IOException; method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public android.net.IpSecManager.IpSecTunnelInterface createIpSecTunnelInterface(@NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull android.net.Network) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException; @@ -7235,68 +7132,6 @@ package android.net { method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS) public android.net.IpSecTransform buildTunnelModeTransform(@NonNull java.net.InetAddress, @NonNull android.net.IpSecManager.SecurityParameterIndex) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException; } - public class KeepalivePacketData { - ctor protected KeepalivePacketData(@NonNull java.net.InetAddress, @IntRange(from=0, to=65535) int, @NonNull java.net.InetAddress, @IntRange(from=0, to=65535) int, @NonNull byte[]) throws android.net.InvalidPacketException; - method @NonNull public java.net.InetAddress getDstAddress(); - method public int getDstPort(); - method @NonNull public byte[] getPacket(); - method @NonNull public java.net.InetAddress getSrcAddress(); - method public int getSrcPort(); - } - - public class LinkAddress implements android.os.Parcelable { - ctor public LinkAddress(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int, int, int); - ctor public LinkAddress(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int, int, int, long, long); - ctor public LinkAddress(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int); - ctor public LinkAddress(@NonNull String); - ctor public LinkAddress(@NonNull String, int, int); - method public long getDeprecationTime(); - method public long getExpirationTime(); - method public boolean isGlobalPreferred(); - method public boolean isIpv4(); - method public boolean isIpv6(); - method public boolean isSameAddressAs(@Nullable android.net.LinkAddress); - field public static final long LIFETIME_PERMANENT = 9223372036854775807L; // 0x7fffffffffffffffL - field public static final long LIFETIME_UNKNOWN = -1L; // 0xffffffffffffffffL - } - - public final class LinkProperties implements android.os.Parcelable { - ctor public LinkProperties(@Nullable android.net.LinkProperties); - ctor public LinkProperties(@Nullable android.net.LinkProperties, boolean); - method public boolean addDnsServer(@NonNull java.net.InetAddress); - method public boolean addLinkAddress(@NonNull android.net.LinkAddress); - method public boolean addPcscfServer(@NonNull java.net.InetAddress); - method @NonNull public java.util.List<java.net.InetAddress> getAddresses(); - method @NonNull public java.util.List<java.lang.String> getAllInterfaceNames(); - method @NonNull public java.util.List<android.net.LinkAddress> getAllLinkAddresses(); - method @NonNull public java.util.List<android.net.RouteInfo> getAllRoutes(); - method @Nullable public android.net.Uri getCaptivePortalApiUrl(); - method @Nullable public android.net.CaptivePortalData getCaptivePortalData(); - method @NonNull public java.util.List<java.net.InetAddress> getPcscfServers(); - method @Nullable public String getTcpBufferSizes(); - method @NonNull public java.util.List<java.net.InetAddress> getValidatedPrivateDnsServers(); - method public boolean hasGlobalIpv6Address(); - method public boolean hasIpv4Address(); - method public boolean hasIpv4DefaultRoute(); - method public boolean hasIpv4DnsServer(); - method public boolean hasIpv6DefaultRoute(); - method public boolean hasIpv6DnsServer(); - method public boolean isIpv4Provisioned(); - method public boolean isIpv6Provisioned(); - method public boolean isProvisioned(); - method public boolean isReachable(@NonNull java.net.InetAddress); - method public boolean removeDnsServer(@NonNull java.net.InetAddress); - method public boolean removeLinkAddress(@NonNull android.net.LinkAddress); - method public boolean removeRoute(@NonNull android.net.RouteInfo); - method public void setCaptivePortalApiUrl(@Nullable android.net.Uri); - method public void setCaptivePortalData(@Nullable android.net.CaptivePortalData); - method public void setPcscfServers(@NonNull java.util.Collection<java.net.InetAddress>); - method public void setPrivateDnsServerName(@Nullable String); - method public void setTcpBufferSizes(@Nullable String); - method public void setUsePrivateDns(boolean); - method public void setValidatedPrivateDnsServers(@NonNull java.util.Collection<java.net.InetAddress>); - } - public final class MatchAllNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable { ctor public MatchAllNetworkSpecifier(); method public int describeContents(); @@ -7304,104 +7139,6 @@ package android.net { field @NonNull public static final android.os.Parcelable.Creator<android.net.MatchAllNetworkSpecifier> CREATOR; } - public final class NattKeepalivePacketData extends android.net.KeepalivePacketData implements android.os.Parcelable { - ctor public NattKeepalivePacketData(@NonNull java.net.InetAddress, int, @NonNull java.net.InetAddress, int, @NonNull byte[]) throws android.net.InvalidPacketException; - method public int describeContents(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.NattKeepalivePacketData> CREATOR; - } - - public class Network implements android.os.Parcelable { - ctor public Network(@NonNull android.net.Network); - method public int getNetId(); - method @NonNull public android.net.Network getPrivateDnsBypassingCopy(); - } - - public abstract class NetworkAgent { - ctor public NetworkAgent(@NonNull android.content.Context, @NonNull android.os.Looper, @NonNull String, @NonNull android.net.NetworkCapabilities, @NonNull android.net.LinkProperties, int, @NonNull android.net.NetworkAgentConfig, @Nullable android.net.NetworkProvider); - method @Nullable public android.net.Network getNetwork(); - method public void markConnected(); - method public void onAddKeepalivePacketFilter(int, @NonNull android.net.KeepalivePacketData); - method public void onAutomaticReconnectDisabled(); - method public void onNetworkUnwanted(); - method public void onQosCallbackRegistered(int, @NonNull android.net.QosFilter); - method public void onQosCallbackUnregistered(int); - method public void onRemoveKeepalivePacketFilter(int); - method public void onSaveAcceptUnvalidated(boolean); - method public void onSignalStrengthThresholdsUpdated(@NonNull int[]); - method public void onStartSocketKeepalive(int, @NonNull java.time.Duration, @NonNull android.net.KeepalivePacketData); - method public void onStopSocketKeepalive(int); - method public void onValidationStatus(int, @Nullable android.net.Uri); - method @NonNull public android.net.Network register(); - method public final void sendLinkProperties(@NonNull android.net.LinkProperties); - method public final void sendNetworkCapabilities(@NonNull android.net.NetworkCapabilities); - method public final void sendNetworkScore(@IntRange(from=0, to=99) int); - method public final void sendQosCallbackError(int, int); - method public final void sendQosSessionAvailable(int, int, @NonNull android.telephony.data.EpsBearerQosSessionAttributes); - method public final void sendQosSessionLost(int, int); - method public final void sendSocketKeepaliveEvent(int, int); - method public final void setUnderlyingNetworks(@Nullable java.util.List<android.net.Network>); - method public void unregister(); - field public static final int VALIDATION_STATUS_NOT_VALID = 2; // 0x2 - field public static final int VALIDATION_STATUS_VALID = 1; // 0x1 - } - - public final class NetworkAgentConfig implements android.os.Parcelable { - method public int describeContents(); - method public int getLegacyType(); - method @NonNull public String getLegacyTypeName(); - method public boolean isExplicitlySelected(); - method public boolean isPartialConnectivityAcceptable(); - method public boolean isUnvalidatedConnectivityAcceptable(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkAgentConfig> CREATOR; - } - - public static final class NetworkAgentConfig.Builder { - ctor public NetworkAgentConfig.Builder(); - method @NonNull public android.net.NetworkAgentConfig build(); - method @NonNull public android.net.NetworkAgentConfig.Builder setExplicitlySelected(boolean); - method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyType(int); - method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyTypeName(@NonNull String); - method @NonNull public android.net.NetworkAgentConfig.Builder setPartialConnectivityAcceptable(boolean); - method @NonNull public android.net.NetworkAgentConfig.Builder setUnvalidatedConnectivityAcceptable(boolean); - } - - public final class NetworkCapabilities implements android.os.Parcelable { - ctor public NetworkCapabilities(@Nullable android.net.NetworkCapabilities, boolean); - method @NonNull public int[] getAdministratorUids(); - method @Nullable public String getSsid(); - method @NonNull public int[] getTransportTypes(); - method public boolean isPrivateDnsBroken(); - method public boolean satisfiedByNetworkCapabilities(@Nullable android.net.NetworkCapabilities); - field public static final int NET_CAPABILITY_NOT_VCN_MANAGED = 28; // 0x1c - field public static final int NET_CAPABILITY_OEM_PAID = 22; // 0x16 - field public static final int NET_CAPABILITY_OEM_PRIVATE = 26; // 0x1a - field public static final int NET_CAPABILITY_PARTIAL_CONNECTIVITY = 24; // 0x18 - field public static final int NET_CAPABILITY_VEHICLE_INTERNAL = 27; // 0x1b - } - - public static final class NetworkCapabilities.Builder { - ctor public NetworkCapabilities.Builder(); - ctor public NetworkCapabilities.Builder(@NonNull android.net.NetworkCapabilities); - method @NonNull public android.net.NetworkCapabilities.Builder addCapability(int); - method @NonNull public android.net.NetworkCapabilities.Builder addTransportType(int); - method @NonNull public android.net.NetworkCapabilities build(); - method @NonNull public android.net.NetworkCapabilities.Builder clearAll(); - method @NonNull public android.net.NetworkCapabilities.Builder removeCapability(int); - method @NonNull public android.net.NetworkCapabilities.Builder removeTransportType(int); - method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setAdministratorUids(@NonNull int[]); - method @NonNull public android.net.NetworkCapabilities.Builder setLinkDownstreamBandwidthKbps(int); - method @NonNull public android.net.NetworkCapabilities.Builder setLinkUpstreamBandwidthKbps(int); - method @NonNull public android.net.NetworkCapabilities.Builder setNetworkSpecifier(@Nullable android.net.NetworkSpecifier); - method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setOwnerUid(int); - method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setRequestorPackageName(@Nullable String); - method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setRequestorUid(int); - method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) public android.net.NetworkCapabilities.Builder setSignalStrength(int); - method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setSsid(@Nullable String); - method @NonNull public android.net.NetworkCapabilities.Builder setTransportInfo(@Nullable android.net.TransportInfo); - } - public class NetworkKey implements android.os.Parcelable { ctor public NetworkKey(android.net.WifiKey); method @Nullable public static android.net.NetworkKey createFromScanResult(@NonNull android.net.wifi.ScanResult); @@ -7413,15 +7150,6 @@ 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 public int getProviderId(); - method public void onNetworkRequestWithdrawn(@NonNull android.net.NetworkRequest); - method public void onNetworkRequested(@NonNull android.net.NetworkRequest, @IntRange(from=0, to=99) int, int); - 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(); @@ -7431,15 +7159,6 @@ package android.net { public class NetworkReleasedException extends java.lang.Exception { } - public class NetworkRequest implements android.os.Parcelable { - method @Nullable public String getRequestorPackageName(); - method public int getRequestorUid(); - } - - public static class NetworkRequest.Builder { - method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) public android.net.NetworkRequest.Builder setSignalStrength(int); - } - public class NetworkScoreManager { method @RequiresPermission(anyOf={android.Manifest.permission.SCORE_NETWORKS, android.Manifest.permission.REQUEST_NETWORK_SCORES}) public boolean clearScores() throws java.lang.SecurityException; method @RequiresPermission(anyOf={android.Manifest.permission.SCORE_NETWORKS, android.Manifest.permission.REQUEST_NETWORK_SCORES}) public void disableScoring() throws java.lang.SecurityException; @@ -7564,16 +7283,6 @@ package android.net { field @NonNull public static final android.os.Parcelable.Creator<android.net.QosSocketInfo> CREATOR; } - public final class RouteInfo implements android.os.Parcelable { - ctor public RouteInfo(@Nullable android.net.IpPrefix, @Nullable java.net.InetAddress, @Nullable String, int); - ctor public RouteInfo(@Nullable android.net.IpPrefix, @Nullable java.net.InetAddress, @Nullable String, int, int); - method public int getMtu(); - method public int getType(); - field public static final int RTN_THROW = 9; // 0x9 - field public static final int RTN_UNICAST = 1; // 0x1 - field public static final int RTN_UNREACHABLE = 7; // 0x7 - } - public class RssiCurve implements android.os.Parcelable { ctor public RssiCurve(int, int, byte[]); ctor public RssiCurve(int, int, byte[], int); @@ -7605,53 +7314,12 @@ package android.net { field public final android.net.RssiCurve rssiCurve; } - public abstract class SocketKeepalive implements java.lang.AutoCloseable { - field public static final int SUCCESS = 0; // 0x0 - } - public class SocketLocalAddressChangedException extends java.lang.Exception { } public class SocketNotBoundException extends java.lang.Exception { } - public final class StaticIpConfiguration implements android.os.Parcelable { - ctor public StaticIpConfiguration(); - ctor public StaticIpConfiguration(@Nullable android.net.StaticIpConfiguration); - method public void addDnsServer(@NonNull java.net.InetAddress); - method public void clear(); - method public int describeContents(); - method @NonNull public java.util.List<java.net.InetAddress> getDnsServers(); - method @Nullable public String getDomains(); - method @Nullable public java.net.InetAddress getGateway(); - method @Nullable public android.net.LinkAddress getIpAddress(); - method @NonNull public java.util.List<android.net.RouteInfo> getRoutes(@Nullable String); - method public void writeToParcel(android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.StaticIpConfiguration> CREATOR; - } - - public static final class StaticIpConfiguration.Builder { - ctor public StaticIpConfiguration.Builder(); - method @NonNull public android.net.StaticIpConfiguration build(); - method @NonNull public android.net.StaticIpConfiguration.Builder setDnsServers(@NonNull Iterable<java.net.InetAddress>); - method @NonNull public android.net.StaticIpConfiguration.Builder setDomains(@Nullable String); - method @NonNull public android.net.StaticIpConfiguration.Builder setGateway(@Nullable java.net.InetAddress); - method @NonNull public android.net.StaticIpConfiguration.Builder setIpAddress(@Nullable android.net.LinkAddress); - } - - public final class TcpKeepalivePacketData extends android.net.KeepalivePacketData implements android.os.Parcelable { - ctor public TcpKeepalivePacketData(@NonNull java.net.InetAddress, int, @NonNull java.net.InetAddress, int, @NonNull byte[], int, int, int, int, int, int) throws android.net.InvalidPacketException; - method public int describeContents(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.TcpKeepalivePacketData> CREATOR; - field public final int ipTos; - field public final int ipTtl; - field public final int tcpAck; - field public final int tcpSeq; - field public final int tcpWindow; - field public final int tcpWindowScale; - } - public class TrafficStats { method public static void setThreadStatsTagApp(); method public static void setThreadStatsTagBackup(); @@ -7664,11 +7332,6 @@ package android.net { field public static final int TAG_SYSTEM_IMPERSONATION_RANGE_START = -256; // 0xffffff00 } - public interface TransportInfo { - method public default boolean hasLocationSensitiveFields(); - method @NonNull public default android.net.TransportInfo makeCopy(boolean); - } - public abstract class Uri implements java.lang.Comparable<android.net.Uri> android.os.Parcelable { method @NonNull public String toSafeString(); } @@ -7692,23 +7355,6 @@ package android.net { } -package android.net.apf { - - public final class ApfCapabilities implements android.os.Parcelable { - ctor public ApfCapabilities(int, int, int); - method public int describeContents(); - method public static boolean getApfDrop8023Frames(); - method @NonNull public static int[] getApfEtherTypeBlackList(); - method public boolean hasDataAccess(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator<android.net.apf.ApfCapabilities> CREATOR; - field public final int apfPacketFormat; - field public final int apfVersionSupported; - field public final int maximumApfProgramSize; - } - -} - package android.net.metrics { @Deprecated public final class ApfProgramEvent implements android.net.metrics.IpConnectivityLog.Event { @@ -7903,19 +7549,6 @@ package android.net.sip { } -package android.net.util { - - public final class SocketUtils { - method public static void bindSocketToInterface(@NonNull java.io.FileDescriptor, @NonNull String) throws android.system.ErrnoException; - method public static void closeSocket(@Nullable java.io.FileDescriptor) throws java.io.IOException; - method @NonNull public static java.net.SocketAddress makeNetlinkSocketAddress(int, int); - method @NonNull public static java.net.SocketAddress makePacketSocketAddress(int, int); - method @Deprecated @NonNull public static java.net.SocketAddress makePacketSocketAddress(int, @NonNull byte[]); - method @NonNull public static java.net.SocketAddress makePacketSocketAddress(int, int, @NonNull byte[]); - } - -} - package android.net.vcn { public class VcnManager { @@ -8607,11 +8240,18 @@ package android.os { method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void addToWhitelist(@NonNull String); method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void addToWhitelist(@NonNull java.util.List<java.lang.String>); method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void removeFromWhitelist(@NonNull String); - method @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public void whitelistAppTemporarily(@NonNull String, long); - method @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public long whitelistAppTemporarilyForEvent(@NonNull String, int, @NonNull String); + method @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public void whitelistAppTemporarily(@NonNull String, long, int, @Nullable String); + method @Deprecated @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public void whitelistAppTemporarily(@NonNull String, long); + method @Deprecated @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public long whitelistAppTemporarilyForEvent(@NonNull String, int, @Nullable String); + method @RequiresPermission(android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST) public long whitelistAppTemporarilyForEvent(@NonNull String, int, int, @Nullable String); field public static final int EVENT_MMS = 2; // 0x2 field public static final int EVENT_SMS = 1; // 0x1 field public static final int EVENT_UNSPECIFIED = 0; // 0x0 + field public static final int REASON_ACTIVITY_RECOGNITION = 102; // 0x66 + field public static final int REASON_GEOFENCING = 100; // 0x64 + field public static final int REASON_OTHER = 1; // 0x1 + field public static final int REASON_PUSH_MESSAGING = 101; // 0x65 + field public static final int REASON_UNKNOWN = 0; // 0x0 field public static final int TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED = 0; // 0x0 field public static final int TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED = 1; // 0x1 } @@ -8760,13 +8400,13 @@ package android.os { method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.os.UserHandle createProfile(@NonNull String, @NonNull String, @NonNull java.util.Set<java.lang.String>) throws android.os.UserManager.UserOperationException; method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}, conditional=true) public java.util.List<android.os.UserHandle> getAllProfiles(); method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}, conditional=true) public java.util.List<android.os.UserHandle> getEnabledProfiles(); - method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public android.os.UserHandle getProfileParent(@NonNull android.os.UserHandle); + method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public android.os.UserHandle getProfileParent(@NonNull android.os.UserHandle); method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.os.UserHandle getRestrictedProfileParent(); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getSeedAccountName(); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public android.os.PersistableBundle getSeedAccountOptions(); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getSeedAccountType(); - method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public long[] getSerialNumbersOfUsers(boolean); - method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public java.util.List<android.os.UserHandle> getUserHandles(boolean); + method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public long[] getSerialNumbersOfUsers(boolean); + method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.os.UserHandle> getUserHandles(boolean); method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}) public android.graphics.Bitmap getUserIcon(); method @Deprecated @android.os.UserManager.UserRestrictionSource @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public int getUserRestrictionSource(String, android.os.UserHandle); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public java.util.List<android.os.UserManager.EnforcingUser> getUserRestrictionSources(String, android.os.UserHandle); @@ -9204,6 +8844,7 @@ package android.provider { field public static final String NAMESPACE_BIOMETRICS = "biometrics"; field public static final String NAMESPACE_BLOBSTORE = "blobstore"; field public static final String NAMESPACE_BLUETOOTH = "bluetooth"; + field public static final String NAMESPACE_CLIPBOARD = "clipboard"; field public static final String NAMESPACE_CONNECTIVITY = "connectivity"; field public static final String NAMESPACE_CONTENT_CAPTURE = "content_capture"; field @Deprecated public static final String NAMESPACE_DEX_BOOT = "dex_boot"; @@ -11131,6 +10772,10 @@ package android.telephony { field public static final String KEY_SUPPORT_CDMA_1X_VOICE_CALLS_BOOL = "support_cdma_1x_voice_calls_bool"; } + public static final class CarrierConfigManager.Ims { + field public static final String KEY_PUBLISH_SERVICE_DESC_FEATURE_TAG_MAP_OVERRIDE_STRING_ARRAY = "ims.publish_service_desc_feature_tag_map_override_string_array"; + } + public static final class CarrierConfigManager.Wifi { field public static final String KEY_AVOID_5GHZ_SOFTAP_FOR_LAA_BOOL = "wifi.avoid_5ghz_softap_for_laa_bool"; field public static final String KEY_AVOID_5GHZ_WIFI_DIRECT_FOR_LAA_BOOL = "wifi.avoid_5ghz_wifi_direct_for_laa_bool"; diff --git a/core/api/test-current.txt b/core/api/test-current.txt index f0a2a492c3cf..ca261cda0a56 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -5,14 +5,17 @@ package android { field public static final String ACCESS_NOTIFICATIONS = "android.permission.ACCESS_NOTIFICATIONS"; field public static final String ACTIVITY_EMBEDDING = "android.permission.ACTIVITY_EMBEDDING"; field public static final String APPROVE_INCIDENT_REPORTS = "android.permission.APPROVE_INCIDENT_REPORTS"; + field public static final String BACKGROUND_CAMERA = "android.permission.BACKGROUND_CAMERA"; field public static final String BIND_CELL_BROADCAST_SERVICE = "android.permission.BIND_CELL_BROADCAST_SERVICE"; field public static final String BRIGHTNESS_SLIDER_USAGE = "android.permission.BRIGHTNESS_SLIDER_USAGE"; field public static final String BROADCAST_CLOSE_SYSTEM_DIALOGS = "android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS"; field public static final String CHANGE_APP_IDLE_STATE = "android.permission.CHANGE_APP_IDLE_STATE"; field public static final String CLEAR_APP_USER_DATA = "android.permission.CLEAR_APP_USER_DATA"; + field public static final String CLEAR_FREEZE_PERIOD = "android.permission.CLEAR_FREEZE_PERIOD"; field public static final String CONFIGURE_DISPLAY_BRIGHTNESS = "android.permission.CONFIGURE_DISPLAY_BRIGHTNESS"; field public static final String CONTROL_DEVICE_LIGHTS = "android.permission.CONTROL_DEVICE_LIGHTS"; field public static final String CONTROL_DEVICE_STATE = "android.permission.CONTROL_DEVICE_STATE"; + field public static final String FORCE_DEVICE_POLICY_MANAGER_LOGS = "android.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS"; field public static final String FORCE_STOP_PACKAGES = "android.permission.FORCE_STOP_PACKAGES"; field public static final String KEEP_UNINSTALLED_PACKAGES = "android.permission.KEEP_UNINSTALLED_PACKAGES"; field @Deprecated public static final String MANAGE_ACTIVITY_STACKS = "android.permission.MANAGE_ACTIVITY_STACKS"; @@ -28,6 +31,7 @@ package android { field public static final String QUERY_USERS = "android.permission.QUERY_USERS"; field public static final String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS"; field public static final String READ_PRIVILEGED_PHONE_STATE = "android.permission.READ_PRIVILEGED_PHONE_STATE"; + field public static final String RECORD_BACKGROUND_AUDIO = "android.permission.RECORD_BACKGROUND_AUDIO"; field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS"; field public static final String RESET_APP_ERRORS = "android.permission.RESET_APP_ERRORS"; field public static final String START_TASKS_FROM_RECENTS = "android.permission.START_TASKS_FROM_RECENTS"; @@ -91,6 +95,8 @@ package android.app { method @RequiresPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER) public void addHomeVisibilityListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.HomeVisibilityListener); method public void alwaysShowUnsupportedCompileSdkWarning(android.content.ComponentName); method public long getTotalRam(); + method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public int getUidProcessCapabilities(int); + method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public int getUidProcessState(int); method public void holdLock(android.os.IBinder, int); method public static boolean isHighEndGfx(); method @RequiresPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER) public void removeHomeVisibilityListener(@NonNull android.app.HomeVisibilityListener); @@ -106,7 +112,10 @@ package android.app { field public static final int PROCESS_CAPABILITY_FOREGROUND_CAMERA = 2; // 0x2 field public static final int PROCESS_CAPABILITY_FOREGROUND_LOCATION = 1; // 0x1 field public static final int PROCESS_CAPABILITY_FOREGROUND_MICROPHONE = 4; // 0x4 + field public static final int PROCESS_CAPABILITY_NETWORK = 8; // 0x8 field public static final int PROCESS_CAPABILITY_NONE = 0; // 0x0 + field public static final int PROCESS_STATE_FOREGROUND_SERVICE = 4; // 0x4 + field public static final int PROCESS_STATE_TOP = 2; // 0x2 } public static class ActivityManager.RunningAppProcessInfo implements android.os.Parcelable { @@ -384,7 +393,11 @@ package android.app.admin { public class DevicePolicyManager { method public int checkProvisioningPreCondition(@Nullable String, @NonNull String); + method @RequiresPermission(android.Manifest.permission.CLEAR_FREEZE_PERIOD) public void clearSystemUpdatePolicyFreezePeriodRecord(); method @Nullable public android.os.UserHandle createAndProvisionManagedProfile(@NonNull android.app.admin.ManagedProfileProvisioningParams) throws android.app.admin.ProvisioningException; + method @RequiresPermission(android.Manifest.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS) public long forceNetworkLogs(); + method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public void forceRemoveActiveAdmin(@NonNull android.content.ComponentName, int); + method @RequiresPermission(android.Manifest.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS) public long forceSecurityLogs(); method public void forceUpdateUserSetupComplete(); method public long getLastBugReportRequestTime(); method public long getLastNetworkLogRetrievalTime(); @@ -392,10 +405,13 @@ package android.app.admin { method public java.util.List<java.lang.String> getOwnerInstalledCaCerts(@NonNull android.os.UserHandle); method public boolean isCurrentInputMethodSetByOwner(); method public boolean isFactoryResetProtectionPolicySupported(); + method @RequiresPermission(anyOf={"android.permission.MARK_DEVICE_ORGANIZATION_OWNED", "android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS"}, conditional=true) public void markProfileOwnerOnOrganizationOwnedDevice(@NonNull android.content.ComponentName); method @NonNull public static String operationSafetyReasonToString(int); method @NonNull public static String operationToString(int); method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public void provisionFullyManagedDevice(@NonNull android.app.admin.FullyManagedDeviceProvisioningParams) throws android.app.admin.ProvisioningException; method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public void resetDefaultCrossProfileIntentFilters(int); + method @RequiresPermission(allOf={"android.permission.MANAGE_DEVICE_ADMINS", android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public void setActiveAdmin(@NonNull android.content.ComponentName, boolean, int); + method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public boolean setDeviceOwner(@NonNull android.content.ComponentName, @Nullable String, int); method @RequiresPermission("android.permission.MANAGE_DEVICE_ADMINS") public void setNextOperationSafety(int, int); field public static final String ACTION_DATA_SHARING_RESTRICTION_APPLIED = "android.app.action.DATA_SHARING_RESTRICTION_APPLIED"; field public static final int CODE_ACCOUNTS_NOT_EMPTY = 6; // 0x6 @@ -704,8 +720,10 @@ package android.content.pm { method public void holdLock(android.os.IBinder, int); method @RequiresPermission(android.Manifest.permission.KEEP_UNINSTALLED_PACKAGES) public void setKeepUninstalledPackages(@NonNull java.util.List<java.lang.String>); field public static final String FEATURE_ADOPTABLE_STORAGE = "android.software.adoptable_storage"; + field public static final String FEATURE_CAMERA_TOGGLE = "android.hardware.camera.toggle"; field public static final String FEATURE_FILE_BASED_ENCRYPTION = "android.software.file_based_encryption"; field public static final String FEATURE_HDMI_CEC = "android.hardware.hdmi.cec"; + field public static final String FEATURE_MICROPHONE_TOGGLE = "android.hardware.microphone.toggle"; field public static final int FLAG_PERMISSION_REVOKE_WHEN_REQUESTED = 128; // 0x80 field public static final int MATCH_KNOWN_PACKAGES = 4202496; // 0x402000 field public static final String SYSTEM_SHARED_LIBRARY_SERVICES = "android.ext.services"; @@ -732,6 +750,65 @@ package android.content.pm { ctor public ShortcutManager(android.content.Context); } + public class UserInfo implements android.os.Parcelable { + ctor public UserInfo(int, String, int); + ctor public UserInfo(int, String, String, int); + ctor public UserInfo(int, String, String, int, String); + ctor @Deprecated public UserInfo(); + ctor public UserInfo(android.content.pm.UserInfo); + method public boolean canHaveProfile(); + method public int describeContents(); + method public android.os.UserHandle getUserHandle(); + method public boolean isAdmin(); + method public boolean isDemo(); + method public boolean isEnabled(); + method public boolean isEphemeral(); + method public boolean isFull(); + method public boolean isGuest(); + method public boolean isInitialized(); + method public boolean isManagedProfile(); + method public boolean isPrimary(); + method public boolean isProfile(); + method public boolean isQuietModeEnabled(); + method public boolean isRestricted(); + method public boolean isSystemOnly(); + method public static boolean isSystemOnly(int); + method public boolean supportsSwitchTo(); + method public boolean supportsSwitchToByUser(); + method public void writeToParcel(android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.UserInfo> CREATOR; + field public static final int FLAG_ADMIN = 2; // 0x2 + field @Deprecated public static final int FLAG_DEMO = 512; // 0x200 + field public static final int FLAG_DISABLED = 64; // 0x40 + field public static final int FLAG_EPHEMERAL = 256; // 0x100 + field public static final int FLAG_FULL = 1024; // 0x400 + field @Deprecated public static final int FLAG_GUEST = 4; // 0x4 + field public static final int FLAG_INITIALIZED = 16; // 0x10 + field @Deprecated public static final int FLAG_MANAGED_PROFILE = 32; // 0x20 + field public static final int FLAG_PRIMARY = 1; // 0x1 + field public static final int FLAG_PROFILE = 4096; // 0x1000 + field public static final int FLAG_QUIET_MODE = 128; // 0x80 + field @Deprecated public static final int FLAG_RESTRICTED = 8; // 0x8 + field public static final int FLAG_SYSTEM = 2048; // 0x800 + field public static final int NO_PROFILE_GROUP_ID = -10000; // 0xffffd8f0 + field public boolean convertedFromPreCreated; + field public long creationTime; + field public int flags; + field public boolean guestToRemove; + field public String iconPath; + field public int id; + field public String lastLoggedInFingerprint; + field public long lastLoggedInTime; + field public String name; + field public boolean partial; + field public boolean preCreated; + field public int profileBadge; + field public int profileGroupId; + field public int restrictedProfileParentId; + field public int serialNumber; + field public String userType; + } + } package android.content.res { @@ -845,7 +922,7 @@ package android.graphics.drawable { package android.graphics.fonts { public class FontManager { - method @Nullable public android.text.FontConfig getFontConfig(); + method @NonNull public android.text.FontConfig getFontConfig(); method @RequiresPermission(android.Manifest.permission.UPDATE_FONTS) public int updateFontFamily(@NonNull android.graphics.fonts.FontFamilyUpdateRequest, @IntRange(from=0) int); method @RequiresPermission(android.Manifest.permission.UPDATE_FONTS) public int updateFontFile(@NonNull android.graphics.fonts.FontFileUpdateRequest, @IntRange(from=0) int); method @Deprecated @RequiresPermission(android.Manifest.permission.UPDATE_FONTS) public int updateFontFile(@NonNull android.os.ParcelFileDescriptor, @NonNull byte[], @IntRange(from=0) int); @@ -866,11 +943,21 @@ package android.graphics.fonts { package android.hardware { public final class SensorPrivacyManager { - method public boolean isIndividualSensorPrivacyEnabled(int); - method @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setIndividualSensorPrivacy(int, boolean); - method @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setIndividualSensorPrivacyForProfileGroup(int, boolean); - field public static final int INDIVIDUAL_SENSOR_CAMERA = 2; // 0x2 - field public static final int INDIVIDUAL_SENSOR_MICROPHONE = 1; // 0x1 + method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void addSensorPrivacyListener(int, @NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener); + method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void addSensorPrivacyListener(int, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener); + method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean isSensorPrivacyEnabled(int); + method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void removeSensorPrivacyListener(@NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener); + method @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacy(int, boolean); + method @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacyForProfileGroup(int, boolean); + } + + public static interface SensorPrivacyManager.OnSensorPrivacyChangedListener { + method public void onSensorPrivacyChanged(boolean); + } + + public static class SensorPrivacyManager.Sensors { + field public static final int CAMERA = 2; // 0x2 + field public static final int MICROPHONE = 1; // 0x1 } } @@ -1485,8 +1572,15 @@ package android.os { } public class UserManager { + method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo createProfileForUser(@Nullable String, @NonNull String, int, int, @Nullable String[]); + method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo createRestrictedProfile(@Nullable String); + method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo createUser(@Nullable String, @NonNull String, int); + method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public String getUserType(); + method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.content.pm.UserInfo> getUsers(boolean, boolean, boolean); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean hasBaseUserRestriction(@NonNull String, @NonNull android.os.UserHandle); + method public static boolean isGuestUserEphemeral(); method public static boolean isSplitSystemUser(); + method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo preCreateUser(@NonNull String) throws android.os.UserManager.UserOperationException; } public final class VibrationAttributes implements android.os.Parcelable { diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt index 87fb5b1b3b2e..a536efb2b488 100644 --- a/core/api/test-lint-baseline.txt +++ b/core/api/test-lint-baseline.txt @@ -468,7 +468,7 @@ GetterSetterNames: android.location.GnssMeasurement#setBasebandCn0DbHz(double): GetterSetterNames: android.location.GnssMeasurement#setCarrierFrequencyHz(float): GetterSetterNames: android.location.GnssMeasurement#setCodeType(String): - + GetterSetterNames: android.location.GnssMeasurement#setCorrelationVectors(java.util.Collection<android.location.CorrelationVector>): GetterSetterNames: android.location.GnssMeasurement#setFullInterSignalBiasNanos(double): @@ -480,7 +480,7 @@ GetterSetterNames: android.location.GnssMeasurement#setSatelliteInterSignalBiasN GetterSetterNames: android.location.GnssMeasurement#setSatelliteInterSignalBiasUncertaintyNanos(double): GetterSetterNames: android.location.GnssMeasurement#setSatellitePvt(android.location.SatellitePvt): - + GetterSetterNames: android.location.GnssMeasurement#setSnrInDb(double): GetterSetterNames: android.location.LocationRequest#isLocationSettingsIgnored(): @@ -953,6 +953,32 @@ MissingNullability: android.content.pm.PackageManager#holdLock(android.os.IBinde MissingNullability: android.content.pm.ShortcutManager#ShortcutManager(android.content.Context) parameter #0: +MissingNullability: android.content.pm.UserInfo#UserInfo(android.content.pm.UserInfo) parameter #0: + Missing nullability on parameter `orig` in method `UserInfo` +MissingNullability: android.content.pm.UserInfo#UserInfo(int, String, String, int) parameter #1: + Missing nullability on parameter `name` in method `UserInfo` +MissingNullability: android.content.pm.UserInfo#UserInfo(int, String, String, int) parameter #2: + Missing nullability on parameter `iconPath` in method `UserInfo` +MissingNullability: android.content.pm.UserInfo#UserInfo(int, String, String, int, String) parameter #1: + Missing nullability on parameter `name` in method `UserInfo` +MissingNullability: android.content.pm.UserInfo#UserInfo(int, String, String, int, String) parameter #2: + Missing nullability on parameter `iconPath` in method `UserInfo` +MissingNullability: android.content.pm.UserInfo#UserInfo(int, String, String, int, String) parameter #4: + Missing nullability on parameter `userType` in method `UserInfo` +MissingNullability: android.content.pm.UserInfo#UserInfo(int, String, int) parameter #1: + Missing nullability on parameter `name` in method `UserInfo` +MissingNullability: android.content.pm.UserInfo#getUserHandle(): + Missing nullability on method `getUserHandle` return +MissingNullability: android.content.pm.UserInfo#iconPath: + Missing nullability on field `iconPath` in class `class android.content.pm.UserInfo` +MissingNullability: android.content.pm.UserInfo#lastLoggedInFingerprint: + Missing nullability on field `lastLoggedInFingerprint` in class `class android.content.pm.UserInfo` +MissingNullability: android.content.pm.UserInfo#name: + Missing nullability on field `name` in class `class android.content.pm.UserInfo` +MissingNullability: android.content.pm.UserInfo#userType: + Missing nullability on field `userType` in class `class android.content.pm.UserInfo` +MissingNullability: android.content.pm.UserInfo#writeToParcel(android.os.Parcel, int) parameter #0: + Missing nullability on parameter `dest` in method `writeToParcel` MissingNullability: android.content.res.AssetManager#getOverlayablesToString(String) parameter #0: MissingNullability: android.content.res.Configuration#windowConfiguration: @@ -2433,6 +2459,38 @@ MutableBareField: android.content.AutofillOptions#disabledActivities: MutableBareField: android.content.AutofillOptions#whitelistedActivitiesForAugmentedAutofill: +MutableBareField: android.content.pm.UserInfo#convertedFromPreCreated: + Bare field convertedFromPreCreated must be marked final, or moved behind accessors if mutable +MutableBareField: android.content.pm.UserInfo#creationTime: + Bare field creationTime must be marked final, or moved behind accessors if mutable +MutableBareField: android.content.pm.UserInfo#flags: + Bare field flags must be marked final, or moved behind accessors if mutable +MutableBareField: android.content.pm.UserInfo#guestToRemove: + Bare field guestToRemove must be marked final, or moved behind accessors if mutable +MutableBareField: android.content.pm.UserInfo#iconPath: + Bare field iconPath must be marked final, or moved behind accessors if mutable +MutableBareField: android.content.pm.UserInfo#id: + Bare field id must be marked final, or moved behind accessors if mutable +MutableBareField: android.content.pm.UserInfo#lastLoggedInFingerprint: + Bare field lastLoggedInFingerprint must be marked final, or moved behind accessors if mutable +MutableBareField: android.content.pm.UserInfo#lastLoggedInTime: + Bare field lastLoggedInTime must be marked final, or moved behind accessors if mutable +MutableBareField: android.content.pm.UserInfo#name: + Bare field name must be marked final, or moved behind accessors if mutable +MutableBareField: android.content.pm.UserInfo#partial: + Bare field partial must be marked final, or moved behind accessors if mutable +MutableBareField: android.content.pm.UserInfo#preCreated: + Bare field preCreated must be marked final, or moved behind accessors if mutable +MutableBareField: android.content.pm.UserInfo#profileBadge: + Bare field profileBadge must be marked final, or moved behind accessors if mutable +MutableBareField: android.content.pm.UserInfo#profileGroupId: + Bare field profileGroupId must be marked final, or moved behind accessors if mutable +MutableBareField: android.content.pm.UserInfo#restrictedProfileParentId: + Bare field restrictedProfileParentId must be marked final, or moved behind accessors if mutable +MutableBareField: android.content.pm.UserInfo#serialNumber: + Bare field serialNumber must be marked final, or moved behind accessors if mutable +MutableBareField: android.content.pm.UserInfo#userType: + Bare field userType must be marked final, or moved behind accessors if mutable MutableBareField: android.database.sqlite.SQLiteDebug.DbStats#cache: MutableBareField: android.database.sqlite.SQLiteDebug.DbStats#dbName: @@ -2548,15 +2606,15 @@ NoSettingsProvider: android.provider.Settings.Global#TETHER_OFFLOAD_DISABLED: NoSettingsProvider: android.provider.Settings.Global#USE_OPEN_WIFI_PACKAGE: NoSettingsProvider: android.provider.Settings.Secure#ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED: - + NoSettingsProvider: android.provider.Settings.Secure#ACCESSIBILITY_MAGNIFICATION_CAPABILITY: - + NoSettingsProvider: android.provider.Settings.Secure#ACCESSIBILITY_MAGNIFICATION_MODE: - + NoSettingsProvider: android.provider.Settings.Secure#ACCESSIBILITY_MAGNIFICATION_MODE_ALL: - + NoSettingsProvider: android.provider.Settings.Secure#ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN: - + NoSettingsProvider: android.provider.Settings.Secure#ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW: NoSettingsProvider: android.provider.Settings.Secure#ACCESSIBILITY_SHORTCUT_TARGET_SERVICE: @@ -2619,6 +2677,10 @@ NotCloseable: android.telephony.ims.stub.ImsUtImplBase: +NullableCollection: android.os.UserManager#createProfileForUser(String, String, int, int, String[]) parameter #4: + Type of parameter disallowedPackages in android.os.UserManager.createProfileForUser(String name, String userType, int flags, int userId, String[] disallowedPackages) is a nullable collection (`java.lang.String[]`); must be non-null + + OnNameExpected: android.service.autofill.augmented.AugmentedAutofillService#dump(java.io.PrintWriter, String[]): OnNameExpected: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]): @@ -2729,6 +2791,8 @@ ParcelCreator: android.service.autofill.InternalValidator: ParcelNotFinal: android.app.WindowConfiguration: +ParcelNotFinal: android.content.pm.UserInfo: + Parcelable classes must be final: android.content.pm.UserInfo is not final ParcelNotFinal: android.net.metrics.IpConnectivityLog.Event: ParcelNotFinal: android.os.IncidentManager.IncidentReport: diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 07e70dca3511..f905ec86aab7 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -511,6 +511,7 @@ public class ActivityManager { /** @hide Process is hosting the current top activities. Note that this covers * all activities that are visible to the user. */ @UnsupportedAppUsage + @TestApi public static final int PROCESS_STATE_TOP = ProcessStateEnum.TOP; /** @hide Process is bound to a TOP app. */ @@ -518,6 +519,7 @@ public class ActivityManager { /** @hide Process is hosting a foreground service. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @TestApi public static final int PROCESS_STATE_FOREGROUND_SERVICE = ProcessStateEnum.FOREGROUND_SERVICE; /** @hide Process is hosting a foreground service due to a system binding. */ @@ -617,6 +619,7 @@ public class ActivityManager { public static final int PROCESS_CAPABILITY_FOREGROUND_MICROPHONE = 1 << 2; /** @hide Process can access network despite any power saving resrictions */ + @TestApi public static final int PROCESS_CAPABILITY_NETWORK = 1 << 3; /** @hide all capabilities, the ORing of all flags in {@link ProcessCapability}*/ @@ -3432,6 +3435,36 @@ public class ActivityManager { } /** + * Returns the process state of this uid. + * + * @hide + */ + @TestApi + @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS) + public int getUidProcessState(int uid) { + try { + return getService().getUidProcessState(uid, mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns the process capability of this uid. + * + * @hide + */ + @TestApi + @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS) + public @ProcessCapability int getUidProcessCapabilities(int uid) { + try { + return getService().getUidProcessCapabilities(uid, mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Return the importance of a given package name, based on the processes that are * currently running. The return value is one of the importance constants defined * in {@link RunningAppProcessInfo}, giving you the highest importance of all the diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index c31c22cca329..c812e8e1782a 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -30,6 +30,7 @@ import android.content.pm.UserInfo; import android.net.Uri; import android.os.Bundle; import android.os.IBinder; +import android.os.PowerWhitelistManager.ReasonCode; import android.os.PowerWhitelistManager.TempAllowListType; import android.os.TransactionTooLargeException; import android.os.WorkSource; @@ -102,12 +103,15 @@ public abstract class ActivityManagerInternal { * Sets how long a {@link PendingIntent} can be temporarily allowlisted to bypass restrictions * such as Power Save mode. * @param target - * @param whitelistToken + * @param allowlistToken * @param duration temp allowlist duration in milliseconds. * @param type temp allowlist type defined at {@link TempAllowListType} + * @param reasonCode one of {@link ReasonCode} + * @param reason A human-readable reason for logging purposes. */ - public abstract void setPendingIntentWhitelistDuration(IIntentSender target, - IBinder whitelistToken, long duration, int type); + public abstract void setPendingIntentAllowlistDuration(IIntentSender target, + IBinder allowlistToken, long duration, @TempAllowListType int type, + @ReasonCode int reasonCode, @Nullable String reason); /** * Returns the flags set for a {@link PendingIntent}. @@ -127,20 +131,26 @@ public abstract class ActivityManagerInternal { IBinder allowlistToken); /** - * Allow DeviceIdleController to tell us about what apps are whitelisted. + * Allow DeviceIdleController to tell us about what apps are allowlisted. */ - public abstract void setDeviceIdleWhitelist(int[] allAppids, int[] exceptIdleAppids); + public abstract void setDeviceIdleAllowlist(int[] allAppids, int[] exceptIdleAppids); /** - * Update information about which app IDs are on the temp whitelist. + * Update information about which app IDs are on the temp allowlist. * @param appids the updated list of appIds in temp allowlist. * @param changingUid uid to add or remove to temp allowlist. * @param adding true to add to temp allowlist, false to remove from temp allowlist. * @param durationMs when adding is true, the duration to be in temp allowlist. * @param type temp allowlist type defined at {@link TempAllowListType}. + * @param reasonCode one of {@link ReasonCode} + * @param reason A human-readable reason for logging purposes. + * @param callingUid the callingUid that setup this temp allowlist, only valid when param adding + * is true. */ - public abstract void updateDeviceIdleTempWhitelist(int[] appids, int changingUid, - boolean adding, long durationMs, @TempAllowListType int type); + public abstract void updateDeviceIdleTempAllowlist(int[] appids, int changingUid, + boolean adding, long durationMs, @TempAllowListType int type, + @ReasonCode int reasonCode, + @Nullable String reason, int callingUid); /** * Get the procstate for the UID. The return value will be between @@ -335,10 +345,11 @@ public abstract class ActivityManagerInternal { * @param targetUid the UID that is been temp allowlisted. * @param duration temp allowlist duration in milliseconds. * @param type temp allowlist type defined at {@link TempAllowListType} - * @param tag + * @param reasonCode one of {@link ReasonCode} + * @param reason */ - public abstract void tempWhitelistForPendingIntent(int callerPid, int callerUid, int targetUid, - long duration, int type, String tag); + public abstract void tempAllowlistForPendingIntent(int callerPid, int callerUid, int targetUid, + long duration, int type, @ReasonCode int reasonCode, String reason); public abstract int broadcastIntentInPackage(String packageName, @Nullable String featureId, int uid, int realCallingUid, int realCallingPid, Intent intent, String resolvedType, @@ -495,9 +506,9 @@ public abstract class ActivityManagerInternal { /** * Sends a broadcast, assuming the caller to be the system and allowing the inclusion of an - * approved whitelist of app Ids >= {@link android.os.Process#FIRST_APPLICATION_UID} that the + * approved allowlist of app Ids >= {@link android.os.Process#FIRST_APPLICATION_UID} that the * broadcast my be sent to; any app Ids < {@link android.os.Process#FIRST_APPLICATION_UID} are - * automatically whitelisted. + * automatically allowlisted. * * @see com.android.server.am.ActivityManagerService#broadcastIntentWithFeature( * IApplicationThread, String, Intent, String, IIntentReceiver, int, String, Bundle, diff --git a/core/java/android/app/AnrController.java b/core/java/android/app/AnrController.java index cfc9d2715720..a0d4b3a6a753 100644 --- a/core/java/android/app/AnrController.java +++ b/core/java/android/app/AnrController.java @@ -23,7 +23,29 @@ package android.app; public interface AnrController { /** * Returns the delay in milliseconds for an ANR dialog that is about to be shown for - * {@code packageName}. + * {@code packageName} with {@code uid}. + * + * Implementations should only return a positive value if they actually expect the + * {@code packageName} to be delayed due to them. + + * If there are multiple controllers registered, the controller with the max delay will + * be selected and will receive an {@link #onAnrDelayStarted} callback at the start of the + * delay and an {@link #onAnrDelayCompleted} at the end of the delay. */ long getAnrDelayMillis(String packageName, int uid); + + /** + * Notifies the controller at the start of the ANR dialog delay for {@code packageName} with + * {@code uid}. The controller can decide to show a progress UI after this notification. + */ + void onAnrDelayStarted(String packageName, int uid); + + /** + * Notifies the controller at the end of the ANR dialog delay for {@code packageName} with + * {@code uid}. + * + * @return whether the ANR dialog should be shown or cancelled. {@code true} if the + * ANR dialog should be shown, {@code false} if it should be cancelled. + */ + boolean onAnrDelayCompleted(String packageName, int uid); } diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 2f3b50b17d51..160844aacc46 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -1141,23 +1141,20 @@ public class AppOpsManager { * * @hide */ - // TODO: Add as AppProtoEnums - public static final int OP_PHONE_CALL_MICROPHONE = 100; + public static final int OP_PHONE_CALL_MICROPHONE = AppProtoEnums.APP_OP_PHONE_CALL_MICROPHONE; /** * Phone call is using camera * * @hide */ - // TODO: Add as AppProtoEnums - public static final int OP_PHONE_CALL_CAMERA = 101; + public static final int OP_PHONE_CALL_CAMERA = AppProtoEnums.APP_OP_PHONE_CALL_CAMERA; /** * Audio is being recorded for hotword detection. * * @hide */ - // TODO: Add as AppProtoEnums - public static final int OP_RECORD_AUDIO_HOTWORD = 102; + public static final int OP_RECORD_AUDIO_HOTWORD = AppProtoEnums.APP_OP_RECORD_AUDIO_HOTWORD; /** * Manage credentials in the system KeyChain. @@ -1184,10 +1181,29 @@ public class AppOpsManager { */ public static final int OP_SCHEDULE_EXACT_ALARM = AppProtoEnums.APP_OP_SCHEDULE_EXACT_ALARM; + /** + * Fine location being accessed by a location source, which is + * a component that already has location data since it is the one + * that produces location, which is it is a data source for + * location data. + * + * @hide + */ + public static final int OP_FINE_LOCATION_SOURCE = AppProtoEnums.APP_OP_FINE_LOCATION_SOURCE; + + /** + * Coarse location being accessed by a location source, which is + * a component that already has location data since it is the one + * that produces location, which is it is a data source for + * location data. + * + * @hide + */ + public static final int OP_COARSE_LOCATION_SOURCE = AppProtoEnums.APP_OP_COARSE_LOCATION_SOURCE; /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final int _NUM_OP = 108; + public static final int _NUM_OP = 110; /** Access to coarse location information. */ public static final String OPSTR_COARSE_LOCATION = "android:coarse_location"; @@ -1567,6 +1583,24 @@ public class AppOpsManager { */ public static final String OPSTR_SCHEDULE_EXACT_ALARM = "android:schedule_exact_alarm"; + /** + * Fine location being accessed by a location source, which is + * a component that already has location since it is the one that + * produces location. + * + * @hide + */ + public static final String OPSTR_FINE_LOCATION_SOURCE = "android:fine_location_source"; + + /** + * Coarse location being accessed by a location source, which is + * a component that already has location since it is the one that + * produces location. + * + * @hide + */ + public static final String OPSTR_COARSE_LOCATION_SOURCE = "android:coarse_location_source"; + /** {@link #sAppOpsToNote} not initialized yet for this op */ private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0; /** Should not collect noting of this app-op in {@link #sAppOpsToNote} */ @@ -1767,6 +1801,8 @@ public class AppOpsManager { OP_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER OP_RECORD_AUDIO_OUTPUT, // RECORD_AUDIO_OUTPUT OP_SCHEDULE_EXACT_ALARM, // SCHEDULE_EXACT_ALARM + OP_FINE_LOCATION, // OP_FINE_LOCATION_SOURCE + OP_COARSE_LOCATION, // OP_COARSE_LOCATION_SOURCE }; /** @@ -1881,6 +1917,8 @@ public class AppOpsManager { OPSTR_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER, OPSTR_RECORD_AUDIO_OUTPUT, OPSTR_SCHEDULE_EXACT_ALARM, + OPSTR_FINE_LOCATION_SOURCE, + OPSTR_COARSE_LOCATION_SOURCE, }; /** @@ -1996,6 +2034,8 @@ public class AppOpsManager { "USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER", "RECORD_AUDIO_OUTPUT", "SCHEDULE_EXACT_ALARM", + "FINE_LOCATION_SOURCE", + "COARSE_LOCATION_SOURCE", }; /** @@ -2112,6 +2152,8 @@ public class AppOpsManager { Manifest.permission.USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER, null, // no permission for OP_RECORD_AUDIO_OUTPUT Manifest.permission.SCHEDULE_EXACT_ALARM, + null, // no permission for OP_ACCESS_FINE_LOCATION_SOURCE, + null, // no permission for OP_ACCESS_COARSE_LOCATION_SOURCE, }; /** @@ -2228,6 +2270,8 @@ public class AppOpsManager { null, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER null, // RECORD_AUDIO_OUTPUT null, // SCHEDULE_EXACT_ALARM + null, // ACCESS_FINE_LOCATION_SOURCE + null, // ACCESS_COARSE_LOCATION_SOURCE }; /** @@ -2343,6 +2387,8 @@ public class AppOpsManager { null, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER null, // RECORD_AUDIO_OUTPUT null, // SCHEDULE_EXACT_ALARM + null, // ACCESS_FINE_LOCATION_SOURCE + null, // ACCESS_COARSE_LOCATION_SOURCE }; /** @@ -2457,6 +2503,8 @@ public class AppOpsManager { AppOpsManager.MODE_DEFAULT, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER AppOpsManager.MODE_ALLOWED, // RECORD_AUDIO_OUTPUT AppOpsManager.MODE_DEFAULT, // SCHEDULE_EXACT_ALARM + AppOpsManager.MODE_ALLOWED, // ACCESS_FINE_LOCATION_SOURCE + AppOpsManager.MODE_ALLOWED, // ACCESS_COARSE_LOCATION_SOURCE }; /** @@ -2575,6 +2623,8 @@ public class AppOpsManager { true, // USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER false, // RECORD_AUDIO_OUTPUT false, // SCHEDULE_EXACT_ALARM + false, // ACCESS_FINE_LOCATION_SOURCE + false, // ACCESS_COARSE_LOCATION_SOURCE }; /** diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 062cab457ebe..a6260d6d9cad 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -1797,6 +1797,7 @@ public class ApplicationPackageManager extends PackageManager { } } + @UnsupportedAppUsage protected ApplicationPackageManager(ContextImpl context, IPackageManager pm) { mContext = context; mPM = pm; diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java index 445fdd83f34a..2e06e9b80595 100644 --- a/core/java/android/app/BroadcastOptions.java +++ b/core/java/android/app/BroadcastOptions.java @@ -16,11 +16,13 @@ package android.app; +import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.os.Build; import android.os.Bundle; import android.os.PowerWhitelistManager; +import android.os.PowerWhitelistManager.ReasonCode; import android.os.PowerWhitelistManager.TempAllowListType; /** @@ -31,8 +33,11 @@ import android.os.PowerWhitelistManager.TempAllowListType; */ @SystemApi public class BroadcastOptions { - private long mTemporaryAppWhitelistDuration; - private @TempAllowListType int mTemporaryAppWhitelistType; + private long mTemporaryAppAllowlistDuration; + private @TempAllowListType int mTemporaryAppAllowlistType; + private @ReasonCode int mTemporaryAppAllowlistReasonCode = + PowerWhitelistManager.REASON_UNKNOWN; + private @Nullable String mTemporaryAppAllowlistReason; private int mMinManifestReceiverApiLevel = 0; private int mMaxManifestReceiverApiLevel = Build.VERSION_CODES.CUR_DEVELOPMENT; private boolean mDontSendToRestrictedApps = false; @@ -42,11 +47,17 @@ public class BroadcastOptions { * How long to temporarily put an app on the power allowlist when executing this broadcast * to it. */ - static final String KEY_TEMPORARY_APP_WHITELIST_DURATION - = "android:broadcast.temporaryAppWhitelistDuration"; + static final String KEY_TEMPORARY_APP_ALLOWLIST_DURATION + = "android:broadcast.temporaryAppAllowlistDuration"; - static final String KEY_TEMPORARY_APP_WHITELIST_TYPE - = "android:broadcast.temporaryAppWhitelistType"; + static final String KEY_TEMPORARY_APP_ALLOWLIST_TYPE + = "android:broadcast.temporaryAppAllowlistType"; + + static final String KEY_TEMPORARY_APP_ALLOWLIST_REASON_CODE = + "android:broadcast.temporaryAppAllowlistReasonCode"; + + static final String KEY_TEMPORARY_APP_ALLOWLIST_REASON = + "android:broadcast.temporaryAppAllowlistReason"; /** * Corresponds to {@link #setMinManifestReceiverApiLevel}. @@ -80,6 +91,7 @@ public class BroadcastOptions { @Deprecated public static final int TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_ALLOWED = PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED; + /** * @hide * @deprecated Use {@link android.os.PowerWhitelistManager# @@ -99,8 +111,11 @@ public class BroadcastOptions { /** @hide */ public BroadcastOptions(Bundle opts) { - mTemporaryAppWhitelistDuration = opts.getLong(KEY_TEMPORARY_APP_WHITELIST_DURATION); - mTemporaryAppWhitelistType = opts.getInt(KEY_TEMPORARY_APP_WHITELIST_TYPE); + mTemporaryAppAllowlistDuration = opts.getLong(KEY_TEMPORARY_APP_ALLOWLIST_DURATION); + mTemporaryAppAllowlistType = opts.getInt(KEY_TEMPORARY_APP_ALLOWLIST_TYPE); + mTemporaryAppAllowlistReasonCode = opts.getInt(KEY_TEMPORARY_APP_ALLOWLIST_REASON_CODE, + PowerWhitelistManager.REASON_UNKNOWN); + mTemporaryAppAllowlistReason = opts.getString(KEY_TEMPORARY_APP_ALLOWLIST_REASON); mMinManifestReceiverApiLevel = opts.getInt(KEY_MIN_MANIFEST_RECEIVER_API_LEVEL, 0); mMaxManifestReceiverApiLevel = opts.getInt(KEY_MAX_MANIFEST_RECEIVER_API_LEVEL, Build.VERSION_CODES.CUR_DEVELOPMENT); @@ -113,14 +128,16 @@ public class BroadcastOptions { * Set a duration for which the system should temporary place an application on the * power allowlist when this broadcast is being delivered to it. * @param duration The duration in milliseconds; 0 means to not place on allowlist. + * @deprecated use {@link #setTemporaryAppAllowlist(long, int, int, String)} instead. */ + @Deprecated @RequiresPermission(anyOf = {android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}) public void setTemporaryAppWhitelistDuration(long duration) { - mTemporaryAppWhitelistDuration = duration; - mTemporaryAppWhitelistType = - PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED; + setTemporaryAppAllowlist(duration, + PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, + PowerWhitelistManager.REASON_UNKNOWN, null); } /** @@ -129,29 +146,69 @@ public class BroadcastOptions { * type. * @param type one of {@link TempAllowListType} * @param duration the duration in milliseconds; 0 means to not place on allowlist. + * @deprecated use {@link #setTemporaryAppAllowlist(long, int, int, String)} instead. */ + @Deprecated @RequiresPermission(anyOf = {android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}) public void setTemporaryAppWhitelistDuration(@TempAllowListType int type, long duration) { - mTemporaryAppWhitelistDuration = duration; - mTemporaryAppWhitelistType = type; + setTemporaryAppAllowlist(duration, type, + PowerWhitelistManager.REASON_UNKNOWN, null); } /** - * Return {@link #setTemporaryAppWhitelistDuration}. + * Set a duration for which the system should temporary place an application on the + * power allowlist when this broadcast is being delivered to it, specify the temp allowlist + * type. + * @param duration the duration in milliseconds; 0 means to not place on allowlist. + * @param type one of {@link TempAllowListType} + * @param reasonCode one of {@link ReasonCode}, use + * {@link PowerWhitelistManager#REASON_UNKNOWN} if not sure. + * @param reason A human-readable reason explaining why the app is temp allowlisted. Only + * used for logging purposes. Could be null or empty string. + */ + @RequiresPermission(anyOf = {android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, + android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, + android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}) + public void setTemporaryAppAllowlist(long duration, @TempAllowListType int type, + @ReasonCode int reasonCode, @Nullable String reason) { + mTemporaryAppAllowlistDuration = duration; + mTemporaryAppAllowlistType = type; + mTemporaryAppAllowlistReasonCode = reasonCode; + mTemporaryAppAllowlistReason = reason; + } + + /** + * Return {@link #setTemporaryAppAllowlist}. + * @hide + */ + public long getTemporaryAppAllowlistDuration() { + return mTemporaryAppAllowlistDuration; + } + + /** + * Return {@link #mTemporaryAppAllowlistType}. * @hide */ - public long getTemporaryAppWhitelistDuration() { - return mTemporaryAppWhitelistDuration; + public @TempAllowListType int getTemporaryAppAllowlistType() { + return mTemporaryAppAllowlistType; } /** - * Return {@link #mTemporaryAppWhitelistType}. + * Return {@link #mTemporaryAppAllowlistReasonCode}. * @hide */ - public @TempAllowListType int getTemporaryAppWhitelistType() { - return mTemporaryAppWhitelistType; + public @ReasonCode int getTemporaryAppAllowlistReasonCode() { + return mTemporaryAppAllowlistReasonCode; + } + + /** + * Return {@link #mTemporaryAppAllowlistReason}. + * @hide + */ + public @Nullable String getTemporaryAppAllowlistReason() { + return mTemporaryAppAllowlistReason; } /** @@ -236,11 +293,17 @@ public class BroadcastOptions { */ public Bundle toBundle() { Bundle b = new Bundle(); - if (mTemporaryAppWhitelistDuration > 0) { - b.putLong(KEY_TEMPORARY_APP_WHITELIST_DURATION, mTemporaryAppWhitelistDuration); + if (mTemporaryAppAllowlistDuration > 0) { + b.putLong(KEY_TEMPORARY_APP_ALLOWLIST_DURATION, mTemporaryAppAllowlistDuration); + } + if (mTemporaryAppAllowlistType != 0) { + b.putInt(KEY_TEMPORARY_APP_ALLOWLIST_TYPE, mTemporaryAppAllowlistType); + } + if (mTemporaryAppAllowlistReasonCode != PowerWhitelistManager.REASON_UNKNOWN) { + b.putInt(KEY_TEMPORARY_APP_ALLOWLIST_REASON_CODE, mTemporaryAppAllowlistReasonCode); } - if (mTemporaryAppWhitelistType != 0) { - b.putInt(KEY_TEMPORARY_APP_WHITELIST_TYPE, mTemporaryAppWhitelistType); + if (mTemporaryAppAllowlistReason != null) { + b.putString(KEY_TEMPORARY_APP_ALLOWLIST_REASON, mTemporaryAppAllowlistReason); } if (mMinManifestReceiverApiLevel != 0) { b.putInt(KEY_MIN_MANIFEST_RECEIVER_API_LEVEL, mMinManifestReceiverApiLevel); diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 0bc5958d4539..3a8172ea98b8 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -549,7 +549,7 @@ interface IActivityManager { /** * Add a bare uid to the background restrictions whitelist. Only the system uid may call this. */ - void backgroundWhitelistUid(int uid); + void backgroundAllowlistUid(int uid); // Start of P transactions /** @@ -707,4 +707,6 @@ interface IActivityManager { /** Called by PendingIntent.queryIntentComponents() */ List<ResolveInfo> queryIntentComponentsForIntentSender(in IIntentSender sender, int matchFlags); + + int getUidProcessCapabilities(int uid, in String callingPackage); } diff --git a/core/java/android/app/ITaskStackListener.aidl b/core/java/android/app/ITaskStackListener.aidl index b1c39d39d414..7bb5d9a8d387 100644 --- a/core/java/android/app/ITaskStackListener.aidl +++ b/core/java/android/app/ITaskStackListener.aidl @@ -60,9 +60,9 @@ oneway interface ITaskStackListener { void onActivityForcedResizable(String packageName, int taskId, int reason); /** - * Called when we launched an activity that dismissed the docked stack. + * Called when we launched an activity that dismissed the docked task. */ - void onActivityDismissingDockedStack(); + void onActivityDismissingDockedTask(); /** * Called when an activity was requested to be launched on a secondary display but was not diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java index 2f06bdce9087..549bd4b9fe6a 100644 --- a/core/java/android/app/PendingIntent.java +++ b/core/java/android/app/PendingIntent.java @@ -61,6 +61,7 @@ import com.android.internal.os.IResultReceiver; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; +import java.util.Objects; /** * A description of an Intent and target action to perform with it. Instances @@ -464,8 +465,7 @@ public final class PendingIntent implements Parcelable { public static PendingIntent getActivityAsUser(Context context, int requestCode, @NonNull Intent intent, int flags, Bundle options, UserHandle user) { String packageName = context.getPackageName(); - String resolvedType = intent != null ? intent.resolveTypeIfNeeded( - context.getContentResolver()) : null; + String resolvedType = intent.resolveTypeIfNeeded(context.getContentResolver()); checkFlags(flags, packageName); try { intent.migrateExtraStreamToClipData(context); @@ -639,7 +639,7 @@ public final class PendingIntent implements Parcelable { */ @SuppressWarnings("AndroidFrameworkPendingIntentMutability") public static PendingIntent getBroadcast(Context context, int requestCode, - Intent intent, @Flags int flags) { + @NonNull Intent intent, @Flags int flags) { return getBroadcastAsUser(context, requestCode, intent, flags, context.getUser()); } @@ -652,8 +652,7 @@ public final class PendingIntent implements Parcelable { public static PendingIntent getBroadcastAsUser(Context context, int requestCode, Intent intent, int flags, UserHandle userHandle) { String packageName = context.getPackageName(); - String resolvedType = intent != null ? intent.resolveTypeIfNeeded( - context.getContentResolver()) : null; + String resolvedType = intent.resolveTypeIfNeeded(context.getContentResolver()); checkFlags(flags, packageName); try { intent.prepareToLeaveProcess(context); @@ -732,8 +731,7 @@ public final class PendingIntent implements Parcelable { private static PendingIntent buildServicePendingIntent(Context context, int requestCode, Intent intent, int flags, int serviceKind) { String packageName = context.getPackageName(); - String resolvedType = intent != null ? intent.resolveTypeIfNeeded( - context.getContentResolver()) : null; + String resolvedType = intent.resolveTypeIfNeeded(context.getContentResolver()); checkFlags(flags, packageName); try { intent.prepareToLeaveProcess(context); @@ -755,6 +753,7 @@ public final class PendingIntent implements Parcelable { * @return Returns a IntentSender object that wraps the sender of PendingIntent * */ + @NonNull public IntentSender getIntentSender() { return new IntentSender(mTarget, mWhitelistToken); } @@ -767,6 +766,7 @@ public final class PendingIntent implements Parcelable { try { ActivityManager.getService().cancelIntentSender(mTarget); } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); } } @@ -1009,6 +1009,7 @@ public final class PendingIntent implements Parcelable { * @deprecated Renamed to {@link #getCreatorPackage()}. */ @Deprecated + @NonNull public String getTargetPackage() { return getCreatorPackage(); } @@ -1028,10 +1029,9 @@ public final class PendingIntent implements Parcelable { * only use this information to identify who you expect to be interacting with * through a {@link #send} call, not who gave you the PendingIntent.</p> * - * @return The package name of the PendingIntent, or null if there is - * none associated with it. + * @return The package name of the PendingIntent. */ - @Nullable + @NonNull public String getCreatorPackage() { return getCachedInfo().getCreatorPackage(); } @@ -1143,13 +1143,12 @@ public final class PendingIntent implements Parcelable { * only use this information to identify who you expect to be interacting with * through a {@link #send} call, not who gave you the PendingIntent.</p> * - * @return The user handle of the PendingIntent, or null if there is - * none associated with it. + * @return The user handle of the PendingIntent */ - @Nullable + @NonNull public UserHandle getCreatorUserHandle() { int uid = getCachedInfo().getCreatorUid(); - return uid > 0 ? new UserHandle(UserHandle.getUserId(uid)) : null; + return UserHandle.getUserHandleForUid(uid); } /** @@ -1282,7 +1281,7 @@ public final class PendingIntent implements Parcelable { sb.append("PendingIntent{"); sb.append(Integer.toHexString(System.identityHashCode(this))); sb.append(": "); - sb.append(mTarget != null ? mTarget.asBinder() : null); + sb.append(mTarget.asBinder()); sb.append('}'); return sb.toString(); } @@ -1290,9 +1289,7 @@ public final class PendingIntent implements Parcelable { /** @hide */ public void dumpDebug(ProtoOutputStream proto, long fieldId) { final long token = proto.start(fieldId); - if (mTarget != null) { - proto.write(PendingIntentProto.TARGET, mTarget.asBinder().toString()); - } + proto.write(PendingIntentProto.TARGET, mTarget.asBinder().toString()); proto.end(token); } @@ -1309,8 +1306,7 @@ public final class PendingIntent implements Parcelable { } - public static final @android.annotation.NonNull Parcelable.Creator<PendingIntent> CREATOR - = new Parcelable.Creator<PendingIntent>() { + public static final @NonNull Creator<PendingIntent> CREATOR = new Creator<PendingIntent>() { public PendingIntent createFromParcel(Parcel in) { IBinder target = in.readStrongBinder(); return target != null @@ -1364,11 +1360,11 @@ public final class PendingIntent implements Parcelable { * @hide */ public PendingIntent(IIntentSender target) { - mTarget = target; + mTarget = Objects.requireNonNull(target); } /*package*/ PendingIntent(IBinder target, Object cookie) { - mTarget = IIntentSender.Stub.asInterface(target); + mTarget = Objects.requireNonNull(IIntentSender.Stub.asInterface(target)); if (cookie != null) { mWhitelistToken = (IBinder)cookie; } diff --git a/core/java/android/app/TaskStackListener.java b/core/java/android/app/TaskStackListener.java index 1e382307a1a3..f523a7d29713 100644 --- a/core/java/android/app/TaskStackListener.java +++ b/core/java/android/app/TaskStackListener.java @@ -43,7 +43,7 @@ public abstract class TaskStackListener extends ITaskStackListener.Stub { @Override @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public void onActivityPinned(String packageName, int userId, int taskId, int stackId) + public void onActivityPinned(String packageName, int userId, int taskId, int rootTaskId) throws RemoteException { } @@ -66,7 +66,7 @@ public abstract class TaskStackListener extends ITaskStackListener.Stub { @Override @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public void onActivityDismissingDockedStack() throws RemoteException { + public void onActivityDismissingDockedTask() throws RemoteException { } @Override diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java index 4ae1670e9041..d04ca1d9a48e 100644 --- a/core/java/android/app/WindowConfiguration.java +++ b/core/java/android/app/WindowConfiguration.java @@ -57,7 +57,7 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu * TODO: Investigate combining with {@link #mAppBounds}. Can the latter be a product of the * former? */ - private Rect mBounds = new Rect(); + private final Rect mBounds = new Rect(); /** * {@link android.graphics.Rect} defining app bounds. The dimensions override usages of @@ -71,7 +71,7 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu * The maximum {@link Rect} bounds that an app can expect. It is used to report value of * {@link WindowManager#getMaximumWindowMetrics()}. */ - private Rect mMaxBounds = new Rect(); + private final Rect mMaxBounds = new Rect(); /** * The current rotation of this window container relative to the default @@ -240,9 +240,9 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeParcelable(mBounds, flags); - dest.writeParcelable(mAppBounds, flags); - dest.writeParcelable(mMaxBounds, flags); + mBounds.writeToParcel(dest, flags); + dest.writeTypedObject(mAppBounds, flags); + mMaxBounds.writeToParcel(dest, flags); dest.writeInt(mWindowingMode); dest.writeInt(mActivityType); dest.writeInt(mAlwaysOnTop); @@ -250,10 +250,11 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu dest.writeInt(mDisplayWindowingMode); } - private void readFromParcel(Parcel source) { - mBounds = source.readParcelable(Rect.class.getClassLoader()); - mAppBounds = source.readParcelable(Rect.class.getClassLoader()); - mMaxBounds = source.readParcelable(Rect.class.getClassLoader()); + /** @hide */ + public void readFromParcel(@NonNull Parcel source) { + mBounds.readFromParcel(source); + mAppBounds = source.readTypedObject(Rect.CREATOR); + mMaxBounds.readFromParcel(source); mWindowingMode = source.readInt(); mActivityType = source.readInt(); mAlwaysOnTop = source.readInt(); @@ -693,9 +694,7 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu } protoOutputStream.write(WINDOWING_MODE, mWindowingMode); protoOutputStream.write(ACTIVITY_TYPE, mActivityType); - if (mBounds != null) { - mBounds.dumpDebug(protoOutputStream, BOUNDS); - } + mBounds.dumpDebug(protoOutputStream, BOUNDS); mMaxBounds.dumpDebug(protoOutputStream, MAX_BOUNDS); protoOutputStream.end(token); } @@ -719,11 +718,9 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu mAppBounds.readFromProto(proto, APP_BOUNDS); break; case (int) BOUNDS: - mBounds = new Rect(); mBounds.readFromProto(proto, BOUNDS); break; case (int) MAX_BOUNDS: - mMaxBounds = new Rect(); mMaxBounds.readFromProto(proto, MAX_BOUNDS); break; case (int) WINDOWING_MODE: diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index b7b3ec16d24f..bb1ff6051d56 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -7302,7 +7302,12 @@ public class DevicePolicyManager { /** * @hide */ + @TestApi @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @RequiresPermission(allOf = { + android.Manifest.permission.MANAGE_DEVICE_ADMINS, + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL + }) public void setActiveAdmin(@NonNull ComponentName policyReceiver, boolean refreshing, int userHandle) { if (mService != null) { @@ -7479,8 +7484,10 @@ public class DevicePolicyManager { * @throws IllegalArgumentException if the package name is null or invalid * @throws IllegalStateException If the preconditions mentioned are not met. */ - public boolean setDeviceOwner(ComponentName who, String ownerName, int userId) - throws IllegalArgumentException, IllegalStateException { + @TestApi + @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) + public boolean setDeviceOwner( + @NonNull ComponentName who, @Nullable String ownerName, @UserIdInt int userId) { if (mService != null) { try { return mService.setDeviceOwner(who, ownerName, userId); @@ -7547,7 +7554,10 @@ public class DevicePolicyManager { * @hide */ @SystemApi - @RequiresPermission(android.Manifest.permission.MANAGE_USERS) + @RequiresPermission(anyOf = { + android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS, + }) public ComponentName getDeviceOwnerComponentOnAnyUser() { return getDeviceOwnerComponentInner(/* callingUserOnly =*/ false); } @@ -10503,9 +10513,10 @@ public class DevicePolicyManager { /** * Reset record of previous system update freeze period the device went through. - * Only callable by ADB. * @hide */ + @TestApi + @RequiresPermission(android.Manifest.permission.CLEAR_FREEZE_PERIOD) public void clearSystemUpdatePolicyFreezePeriodRecord() { throwIfParentInstance("clearSystemUpdatePolicyFreezePeriodRecord"); if (mService == null) { @@ -11233,9 +11244,11 @@ public class DevicePolicyManager { /** * Makes all accumulated network logs available to DPC in a new batch. - * Only callable by ADB. If throttled, returns time to wait in milliseconds, otherwise 0. + * If throttled, returns time to wait in milliseconds, otherwise 0. * @hide */ + @TestApi + @RequiresPermission(android.Manifest.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS) public long forceNetworkLogs() { if (mService == null) { return -1; @@ -11249,9 +11262,11 @@ public class DevicePolicyManager { /** * Forces a batch of security logs to be fetched from logd and makes it available for DPC. - * Only callable by ADB. If throttled, returns time to wait in milliseconds, otherwise 0. + * If throttled, returns time to wait in milliseconds, otherwise 0. * @hide */ + @TestApi + @RequiresPermission(android.Manifest.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS) public long forceSecurityLogs() { if (mService == null) { return 0; @@ -11683,7 +11698,10 @@ public class DevicePolicyManager { * @throws SecurityException if the caller is not shell / root or the admin package * isn't a test application see {@link ApplicationInfo#FLAG_TEST_APP}. */ - public void forceRemoveActiveAdmin(ComponentName adminReceiver, int userHandle) { + @TestApi + @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) + public void forceRemoveActiveAdmin( + @NonNull ComponentName adminReceiver, @UserIdInt int userHandle) { try { mService.forceRemoveActiveAdmin(adminReceiver, userHandle); } catch (RemoteException re) { @@ -12753,8 +12771,11 @@ public class DevicePolicyManager { * * @hide */ - @RequiresPermission(value = android.Manifest.permission.MARK_DEVICE_ORGANIZATION_OWNED, - conditional = true) + @TestApi + @RequiresPermission(anyOf = { + android.Manifest.permission.MARK_DEVICE_ORGANIZATION_OWNED, + android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS + }, conditional = true) public void markProfileOwnerOnOrganizationOwnedDevice(@NonNull ComponentName who) { if (mService == null) { return; diff --git a/core/java/android/app/people/PeopleSpaceTile.java b/core/java/android/app/people/PeopleSpaceTile.java index 132af4b2f67b..dd2ba7db03ae 100644 --- a/core/java/android/app/people/PeopleSpaceTile.java +++ b/core/java/android/app/people/PeopleSpaceTile.java @@ -29,6 +29,7 @@ import android.graphics.drawable.Icon; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; +import android.os.UserHandle; import java.util.ArrayList; import java.util.List; @@ -45,7 +46,7 @@ public class PeopleSpaceTile implements Parcelable { private String mId; private CharSequence mUserName; private Icon mUserIcon; - private int mUid; + private UserHandle mUserHandle; private Uri mContactUri; private String mPackageName; private String mBirthdayText; @@ -64,7 +65,7 @@ public class PeopleSpaceTile implements Parcelable { mUserName = b.mUserName; mUserIcon = b.mUserIcon; mContactUri = b.mContactUri; - mUid = b.mUid; + mUserHandle = b.mUserHandle; mPackageName = b.mPackageName; mBirthdayText = b.mBirthdayText; mLastInteractionTimestamp = b.mLastInteractionTimestamp; @@ -95,8 +96,8 @@ public class PeopleSpaceTile implements Parcelable { return mContactUri; } - public int getUid() { - return mUid; + public UserHandle getUserHandle() { + return mUserHandle; } public String getPackageName() { @@ -165,7 +166,7 @@ public class PeopleSpaceTile implements Parcelable { Builder builder = new Builder(mId, mUserName.toString(), mUserIcon, mIntent); builder.setContactUri(mContactUri); - builder.setUid(mUid); + builder.setUserHandle(mUserHandle); builder.setPackageName(mPackageName); builder.setBirthdayText(mBirthdayText); builder.setLastInteractionTimestamp(mLastInteractionTimestamp); @@ -186,7 +187,7 @@ public class PeopleSpaceTile implements Parcelable { private CharSequence mUserName; private Icon mUserIcon; private Uri mContactUri; - private int mUid; + private UserHandle mUserHandle; private String mPackageName; private String mBirthdayText; private long mLastInteractionTimestamp; @@ -212,7 +213,7 @@ public class PeopleSpaceTile implements Parcelable { mId = info.getId(); mUserName = info.getLabel(); mUserIcon = convertDrawableToIcon(launcherApps.getShortcutIconDrawable(info, 0)); - mUid = info.getUserId(); + mUserHandle = info.getUserHandle(); mPackageName = info.getPackage(); mContactUri = getContactUri(info); } @@ -222,7 +223,7 @@ public class PeopleSpaceTile implements Parcelable { mId = info.getId(); mUserName = info.getLabel(); mUserIcon = convertDrawableToIcon(launcherApps.getShortcutIconDrawable(info, 0)); - mUid = info.getUserId(); + mUserHandle = info.getUserHandle(); mPackageName = info.getPackage(); mContactUri = getContactUri(info); mStatuses = channel.getStatuses(); @@ -265,9 +266,9 @@ public class PeopleSpaceTile implements Parcelable { return this; } - /** Sets the associated uid. */ - public Builder setUid(int uid) { - mUid = uid; + /** Sets the associated {@code userHandle}. */ + public Builder setUserHandle(UserHandle userHandle) { + mUserHandle = userHandle; return this; } @@ -349,7 +350,7 @@ public class PeopleSpaceTile implements Parcelable { mUserName = in.readCharSequence(); mUserIcon = in.readParcelable(Icon.class.getClassLoader()); mContactUri = in.readParcelable(Uri.class.getClassLoader()); - mUid = in.readInt(); + mUserHandle = in.readParcelable(UserHandle.class.getClassLoader()); mPackageName = in.readString(); mBirthdayText = in.readString(); mLastInteractionTimestamp = in.readLong(); @@ -375,7 +376,7 @@ public class PeopleSpaceTile implements Parcelable { dest.writeCharSequence(mUserName); dest.writeParcelable(mUserIcon, flags); dest.writeParcelable(mContactUri, flags); - dest.writeInt(mUid); + dest.writeParcelable(mUserHandle, flags); dest.writeString(mPackageName); dest.writeString(mBirthdayText); dest.writeLong(mLastInteractionTimestamp); diff --git a/core/java/android/app/time/LocationTimeZoneManager.java b/core/java/android/app/time/LocationTimeZoneManager.java index 71a800f2085e..066aadae1476 100644 --- a/core/java/android/app/time/LocationTimeZoneManager.java +++ b/core/java/android/app/time/LocationTimeZoneManager.java @@ -37,7 +37,7 @@ public final class LocationTimeZoneManager { /** * The name of the service for shell commands */ - public static final String SHELL_COMMAND_SERVICE_NAME = "location_time_zone_manager"; + public static final String SERVICE_NAME = "location_time_zone_manager"; /** * A shell command that starts the service (after stop). diff --git a/core/java/android/companion/DeviceNotAssociatedException.java b/core/java/android/companion/DeviceNotAssociatedException.java index bebb6c9ff7eb..f8a7a7cdeffe 100644 --- a/core/java/android/companion/DeviceNotAssociatedException.java +++ b/core/java/android/companion/DeviceNotAssociatedException.java @@ -23,7 +23,7 @@ import android.annotation.Nullable; * An exception for a case when a given device was not * {@link CompanionDeviceManager#associate associated} to the calling app. */ -public class DeviceNotAssociatedException extends Exception { +public class DeviceNotAssociatedException extends RuntimeException { /** @hide */ public DeviceNotAssociatedException(@Nullable String deviceName) { super("Device not associated with the current app: " + deviceName); diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index e20f706c2c35..f3a4e1f79955 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -370,6 +370,25 @@ public abstract class Context { /*********** Hidden flags below this line ***********/ /** + * Flag for {@link #bindService}: allow the process hosting the target service to be treated + * as if it's as important as a perceptible app to the user and avoid the oom killer killing + * this process in low memory situations until there aren't any other processes left but the + * ones which are user-perceptible. + * + * @hide + */ + public static final int BIND_ALMOST_PERCEPTIBLE = 0x000010000; + + /** + * Flag for {@link #bindService}: allow the process hosting the target service to gain + * {@link ActivityManager#PROCESS_CAPABILITY_NETWORK}, which allows it be able + * to access network regardless of any power saving restrictions. + * + * @hide + */ + public static final int BIND_ALLOW_NETWORK_ACCESS = 0x00020000; + + /** * Flag for {@link #bindService}: allow background foreground service starts from the bound * service's process. * This flag is only respected if the caller is holding @@ -6674,15 +6693,6 @@ public abstract class Context { } /** - * Indicates if this context is a visual context such as {@link android.app.Activity} or - * a context created from {@link #createWindowContext(int, Bundle)}. - * @hide - */ - public boolean isUiContext() { - throw new RuntimeException("Not implemented. Must override in a subclass."); - } - - /** * Returns {@code true} if the context is a UI context which can access UI components such as * {@link WindowManager}, {@link android.view.LayoutInflater LayoutInflater} or * {@link android.app.WallpaperManager WallpaperManager}. Accessing UI components from non-UI @@ -6694,12 +6704,16 @@ public abstract class Context { * {@link #createWindowContext(int, Bundle)} or * {@link android.inputmethodservice.InputMethodService InputMethodService} * </p> + * <p> + * Note that even if it is allowed programmatically, it is not suggested to override this + * method to bypass {@link android.os.strictmode.IncorrectContextUseViolation} verification. + * </p> * * @see #getDisplay() * @see #getSystemService(String) * @see android.os.StrictMode.VmPolicy.Builder#detectIncorrectContextUse() */ - public static boolean isUiContext(@NonNull Context context) { - return context.isUiContext(); + public boolean isUiContext() { + throw new RuntimeException("Not implemented. Must override in a subclass."); } } diff --git a/core/java/android/content/pm/DataLoaderParamsParcel.aidl b/core/java/android/content/pm/DataLoaderParamsParcel.aidl index d40012fd5718..29d472e6e927 100644 --- a/core/java/android/content/pm/DataLoaderParamsParcel.aidl +++ b/core/java/android/content/pm/DataLoaderParamsParcel.aidl @@ -23,7 +23,7 @@ import android.content.pm.DataLoaderType; * @hide */ parcelable DataLoaderParamsParcel { - DataLoaderType type; + DataLoaderType type = DataLoaderType.NONE; @utf8InCpp String packageName; @utf8InCpp String className; @utf8InCpp String arguments; diff --git a/core/java/android/content/pm/InstallationFileParcel.aidl b/core/java/android/content/pm/InstallationFileParcel.aidl index b7efc1947cc3..09d1a3291b69 100644 --- a/core/java/android/content/pm/InstallationFileParcel.aidl +++ b/core/java/android/content/pm/InstallationFileParcel.aidl @@ -24,7 +24,7 @@ import android.content.pm.InstallationFileLocation; */ parcelable InstallationFileParcel { String name; - InstallationFileLocation location; + InstallationFileLocation location = InstallationFileLocation.UNKNOWN; long size; byte[] metadata; byte[] signature; diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 567501cba70b..42cbe3586db3 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -1173,7 +1173,6 @@ public class PackageInstaller { * {@hide} */ @SystemApi - @RequiresPermission(android.Manifest.permission.USE_INSTALLER_V2) public @Nullable DataLoaderParams getDataLoaderParams() { try { DataLoaderParamsParcel data = mSession.getDataLoaderParams(); @@ -1213,7 +1212,6 @@ public class PackageInstaller { * {@hide} */ @SystemApi - @RequiresPermission(android.Manifest.permission.USE_INSTALLER_V2) public void addFile(@FileLocation int location, @NonNull String name, long lengthBytes, @NonNull byte[] metadata, @Nullable byte[] signature) { try { @@ -1237,7 +1235,6 @@ public class PackageInstaller { * {@hide} */ @SystemApi - @RequiresPermission(android.Manifest.permission.USE_INSTALLER_V2) public void removeFile(@FileLocation int location, @NonNull String name) { try { mSession.removeFile(location, name); @@ -2050,9 +2047,7 @@ public class PackageInstaller { * {@hide} */ @SystemApi - @RequiresPermission(allOf = { - Manifest.permission.INSTALL_PACKAGES, - Manifest.permission.USE_INSTALLER_V2}) + @RequiresPermission(Manifest.permission.INSTALL_PACKAGES) public void setDataLoaderParams(@NonNull DataLoaderParams dataLoaderParams) { this.dataLoaderParams = dataLoaderParams; } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 9a3fbdcea9ff..29dea6bb09db 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -3623,11 +3623,26 @@ public abstract class PackageManager { /** * Feature for {@link #getSystemAvailableFeatures} and * {@link #hasSystemFeature}: The device supports a enabling/disabling sensor privacy for - * camera. When sensory privacy for the camera is enabled no camera data is send to clients, + * microphone. When sensory privacy for the microphone is enabled no microphone data is sent to + * clients, e.g. all audio data is silent. + * + * @hide + */ + @SystemApi + @TestApi + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_MICROPHONE_TOGGLE = "android.hardware.microphone.toggle"; + + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device supports a enabling/disabling sensor privacy for + * camera. When sensory privacy for the camera is enabled no camera data is sent to clients, * e.g. the view finder in a camera app would appear blank. * * @hide */ + @SystemApi + @TestApi @SdkConstant(SdkConstantType.FEATURE) public static final String FEATURE_CAMERA_TOGGLE = "android.hardware.camera.toggle"; @@ -4060,16 +4075,6 @@ public abstract class PackageManager { public static final int FLAG_PERMISSION_AUTO_REVOKED = 1 << 17; /** - * Permission flag: The permission is restricted but the app is exempt - * from the restriction and is allowed to hold this permission in its - * full form and the exemption is provided by the held roles. - * - * @hide - */ - @SystemApi - public static final int FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT = 1 << 18; - - /** * Permission flag: This location permission is selected as the level of granularity of * location accuracy. * Example: If this flag is set for ACCESS_FINE_LOCATION, FINE location is the selected location @@ -4098,8 +4103,7 @@ public abstract class PackageManager { public static final int FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT = FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT | FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT - | FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT - | FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT; + | FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT; /** * Mask for all permission flags. @@ -4184,20 +4188,11 @@ public abstract class PackageManager { */ public static final int FLAG_PERMISSION_WHITELIST_UPGRADE = 1 << 2; - /** - * Permission allowlist flag: permissions exempted by the system - * when being granted a role. - * Permissions can also be exempted by the installer, the system, or on - * upgrade. - */ - public static final int FLAG_PERMISSION_ALLOWLIST_ROLE = 1 << 3; - /** @hide */ @IntDef(flag = true, prefix = {"FLAG_PERMISSION_WHITELIST_"}, value = { FLAG_PERMISSION_WHITELIST_SYSTEM, FLAG_PERMISSION_WHITELIST_INSTALLER, - FLAG_PERMISSION_WHITELIST_UPGRADE, - FLAG_PERMISSION_ALLOWLIST_ROLE + FLAG_PERMISSION_WHITELIST_UPGRADE }) @Retention(RetentionPolicy.SOURCE) public @interface PermissionWhitelistFlags {} @@ -5229,10 +5224,6 @@ public abstract class PackageManager { * This list corresponds to the {@link #FLAG_PERMISSION_WHITELIST_INSTALLER} flag. * Can be accessed by pre-installed holders of a dedicated permission or the * installer on record. - * - * <li>one for cases where the system exempts the permission when granting a role. - * This list corresponds to the {@link #FLAG_PERMISSION_ALLOWLIST_ROLE} flag. Can - * be accessed by pre-installed holders of a dedicated permission. * </ol> * * <p> @@ -5251,7 +5242,6 @@ public abstract class PackageManager { * @see #FLAG_PERMISSION_WHITELIST_SYSTEM * @see #FLAG_PERMISSION_WHITELIST_UPGRADE * @see #FLAG_PERMISSION_WHITELIST_INSTALLER - * @see #FLAG_PERMISSION_ALLOWLIST_ROLE * * @throws SecurityException if you try to access a whitelist that you have no access to. */ @@ -5291,10 +5281,6 @@ public abstract class PackageManager { * This list corresponds to the {@link #FLAG_PERMISSION_WHITELIST_INSTALLER} flag. * Can be modified by pre-installed holders of a dedicated permission or the installer * on record. - * - * <li>one for cases where the system exempts the permission when permission when - * granting a role. This list corresponds to the {@link #FLAG_PERMISSION_ALLOWLIST_ROLE} - * flag. Can be modified by pre-installed holders of a dedicated permission. * </ol> * * <p>You need to specify the whitelists for which to set the whitelisted permissions @@ -5318,7 +5304,6 @@ public abstract class PackageManager { * @see #FLAG_PERMISSION_WHITELIST_SYSTEM * @see #FLAG_PERMISSION_WHITELIST_UPGRADE * @see #FLAG_PERMISSION_WHITELIST_INSTALLER - * @see #FLAG_PERMISSION_ALLOWLIST_ROLE * * @throws SecurityException if you try to modify a whitelist that you have no access to. */ @@ -5388,7 +5373,6 @@ public abstract class PackageManager { * @see #FLAG_PERMISSION_WHITELIST_SYSTEM * @see #FLAG_PERMISSION_WHITELIST_UPGRADE * @see #FLAG_PERMISSION_WHITELIST_INSTALLER - * @see #FLAG_PERMISSION_ALLOWLIST_ROLE * * @throws SecurityException if you try to modify a whitelist that you have no access to. */ diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java index 0e70a3e4e600..83baca668d55 100644 --- a/core/java/android/content/pm/PermissionInfo.java +++ b/core/java/android/content/pm/PermissionInfo.java @@ -411,14 +411,6 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable { public static final int FLAG_IMMUTABLY_RESTRICTED = 1<<4; /** - * Flag for {@link #flags}, corresponding to <code>installerExemptIgnored</code> - * value of {@link android.R.attr#permissionFlags}. - * - * <p> Modifier for permission restriction. This permission cannot be exempted by the installer. - */ - public static final int FLAG_INSTALLER_EXEMPT_IGNORED = 1 << 5; - - /** * Flag for {@link #flags}, indicating that this permission has been * installed into the system's globally defined permissions. */ @@ -724,11 +716,6 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable { } /** @hide */ - public boolean isInstallerExemptIgnored() { - return (flags & PermissionInfo.FLAG_INSTALLER_EXEMPT_IGNORED) != 0; - } - - /** @hide */ public boolean isAppOp() { return (protectionLevel & PermissionInfo.PROTECTION_FLAG_APPOP) != 0; } diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java index d81dff8f2908..cfb6e1b572aa 100644 --- a/core/java/android/content/pm/UserInfo.java +++ b/core/java/android/content/pm/UserInfo.java @@ -18,6 +18,7 @@ package android.content.pm; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.TestApi; import android.annotation.UserIdInt; import android.compat.annotation.UnsupportedAppUsage; import android.os.Parcel; @@ -47,6 +48,7 @@ import java.lang.annotation.RetentionPolicy; * * @hide */ +@TestApi public class UserInfo implements Parcelable { /** diff --git a/core/java/android/content/pm/verify/domain/DomainOwner.aidl b/core/java/android/content/pm/verify/domain/DomainOwner.aidl new file mode 100644 index 000000000000..41366d1a29b2 --- /dev/null +++ b/core/java/android/content/pm/verify/domain/DomainOwner.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm.verify.domain; + +parcelable DomainOwner; diff --git a/core/java/android/content/pm/verify/domain/DomainOwner.java b/core/java/android/content/pm/verify/domain/DomainOwner.java new file mode 100644 index 000000000000..b050f5da7928 --- /dev/null +++ b/core/java/android/content/pm/verify/domain/DomainOwner.java @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm.verify.domain; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcelable; + +import com.android.internal.util.DataClass; + +import java.util.Set; +import java.util.UUID; + +/** + * @hide + */ +@SystemApi +@DataClass(genParcelable = true, genEqualsHashCode = true, genAidl = true, genToString = true) +public final class DomainOwner implements Parcelable { + + /** + * Package name of that owns the domain. + */ + @NonNull + private final String mPackageName; + + /** + * Whether or not this owner can be automatically overridden. + * + * @see DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set, boolean) + */ + private final boolean mOverrideable; + + + + // Code below generated by codegen v1.0.22. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain/DomainOwner.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + /** + * Creates a new DomainOwner. + * + * @param packageName + * Package name of that owns the domain. + * @param overrideable + * Whether or not this owner can be automatically overridden. If all owners for a domain are + * overrideable, then calling + * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, + * Set, boolean)} to enable the domain will disable all other owners. On the other hand, if any + * of the owners are non-overrideable, then + * {@link DomainVerificationManager#setDomainVerificationLinkHandlingAllowed(String, + * boolean)} must be called with false to disable all of the other owners before this domain can + * be taken by a new owner through + * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, + * Set, boolean)}. + */ + @DataClass.Generated.Member + public DomainOwner( + @NonNull String packageName, + boolean overrideable) { + this.mPackageName = packageName; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mPackageName); + this.mOverrideable = overrideable; + + // onConstructed(); // You can define this method to get a callback + } + + /** + * Package name of that owns the domain. + */ + @DataClass.Generated.Member + public @NonNull String getPackageName() { + return mPackageName; + } + + /** + * Whether or not this owner can be automatically overridden. If all owners for a domain are + * overrideable, then calling + * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, + * Set, boolean)} to enable the domain will disable all other owners. On the other hand, if any + * of the owners are non-overrideable, then + * {@link DomainVerificationManager#setDomainVerificationLinkHandlingAllowed(String, + * boolean)} must be called with false to disable all of the other owners before this domain can + * be taken by a new owner through + * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, + * Set, boolean)}. + */ + @DataClass.Generated.Member + public boolean isOverrideable() { + return mOverrideable; + } + + @Override + @DataClass.Generated.Member + public String toString() { + // You can override field toString logic by defining methods like: + // String fieldNameToString() { ... } + + return "DomainOwner { " + + "packageName = " + mPackageName + ", " + + "overrideable = " + mOverrideable + + " }"; + } + + @Override + @DataClass.Generated.Member + public boolean equals(@android.annotation.Nullable Object o) { + // You can override field equality logic by defining either of the methods like: + // boolean fieldNameEquals(DomainOwner other) { ... } + // boolean fieldNameEquals(FieldType otherValue) { ... } + + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + @SuppressWarnings("unchecked") + DomainOwner that = (DomainOwner) o; + //noinspection PointlessBooleanExpression + return true + && java.util.Objects.equals(mPackageName, that.mPackageName) + && mOverrideable == that.mOverrideable; + } + + @Override + @DataClass.Generated.Member + public int hashCode() { + // You can override field hashCode logic by defining methods like: + // int fieldNameHashCode() { ... } + + int _hash = 1; + _hash = 31 * _hash + java.util.Objects.hashCode(mPackageName); + _hash = 31 * _hash + Boolean.hashCode(mOverrideable); + return _hash; + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + byte flg = 0; + if (mOverrideable) flg |= 0x2; + dest.writeByte(flg); + dest.writeString(mPackageName); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + /* package-private */ DomainOwner(@NonNull android.os.Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + byte flg = in.readByte(); + boolean overrideable = (flg & 0x2) != 0; + String packageName = in.readString(); + + this.mPackageName = packageName; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mPackageName); + this.mOverrideable = overrideable; + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator<DomainOwner> CREATOR + = new Parcelable.Creator<DomainOwner>() { + @Override + public DomainOwner[] newArray(int size) { + return new DomainOwner[size]; + } + + @Override + public DomainOwner createFromParcel(@NonNull android.os.Parcel in) { + return new DomainOwner(in); + } + }; + + @DataClass.Generated( + time = 1614119379978L, + codegenVersion = "1.0.22", + sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainOwner.java", + inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final boolean mOverrideable\nclass DomainOwner extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genEqualsHashCode=true, genAidl=true, genToString=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/core/java/android/content/pm/verify/domain/DomainSet.aidl b/core/java/android/content/pm/verify/domain/DomainSet.aidl new file mode 100644 index 000000000000..fab131dfa317 --- /dev/null +++ b/core/java/android/content/pm/verify/domain/DomainSet.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm.verify.domain; + +parcelable DomainSet; diff --git a/core/java/android/content/pm/verify/domain/DomainSet.java b/core/java/android/content/pm/verify/domain/DomainSet.java new file mode 100644 index 000000000000..243ff0820e24 --- /dev/null +++ b/core/java/android/content/pm/verify/domain/DomainSet.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm.verify.domain; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.DataClass; + +import java.util.Set; + +/** + * Wraps an input set of domains from the client process, to be sent to the server. Handles cases + * where the data size is too large by writing data using {@link Parcel#writeBlob(byte[])}. + * + * @hide + */ +@DataClass(genParcelable = true, genAidl = true, genEqualsHashCode = true) +public class DomainSet implements Parcelable { + + @NonNull + private final Set<String> mDomains; + + private void parcelDomains(@NonNull Parcel dest, @SuppressWarnings("unused") int flags) { + DomainVerificationUtils.writeHostSet(dest, mDomains); + } + + private Set<String> unparcelDomains(@NonNull Parcel in) { + return DomainVerificationUtils.readHostSet(in); + } + + + + // Code below generated by codegen v1.0.22. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain + // /DomainSet.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + @DataClass.Generated.Member + public DomainSet( + @NonNull Set<String> domains) { + this.mDomains = domains; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mDomains); + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public @NonNull Set<String> getDomains() { + return mDomains; + } + + @Override + @DataClass.Generated.Member + public boolean equals(@android.annotation.Nullable Object o) { + // You can override field equality logic by defining either of the methods like: + // boolean fieldNameEquals(DomainSet other) { ... } + // boolean fieldNameEquals(FieldType otherValue) { ... } + + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + @SuppressWarnings("unchecked") + DomainSet that = (DomainSet) o; + //noinspection PointlessBooleanExpression + return true + && java.util.Objects.equals(mDomains, that.mDomains); + } + + @Override + @DataClass.Generated.Member + public int hashCode() { + // You can override field hashCode logic by defining methods like: + // int fieldNameHashCode() { ... } + + int _hash = 1; + _hash = 31 * _hash + java.util.Objects.hashCode(mDomains); + 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) { ... } + + parcelDomains(dest, flags); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + protected DomainSet(@NonNull Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + Set<String> domains = unparcelDomains(in); + + this.mDomains = domains; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mDomains); + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator<DomainSet> CREATOR + = new Parcelable.Creator<DomainSet>() { + @Override + public DomainSet[] newArray(int size) { + return new DomainSet[size]; + } + + @Override + public DomainSet createFromParcel(@NonNull Parcel in) { + return new DomainSet(in); + } + }; + + @DataClass.Generated( + time = 1613169242020L, + codegenVersion = "1.0.22", + sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainSet.java", + inputSignatures = "private final @android.annotation.NonNull java.util.Set<java.lang.String> mDomains\nprivate void parcelDomains(android.os.Parcel,int)\nprivate java.util.Set<java.lang.String> unparcelDomains(android.os.Parcel)\nclass DomainSet extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genEqualsHashCode=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java b/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java index 7afbe1fcb69f..809587524f58 100644 --- a/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java +++ b/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java @@ -19,7 +19,9 @@ package android.content.pm.verify.domain; import android.annotation.NonNull; import android.annotation.SystemApi; import android.content.pm.PackageManager; +import android.os.Parcel; import android.os.Parcelable; +import android.util.ArrayMap; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling; @@ -34,12 +36,12 @@ import java.util.UUID; * against the digital asset links response from the server hosting that domain. * <p> * These values for each domain can be modified through - * {@link DomainVerificationManager#setDomainVerificationStatus(UUID, Set, int)}. + * {@link DomainVerificationManager#setDomainVerificationStatus(UUID, + * Set, int)}. * * @hide */ @SystemApi -@SuppressWarnings("DefaultAnnotationParam") @DataClass(genAidl = true, genHiddenConstructor = true, genParcelable = true, genToString = true, genEqualsHashCode = true) public final class DomainVerificationInfo implements Parcelable { @@ -71,22 +73,30 @@ public final class DomainVerificationInfo implements Parcelable { private final String mPackageName; /** - * Map of host names to their current state. State is an integer, which defaults to - * {@link DomainVerificationManager#STATE_NO_RESPONSE}. State can be modified by the - * domain verification agent (the intended consumer of this API), which can be equal - * to {@link DomainVerificationManager#STATE_SUCCESS} when verified, or equal to or - * greater than {@link DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED} for - * any unsuccessful response. + * Map of host names to their current state. State is an integer, which defaults to {@link + * DomainVerificationManager#STATE_NO_RESPONSE}. State can be modified by the domain + * verification agent (the intended consumer of this API), which can be equal to {@link + * DomainVerificationManager#STATE_SUCCESS} when verified, or equal to or greater than {@link + * DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED} for any unsuccessful response. * <p> - * Any value non-inclusive between those 2 values are reserved for use by the system. - * The domain verification agent may be able to act on these reserved values, and this - * ability can be queried using {@link DomainVerificationManager#isStateModifiable(int)}. - * It is expected that the agent attempt to verify all domains that it can modify the - * state of, even if it does not understand the meaning of those values. + * Any value non-inclusive between those 2 values are reserved for use by the system. The domain + * verification agent may be able to act on these reserved values, and this ability can be + * queried using {@link DomainVerificationManager#isStateModifiable(int)}. It is expected that + * the agent attempt to verify all domains that it can modify the state of, even if it does not + * understand the meaning of those values. */ @NonNull private final Map<String, Integer> mHostToStateMap; + private void parcelHostToStateMap(Parcel dest, @SuppressWarnings("unused") int flags) { + DomainVerificationUtils.writeHostMap(dest, mHostToStateMap); + } + + private Map<String, Integer> unparcelHostToStateMap(Parcel in) { + return DomainVerificationUtils.readHostMap(in, new ArrayMap<>(), + DomainVerificationUserSelection.class.getClassLoader()); + } + // Code below generated by codegen v1.0.22. @@ -95,7 +105,8 @@ public final class DomainVerificationInfo implements Parcelable { // CHECKSTYLE:OFF Generated code // // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain + // /DomainVerificationInfo.java // // To exclude the generated code from IntelliJ auto-formatting enable (one-time): // Settings > Editor > Code Style > Formatter Control @@ -123,18 +134,17 @@ public final class DomainVerificationInfo implements Parcelable { * @param packageName * The package name that this data corresponds to. * @param hostToStateMap - * Map of host names to their current state. State is an integer, which defaults to - * {@link DomainVerificationManager#STATE_NO_RESPONSE}. State can be modified by the - * domain verification agent (the intended consumer of this API), which can be equal - * to {@link DomainVerificationManager#STATE_SUCCESS} when verified, or equal to or - * greater than {@link DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED} for - * any unsuccessful response. + * Map of host names to their current state. State is an integer, which defaults to {@link + * DomainVerificationManager#STATE_NO_RESPONSE}. State can be modified by the domain + * verification agent (the intended consumer of this API), which can be equal to {@link + * DomainVerificationManager#STATE_SUCCESS} when verified, or equal to or greater than {@link + * DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED} for any unsuccessful response. * <p> - * Any value non-inclusive between those 2 values are reserved for use by the system. - * The domain verification agent may be able to act on these reserved values, and this - * ability can be queried using {@link DomainVerificationManager#isStateModifiable(int)}. - * It is expected that the agent attempt to verify all domains that it can modify the - * state of, even if it does not understand the meaning of those values. + * Any value non-inclusive between those 2 values are reserved for use by the system. The domain + * verification agent may be able to act on these reserved values, and this ability can be + * queried using {@link DomainVerificationManager#isStateModifiable(int)}. It is expected that + * the agent attempt to verify all domains that it can modify the state of, even if it does not + * understand the meaning of those values. * @hide */ @DataClass.Generated.Member @@ -185,18 +195,17 @@ public final class DomainVerificationInfo implements Parcelable { } /** - * Map of host names to their current state. State is an integer, which defaults to - * {@link DomainVerificationManager#STATE_NO_RESPONSE}. State can be modified by the - * domain verification agent (the intended consumer of this API), which can be equal - * to {@link DomainVerificationManager#STATE_SUCCESS} when verified, or equal to or - * greater than {@link DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED} for - * any unsuccessful response. + * Map of host names to their current state. State is an integer, which defaults to {@link + * DomainVerificationManager#STATE_NO_RESPONSE}. State can be modified by the domain + * verification agent (the intended consumer of this API), which can be equal to {@link + * DomainVerificationManager#STATE_SUCCESS} when verified, or equal to or greater than {@link + * DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED} for any unsuccessful response. * <p> - * Any value non-inclusive between those 2 values are reserved for use by the system. - * The domain verification agent may be able to act on these reserved values, and this - * ability can be queried using {@link DomainVerificationManager#isStateModifiable(int)}. - * It is expected that the agent attempt to verify all domains that it can modify the - * state of, even if it does not understand the meaning of those values. + * Any value non-inclusive between those 2 values are reserved for use by the system. The domain + * verification agent may be able to act on these reserved values, and this ability can be + * queried using {@link DomainVerificationManager#isStateModifiable(int)}. It is expected that + * the agent attempt to verify all domains that it can modify the state of, even if it does not + * understand the meaning of those values. */ @DataClass.Generated.Member public @NonNull Map<String,Integer> getHostToStateMap() { @@ -260,13 +269,13 @@ public final class DomainVerificationInfo implements Parcelable { @Override @DataClass.Generated.Member - public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { + public void writeToParcel(@NonNull Parcel dest, int flags) { // You can override field parcelling by defining methods like: // void parcelFieldName(Parcel dest, int flags) { ... } sParcellingForIdentifier.parcel(mIdentifier, dest, flags); dest.writeString(mPackageName); - dest.writeMap(mHostToStateMap); + parcelHostToStateMap(dest, flags); } @Override @@ -276,14 +285,13 @@ public final class DomainVerificationInfo implements Parcelable { /** @hide */ @SuppressWarnings({"unchecked", "RedundantCast"}) @DataClass.Generated.Member - /* package-private */ DomainVerificationInfo(@NonNull android.os.Parcel in) { + /* package-private */ DomainVerificationInfo(@NonNull Parcel in) { // You can override field unparcelling by defining methods like: // static FieldType unparcelFieldName(Parcel in) { ... } UUID identifier = sParcellingForIdentifier.unparcel(in); String packageName = in.readString(); - Map<String,Integer> hostToStateMap = new java.util.LinkedHashMap<>(); - in.readMap(hostToStateMap, Integer.class.getClassLoader()); + Map<String,Integer> hostToStateMap = unparcelHostToStateMap(in); this.mIdentifier = identifier; com.android.internal.util.AnnotationValidations.validate( @@ -307,16 +315,16 @@ public final class DomainVerificationInfo implements Parcelable { } @Override - public DomainVerificationInfo createFromParcel(@NonNull android.os.Parcel in) { + public DomainVerificationInfo createFromParcel(@NonNull Parcel in) { return new DomainVerificationInfo(in); } }; @DataClass.Generated( - time = 1611862790369L, + time = 1613002530369L, codegenVersion = "1.0.22", sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java", - inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForUUID.class) java.util.UUID mIdentifier\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Integer> mHostToStateMap\nclass DomainVerificationInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true, genHiddenConstructor=true, genParcelable=true, genToString=true, genEqualsHashCode=true)") + inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForUUID.class) java.util.UUID mIdentifier\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Integer> mHostToStateMap\nprivate void parcelHostToStateMap(android.os.Parcel,int)\nprivate java.util.Map<java.lang.String,java.lang.Integer> unparcelHostToStateMap(android.os.Parcel)\nclass DomainVerificationInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true, genHiddenConstructor=true, genParcelable=true, genToString=true, genEqualsHashCode=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java index cbb3baaa6700..11402afac8b6 100644 --- a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java +++ b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java @@ -239,7 +239,15 @@ public interface DomainVerificationManager { * {@link Context#createPackageContextAsUser(String, int, UserHandle)} should be used. * * Enabling an unverified domain will allow an application to open it, but this can only occur - * if no other app on the device is approved for the domain. + * if no other app on the device is approved for a higher approval level. This can queried + * using {@link #getOwnersForDomain(String)}. + * + * If all owners for a domain are {@link DomainOwner#isOverrideable()}, then calling this to + * enable that domain will disable all other owners. + * + * On the other hand, if any of the owners are non-overrideable, then this must be called with + * false for all of the other owners to disable them before the domain can be taken by a new + * owner. * * @param domainSetId See {@link DomainVerificationInfo#getIdentifier()}. * @param domains The domains to toggle the state of. @@ -276,6 +284,19 @@ public interface DomainVerificationManager { throws NameNotFoundException; /** + * For the given domain, return all apps which are approved to open it in a + * greater than 0 priority. This does not mean that all apps can actually open + * an Intent with that domain. That will be decided by the set of apps which + * are the highest priority level, ignoring all lower priority levels. + * + * By default the list will be returned ordered from lowest to highest + * priority. + */ + @NonNull + @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) + List<DomainOwner> getOwnersForDomain(@NonNull String domain); + + /** * Thrown if a {@link DomainVerificationInfo#getIdentifier()}} or an associated set of domains * provided by the caller is no longer valid. This may be recoverable, and the caller should * re-query the package name associated with the ID using diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationManagerImpl.java b/core/java/android/content/pm/verify/domain/DomainVerificationManagerImpl.java index 5938def5c83c..8b9865c2b436 100644 --- a/core/java/android/content/pm/verify/domain/DomainVerificationManagerImpl.java +++ b/core/java/android/content/pm/verify/domain/DomainVerificationManagerImpl.java @@ -21,11 +21,9 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.content.pm.PackageManager.NameNotFoundException; -import android.content.pm.verify.domain.IDomainVerificationManager; import android.os.RemoteException; import android.os.ServiceSpecificException; -import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.UUID; @@ -89,7 +87,7 @@ public class DomainVerificationManagerImpl implements DomainVerificationManager int state) throws IllegalArgumentException, NameNotFoundException { try { mDomainVerificationManager.setDomainVerificationStatus(domainSetId.toString(), - new ArrayList<>(domains), state); + new DomainSet(domains), state); } catch (Exception e) { Exception converted = rethrow(e, domainSetId); if (converted instanceof NameNotFoundException) { @@ -126,7 +124,7 @@ public class DomainVerificationManagerImpl implements DomainVerificationManager throws IllegalArgumentException, NameNotFoundException { try { mDomainVerificationManager.setDomainVerificationUserSelection(domainSetId.toString(), - new ArrayList<>(domains), enabled, mContext.getUserId()); + new DomainSet(domains), enabled, mContext.getUserId()); } catch (Exception e) { Exception converted = rethrow(e, domainSetId); if (converted instanceof NameNotFoundException) { @@ -158,6 +156,16 @@ public class DomainVerificationManagerImpl implements DomainVerificationManager } } + @NonNull + @Override + public List<DomainOwner> getOwnersForDomain(@NonNull String domain) { + try { + return mDomainVerificationManager.getOwnersForDomain(domain, mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + private Exception rethrow(Exception exception, @Nullable UUID domainSetId) { return rethrow(exception, domainSetId, null); } diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationRequest.java b/core/java/android/content/pm/verify/domain/DomainVerificationRequest.java index 473abce26d81..65f6d7c18135 100644 --- a/core/java/android/content/pm/verify/domain/DomainVerificationRequest.java +++ b/core/java/android/content/pm/verify/domain/DomainVerificationRequest.java @@ -19,6 +19,7 @@ package android.content.pm.verify.domain; import android.annotation.NonNull; import android.annotation.SystemApi; import android.content.Intent; +import android.os.Parcel; import android.os.Parcelable; import com.android.internal.util.DataClass; @@ -27,11 +28,11 @@ import com.android.internal.util.Parcelling; import java.util.Set; /** - * Request object sent in the {@link Intent} that's broadcast to the domain verification - * agent, retrieved through {@link DomainVerificationManager#EXTRA_VERIFICATION_REQUEST}. + * Request object sent in the {@link Intent} that's broadcast to the domain verification agent, + * retrieved through {@link DomainVerificationManager#EXTRA_VERIFICATION_REQUEST}. * <p> - * This contains the set of packages which have been invalidated and will require - * re-verification. The exact domains can be retrieved with + * This contains the set of packages which have been invalidated and will require re-verification. + * The exact domains can be retrieved with * {@link DomainVerificationManager#getDomainVerificationInfo(String)} * * @hide @@ -42,14 +43,22 @@ import java.util.Set; public final class DomainVerificationRequest implements Parcelable { /** - * The package names of the apps that need to be verified. The receiver should call - * {@link DomainVerificationManager#getDomainVerificationInfo(String)} with each of - * these values to get the actual set of domains that need to be acted on. + * The package names of the apps that need to be verified. The receiver should call {@link + * DomainVerificationManager#getDomainVerificationInfo(String)} with each of these values to get + * the actual set of domains that need to be acted on. */ @NonNull @DataClass.ParcelWith(Parcelling.BuiltIn.ForStringSet.class) private final Set<String> mPackageNames; + private void parcelPackageNames(@NonNull Parcel dest, @SuppressWarnings("unused") int flags) { + DomainVerificationUtils.writeHostSet(dest, mPackageNames); + } + + private Set<String> unparcelPackageNames(@NonNull Parcel in) { + return DomainVerificationUtils.readHostSet(in); + } + // Code below generated by codegen v1.0.22. @@ -58,7 +67,8 @@ public final class DomainVerificationRequest implements Parcelable { // CHECKSTYLE:OFF Generated code // // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationRequest.java + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain + // /DomainVerificationRequest.java // // To exclude the generated code from IntelliJ auto-formatting enable (one-time): // Settings > Editor > Code Style > Formatter Control @@ -69,9 +79,9 @@ public final class DomainVerificationRequest implements Parcelable { * Creates a new DomainVerificationRequest. * * @param packageNames - * The package names of the apps that need to be verified. The receiver should call - * {@link DomainVerificationManager#getDomainVerificationInfo(String)} with each of - * these values to get the actual set of domains that need to be acted on. + * The package names of the apps that need to be verified. The receiver should call {@link + * DomainVerificationManager#getDomainVerificationInfo(String)} with each of these values to get + * the actual set of domains that need to be acted on. * @hide */ @DataClass.Generated.Member @@ -85,9 +95,9 @@ public final class DomainVerificationRequest implements Parcelable { } /** - * The package names of the apps that need to be verified. The receiver should call - * {@link DomainVerificationManager#getDomainVerificationInfo(String)} with each of - * these values to get the actual set of domains that need to be acted on. + * The package names of the apps that need to be verified. The receiver should call {@link + * DomainVerificationManager#getDomainVerificationInfo(String)} with each of these values to get + * the actual set of domains that need to be acted on. */ @DataClass.Generated.Member public @NonNull Set<String> getPackageNames() { @@ -134,11 +144,11 @@ public final class DomainVerificationRequest implements Parcelable { @Override @DataClass.Generated.Member - public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { + public void writeToParcel(@NonNull Parcel dest, int flags) { // You can override field parcelling by defining methods like: // void parcelFieldName(Parcel dest, int flags) { ... } - sParcellingForPackageNames.parcel(mPackageNames, dest, flags); + parcelPackageNames(dest, flags); } @Override @@ -148,11 +158,11 @@ public final class DomainVerificationRequest implements Parcelable { /** @hide */ @SuppressWarnings({"unchecked", "RedundantCast"}) @DataClass.Generated.Member - /* package-private */ DomainVerificationRequest(@NonNull android.os.Parcel in) { + /* package-private */ DomainVerificationRequest(@NonNull Parcel in) { // You can override field unparcelling by defining methods like: // static FieldType unparcelFieldName(Parcel in) { ... } - Set<String> packageNames = sParcellingForPackageNames.unparcel(in); + Set<String> packageNames = unparcelPackageNames(in); this.mPackageNames = packageNames; com.android.internal.util.AnnotationValidations.validate( @@ -170,16 +180,16 @@ public final class DomainVerificationRequest implements Parcelable { } @Override - public DomainVerificationRequest createFromParcel(@NonNull android.os.Parcel in) { + public DomainVerificationRequest createFromParcel(@NonNull Parcel in) { return new DomainVerificationRequest(in); } }; @DataClass.Generated( - time = 1611862814990L, + time = 1613169505495L, codegenVersion = "1.0.22", sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationRequest.java", - inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForStringSet.class) java.util.Set<java.lang.String> mPackageNames\nclass DomainVerificationRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genHiddenConstructor=true, genAidl=false, genEqualsHashCode=true)") + inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForStringSet.class) java.util.Set<java.lang.String> mPackageNames\nprivate void parcelPackageNames(android.os.Parcel,int)\nprivate java.util.Set<java.lang.String> unparcelPackageNames(android.os.Parcel)\nclass DomainVerificationRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genHiddenConstructor=true, genAidl=false, genEqualsHashCode=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java b/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java index 73346ef0273b..d23f5f133841 100644 --- a/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java +++ b/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java @@ -20,8 +20,10 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.content.Context; +import android.os.Parcel; import android.os.Parcelable; import android.os.UserHandle; +import android.util.ArrayMap; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling; @@ -40,29 +42,46 @@ import java.util.UUID; * toggle affects <b>all</b> links and is not based on the verification state of the domains. * <p> * Assuming the toggle is enabled, the user can also select additional unverified domains to grant - * to the application to open, which is reflected in {@link #getHostToUserSelectionMap()}. But only - * a single application can be approved for a domain unless the applications are both approved. If - * another application is approved, the user will not be allowed to enable the domain. + * to the application to open, which is reflected in {@link #getHostToStateMap()}. But only a single + * application can be approved for a domain unless the applications are both approved. If another + * application is approved, the user will not be allowed to enable the domain. * <p> * These values can be changed through the * {@link DomainVerificationManager#setDomainVerificationLinkHandlingAllowed(String, - * boolean)} and - * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set, + * boolean)} and {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set, * boolean)} APIs. * <p> - * Note that because state is per user, if a different user needs to be changed, one will - * need to use {@link Context#createContextAsUser(UserHandle, int)} and hold the - * {@link android.Manifest.permission#INTERACT_ACROSS_USERS} permission. + * Note that because state is per user, if a different user needs to be changed, one will need to + * use {@link Context#createContextAsUser(UserHandle, int)} and hold the {@link + * android.Manifest.permission#INTERACT_ACROSS_USERS} permission. * * @hide */ @SystemApi @SuppressWarnings("DefaultAnnotationParam") @DataClass(genAidl = true, genHiddenConstructor = true, genParcelable = true, genToString = true, - genEqualsHashCode = true) + genEqualsHashCode = true, genHiddenConstDefs = true) public final class DomainVerificationUserSelection implements Parcelable { /** + * The domain is unverified and unselected, and the application is unable to open web links + * that resolve to the domain. + */ + public static final int DOMAIN_STATE_NONE = 0; + + /** + * The domain has been selected through the + * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set, boolean)} + * API, under the assumption it has not been reset by the system. + */ + public static final int DOMAIN_STATE_SELECTED = 1; + + /** + * The domain has been previously verified by the domain verification agent. + */ + public static final int DOMAIN_STATE_VERIFIED = 2; + + /** * @see DomainVerificationInfo#getIdentifier */ @NonNull @@ -88,15 +107,20 @@ public final class DomainVerificationUserSelection implements Parcelable { private final boolean mLinkHandlingAllowed; /** - * Retrieve the existing user selection state for the matching - * {@link #getPackageName()}, as was previously set by - * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set, - * boolean)}. - * - * @return Map of hosts to enabled state for the given package and user. + * Mapping of domain host to state, as defined by {@link DomainState}. */ @NonNull - private final Map<String, Boolean> mHostToUserSelectionMap; + private final Map<String, Integer> mHostToStateMap; + + private void parcelHostToStateMap(Parcel dest, @SuppressWarnings("unused") int flags) { + DomainVerificationUtils.writeHostMap(dest, mHostToStateMap); + } + + @NonNull + private Map<String, Integer> unparcelHostToStateMap(Parcel in) { + return DomainVerificationUtils.readHostMap(in, new ArrayMap<>(), + DomainVerificationUserSelection.class.getClassLoader()); + } @@ -106,14 +130,37 @@ public final class DomainVerificationUserSelection implements Parcelable { // CHECKSTYLE:OFF Generated code // // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain - // /DomainVerificationUserSelection.java + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java // // To exclude the generated code from IntelliJ auto-formatting enable (one-time): // Settings > Editor > Code Style > Formatter Control //@formatter:off + /** @hide */ + @android.annotation.IntDef(prefix = "DOMAIN_STATE_", value = { + DOMAIN_STATE_NONE, + DOMAIN_STATE_SELECTED, + DOMAIN_STATE_VERIFIED + }) + @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) + @DataClass.Generated.Member + public @interface DomainState {} + + /** @hide */ + @DataClass.Generated.Member + public static String domainStateToString(@DomainState int value) { + switch (value) { + case DOMAIN_STATE_NONE: + return "DOMAIN_STATE_NONE"; + case DOMAIN_STATE_SELECTED: + return "DOMAIN_STATE_SELECTED"; + case DOMAIN_STATE_VERIFIED: + return "DOMAIN_STATE_VERIFIED"; + default: return Integer.toHexString(value); + } + } + /** * Creates a new DomainVerificationUserSelection. * @@ -123,11 +170,8 @@ public final class DomainVerificationUserSelection implements Parcelable { * The user that this data corresponds to. * @param linkHandlingAllowed * Whether or not this package is allowed to open links. - * @param hostToUserSelectionMap - * Retrieve the existing user selection state for the matching - * {@link #getPackageName()}, as was previously set by - * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set, - * boolean)}. + * @param hostToStateMap + * Mapping of domain host to state, as defined by {@link DomainState}. * @hide */ @DataClass.Generated.Member @@ -136,7 +180,7 @@ public final class DomainVerificationUserSelection implements Parcelable { @NonNull String packageName, @NonNull UserHandle user, @NonNull boolean linkHandlingAllowed, - @NonNull Map<String,Boolean> hostToUserSelectionMap) { + @NonNull Map<String,Integer> hostToStateMap) { this.mIdentifier = identifier; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mIdentifier); @@ -149,9 +193,9 @@ public final class DomainVerificationUserSelection implements Parcelable { this.mLinkHandlingAllowed = linkHandlingAllowed; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mLinkHandlingAllowed); - this.mHostToUserSelectionMap = hostToUserSelectionMap; + this.mHostToStateMap = hostToStateMap; com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mHostToUserSelectionMap); + NonNull.class, null, mHostToStateMap); // onConstructed(); // You can define this method to get a callback } @@ -189,16 +233,11 @@ public final class DomainVerificationUserSelection implements Parcelable { } /** - * Retrieve the existing user selection state for the matching - * {@link #getPackageName()}, as was previously set by - * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set, - * boolean)}. - * - * @return Map of hosts to enabled state for the given package and user. + * Mapping of domain host to state, as defined by {@link DomainState}. */ @DataClass.Generated.Member - public @NonNull Map<String,Boolean> getHostToUserSelectionMap() { - return mHostToUserSelectionMap; + public @NonNull Map<String,Integer> getHostToStateMap() { + return mHostToStateMap; } @Override @@ -212,7 +251,7 @@ public final class DomainVerificationUserSelection implements Parcelable { "packageName = " + mPackageName + ", " + "user = " + mUser + ", " + "linkHandlingAllowed = " + mLinkHandlingAllowed + ", " + - "hostToUserSelectionMap = " + mHostToUserSelectionMap + + "hostToStateMap = " + mHostToStateMap + " }"; } @@ -233,7 +272,7 @@ public final class DomainVerificationUserSelection implements Parcelable { && java.util.Objects.equals(mPackageName, that.mPackageName) && java.util.Objects.equals(mUser, that.mUser) && mLinkHandlingAllowed == that.mLinkHandlingAllowed - && java.util.Objects.equals(mHostToUserSelectionMap, that.mHostToUserSelectionMap); + && java.util.Objects.equals(mHostToStateMap, that.mHostToStateMap); } @Override @@ -247,7 +286,7 @@ public final class DomainVerificationUserSelection implements Parcelable { _hash = 31 * _hash + java.util.Objects.hashCode(mPackageName); _hash = 31 * _hash + java.util.Objects.hashCode(mUser); _hash = 31 * _hash + Boolean.hashCode(mLinkHandlingAllowed); - _hash = 31 * _hash + java.util.Objects.hashCode(mHostToUserSelectionMap); + _hash = 31 * _hash + java.util.Objects.hashCode(mHostToStateMap); return _hash; } @@ -264,7 +303,7 @@ public final class DomainVerificationUserSelection implements Parcelable { @Override @DataClass.Generated.Member - public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { + public void writeToParcel(@NonNull Parcel dest, int flags) { // You can override field parcelling by defining methods like: // void parcelFieldName(Parcel dest, int flags) { ... } @@ -274,7 +313,7 @@ public final class DomainVerificationUserSelection implements Parcelable { sParcellingForIdentifier.parcel(mIdentifier, dest, flags); dest.writeString(mPackageName); dest.writeTypedObject(mUser, flags); - dest.writeMap(mHostToUserSelectionMap); + parcelHostToStateMap(dest, flags); } @Override @@ -284,7 +323,7 @@ public final class DomainVerificationUserSelection implements Parcelable { /** @hide */ @SuppressWarnings({"unchecked", "RedundantCast"}) @DataClass.Generated.Member - /* package-private */ DomainVerificationUserSelection(@NonNull android.os.Parcel in) { + /* package-private */ DomainVerificationUserSelection(@NonNull Parcel in) { // You can override field unparcelling by defining methods like: // static FieldType unparcelFieldName(Parcel in) { ... } @@ -293,8 +332,7 @@ public final class DomainVerificationUserSelection implements Parcelable { UUID identifier = sParcellingForIdentifier.unparcel(in); String packageName = in.readString(); UserHandle user = (UserHandle) in.readTypedObject(UserHandle.CREATOR); - Map<String,Boolean> hostToUserSelectionMap = new java.util.LinkedHashMap<>(); - in.readMap(hostToUserSelectionMap, Boolean.class.getClassLoader()); + Map<String,Integer> hostToStateMap = unparcelHostToStateMap(in); this.mIdentifier = identifier; com.android.internal.util.AnnotationValidations.validate( @@ -308,9 +346,9 @@ public final class DomainVerificationUserSelection implements Parcelable { this.mLinkHandlingAllowed = linkHandlingAllowed; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mLinkHandlingAllowed); - this.mHostToUserSelectionMap = hostToUserSelectionMap; + this.mHostToStateMap = hostToStateMap; com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mHostToUserSelectionMap); + NonNull.class, null, mHostToStateMap); // onConstructed(); // You can define this method to get a callback } @@ -324,16 +362,16 @@ public final class DomainVerificationUserSelection implements Parcelable { } @Override - public DomainVerificationUserSelection createFromParcel(@NonNull android.os.Parcel in) { + public DomainVerificationUserSelection createFromParcel(@NonNull Parcel in) { return new DomainVerificationUserSelection(in); } }; @DataClass.Generated( - time = 1612829797220L, + time = 1613683603297L, codegenVersion = "1.0.22", sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java", - inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForUUID.class) java.util.UUID mIdentifier\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull android.os.UserHandle mUser\nprivate final @android.annotation.NonNull boolean mLinkHandlingAllowed\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Boolean> mHostToUserSelectionMap\nclass DomainVerificationUserSelection extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true, genHiddenConstructor=true, genParcelable=true, genToString=true, genEqualsHashCode=true)") + inputSignatures = "public static final int DOMAIN_STATE_NONE\npublic static final int DOMAIN_STATE_SELECTED\npublic static final int DOMAIN_STATE_VERIFIED\nprivate final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForUUID.class) java.util.UUID mIdentifier\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull android.os.UserHandle mUser\nprivate final @android.annotation.NonNull boolean mLinkHandlingAllowed\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Integer> mHostToStateMap\nprivate void parcelHostToStateMap(android.os.Parcel,int)\nprivate @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Integer> unparcelHostToStateMap(android.os.Parcel)\nclass DomainVerificationUserSelection extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true, genHiddenConstructor=true, genParcelable=true, genToString=true, genEqualsHashCode=true, genHiddenConstDefs=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationUtils.java b/core/java/android/content/pm/verify/domain/DomainVerificationUtils.java new file mode 100644 index 000000000000..93005fae1772 --- /dev/null +++ b/core/java/android/content/pm/verify/domain/DomainVerificationUtils.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm.verify.domain; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.IBinder; +import android.os.Parcel; +import android.util.ArraySet; + +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +/** + * @hide + */ +public class DomainVerificationUtils { + + private static final int STRINGS_TARGET_BYTE_SIZE = IBinder.getSuggestedMaxIpcSizeBytes() / 2; + + /** + * Write a map containing web hosts to the given parcel, using {@link Parcel#writeBlob(byte[])} + * if the limit exceeds {@link IBinder#getSuggestedMaxIpcSizeBytes()} / 2. This assumes that the + * written map is the only data structure in the caller that varies based on the host data set. + * Other data that will be written to the parcel after this method will not be considered in the + * calculation. + */ + public static void writeHostMap(@NonNull Parcel dest, @NonNull Map<String, ?> map) { + boolean targetSizeExceeded = false; + int totalSize = dest.dataSize(); + for (String host : map.keySet()) { + totalSize += estimatedByteSizeOf(host); + if (totalSize > STRINGS_TARGET_BYTE_SIZE) { + targetSizeExceeded = true; + break; + } + } + + dest.writeBoolean(targetSizeExceeded); + + if (!targetSizeExceeded) { + dest.writeMap(map); + return; + } + + Parcel data = Parcel.obtain(); + try { + data.writeMap(map); + dest.writeBlob(data.marshall()); + } finally { + data.recycle(); + } + } + + /** + * Retrieve a map previously written by {@link #writeHostMap(Parcel, Map)}. + */ + @NonNull + @SuppressWarnings("rawtypes") + public static <T extends Map> T readHostMap(@NonNull Parcel in, @NonNull T map, + @NonNull ClassLoader classLoader) { + boolean targetSizeExceeded = in.readBoolean(); + + if (!targetSizeExceeded) { + in.readMap(map, classLoader); + return map; + } + + Parcel data = Parcel.obtain(); + try { + byte[] blob = in.readBlob(); + data.unmarshall(blob, 0, blob.length); + data.setDataPosition(0); + data.readMap(map, classLoader); + } finally { + data.recycle(); + } + + return map; + } + + /** + * {@link ArraySet} variant of {@link #writeHostMap(Parcel, Map)}. + */ + public static void writeHostSet(@NonNull Parcel dest, @NonNull Set<String> set) { + boolean targetSizeExceeded = false; + int totalSize = dest.dataSize(); + for (String host : set) { + totalSize += estimatedByteSizeOf(host); + if (totalSize > STRINGS_TARGET_BYTE_SIZE) { + targetSizeExceeded = true; + break; + } + } + + dest.writeBoolean(targetSizeExceeded); + + if (!targetSizeExceeded) { + writeSet(dest, set); + return; + } + + Parcel data = Parcel.obtain(); + try { + writeSet(data, set); + dest.writeBlob(data.marshall()); + } finally { + data.recycle(); + } + } + + /** + * {@link ArraySet} variant of {@link #readHostMap(Parcel, Map, ClassLoader)}. + */ + @NonNull + public static Set<String> readHostSet(@NonNull Parcel in) { + boolean targetSizeExceeded = in.readBoolean(); + + if (!targetSizeExceeded) { + return readSet(in); + } + + Parcel data = Parcel.obtain(); + try { + byte[] blob = in.readBlob(); + data.unmarshall(blob, 0, blob.length); + data.setDataPosition(0); + return readSet(data); + } finally { + data.recycle(); + } + } + + private static void writeSet(@NonNull Parcel dest, @Nullable Set<String> set) { + if (set == null) { + dest.writeInt(-1); + return; + } + dest.writeInt(set.size()); + for (String string : set) { + dest.writeString(string); + } + } + + @NonNull + private static Set<String> readSet(@NonNull Parcel in) { + int size = in.readInt(); + if (size == -1) { + return Collections.emptySet(); + } + + ArraySet<String> set = new ArraySet<>(size); + for (int count = 0; count < size; count++) { + set.add(in.readString()); + } + return set; + } + + /** + * Ballpark the size of domains to avoid unnecessary allocation of ashmem when sending domains + * across the client-server API. + */ + public static int estimatedByteSizeOf(String string) { + return string.length() * 2 + 12; + } +} diff --git a/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl b/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl index 21dd623b46bc..701af320fb01 100644 --- a/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl +++ b/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl @@ -16,6 +16,8 @@ package android.content.pm.verify.domain; +import android.content.pm.verify.domain.DomainOwner; +import android.content.pm.verify.domain.DomainSet; import android.content.pm.verify.domain.DomainVerificationInfo; import android.content.pm.verify.domain.DomainVerificationUserSelection; import java.util.List; @@ -35,10 +37,13 @@ interface IDomainVerificationManager { DomainVerificationUserSelection getDomainVerificationUserSelection(String packageName, int userId); - void setDomainVerificationStatus(String domainSetId, in List<String> domains, int state); + @nullable + List<DomainOwner> getOwnersForDomain(String domain, int userId); + + void setDomainVerificationStatus(String domainSetId, in DomainSet domains, int state); void setDomainVerificationLinkHandlingAllowed(String packageName, boolean allowed, int userId); - void setDomainVerificationUserSelection(String domainSetId, in List<String> domains, + void setDomainVerificationUserSelection(String domainSetId, in DomainSet domains, boolean enabled, int userId); } diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java index 2f7aeb80986b..b66f048b829d 100644 --- a/core/java/android/content/res/Configuration.java +++ b/core/java/android/content/res/Configuration.java @@ -1956,7 +1956,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration dest.writeInt(mnc); fixUpLocaleList(); - dest.writeParcelable(mLocaleList, flags); + dest.writeTypedObject(mLocaleList, flags); if(userSetLocale) { dest.writeInt(1); @@ -1980,7 +1980,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration dest.writeInt(compatScreenWidthDp); dest.writeInt(compatScreenHeightDp); dest.writeInt(compatSmallestScreenWidthDp); - dest.writeValue(windowConfiguration); + windowConfiguration.writeToParcel(dest, flags); dest.writeInt(assetsSeq); dest.writeInt(seq); dest.writeInt(fontWeightAdjustment); @@ -1991,7 +1991,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration mcc = source.readInt(); mnc = source.readInt(); - mLocaleList = source.readParcelable(LocaleList.class.getClassLoader()); + mLocaleList = source.readTypedObject(LocaleList.CREATOR); locale = mLocaleList.get(0); userSetLocale = (source.readInt()==1); @@ -2012,7 +2012,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration compatScreenWidthDp = source.readInt(); compatScreenHeightDp = source.readInt(); compatSmallestScreenWidthDp = source.readInt(); - windowConfiguration.setTo((WindowConfiguration) source.readValue(null)); + windowConfiguration.readFromParcel(source); assetsSeq = source.readInt(); seq = source.readInt(); fontWeightAdjustment = source.readInt(); diff --git a/core/java/android/graphics/fonts/FontManager.java b/core/java/android/graphics/fonts/FontManager.java index e512cf1bbb1f..429eef95f952 100644 --- a/core/java/android/graphics/fonts/FontManager.java +++ b/core/java/android/graphics/fonts/FontManager.java @@ -20,7 +20,6 @@ import android.Manifest; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; -import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; @@ -29,7 +28,6 @@ import android.content.Context; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.text.FontConfig; -import android.util.Log; import com.android.internal.graphics.fonts.IFontManager; @@ -198,12 +196,11 @@ public class FontManager { * @return The current font configuration. null if failed to fetch information from the system * service. */ - public @Nullable FontConfig getFontConfig() { + public @NonNull FontConfig getFontConfig() { try { return mIFontManager.getFontConfig(); } catch (RemoteException e) { - Log.e(TAG, "Failed to call getFontConfig", e); - return null; + throw e.rethrowAsRuntimeException(); } } diff --git a/core/java/android/hardware/SensorPrivacyManager.java b/core/java/android/hardware/SensorPrivacyManager.java index f4f9e1775d1a..e03c1f48773a 100644 --- a/core/java/android/hardware/SensorPrivacyManager.java +++ b/core/java/android/hardware/SensorPrivacyManager.java @@ -19,6 +19,7 @@ package android.hardware; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.RequiresPermission; +import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; import android.content.Context; @@ -33,6 +34,7 @@ import com.android.internal.annotations.GuardedBy; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.concurrent.Executor; /** * This class provides access to the sensor privacy services; sensor privacy allows the @@ -42,9 +44,21 @@ import java.lang.annotation.RetentionPolicy; * * @hide */ +@SystemApi @TestApi @SystemService(Context.SENSOR_PRIVACY_SERVICE) public final class SensorPrivacyManager { + + /** + * @hide + */ + public static final boolean USE_MICROPHONE_TOGGLE = true; + + /** + * @hide + */ + public static final boolean USE_CAMERA_TOGGLE = true; + /** * Unique Id of this manager to identify to the service * @hide @@ -58,28 +72,39 @@ public final class SensorPrivacyManager { public static final String EXTRA_SENSOR = SensorPrivacyManager.class.getName() + ".extra.sensor"; - /** Microphone - * @hide */ - @TestApi - public static final int INDIVIDUAL_SENSOR_MICROPHONE = - SensorPrivacyIndividualEnabledSensorProto.MICROPHONE; - - /** Camera - * @hide */ - @TestApi - public static final int INDIVIDUAL_SENSOR_CAMERA = - SensorPrivacyIndividualEnabledSensorProto.CAMERA; - /** - * Individual sensors not listed in {@link Sensor} + * Individual sensors not listed in {@link Sensors} * @hide */ - @IntDef(prefix = "INDIVIDUAL_SENSOR_", value = { - INDIVIDUAL_SENSOR_MICROPHONE, - INDIVIDUAL_SENSOR_CAMERA - }) - @Retention(RetentionPolicy.SOURCE) - public @interface IndividualSensor {} + @SystemApi + @TestApi + public static class Sensors { + + private Sensors() {} + + /** Microphone + * @hide */ + @SystemApi + @TestApi + public static final int MICROPHONE = SensorPrivacyIndividualEnabledSensorProto.MICROPHONE; + /** Camera + * @hide */ + @SystemApi + @TestApi + public static final int CAMERA = SensorPrivacyIndividualEnabledSensorProto.CAMERA; + + /** + * Individual sensors not listed in {@link Sensors} + * + * @hide + */ + @IntDef(value = { + MICROPHONE, + CAMERA + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Sensor {} + } /** * A class implementing this interface can register with the {@link @@ -88,6 +113,8 @@ public final class SensorPrivacyManager { * * @hide */ + @SystemApi + @TestApi public interface OnSensorPrivacyChangedListener { /** * Callback invoked when the sensor privacy state changes. @@ -165,7 +192,8 @@ public final class SensorPrivacyManager { * * @hide */ - public void addSensorPrivacyListener(final OnSensorPrivacyChangedListener listener) { + @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) + public void addSensorPrivacyListener(@NonNull final OnSensorPrivacyChangedListener listener) { synchronized (mListeners) { ISensorPrivacyListener iListener = mListeners.get(listener); if (iListener == null) { @@ -196,15 +224,37 @@ public final class SensorPrivacyManager { * * @hide */ - public void addSensorPrivacyListener(@IndividualSensor int sensor, - final OnSensorPrivacyChangedListener listener) { + @SystemApi + @TestApi + @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) + public void addSensorPrivacyListener(@Sensors.Sensor int sensor, + @NonNull OnSensorPrivacyChangedListener listener) { + addSensorPrivacyListener(sensor, mContext.getMainExecutor(), listener); + } + + /** + * Registers a new listener to receive notification when the state of sensor privacy + * changes. + * + * @param sensor the sensor to listen to changes to + * @param executor the executor to dispatch the callback on + * @param listener the OnSensorPrivacyChangedListener to be notified when the state of sensor + * privacy changes. + * + * @hide + */ + @SystemApi + @TestApi + @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) + public void addSensorPrivacyListener(@Sensors.Sensor int sensor, @NonNull Executor executor, + @NonNull OnSensorPrivacyChangedListener listener) { synchronized (mListeners) { ISensorPrivacyListener iListener = mListeners.get(listener); if (iListener == null) { iListener = new ISensorPrivacyListener.Stub() { @Override public void onSensorPrivacyChanged(boolean enabled) { - listener.onSensorPrivacyChanged(enabled); + executor.execute(() -> listener.onSensorPrivacyChanged(enabled)); } }; mListeners.put(listener, iListener); @@ -228,7 +278,10 @@ public final class SensorPrivacyManager { * * @hide */ - public void removeSensorPrivacyListener(OnSensorPrivacyChangedListener listener) { + @SystemApi + @TestApi + @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) + public void removeSensorPrivacyListener(@NonNull OnSensorPrivacyChangedListener listener) { synchronized (mListeners) { ISensorPrivacyListener iListener = mListeners.get(listener); if (iListener != null) { @@ -249,6 +302,7 @@ public final class SensorPrivacyManager { * * @hide */ + @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean isSensorPrivacyEnabled() { try { return mService.isSensorPrivacyEnabled(); @@ -264,8 +318,10 @@ public final class SensorPrivacyManager { * * @hide */ + @SystemApi @TestApi - public boolean isIndividualSensorPrivacyEnabled(@IndividualSensor int sensor) { + @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) + public boolean isSensorPrivacyEnabled(@Sensors.Sensor int sensor) { try { return mService.isIndividualSensorPrivacyEnabled(mContext.getUserId(), sensor); } catch (RemoteException e) { @@ -283,8 +339,7 @@ public final class SensorPrivacyManager { */ @TestApi @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) - public void setIndividualSensorPrivacy(@IndividualSensor int sensor, - boolean enable) { + public void setSensorPrivacy(@Sensors.Sensor int sensor, boolean enable) { try { mService.setIndividualSensorPrivacy(mContext.getUserId(), sensor, enable); } catch (RemoteException e) { @@ -303,7 +358,7 @@ public final class SensorPrivacyManager { */ @TestApi @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) - public void setIndividualSensorPrivacyForProfileGroup(@IndividualSensor int sensor, + public void setSensorPrivacyForProfileGroup(@Sensors.Sensor int sensor, boolean enable) { try { mService.setIndividualSensorPrivacyForProfileGroup(mContext.getUserId(), sensor, @@ -321,7 +376,8 @@ public final class SensorPrivacyManager { * * @hide */ - public void suppressIndividualSensorPrivacyReminders(@NonNull String packageName, + @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) + public void suppressSensorPrivacyReminders(@NonNull String packageName, boolean suppress) { try { mService.suppressIndividualSensorPrivacyReminders(mContext.getUserId(), packageName, diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java index f868a5066c25..0256b7bc6de0 100644 --- a/core/java/android/hardware/face/FaceManager.java +++ b/core/java/android/hardware/face/FaceManager.java @@ -1448,13 +1448,17 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan case FACE_ACQUIRED_TOO_FAR: return context.getString(R.string.face_acquired_too_far); case FACE_ACQUIRED_TOO_HIGH: - return context.getString(R.string.face_acquired_too_high); - case FACE_ACQUIRED_TOO_LOW: + // TODO(b/181269243): Change back once error codes are fixed. return context.getString(R.string.face_acquired_too_low); + case FACE_ACQUIRED_TOO_LOW: + // TODO(b/181269243) Change back once error codes are fixed. + return context.getString(R.string.face_acquired_too_high); case FACE_ACQUIRED_TOO_RIGHT: - return context.getString(R.string.face_acquired_too_right); - case FACE_ACQUIRED_TOO_LEFT: + // TODO(b/181269243) Change back once error codes are fixed. return context.getString(R.string.face_acquired_too_left); + case FACE_ACQUIRED_TOO_LEFT: + // TODO(b/181269243) Change back once error codes are fixed. + return context.getString(R.string.face_acquired_too_right); case FACE_ACQUIRED_POOR_GAZE: return context.getString(R.string.face_acquired_poor_gaze); case FACE_ACQUIRED_NOT_DETECTED: diff --git a/core/java/android/hardware/soundtrigger/OWNERS b/core/java/android/hardware/soundtrigger/OWNERS index 816bc6bba639..e5d037003ac4 100644 --- a/core/java/android/hardware/soundtrigger/OWNERS +++ b/core/java/android/hardware/soundtrigger/OWNERS @@ -1 +1,2 @@ -include /core/java/android/media/soundtrigger/OWNERS +ytai@google.com +elaurent@google.com diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java index c6efaace76d7..2b6f336848c3 100644 --- a/core/java/android/os/BatteryConsumer.java +++ b/core/java/android/os/BatteryConsumer.java @@ -47,6 +47,7 @@ public abstract class BatteryConsumer { POWER_COMPONENT_SYSTEM_SERVICES, POWER_COMPONENT_SENSORS, POWER_COMPONENT_GNSS, + POWER_COMPONENT_WIFI, POWER_COMPONENT_WAKELOCK, POWER_COMPONENT_SCREEN, POWER_COMPONENT_REATTRIBUTED_TO_OTHER_CONSUMERS, @@ -66,6 +67,7 @@ public abstract class BatteryConsumer { public static final int POWER_COMPONENT_MOBILE_RADIO = 8; public static final int POWER_COMPONENT_SENSORS = 9; public static final int POWER_COMPONENT_GNSS = 10; + public static final int POWER_COMPONENT_WIFI = 11; public static final int POWER_COMPONENT_WAKELOCK = 12; public static final int POWER_COMPONENT_SCREEN = 13; // Power that is re-attributed to other battery consumers. For example, for System Server @@ -94,6 +96,7 @@ public abstract class BatteryConsumer { TIME_COMPONENT_MOBILE_RADIO, TIME_COMPONENT_SENSORS, TIME_COMPONENT_GNSS, + TIME_COMPONENT_WIFI, TIME_COMPONENT_WAKELOCK, TIME_COMPONENT_SCREEN, }) @@ -112,6 +115,7 @@ public abstract class BatteryConsumer { public static final int TIME_COMPONENT_MOBILE_RADIO = 8; public static final int TIME_COMPONENT_SENSORS = 9; public static final int TIME_COMPONENT_GNSS = 10; + public static final int TIME_COMPONENT_WIFI = 11; public static final int TIME_COMPONENT_WAKELOCK = 12; public static final int TIME_COMPONENT_SCREEN = 13; diff --git a/core/java/android/os/TEST_MAPPING b/core/java/android/os/TEST_MAPPING index 1bdc82a82c6c..97e03e9d0d94 100644 --- a/core/java/android/os/TEST_MAPPING +++ b/core/java/android/os/TEST_MAPPING @@ -38,6 +38,23 @@ "include-filter": "com.android.server.pm.parsing.PackageInfoUserFieldsTest" } ] + }, + { + "file_patterns": ["BatteryStats.java"], + "name": "FrameworksCoreTests", + "options": [ + { "include-filter": "com.android.internal.os.BatteryStatsTests" }, + { "exclude-annotation": "com.android.internal.os.SkipPresubmit" } + ] + }, + { + "file_patterns": ["BatteryStats.java"], + "name": "FrameworksServicesTests", + "options": [ + { "include-filter": "com.android.server.am.BatteryStatsServiceTest" }, + { "include-filter": "com.android.server.am.MeasuredEnergySnapshotTest" }, + { "include-filter": "com.android.server.am.BatteryExternalStatsWorkerTest" } + ] } ], "postsubmit": [ diff --git a/core/java/android/os/UidBatteryConsumer.java b/core/java/android/os/UidBatteryConsumer.java index bb40d905d481..dfa0c396485d 100644 --- a/core/java/android/os/UidBatteryConsumer.java +++ b/core/java/android/os/UidBatteryConsumer.java @@ -16,9 +16,13 @@ package android.os; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Contains power consumption data attributed to a specific UID. * @@ -26,9 +30,37 @@ import android.annotation.Nullable; */ public final class UidBatteryConsumer extends BatteryConsumer implements Parcelable { + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + STATE_FOREGROUND, + STATE_BACKGROUND + }) + public @interface State { + } + + /** + * The state of an application when it is either running a foreground (top) activity + * or a foreground service. + */ + public static final int STATE_FOREGROUND = 0; + + /** + * The state of an application when it is running in the background, including the following + * states: + * + * {@link android.app.ActivityManager#PROCESS_STATE_IMPORTANT_BACKGROUND}, + * {@link android.app.ActivityManager#PROCESS_STATE_TRANSIENT_BACKGROUND}, + * {@link android.app.ActivityManager#PROCESS_STATE_BACKUP}, + * {@link android.app.ActivityManager#PROCESS_STATE_SERVICE}, + * {@link android.app.ActivityManager#PROCESS_STATE_RECEIVER}. + */ + public static final int STATE_BACKGROUND = 1; + private final int mUid; @Nullable private final String mPackageWithHighestDrain; + private final long mTimeInForegroundMs; + private final long mTimeInBackgroundMs; public int getUid() { return mUid; @@ -39,16 +71,33 @@ public final class UidBatteryConsumer extends BatteryConsumer implements Parcela return mPackageWithHighestDrain; } + /** + * Returns the amount of time in milliseconds this UID spent in the specified state. + */ + public long getTimeInStateMs(@State int state) { + switch (state) { + case STATE_BACKGROUND: + return mTimeInBackgroundMs; + case STATE_FOREGROUND: + return mTimeInForegroundMs; + } + return 0; + } + private UidBatteryConsumer(@NonNull Builder builder) { super(builder.mPowerComponentsBuilder.build()); mUid = builder.mUid; mPackageWithHighestDrain = builder.mPackageWithHighestDrain; + mTimeInForegroundMs = builder.mTimeInForegroundMs; + mTimeInBackgroundMs = builder.mTimeInBackgroundMs; } private UidBatteryConsumer(@NonNull Parcel source) { super(new PowerComponents(source)); mUid = source.readInt(); mPackageWithHighestDrain = source.readString(); + mTimeInForegroundMs = source.readLong(); + mTimeInBackgroundMs = source.readLong(); } /** @@ -59,6 +108,8 @@ public final class UidBatteryConsumer extends BatteryConsumer implements Parcela super.writeToParcel(dest, flags); dest.writeInt(mUid); dest.writeString(mPackageWithHighestDrain); + dest.writeLong(mTimeInForegroundMs); + dest.writeLong(mTimeInBackgroundMs); } @NonNull @@ -84,6 +135,8 @@ public final class UidBatteryConsumer extends BatteryConsumer implements Parcela private final BatteryStats.Uid mBatteryStatsUid; private final int mUid; private String mPackageWithHighestDrain; + public long mTimeInForegroundMs; + public long mTimeInBackgroundMs; private boolean mExcludeFromBatteryUsageStats; public Builder(int customPowerComponentCount, int customTimeComponentCount, @@ -113,6 +166,25 @@ public final class UidBatteryConsumer extends BatteryConsumer implements Parcela } /** + * Sets the duration, in milliseconds, that this UID was active in a particular state, + * such as foreground or background. + */ + @NonNull + public Builder setTimeInStateMs(@State int state, long timeInStateMs) { + switch (state) { + case STATE_FOREGROUND: + mTimeInForegroundMs = timeInStateMs; + break; + case STATE_BACKGROUND: + mTimeInBackgroundMs = timeInStateMs; + break; + default: + throw new IllegalArgumentException("Unsupported state: " + state); + } + return this; + } + + /** * Marks the UidBatteryConsumer for exclusion from the result set. */ public Builder excludeFromBatteryUsageStats() { diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 8bdfd3d3d627..682754e66904 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -1688,6 +1688,7 @@ public class UserManager { * @return Whether guest user is always ephemeral * @hide */ + @TestApi public static boolean isGuestUserEphemeral() { return Resources.getSystem() .getBoolean(com.android.internal.R.bool.config_guestUserEphemeral); @@ -1802,6 +1803,20 @@ public class UserManager { } /** + * @return the user type of the context user. + * @hide + */ + @TestApi + @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.CREATE_USERS + }) + @UserHandleAware + public @NonNull String getUserType() { + UserInfo userInfo = getUserInfo(mUserId); + return userInfo == null ? "" : userInfo.userType; + } + + /** * Returns the user name of the context user. This call is only available to applications on * the system image; it requires the {@code android.permission.MANAGE_USERS} or {@code * android.permission.GET_ACCOUNTS_PRIVILEGED} permissions. @@ -1809,7 +1824,8 @@ public class UserManager { * @return the user name */ @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS, - android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}, conditional = true) + android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED, + android.Manifest.permission.CREATE_USERS}, conditional = true) @UserHandleAware public @NonNull String getUserName() { if (UserHandle.myUserId() == mUserId) { @@ -2792,6 +2808,7 @@ public class UserManager { */ @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, Manifest.permission.CREATE_USERS}) + @TestApi public @Nullable UserInfo createUser(@Nullable String name, @NonNull String userType, @UserInfoFlag int flags) { try { @@ -2828,6 +2845,7 @@ public class UserManager { * @throws UserOperationException if the user could not be created. * @hide */ + @TestApi @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, Manifest.permission.CREATE_USERS}) public @NonNull UserInfo preCreateUser(@NonNull String userType) @@ -2976,10 +2994,11 @@ public class UserManager { * * @hide */ + @TestApi @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, Manifest.permission.CREATE_USERS}) - public UserInfo createProfileForUser(String name, @NonNull String userType, - @UserInfoFlag int flags, @UserIdInt int userId, String[] disallowedPackages) { + public @Nullable UserInfo createProfileForUser(@Nullable String name, @NonNull String userType, + @UserInfoFlag int flags, @UserIdInt int userId, @Nullable String[] disallowedPackages) { try { return mService.createProfileForUserWithThrow(name, userType, flags, userId, disallowedPackages); @@ -3022,9 +3041,10 @@ public class UserManager { * * @hide */ + @TestApi @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, Manifest.permission.CREATE_USERS}) - public UserInfo createRestrictedProfile(String name) { + public @Nullable UserInfo createRestrictedProfile(@Nullable String name) { try { UserHandle parentUserHandle = Process.myUserHandle(); UserInfo user = mService.createRestrictedProfileWithThrow(name, @@ -3248,10 +3268,11 @@ public class UserManager { /** * Return the number of users currently created on the device. - * <p>This API is not for use by third-party apps. It requires the {@code MANAGE_USERS} - * permission.</p> */ - @RequiresPermission(android.Manifest.permission.MANAGE_USERS) + @RequiresPermission(anyOf = { + android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.CREATE_USERS + }) public int getUserCount() { List<UserInfo> users = getUsers(); return users != null ? users.size() : 1; @@ -3274,7 +3295,10 @@ public class UserManager { * @hide */ @UnsupportedAppUsage - @RequiresPermission(android.Manifest.permission.MANAGE_USERS) + @RequiresPermission(anyOf = { + android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.CREATE_USERS + }) public List<UserInfo> getUsers() { return getUsers(/*excludePartial= */ true, /* excludeDying= */ false, /* excludePreCreated= */ true); @@ -3292,7 +3316,10 @@ public class UserManager { * @return the list of users that were created. * @hide */ - @RequiresPermission(android.Manifest.permission.MANAGE_USERS) + @RequiresPermission(anyOf = { + android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.CREATE_USERS + }) public @NonNull List<UserInfo> getAliveUsers() { return getUsers(/*excludePartial= */ true, /* excludeDying= */ true, /* excludePreCreated= */ true); @@ -3306,7 +3333,10 @@ public class UserManager { */ @Deprecated @UnsupportedAppUsage - @RequiresPermission(android.Manifest.permission.MANAGE_USERS) + @RequiresPermission(anyOf = { + android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.CREATE_USERS + }) public @NonNull List<UserInfo> getUsers(boolean excludeDying) { return getUsers(/*excludePartial= */ true, excludeDying, /* excludePreCreated= */ true); @@ -3317,8 +3347,12 @@ public class UserManager { * * @hide */ - @RequiresPermission(android.Manifest.permission.MANAGE_USERS) - public List<UserInfo> getUsers(boolean excludePartial, boolean excludeDying, + @TestApi + @RequiresPermission(anyOf = { + android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.CREATE_USERS + }) + public @NonNull List<UserInfo> getUsers(boolean excludePartial, boolean excludeDying, boolean excludePreCreated) { try { return mService.getUsers(excludePartial, excludeDying, excludePreCreated); @@ -3335,7 +3369,10 @@ public class UserManager { * @hide */ @SystemApi - @RequiresPermission(android.Manifest.permission.MANAGE_USERS) + @RequiresPermission(anyOf = { + android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.CREATE_USERS + }) public @NonNull List<UserHandle> getUserHandles(boolean excludeDying) { List<UserInfo> users = getUsers(/* excludePartial= */ true, excludeDying, /* excludePreCreated= */ true); @@ -3354,7 +3391,10 @@ public class UserManager { * @hide */ @SystemApi - @RequiresPermission(android.Manifest.permission.MANAGE_USERS) + @RequiresPermission(anyOf = { + android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.CREATE_USERS + }) public long[] getSerialNumbersOfUsers(boolean excludeDying) { List<UserInfo> users = getUsers(/* excludePartial= */ true, excludeDying, /* excludePreCreated= */ true); @@ -3678,7 +3718,10 @@ public class UserManager { * @hide */ @UnsupportedAppUsage - @RequiresPermission(android.Manifest.permission.MANAGE_USERS) + @RequiresPermission(anyOf = { + android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.INTERACT_ACROSS_USERS + }) public UserInfo getProfileParent(@UserIdInt int userId) { try { return mService.getProfileParent(userId); @@ -3697,7 +3740,10 @@ public class UserManager { * @hide */ @SystemApi - @RequiresPermission(android.Manifest.permission.MANAGE_USERS) + @RequiresPermission(anyOf = { + android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.INTERACT_ACROSS_USERS + }) public @Nullable UserHandle getProfileParent(@NonNull UserHandle user) { UserInfo info = getProfileParent(user.getIdentifier()); diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java index 06203ff15094..9ffc5aa0022c 100644 --- a/core/java/android/os/ZygoteProcess.java +++ b/core/java/android/os/ZygoteProcess.java @@ -426,7 +426,7 @@ public class ZygoteProcess { // avoid writing a partial response to the zygote. for (String arg : args) { // Making two indexOf calls here is faster than running a manually fused loop due - // to the fact that indexOf is a optimized intrinsic. + // to the fact that indexOf is an optimized intrinsic. if (arg.indexOf('\n') >= 0) { throw new ZygoteStartFailedEx("Embedded newlines not allowed"); } else if (arg.indexOf('\r') >= 0) { diff --git a/core/java/android/os/strictmode/IncorrectContextUseViolation.java b/core/java/android/os/strictmode/IncorrectContextUseViolation.java index 11d26cab14b3..d8c22fd94ce9 100644 --- a/core/java/android/os/strictmode/IncorrectContextUseViolation.java +++ b/core/java/android/os/strictmode/IncorrectContextUseViolation.java @@ -24,7 +24,7 @@ import android.content.Context; * instance. * * @see Context#getSystemService(String) - * @see Context#isUiContext(Context) + * @see Context#isUiContext * @see android.os.StrictMode.VmPolicy.Builder#detectIncorrectContextUse() */ public final class IncorrectContextUseViolation extends Violation { diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index ff01011bd19b..bae36b299247 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -485,10 +485,6 @@ public final class PermissionManager { * One for cases where the installer of the package allowlists a permission. This list * corresponds to the {@link PackageManager#FLAG_PERMISSION_WHITELIST_INSTALLER} flag. Can be * accessed by pre-installed holders of a dedicated permission or the installer on record. - * <li> - * One for cases where the system exempts the permission when granting a role. This list - * corresponds to the {@link PackageManager#FLAG_PERMISSION_ALLOWLIST_ROLE} flag. Can be - * accessed by pre-installed holders of a dedicated permission. * </ol> * * @param packageName the app for which to get allowlisted permissions @@ -502,7 +498,6 @@ public final class PermissionManager { * @see PackageManager#FLAG_PERMISSION_WHITELIST_SYSTEM * @see PackageManager#FLAG_PERMISSION_WHITELIST_UPGRADE * @see PackageManager#FLAG_PERMISSION_WHITELIST_INSTALLER - * @see PackageManager#FLAG_PERMISSION_ALLOWLIST_ROLE * * @hide Pending API */ @@ -549,10 +544,6 @@ public final class PermissionManager { * One for cases where the installer of the package allowlists a permission. This list * corresponds to the {@link PackageManager#FLAG_PERMISSION_WHITELIST_INSTALLER} flag. Can be * accessed by pre-installed holders of a dedicated permission or the installer on record. - * <li> - * One for cases where the system exempts the permission when granting a role. This list - * corresponds to the {@link PackageManager#FLAG_PERMISSION_ALLOWLIST_ROLE} flag. Can be - * accessed by pre-installed holders of a dedicated permission. * </ol> * <p> * You need to specify the allowlists for which to set the allowlisted permissions which will @@ -570,7 +561,6 @@ public final class PermissionManager { * @see PackageManager#FLAG_PERMISSION_WHITELIST_SYSTEM * @see PackageManager#FLAG_PERMISSION_WHITELIST_UPGRADE * @see PackageManager#FLAG_PERMISSION_WHITELIST_INSTALLER - * @see PackageManager#FLAG_PERMISSION_ALLOWLIST_ROLE * * @hide Pending API */ @@ -613,10 +603,6 @@ public final class PermissionManager { * One for cases where the installer of the package allowlists a permission. This list * corresponds to the {@link PackageManager#FLAG_PERMISSION_WHITELIST_INSTALLER} flag. Can be * accessed by pre-installed holders of a dedicated permission or the installer on record. - * <li> - * One for cases where the system exempts the permission when granting a role. This list - * corresponds to the {@link PackageManager#FLAG_PERMISSION_ALLOWLIST_ROLE} flag. Can be - * accessed by pre-installed holders of a dedicated permission. * </ol> * <p> * You need to specify the allowlists for which to set the allowlisted permissions which will @@ -634,7 +620,6 @@ public final class PermissionManager { * @see PackageManager#FLAG_PERMISSION_WHITELIST_SYSTEM * @see PackageManager#FLAG_PERMISSION_WHITELIST_UPGRADE * @see PackageManager#FLAG_PERMISSION_WHITELIST_INSTALLER - * @see PackageManager#FLAG_PERMISSION_ALLOWLIST_ROLE * * @hide Pending API */ diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java index 4354920c83ec..6e89faf9c2ed 100644 --- a/core/java/android/provider/DeviceConfig.java +++ b/core/java/android/provider/DeviceConfig.java @@ -157,6 +157,14 @@ public final class DeviceConfig { public static final String NAMESPACE_BLUETOOTH = "bluetooth"; /** + * Namespace for features relating to clipboard. + * + * @hide + */ + @SystemApi + public static final String NAMESPACE_CLIPBOARD = "clipboard"; + + /** * Namespace for all networking connectivity related features. * * @hide diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 345cc84e8593..09d0af11a39f 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -14622,6 +14622,29 @@ public final class Settings { public static final String POWER_BUTTON_VERY_LONG_PRESS = "power_button_very_long_press"; + + /** + * Keyguard should be on the left hand side of the screen, for wide screen layouts. + * + * @hide + */ + public static final int ONE_HANDED_KEYGUARD_SIDE_LEFT = 0; + + /** + * Keyguard should be on the right hand side of the screen, for wide screen layouts. + * + * @hide + */ + public static final int ONE_HANDED_KEYGUARD_SIDE_RIGHT = 1; + /** + * In one handed mode, which side the keyguard should be on. Allowable values are one of + * the ONE_HANDED_KEYGUARD_SIDE_* constants. + * + * @hide + */ + @Readable + public static final String ONE_HANDED_KEYGUARD_SIDE = "one_handed_keyguard_side"; + /** * Keys we no longer back up under the current schema, but want to continue to * process when restoring historical backup datasets. diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java index ffb4a6eeb1ab..3c355d4b6f45 100644 --- a/core/java/android/telephony/TelephonyRegistryManager.java +++ b/core/java/android/telephony/TelephonyRegistryManager.java @@ -270,13 +270,13 @@ public class TelephonyRegistryManager { /** * Notify call state changed on certain subscription. * - * @param subId for which call state changed. * @param slotIndex for which call state changed. Can be derived from subId except when subId is * invalid. + * @param subId for which call state changed. * @param state latest call state. e.g, offhook, ringing * @param incomingNumber incoming phone number. */ - public void notifyCallStateChanged(int subId, int slotIndex, @CallState int state, + public void notifyCallStateChanged(int slotIndex, int subId, @CallState int state, @Nullable String incomingNumber) { try { sRegistry.notifyCallState(slotIndex, subId, state, incomingNumber); @@ -329,12 +329,12 @@ public class TelephonyRegistryManager { /** * Notify {@link ServiceState} update on certain subscription. * - * @param subId for which the service state changed. * @param slotIndex for which the service state changed. Can be derived from subId except * subId is invalid. + * @param subId for which the service state changed. * @param state service state e.g, in service, out of service or roaming status. */ - public void notifyServiceStateChanged(int subId, int slotIndex, @NonNull ServiceState state) { + public void notifyServiceStateChanged(int slotIndex, int subId, @NonNull ServiceState state) { try { sRegistry.notifyServiceStateForPhoneId(slotIndex, subId, state); } catch (RemoteException ex) { @@ -345,12 +345,12 @@ public class TelephonyRegistryManager { /** * Notify {@link SignalStrength} update on certain subscription. * - * @param subId for which the signalstrength changed. * @param slotIndex for which the signalstrength changed. Can be derived from subId except when * subId is invalid. + * @param subId for which the signalstrength changed. * @param signalStrength e.g, signalstrength level {@see SignalStrength#getLevel()} */ - public void notifySignalStrengthChanged(int subId, int slotIndex, + public void notifySignalStrengthChanged(int slotIndex, int subId, @NonNull SignalStrength signalStrength) { try { sRegistry.notifySignalStrengthForPhoneId(slotIndex, subId, signalStrength); @@ -363,13 +363,13 @@ public class TelephonyRegistryManager { * Notify changes to the message-waiting indicator on certain subscription. e.g, The status bar * uses message waiting indicator to determine when to display the voicemail icon. * - * @param subId for which message waiting indicator changed. * @param slotIndex for which message waiting indicator changed. Can be derived from subId * except when subId is invalid. + * @param subId for which message waiting indicator changed. * @param msgWaitingInd {@code true} indicates there is message-waiting indicator, {@code false} * otherwise. */ - public void notifyMessageWaitingChanged(int subId, int slotIndex, boolean msgWaitingInd) { + public void notifyMessageWaitingChanged(int slotIndex, int subId, boolean msgWaitingInd) { try { sRegistry.notifyMessageWaitingChangedForPhoneId(slotIndex, subId, msgWaitingInd); } catch (RemoteException ex) { @@ -410,9 +410,9 @@ public class TelephonyRegistryManager { /** * Notify changes to default (Internet) data connection state on certain subscription. * - * @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 subId for which data connection state changed. * @param preciseState the PreciseDataConnectionState * * @see PreciseDataConnectionState @@ -431,13 +431,13 @@ public class TelephonyRegistryManager { /** * Notify {@link CallQuality} change on certain subscription. * - * @param subId for which call quality state changed. * @param slotIndex for which call quality state changed. Can be derived from subId except when * subId is invalid. + * @param subId for which call quality state changed. * @param callQuality Information about call quality e.g, call quality level * @param networkType associated with this data connection. e.g, LTE */ - public void notifyCallQualityChanged(int subId, int slotIndex, @NonNull CallQuality callQuality, + public void notifyCallQualityChanged(int slotIndex, int subId, @NonNull CallQuality callQuality, @NetworkType int networkType) { try { sRegistry.notifyCallQualityChanged(callQuality, slotIndex, subId, networkType); @@ -449,11 +449,11 @@ public class TelephonyRegistryManager { /** * Notify emergency number list changed on certain subscription. * - * @param subId for which emergency number list changed. * @param slotIndex for which emergency number list changed. Can be derived from subId except * when subId is invalid. + * @param subId for which emergency number list changed. */ - public void notifyEmergencyNumberList(int subId, int slotIndex) { + public void notifyEmergencyNumberList( int slotIndex, int subId) { try { sRegistry.notifyEmergencyNumberList(slotIndex, subId); } catch (RemoteException ex) { @@ -494,13 +494,13 @@ public class TelephonyRegistryManager { /** * Notify radio power state changed on certain subscription. * - * @param subId for which radio power state changed. * @param slotIndex for which radio power state changed. Can be derived from subId except when * subId is invalid. + * @param subId for which radio power state changed. * @param radioPowerState the current modem radio state. */ - public void notifyRadioPowerStateChanged(int subId, int slotIndex, - @RadioPowerState int radioPowerState) { + public void notifyRadioPowerStateChanged(int slotIndex, int subId, + @RadioPowerState int radioPowerState) { try { sRegistry.notifyRadioPowerStateChanged(slotIndex, subId, radioPowerState); } catch (RemoteException ex) { @@ -538,13 +538,13 @@ public class TelephonyRegistryManager { * Notify data activation state changed on certain subscription. * @see TelephonyManager#getDataActivationState() * - * @param subId for which data activation state changed. * @param slotIndex for which data activation state changed. Can be derived from subId except * when subId is invalid. + * @param subId for which data activation state changed. * @param activationState sim activation state e.g, activated. */ - public void notifyDataActivationStateChanged(int subId, int slotIndex, - @SimActivationState int activationState) { + public void notifyDataActivationStateChanged(int slotIndex, int subId, + @SimActivationState int activationState) { try { sRegistry.notifySimActivationStateChangedForPhoneId(slotIndex, subId, SIM_ACTIVATION_TYPE_DATA, activationState); @@ -557,13 +557,13 @@ public class TelephonyRegistryManager { * Notify voice activation state changed on certain subscription. * @see TelephonyManager#getVoiceActivationState() * - * @param subId for which voice activation state changed. * @param slotIndex for which voice activation state changed. Can be derived from subId except * subId is invalid. + * @param subId for which voice activation state changed. * @param activationState sim activation state e.g, activated. */ - public void notifyVoiceActivationStateChanged(int subId, int slotIndex, - @SimActivationState int activationState) { + public void notifyVoiceActivationStateChanged(int slotIndex, int subId, + @SimActivationState int activationState) { try { sRegistry.notifySimActivationStateChangedForPhoneId(slotIndex, subId, SIM_ACTIVATION_TYPE_VOICE, activationState); @@ -576,9 +576,9 @@ public class TelephonyRegistryManager { * Notify User mobile data state changed on certain subscription. e.g, mobile data is enabled * or disabled. * - * @param subId for which mobile data state has changed. * @param slotIndex for which mobile data state has changed. Can be derived from subId except * when subId is invalid. + * @param subId for which mobile data state has changed. * @param state {@code true} indicates mobile data is enabled/on. {@code false} otherwise. */ public void notifyUserMobileDataStateChanged(int slotIndex, int subId, boolean state) { @@ -599,7 +599,7 @@ public class TelephonyRegistryManager { * @param telephonyDisplayInfo The display info. */ public void notifyDisplayInfoChanged(int slotIndex, int subscriptionId, - @NonNull TelephonyDisplayInfo telephonyDisplayInfo) { + @NonNull TelephonyDisplayInfo telephonyDisplayInfo) { try { sRegistry.notifyDisplayInfoChanged(slotIndex, subscriptionId, telephonyDisplayInfo); } catch (RemoteException ex) { @@ -640,14 +640,14 @@ public class TelephonyRegistryManager { * Notify precise call state changed on certain subscription, including foreground, background * and ringcall states. * - * @param subId for which precise call state changed. * @param slotIndex for which precise call state changed. Can be derived from subId except when * subId is invalid. + * @param subId for which precise call state changed. * @param ringCallPreciseState ringCall state. * @param foregroundCallPreciseState foreground call state. * @param backgroundCallPreciseState background call state. */ - public void notifyPreciseCallState(int subId, int slotIndex, + public void notifyPreciseCallState(int slotIndex, int subId, @PreciseCallStates int ringCallPreciseState, @PreciseCallStates int foregroundCallPreciseState, @PreciseCallStates int backgroundCallPreciseState) { @@ -790,9 +790,10 @@ public class TelephonyRegistryManager { * @param reason Reason for data enabled/disabled. See {@code REASON_*} in * {@link TelephonyManager}. */ - public void notifyDataEnabled(boolean enabled, @TelephonyManager.DataEnabledReason int reason) { + public void notifyDataEnabled(int slotIndex, int subId, boolean enabled, + @TelephonyManager.DataEnabledReason int reason) { try { - sRegistry.notifyDataEnabled(enabled, reason); + sRegistry.notifyDataEnabled(slotIndex, subId, enabled, reason); } catch (RemoteException ex) { // system server crash } @@ -801,11 +802,11 @@ public class TelephonyRegistryManager { /** * Notify emergency number list changed on certain subscription. * - * @param subId for which emergency number list changed. * @param slotIndex for which emergency number list changed. Can be derived from subId except * when subId is invalid. + * @param subId for which emergency number list changed. */ - public void notifyAllowedNetworkTypesChanged(int subId, int slotIndex, + public void notifyAllowedNetworkTypesChanged(int slotIndex, int subId, Map<Integer, Long> allowedNetworkTypeList) { try { sRegistry.notifyAllowedNetworkTypesChanged(slotIndex, subId, allowedNetworkTypeList); diff --git a/core/java/android/text/FontConfig.java b/core/java/android/text/FontConfig.java index aef185c77633..33b7c757f1e8 100644 --- a/core/java/android/text/FontConfig.java +++ b/core/java/android/text/FontConfig.java @@ -18,6 +18,7 @@ package android.text; import static java.lang.annotation.RetentionPolicy.SOURCE; +import android.annotation.CurrentTimeMillisLong; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; @@ -101,7 +102,7 @@ public final class FontConfig implements Parcelable { * * If there is no update, this return 0. */ - public long getLastModifiedTimeMillis() { + public @CurrentTimeMillisLong long getLastModifiedTimeMillis() { return mLastModifiedTimeMillis; } diff --git a/core/java/android/util/MergedConfiguration.java b/core/java/android/util/MergedConfiguration.java index 3ac44aacc2c7..4328e0826bd0 100644 --- a/core/java/android/util/MergedConfiguration.java +++ b/core/java/android/util/MergedConfiguration.java @@ -33,9 +33,9 @@ import java.io.PrintWriter; */ public class MergedConfiguration implements Parcelable { - private Configuration mGlobalConfig = new Configuration(); - private Configuration mOverrideConfig = new Configuration(); - private Configuration mMergedConfig = new Configuration(); + private final Configuration mGlobalConfig = new Configuration(); + private final Configuration mOverrideConfig = new Configuration(); + private final Configuration mMergedConfig = new Configuration(); public MergedConfiguration() { } @@ -59,15 +59,15 @@ public class MergedConfiguration implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeParcelable(mGlobalConfig, flags); - dest.writeParcelable(mOverrideConfig, flags); - dest.writeParcelable(mMergedConfig, flags); + mGlobalConfig.writeToParcel(dest, flags); + mOverrideConfig.writeToParcel(dest, flags); + mMergedConfig.writeToParcel(dest, flags); } public void readFromParcel(Parcel source) { - mGlobalConfig = source.readParcelable(Configuration.class.getClassLoader()); - mOverrideConfig = source.readParcelable(Configuration.class.getClassLoader()); - mMergedConfig = source.readParcelable(Configuration.class.getClassLoader()); + mGlobalConfig.readFromParcel(source); + mOverrideConfig.readFromParcel(source); + mMergedConfig.readFromParcel(source); } @Override diff --git a/core/java/android/view/IPinnedStackListener.aidl b/core/java/android/view/IPinnedTaskListener.aidl index 29c9c155e978..c31e67e59a99 100644 --- a/core/java/android/view/IPinnedStackListener.aidl +++ b/core/java/android/view/IPinnedTaskListener.aidl @@ -23,11 +23,11 @@ import android.graphics.Rect; import android.view.DisplayInfo; /** - * Listener for changes to the pinned stack made by the WindowManager. + * Listener for changes to the pinned task made by the WindowManager. * * @hide */ -oneway interface IPinnedStackListener { +oneway interface IPinnedTaskListener { /** * Called when the window manager has detected a change that would cause the movement bounds diff --git a/core/java/android/view/IScrollCaptureCallbacks.aidl b/core/java/android/view/IScrollCaptureCallbacks.aidl index d97e3c66cc5d..26eaac0a2bf5 100644 --- a/core/java/android/view/IScrollCaptureCallbacks.aidl +++ b/core/java/android/view/IScrollCaptureCallbacks.aidl @@ -16,12 +16,10 @@ package android.view; -import android.graphics.Point; import android.graphics.Rect; +import android.view.ScrollCaptureResponse; import android.view.Surface; -import android.view.IScrollCaptureConnection; - /** * Asynchronous callback channel for responses to scroll capture requests. * @@ -29,34 +27,30 @@ import android.view.IScrollCaptureConnection; */ interface IScrollCaptureCallbacks { /** - * Scroll capture is available, and a connection has been provided. + * Provides the result of WindowManagerService#requestScrollCapture * - * @param connection a connection to a window process and scrollable content - * @param scrollAreaInWindow the location of scrolling in global (window) coordinate space + * @param response the response which describes the result */ - oneway void onConnected(in IScrollCaptureConnection connection, in Rect scrollBounds, - in Point positionInWindow); + oneway void onScrollCaptureResponse(in ScrollCaptureResponse response); /** - * The window does not support scroll capture. - */ - oneway void onUnavailable(); - - /** - * Called when the remote end has confirmed the request and is ready to begin providing image - * requests. + * Called in reply to IScrollCaptureConnection#startCapture, when the remote end has confirmed + * the request and is ready to begin capturing images. */ oneway void onCaptureStarted(); /** - * Received a response from a capture request. + * Received a response from a capture request. The provided rectangle indicates the portion + * of the requested rectangle which was captured. An empty rectangle indicates that the request + * could not be satisfied (most commonly due to the available scrolling range). + * + * @param flags flags describing additional status of the result + * @param capturedArea the actual area of the image captured */ - oneway void onCaptureBufferSent(long frameNumber, in Rect capturedArea); + oneway void onImageRequestCompleted(int flags, in Rect capturedArea); /** - * Signals that the capture session has completed and the target window may be returned to - * normal interactive use. This may be due to normal shutdown, or after a timeout or other - * unrecoverable state change such as activity lifecycle, window visibility or focus. + * Signals that the capture session has completed and the target window is ready for normal use. */ - oneway void onConnectionClosed(); + oneway void onCaptureEnded(); } diff --git a/core/java/android/view/IScrollCaptureConnection.aidl b/core/java/android/view/IScrollCaptureConnection.aidl index 63a4f48aeb20..c55e88800393 100644 --- a/core/java/android/view/IScrollCaptureConnection.aidl +++ b/core/java/android/view/IScrollCaptureConnection.aidl @@ -17,33 +17,45 @@ package android.view; import android.graphics.Rect; +import android.os.ICancellationSignal; import android.view.Surface; /** - * Interface implemented by a client of the Scroll Capture framework to receive requests - * to start, capture images and end the session. + * A remote connection to a scroll capture target. * * {@hide} */ interface IScrollCaptureConnection { /** - * Informs the client that it has been selected for scroll capture and should prepare to - * to begin handling capture requests. + * Informs the target that it has been selected for scroll capture. + * + * @param surface a return channel for image buffers + * + * @return a cancallation signal which is used cancel the request + */ + ICancellationSignal startCapture(in Surface surface); + + /** + * Request the target capture an image within the provided rectangle. + * + * @param surface a return channel for image buffers + * @param signal a cancallation signal which can interrupt the request + * + * @return a cancallation signal which is used cancel the request */ - oneway void startCapture(in Surface surface); + ICancellationSignal requestImage(in Rect captureArea); /** - * Request the client capture an image within the provided rectangle. + * Inform the target that capture has ended. * - * @see android.view.ScrollCaptureCallback#onScrollCaptureRequest + * @return a cancallation signal which is used cancel the request */ - oneway void requestImage(in Rect captureArea); + ICancellationSignal endCapture(); /** - * Inform the client that capture has ended. The client should shut down and release all - * local resources in use and prepare for return to normal interactive usage. + * Closes the connection. */ - oneway void endCapture(); + oneway void close(); } diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 4e4ba3fad246..afdf798d03ce 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -35,13 +35,12 @@ import android.os.ParcelFileDescriptor; import android.view.DisplayCutout; 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; import android.view.IOnKeyguardExitResult; -import android.view.IPinnedStackListener; +import android.view.IPinnedTaskListener; import android.view.IScrollCaptureCallbacks; import android.view.RemoteAnimationAdapter; import android.view.IRotationWatcher; @@ -446,12 +445,12 @@ interface IWindowManager * Sets the region the user can touch the divider. This region will be excluded from the region * which is used to cause a focus switch when dispatching touch. */ - void setDockedStackDividerTouchRegion(in Rect touchableRegion); + void setDockedTaskDividerTouchRegion(in Rect touchableRegion); /** - * Registers a listener that will be called when the pinned stack state changes. + * Registers a listener that will be called when the pinned task state changes. */ - void registerPinnedStackListener(int displayId, IPinnedStackListener listener); + void registerPinnedTaskListener(int displayId, IPinnedTaskListener listener); /** * Requests Keyboard Shortcuts from the displayed window. diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java index 5f2bccc8b857..e6cf68367ae6 100644 --- a/core/java/android/view/InsetsSource.java +++ b/core/java/android/view/InsetsSource.java @@ -261,11 +261,7 @@ public class InsetsSource implements Parcelable { public InsetsSource(Parcel in) { mType = in.readInt(); - if (in.readInt() != 0) { - mFrame = Rect.CREATOR.createFromParcel(in); - } else { - mFrame = null; - } + mFrame = Rect.CREATOR.createFromParcel(in); if (in.readInt() != 0) { mVisibleFrame = Rect.CREATOR.createFromParcel(in); } else { @@ -282,12 +278,7 @@ public class InsetsSource implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mType); - if (mFrame != null) { - dest.writeInt(1); - mFrame.writeToParcel(dest, 0); - } else { - dest.writeInt(0); - } + mFrame.writeToParcel(dest, 0); if (mVisibleFrame != null) { dest.writeInt(1); mVisibleFrame.writeToParcel(dest, 0); diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java index c717c2adc32d..1d4b4111f884 100644 --- a/core/java/android/view/InsetsSourceControl.java +++ b/core/java/android/view/InsetsSourceControl.java @@ -77,8 +77,8 @@ public class InsetsSourceControl implements Parcelable { public InsetsSourceControl(Parcel in) { mType = in.readInt(); - mLeash = in.readParcelable(null /* loader */); - mSurfacePosition = in.readParcelable(null /* loader */); + mLeash = in.readTypedObject(SurfaceControl.CREATOR); + mSurfacePosition = in.readTypedObject(Point.CREATOR); mSkipAnimationOnce = in.readBoolean(); } @@ -119,8 +119,8 @@ public class InsetsSourceControl implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mType); - dest.writeParcelable(mLeash, 0 /* flags*/); - dest.writeParcelable(mSurfacePosition, 0 /* flags*/); + dest.writeTypedObject(mLeash, 0 /* parcelableFlags */); + dest.writeTypedObject(mSurfacePosition, 0 /* parcelableFlags */); dest.writeBoolean(mSkipAnimationOnce); } diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index 21dd1fb05615..fce1952de44b 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -156,7 +156,7 @@ public class InsetsState implements Parcelable { static final int ISIDE_FLOATING = 4; static final int ISIDE_UNKNOWN = 5; - private InsetsSource[] mSources = new InsetsSource[SIZE]; + private final InsetsSource[] mSources = new InsetsSource[SIZE]; /** * The frame of the display these sources are relative to. @@ -804,7 +804,7 @@ public class InsetsState implements Parcelable { public void writeToParcel(Parcel dest, int flags) { mDisplayFrame.writeToParcel(dest, flags); mDisplayCutout.writeToParcel(dest, flags); - dest.writeParcelableArray(mSources, 0); + dest.writeTypedArray(mSources, 0 /* parcelableFlags */); dest.writeTypedObject(mRoundedCorners, flags); } @@ -820,9 +820,9 @@ public class InsetsState implements Parcelable { }; public void readFromParcel(Parcel in) { - mDisplayFrame.set(Rect.CREATOR.createFromParcel(in)); - mDisplayCutout.set(DisplayCutout.ParcelableWrapper.CREATOR.createFromParcel(in)); - mSources = in.readParcelableArray(null, InsetsSource.class); + mDisplayFrame.readFromParcel(in); + mDisplayCutout.readFromParcel(in); + in.readTypedArray(mSources, InsetsSource.CREATOR); mRoundedCorners = in.readTypedObject(RoundedCorners.CREATOR); } diff --git a/core/java/android/view/OWNERS b/core/java/android/view/OWNERS index e66b17aa4426..c43c410ab995 100644 --- a/core/java/android/view/OWNERS +++ b/core/java/android/view/OWNERS @@ -77,3 +77,7 @@ per-file SyncRtSurfaceTransactionApplier.java = file:/services/core/java/com/and per-file ViewRootInsetsControllerHost.java = file:/services/core/java/com/android/server/wm/OWNERS per-file Window*.java = file:/services/core/java/com/android/server/wm/OWNERS per-file Window*.aidl = file:/services/core/java/com/android/server/wm/OWNERS + +# Scroll Capture +per-file *ScrollCapture*.java = file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS +per-file *CaptureHelper*.java = file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS diff --git a/core/java/android/view/ScrollCaptureCallback.java b/core/java/android/view/ScrollCaptureCallback.java index d3aad2c72d27..16886160baca 100644 --- a/core/java/android/view/ScrollCaptureCallback.java +++ b/core/java/android/view/ScrollCaptureCallback.java @@ -19,6 +19,7 @@ package android.view; import android.annotation.NonNull; import android.annotation.UiThread; import android.graphics.Rect; +import android.os.CancellationSignal; import java.util.function.Consumer; @@ -58,8 +59,6 @@ import java.util.function.Consumer; * @see View#setScrollCaptureHint(int) * @see View#setScrollCaptureCallback(ScrollCaptureCallback) * @see Window#registerScrollCaptureCallback(ScrollCaptureCallback) - * - * @hide */ @UiThread public interface ScrollCaptureCallback { @@ -68,80 +67,84 @@ public interface ScrollCaptureCallback { * The system is searching for the appropriate scrolling container to capture and would like to * know the size and position of scrolling content handled by this callback. * <p> - * Implementations should inset {@code containingViewBounds} to cover only the area within the - * containing view where scrolling content may be positioned. This should cover only the content - * which tracks with scrolling movement. - * <p> - * Return the updated rectangle to {@code resultConsumer}. If for any reason the scrolling - * content is not available to capture, a {@code null} rectangle may be returned, and this view - * will be excluded as the target for this request. + * To determine scroll bounds, an implementation should inset the visible bounds of the + * containing view to cover only the area where scrolling content may be positioned. This + * should cover only the content which tracks with scrolling movement. * <p> - * Responses received after XXXms will be discarded. + * Return the updated rectangle to {@link Consumer#accept onReady.accept}. If for any reason the + * scrolling content is not available to capture, a empty rectangle may be returned which will + * exclude this view from consideration. * <p> - * TODO: finalize timeout + * This request may be cancelled via the provided {@link CancellationSignal}. When this happens, + * any future call to {@link Consumer#accept onReady.accept} will have no effect and this + * content will be omitted from the search results. * - * @param onReady consumer for the updated rectangle + * @param signal signal to cancel the operation in progress + * @param onReady consumer for the updated rectangle */ - void onScrollCaptureSearch(@NonNull Consumer<Rect> onReady); + void onScrollCaptureSearch(@NonNull CancellationSignal signal, @NonNull Consumer<Rect> onReady); /** * Scroll Capture has selected this callback to provide the scrolling image content. * <p> - * The onReady signal should be called when ready to begin handling image requests. + * {@link Runnable#run onReady.run} should be called when ready to begin handling image + * requests. + * <p> + * This request may be cancelled via the provided {@link CancellationSignal}. When this happens, + * any future call to {@link Runnable#run onReady.run} will have no effect and provided session + * will not be activated. + * + * @param session the current session, resources provided by it are valid for use until the + * {@link #onScrollCaptureEnd(Runnable) session ends} + * @param signal signal to cancel the operation in progress + * @param onReady signal used to report completion of the request */ - void onScrollCaptureStart(@NonNull ScrollCaptureSession session, @NonNull Runnable onReady); + void onScrollCaptureStart(@NonNull ScrollCaptureSession session, + @NonNull CancellationSignal signal, @NonNull Runnable onReady); /** * An image capture has been requested from the scrolling content. * <p> - * <code>captureArea</code> contains the bounds of the image requested, relative to the - * rectangle provided by {@link ScrollCaptureCallback#onScrollCaptureSearch}, referred to as - * {@code scrollBounds}. - * here. - * <p> - * A series of requests will step by a constant vertical amount relative to {@code - * scrollBounds}, moving through the scrolling range of content, above and below the current - * visible area. The rectangle's vertical position will not account for any scrolling movement - * since capture started. Implementations therefore must track any scroll position changes and - * subtract this distance from requests. + * The requested rectangle describes an area inside the target view, relative to + * <code>scrollBounds</code>. The content may be offscreen, above or below the current visible + * portion of the target view. To handle the request, render the available portion of this + * rectangle to a buffer and return it via the Surface available from {@link + * ScrollCaptureSession#getSurface()}. * <p> - * To handle a request, the content should be scrolled to maximize the visible area of the - * requested rectangle. Offset {@code captureArea} again to account for any further scrolling. + * Note: Implementations are only required to render the requested content, and may do so into + * off-screen buffers without scrolling if they are able. * <p> - * Finally, clip this rectangle against scrollBounds to determine what portion, if any is - * visible content to capture. If the rectangle is completely clipped, set it to {@link - * Rect#setEmpty() empty} and skip the next step. - * <p> - * Make a copy of {@code captureArea}, transform to window coordinates and draw the window, - * clipped to this rectangle, into the {@link ScrollCaptureSession#getSurface() surface} at - * offset (0,0). - * <p> - * Finally, return the resulting {@code captureArea} using - * {@link ScrollCaptureSession#notifyBufferSent}. - * <p> - * If the response is not supplied within XXXms, the session will end with a call to {@link - * #onScrollCaptureEnd}, after which {@code session} is invalid and should be discarded. - * <p> - * TODO: finalize timeout + * The resulting available portion of the request must be computed as a portion of {@code + * captureArea}, and sent to signal the operation is complete, using {@link Consumer#accept + * onComplete.accept}. If the requested rectangle is partially or fully out of bounds the + * resulting portion should be returned. If no portion is available (outside of available + * content), then skip sending any buffer and report an empty Rect as result. * <p> + * This request may be cancelled via the provided {@link CancellationSignal}. When this happens, + * any future call to {@link Consumer#accept onComplete.accept} will be ignored until the next + * request. * + * @param session the current session, resources provided by it are valid for use until the + * {@link #onScrollCaptureEnd(Runnable) session ends} + * @param signal signal to cancel the operation in progress * @param captureArea the area to capture, a rectangle within {@code scrollBounds} + * @param onComplete a consumer for the captured area */ - void onScrollCaptureImageRequest( - @NonNull ScrollCaptureSession session, @NonNull Rect captureArea); + void onScrollCaptureImageRequest(@NonNull ScrollCaptureSession session, + @NonNull CancellationSignal signal, @NonNull Rect captureArea, + @NonNull Consumer<Rect> onComplete); /** * Signals that capture has ended. Implementations should release any temporary resources or * references to objects in use during the capture. Any resources obtained from the session are * now invalid and attempts to use them after this point may throw an exception. * <p> - * The window should be returned as much as possible to its original state when capture started. - * At a minimum, the content should be scrolled to its original position. - * <p> - * <code>onReady</code> should be called when the window should be made visible and - * interactive. The system will wait up to XXXms for this call before proceeding. + * The window should be returned to its original state when capture started. At a minimum, the + * content should be scrolled to its original position. * <p> - * TODO: finalize timeout + * {@link Runnable#run onReady.run} should be called as soon as possible after the window is + * ready for normal interactive use. After the callback (or after a timeout, if not called) the + * screenshot tool will be dismissed and the window may become visible to the user at any time. * * @param onReady a callback to inform the system that the application has completed any * cleanup and is ready to become visible diff --git a/core/java/android/view/ScrollCaptureConnection.java b/core/java/android/view/ScrollCaptureConnection.java index 0e6cdd1dbec5..3456e016c42c 100644 --- a/core/java/android/view/ScrollCaptureConnection.java +++ b/core/java/android/view/ScrollCaptureConnection.java @@ -18,18 +18,23 @@ package android.view; import static java.util.Objects.requireNonNull; +import android.annotation.BinderThread; import android.annotation.NonNull; import android.annotation.UiThread; -import android.annotation.WorkerThread; import android.graphics.Point; import android.graphics.Rect; -import android.os.Handler; +import android.os.CancellationSignal; +import android.os.ICancellationSignal; import android.os.RemoteException; +import android.os.Trace; import android.util.CloseGuard; +import android.util.Log; import com.android.internal.annotations.VisibleForTesting; -import java.util.concurrent.atomic.AtomicBoolean; +import java.lang.ref.WeakReference; +import java.util.concurrent.Executor; +import java.util.function.Consumer; /** * Mediator between a selected scroll capture target view and a remote process. @@ -41,270 +46,276 @@ import java.util.concurrent.atomic.AtomicBoolean; public class ScrollCaptureConnection extends IScrollCaptureConnection.Stub { private static final String TAG = "ScrollCaptureConnection"; - private static final int DEFAULT_TIMEOUT = 1000; - - private final Handler mHandler; - private ScrollCaptureTarget mSelectedTarget; - private int mTimeoutMillis = DEFAULT_TIMEOUT; - - protected Surface mSurface; - private IScrollCaptureCallbacks mCallbacks; + private final Object mLock = new Object(); private final Rect mScrollBounds; private final Point mPositionInWindow; private final CloseGuard mCloseGuard; + private final Executor mUiThread; + + private ScrollCaptureCallback mLocal; + private IScrollCaptureCallbacks mRemote; - // The current session instance in use by the callback. private ScrollCaptureSession mSession; - // Helps manage timeout callbacks registered to handler and aids testing. - private DelayedAction mTimeoutAction; + private CancellationSignal mCancellation; + + private volatile boolean mStarted; + private volatile boolean mConnected; /** * Constructs a ScrollCaptureConnection. * * @param selectedTarget the target the client is controlling - * @param callbacks the callbacks to reply to system requests + * @param remote the callbacks to reply to system requests * * @hide */ public ScrollCaptureConnection( + @NonNull Executor uiThread, @NonNull ScrollCaptureTarget selectedTarget, - @NonNull IScrollCaptureCallbacks callbacks) { + @NonNull IScrollCaptureCallbacks remote) { + mUiThread = requireNonNull(uiThread, "<uiThread> must non-null"); requireNonNull(selectedTarget, "<selectedTarget> must non-null"); - requireNonNull(callbacks, "<callbacks> must non-null"); - final Rect scrollBounds = requireNonNull(selectedTarget.getScrollBounds(), + mRemote = requireNonNull(remote, "<callbacks> must non-null"); + mScrollBounds = requireNonNull(Rect.copyOrNull(selectedTarget.getScrollBounds()), "target.getScrollBounds() must be non-null to construct a client"); - mSelectedTarget = selectedTarget; - mHandler = selectedTarget.getContainingView().getHandler(); - mScrollBounds = new Rect(scrollBounds); + mLocal = selectedTarget.getCallback(); mPositionInWindow = new Point(selectedTarget.getPositionInWindow()); - mCallbacks = callbacks; mCloseGuard = new CloseGuard(); mCloseGuard.open("close"); - - selectedTarget.getContainingView().addOnAttachStateChangeListener( - new View.OnAttachStateChangeListener() { - @Override - public void onViewAttachedToWindow(View v) { - - } - - @Override - public void onViewDetachedFromWindow(View v) { - selectedTarget.getContainingView().removeOnAttachStateChangeListener(this); - endCapture(); - } - }); - } - - @VisibleForTesting - public void setTimeoutMillis(int timeoutMillis) { - mTimeoutMillis = timeoutMillis; + mConnected = true; } - @VisibleForTesting - public DelayedAction getTimeoutAction() { - return mTimeoutAction; - } - - private void checkConnected() { - if (mSelectedTarget == null || mCallbacks == null) { - throw new IllegalStateException("This client has been disconnected."); + @BinderThread + @Override + public ICancellationSignal startCapture(Surface surface) throws RemoteException { + checkConnected(); + if (!surface.isValid()) { + throw new RemoteException(new IllegalArgumentException("surface must be valid")); } - } - private void checkStarted() { - if (mSession == null) { - throw new IllegalStateException("Capture session has not been started!"); - } - } + ICancellationSignal cancellation = CancellationSignal.createTransport(); + mCancellation = CancellationSignal.fromTransport(cancellation); + mSession = new ScrollCaptureSession(surface, mScrollBounds, mPositionInWindow); - @WorkerThread // IScrollCaptureConnection - @Override - public void startCapture(Surface surface) throws RemoteException { - checkConnected(); - mSurface = surface; - scheduleTimeout(mTimeoutMillis, this::onStartCaptureTimeout); - mSession = new ScrollCaptureSession(mSurface, mScrollBounds, mPositionInWindow, this); - mHandler.post(() -> mSelectedTarget.getCallback().onScrollCaptureStart(mSession, - this::onStartCaptureCompleted)); + Runnable listener = + SafeCallback.create(mCancellation, mUiThread, this::onStartCaptureCompleted); + // -> UiThread + mUiThread.execute(() -> mLocal.onScrollCaptureStart(mSession, mCancellation, listener)); + return cancellation; } @UiThread private void onStartCaptureCompleted() { - if (cancelTimeout()) { - mHandler.post(() -> { - try { - mCallbacks.onCaptureStarted(); - } catch (RemoteException e) { - doShutdown(); - } - }); + mStarted = true; + try { + mRemote.onCaptureStarted(); + } catch (RemoteException e) { + Log.w(TAG, "Shutting down due to error: ", e); + close(); } } - @UiThread - private void onStartCaptureTimeout() { - endCapture(); - } - @WorkerThread // IScrollCaptureConnection + @BinderThread @Override - public void requestImage(Rect requestRect) { + public ICancellationSignal requestImage(Rect requestRect) throws RemoteException { + Trace.beginSection("requestImage"); checkConnected(); checkStarted(); - scheduleTimeout(mTimeoutMillis, this::onRequestImageTimeout); - // Response is dispatched via ScrollCaptureSession, to onRequestImageCompleted - mHandler.post(() -> mSelectedTarget.getCallback().onScrollCaptureImageRequest( - mSession, new Rect(requestRect))); - } - @UiThread - void onRequestImageCompleted(long frameNumber, Rect capturedArea) { - final Rect finalCapturedArea = new Rect(capturedArea); - if (cancelTimeout()) { - mHandler.post(() -> { - try { - mCallbacks.onCaptureBufferSent(frameNumber, finalCapturedArea); - } catch (RemoteException e) { - doShutdown(); - } - }); - } + ICancellationSignal cancellation = CancellationSignal.createTransport(); + mCancellation = CancellationSignal.fromTransport(cancellation); + + Consumer<Rect> listener = + SafeCallback.create(mCancellation, mUiThread, this::onImageRequestCompleted); + // -> UiThread + mUiThread.execute(() -> mLocal.onScrollCaptureImageRequest( + mSession, mCancellation, new Rect(requestRect), listener)); + Trace.endSection(); + return cancellation; } @UiThread - private void onRequestImageTimeout() { - endCapture(); + void onImageRequestCompleted(Rect capturedArea) { + try { + mRemote.onImageRequestCompleted(0, capturedArea); + } catch (RemoteException e) { + Log.w(TAG, "Shutting down due to error: ", e); + close(); + } } - @WorkerThread // IScrollCaptureConnection + @BinderThread @Override - public void endCapture() { - if (isStarted()) { - scheduleTimeout(mTimeoutMillis, this::onEndCaptureTimeout); - mHandler.post(() -> - mSelectedTarget.getCallback().onScrollCaptureEnd(this::onEndCaptureCompleted)); - } else { - disconnect(); - } - } + public ICancellationSignal endCapture() throws RemoteException { + checkConnected(); + checkStarted(); - private boolean isStarted() { - return mCallbacks != null && mSelectedTarget != null; - } + ICancellationSignal cancellation = CancellationSignal.createTransport(); + mCancellation = CancellationSignal.fromTransport(cancellation); - @UiThread - private void onEndCaptureCompleted() { // onEndCaptureCompleted - if (cancelTimeout()) { - doShutdown(); - } + Runnable listener = + SafeCallback.create(mCancellation, mUiThread, this::onEndCaptureCompleted); + // -> UiThread + mUiThread.execute(() -> mLocal.onScrollCaptureEnd(listener)); + return cancellation; } @UiThread - private void onEndCaptureTimeout() { - doShutdown(); + private void onEndCaptureCompleted() { + synchronized (mLock) { + mStarted = false; + try { + mRemote.onCaptureEnded(); + } catch (RemoteException e) { + Log.w(TAG, "Shutting down due to error: ", e); + close(); + } + } } + @BinderThread + @Override + public void close() { + if (mStarted) { + Log.w(TAG, "close(): capture is still started?! Ending now."); - private void doShutdown() { - try { - if (mCallbacks != null) { - mCallbacks.onConnectionClosed(); - } - } catch (RemoteException e) { - // Ignore - } finally { - disconnect(); + // -> UiThread + mUiThread.execute(() -> mLocal.onScrollCaptureEnd(() -> { /* ignore */ })); + mStarted = false; } + disconnect(); } /** * Shuts down this client and releases references to dependent objects. No attempt is made * to notify the controller, use with caution! */ - public void disconnect() { - if (mSession != null) { - mSession.disconnect(); + private void disconnect() { + synchronized (mLock) { mSession = null; + mConnected = false; + mStarted = false; + mRemote = null; + mLocal = null; + mCloseGuard.close(); } + } + + public boolean isConnected() { + return mConnected; + } - mSelectedTarget = null; - mCallbacks = null; + public boolean isStarted() { + return mStarted; + } + + private synchronized void checkConnected() throws RemoteException { + synchronized (mLock) { + if (!mConnected) { + throw new RemoteException(new IllegalStateException("Not connected")); + } + } + } + + private void checkStarted() throws RemoteException { + synchronized (mLock) { + if (!mStarted) { + throw new RemoteException(new IllegalStateException("Not started!")); + } + } } /** @return a string representation of the state of this client */ public String toString() { return "ScrollCaptureConnection{" + + "connected=" + mConnected + + ", started=" + mStarted + ", session=" + mSession - + ", selectedTarget=" + mSelectedTarget - + ", clientCallbacks=" + mCallbacks + + ", remote=" + mRemote + + ", local=" + mLocal + "}"; } - private boolean cancelTimeout() { - if (mTimeoutAction != null) { - return mTimeoutAction.cancel(); - } - return false; + @VisibleForTesting + public CancellationSignal getCancellation() { + return mCancellation; } - private void scheduleTimeout(long timeoutMillis, Runnable action) { - if (mTimeoutAction != null) { - mTimeoutAction.cancel(); + protected void finalize() throws Throwable { + try { + if (mCloseGuard != null) { + mCloseGuard.warnIfOpen(); + } + close(); + } finally { + super.finalize(); } - mTimeoutAction = new DelayedAction(mHandler, timeoutMillis, action); } - /** @hide */ - @VisibleForTesting - public static class DelayedAction { - private final AtomicBoolean mCompleted = new AtomicBoolean(); - private final Object mToken = new Object(); - private final Handler mHandler; - private final Runnable mAction; - - @VisibleForTesting - public DelayedAction(Handler handler, long timeoutMillis, Runnable action) { - mHandler = handler; - mAction = action; - mHandler.postDelayed(this::onTimeout, mToken, timeoutMillis); + private static class SafeCallback<T> { + private final CancellationSignal mSignal; + private final WeakReference<T> mTargetRef; + private final Executor mExecutor; + private boolean mExecuted; + + protected SafeCallback(CancellationSignal signal, Executor executor, T target) { + mSignal = signal; + mTargetRef = new WeakReference<>(target); + mExecutor = executor; } - private boolean onTimeout() { - if (mCompleted.compareAndSet(false, true)) { - mAction.run(); - return true; + // Provide the target to the consumer to invoke, forward on handler thread ONCE, + // and only if noy cancelled, and the target is still available (not collected) + protected final void maybeAccept(Consumer<T> targetConsumer) { + if (mExecuted) { + return; + } + mExecuted = true; + if (mSignal.isCanceled()) { + return; + } + T target = mTargetRef.get(); + if (target == null) { + return; } - return false; + mExecutor.execute(() -> targetConsumer.accept(target)); } - /** - * Cause the timeout action to run immediately and mark as timed out. - * - * @return true if the timeout was run, false if the timeout had already been canceled - */ - @VisibleForTesting - public boolean timeoutNow() { - return onTimeout(); + static Runnable create(CancellationSignal signal, Executor executor, Runnable target) { + return new RunnableCallback(signal, executor, target); } - /** - * Attempt to cancel the timeout action (such as after a callback is made) - * - * @return true if the timeout was canceled and will not run, false if time has expired and - * the timeout action has or will run momentarily - */ - public boolean cancel() { - if (!mCompleted.compareAndSet(false, true)) { - // Whoops, too late! - return false; - } - mHandler.removeCallbacksAndMessages(mToken); - return true; + static <T> Consumer<T> create(CancellationSignal signal, Executor executor, + Consumer<T> target) { + return new ConsumerCallback<T>(signal, executor, target); + } + } + + private static final class RunnableCallback extends SafeCallback<Runnable> implements Runnable { + RunnableCallback(CancellationSignal signal, Executor executor, Runnable target) { + super(signal, executor, target); + } + + @Override + public void run() { + maybeAccept(Runnable::run); + } + } + + private static final class ConsumerCallback<T> extends SafeCallback<Consumer<T>> + implements Consumer<T> { + ConsumerCallback(CancellationSignal signal, Executor executor, Consumer<T> target) { + super(signal, executor, target); + } + + @Override + public void accept(T value) { + maybeAccept((target) -> target.accept(value)); } } } diff --git a/core/java/android/view/ScrollCaptureResponse.aidl b/core/java/android/view/ScrollCaptureResponse.aidl new file mode 100644 index 000000000000..3de2b80ef16e --- /dev/null +++ b/core/java/android/view/ScrollCaptureResponse.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +parcelable ScrollCaptureResponse; diff --git a/core/java/android/view/ScrollCaptureResponse.java b/core/java/android/view/ScrollCaptureResponse.java new file mode 100644 index 000000000000..564113edb3c7 --- /dev/null +++ b/core/java/android/view/ScrollCaptureResponse.java @@ -0,0 +1,381 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.Rect; +import android.os.Parcelable; + +import com.android.internal.util.DataClass; + +import java.util.ArrayList; + +/** @hide */ +@DataClass(genToString = true, genGetters = true) +public class ScrollCaptureResponse implements Parcelable { + + /** Developer-facing human readable description of the result. */ + @NonNull + private String mDescription = ""; + + // Remaining fields are non-null when isConnected() == true + + /** The active connection for a successful result. */ + @Nullable + @DataClass.MaySetToNull + private IScrollCaptureConnection mConnection = null; + + /** The bounds of the window within the display */ + @Nullable + private Rect mWindowBounds = null; + + /** The bounds of the scrolling content, in window space. */ + @Nullable + private Rect mBoundsInWindow = null; + + /** The current window title. */ + @Nullable + private String mWindowTitle = null; + + /** Carries additional logging and debugging information when enabled. */ + @NonNull + @DataClass.PluralOf("message") + private ArrayList<String> mMessages = new ArrayList<>(); + + /** Whether a connection has been returned. */ + public boolean isConnected() { + return mConnection != null; + } + + + + + // Code below generated by codegen v1.0.22. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/ScrollCaptureResponse.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + @DataClass.Generated.Member + /* package-private */ ScrollCaptureResponse( + @NonNull String description, + @Nullable IScrollCaptureConnection connection, + @Nullable Rect windowBounds, + @Nullable Rect boundsInWindow, + @Nullable String windowTitle, + @NonNull ArrayList<String> messages) { + this.mDescription = description; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mDescription); + this.mConnection = connection; + this.mWindowBounds = windowBounds; + this.mBoundsInWindow = boundsInWindow; + this.mWindowTitle = windowTitle; + this.mMessages = messages; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mMessages); + + // onConstructed(); // You can define this method to get a callback + } + + /** + * Developer-facing human readable description of the result. + */ + @DataClass.Generated.Member + public @NonNull String getDescription() { + return mDescription; + } + + /** + * The active connection for a successful result. + */ + @DataClass.Generated.Member + public @Nullable IScrollCaptureConnection getConnection() { + return mConnection; + } + + /** + * The bounds of the window within the display + */ + @DataClass.Generated.Member + public @Nullable Rect getWindowBounds() { + return mWindowBounds; + } + + /** + * The bounds of the scrolling content, in window space. + */ + @DataClass.Generated.Member + public @Nullable Rect getBoundsInWindow() { + return mBoundsInWindow; + } + + /** + * The current window title. + */ + @DataClass.Generated.Member + public @Nullable String getWindowTitle() { + return mWindowTitle; + } + + /** + * Carries additional logging and debugging information when enabled. + */ + @DataClass.Generated.Member + public @NonNull ArrayList<String> getMessages() { + return mMessages; + } + + @Override + @DataClass.Generated.Member + public String toString() { + // You can override field toString logic by defining methods like: + // String fieldNameToString() { ... } + + return "ScrollCaptureResponse { " + + "description = " + mDescription + ", " + + "connection = " + mConnection + ", " + + "windowBounds = " + mWindowBounds + ", " + + "boundsInWindow = " + mBoundsInWindow + ", " + + "windowTitle = " + mWindowTitle + ", " + + "messages = " + mMessages + + " }"; + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + byte flg = 0; + if (mConnection != null) flg |= 0x2; + if (mWindowBounds != null) flg |= 0x4; + if (mBoundsInWindow != null) flg |= 0x8; + if (mWindowTitle != null) flg |= 0x10; + dest.writeByte(flg); + dest.writeString(mDescription); + if (mConnection != null) dest.writeStrongInterface(mConnection); + if (mWindowBounds != null) dest.writeTypedObject(mWindowBounds, flags); + if (mBoundsInWindow != null) dest.writeTypedObject(mBoundsInWindow, flags); + if (mWindowTitle != null) dest.writeString(mWindowTitle); + dest.writeStringList(mMessages); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + protected ScrollCaptureResponse(@NonNull android.os.Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + byte flg = in.readByte(); + String description = in.readString(); + IScrollCaptureConnection connection = (flg & 0x2) == 0 ? null : IScrollCaptureConnection.Stub.asInterface(in.readStrongBinder()); + Rect windowBounds = (flg & 0x4) == 0 ? null : (Rect) in.readTypedObject(Rect.CREATOR); + Rect boundsInWindow = (flg & 0x8) == 0 ? null : (Rect) in.readTypedObject(Rect.CREATOR); + String windowTitle = (flg & 0x10) == 0 ? null : in.readString(); + ArrayList<String> messages = new ArrayList<>(); + in.readStringList(messages); + + this.mDescription = description; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mDescription); + this.mConnection = connection; + this.mWindowBounds = windowBounds; + this.mBoundsInWindow = boundsInWindow; + this.mWindowTitle = windowTitle; + this.mMessages = messages; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mMessages); + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator<ScrollCaptureResponse> CREATOR + = new Parcelable.Creator<ScrollCaptureResponse>() { + @Override + public ScrollCaptureResponse[] newArray(int size) { + return new ScrollCaptureResponse[size]; + } + + @Override + public ScrollCaptureResponse createFromParcel(@NonNull android.os.Parcel in) { + return new ScrollCaptureResponse(in); + } + }; + + /** + * A builder for {@link ScrollCaptureResponse} + */ + @SuppressWarnings("WeakerAccess") + @DataClass.Generated.Member + public static class Builder { + + private @NonNull String mDescription; + private @Nullable IScrollCaptureConnection mConnection; + private @Nullable Rect mWindowBounds; + private @Nullable Rect mBoundsInWindow; + private @Nullable String mWindowTitle; + private @NonNull ArrayList<String> mMessages; + + private long mBuilderFieldsSet = 0L; + + public Builder() { + } + + /** + * Developer-facing human readable description of the result. + */ + @DataClass.Generated.Member + public @NonNull Builder setDescription(@NonNull String value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x1; + mDescription = value; + return this; + } + + /** + * The active connection for a successful result. + */ + @DataClass.Generated.Member + public @NonNull Builder setConnection(@Nullable IScrollCaptureConnection value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x2; + mConnection = value; + return this; + } + + /** + * The bounds of the window within the display + */ + @DataClass.Generated.Member + public @NonNull Builder setWindowBounds(@NonNull Rect value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x4; + mWindowBounds = value; + return this; + } + + /** + * The bounds of the scrolling content, in window space. + */ + @DataClass.Generated.Member + public @NonNull Builder setBoundsInWindow(@NonNull Rect value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x8; + mBoundsInWindow = value; + return this; + } + + /** + * The current window title. + */ + @DataClass.Generated.Member + public @NonNull Builder setWindowTitle(@NonNull String value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x10; + mWindowTitle = value; + return this; + } + + /** + * Carries additional logging and debugging information when enabled. + */ + @DataClass.Generated.Member + public @NonNull Builder setMessages(@NonNull ArrayList<String> value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x20; + mMessages = value; + return this; + } + + /** @see #setMessages */ + @DataClass.Generated.Member + public @NonNull Builder addMessage(@NonNull String value) { + if (mMessages == null) setMessages(new ArrayList<>()); + mMessages.add(value); + return this; + } + + /** Builds the instance. This builder should not be touched after calling this! */ + public @NonNull ScrollCaptureResponse build() { + checkNotUsed(); + mBuilderFieldsSet |= 0x40; // Mark builder used + + if ((mBuilderFieldsSet & 0x1) == 0) { + mDescription = ""; + } + if ((mBuilderFieldsSet & 0x2) == 0) { + mConnection = null; + } + if ((mBuilderFieldsSet & 0x4) == 0) { + mWindowBounds = null; + } + if ((mBuilderFieldsSet & 0x8) == 0) { + mBoundsInWindow = null; + } + if ((mBuilderFieldsSet & 0x10) == 0) { + mWindowTitle = null; + } + if ((mBuilderFieldsSet & 0x20) == 0) { + mMessages = new ArrayList<>(); + } + ScrollCaptureResponse o = new ScrollCaptureResponse( + mDescription, + mConnection, + mWindowBounds, + mBoundsInWindow, + mWindowTitle, + mMessages); + return o; + } + + private void checkNotUsed() { + if ((mBuilderFieldsSet & 0x40) != 0) { + throw new IllegalStateException( + "This Builder should not be reused. Use a new Builder instance instead"); + } + } + } + + @DataClass.Generated( + time = 1612282689462L, + codegenVersion = "1.0.22", + sourceFile = "frameworks/base/core/java/android/view/ScrollCaptureResponse.java", + inputSignatures = "private @android.annotation.NonNull java.lang.String mDescription\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.MaySetToNull android.view.IScrollCaptureConnection mConnection\nprivate @android.annotation.Nullable android.graphics.Rect mWindowBounds\nprivate @android.annotation.Nullable android.graphics.Rect mBoundsInWindow\nprivate @android.annotation.Nullable java.lang.String mWindowTitle\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"message\") java.util.ArrayList<java.lang.String> mMessages\npublic boolean isConnected()\nclass ScrollCaptureResponse extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genGetters=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/core/java/android/view/ScrollCaptureSearchResults.java b/core/java/android/view/ScrollCaptureSearchResults.java new file mode 100644 index 000000000000..3469b9dc7103 --- /dev/null +++ b/core/java/android/view/ScrollCaptureSearchResults.java @@ -0,0 +1,271 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import static java.util.Objects.requireNonNull; + +import android.annotation.NonNull; +import android.annotation.UiThread; +import android.graphics.Rect; +import android.os.CancellationSignal; +import android.util.IndentingPrintWriter; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.function.Consumer; + +/** + * Collects nodes in the view hierarchy which have been identified as scrollable content. + * + * @hide + */ +@UiThread +public final class ScrollCaptureSearchResults { + private final Executor mExecutor; + private final List<ScrollCaptureTarget> mTargets; + private final CancellationSignal mCancel; + + private Runnable mOnCompleteListener; + private int mCompleted; + private boolean mComplete = true; + + public ScrollCaptureSearchResults(Executor executor) { + mExecutor = executor; + mTargets = new ArrayList<>(); + mCancel = new CancellationSignal(); + } + + // Public + + /** + * Add the given target to the results. + * + * @param target the target to consider + */ + public void addTarget(@NonNull ScrollCaptureTarget target) { + requireNonNull(target); + + mTargets.add(target); + mComplete = false; + final ScrollCaptureCallback callback = target.getCallback(); + final Consumer<Rect> consumer = new SearchRequest(target); + + // Defer so the view hierarchy scan completes first + mExecutor.execute( + () -> callback.onScrollCaptureSearch(mCancel, consumer)); + } + + public boolean isComplete() { + return mComplete; + } + + /** + * Provides a callback to be invoked as soon as all responses have been received from all + * targets to this point. + * + * @param onComplete listener to add + */ + public void setOnCompleteListener(Runnable onComplete) { + if (mComplete) { + onComplete.run(); + } else { + mOnCompleteListener = onComplete; + } + } + + /** + * Indicates whether the search results are empty. + * + * @return true if no targets have been added + */ + public boolean isEmpty() { + return mTargets.isEmpty(); + } + + /** + * Force the results to complete now, cancelling any pending requests and calling a complete + * listener if provided. + */ + public void finish() { + if (!mComplete) { + mCancel.cancel(); + signalComplete(); + } + } + + private void signalComplete() { + mComplete = true; + mTargets.sort(PRIORITY_ORDER); + if (mOnCompleteListener != null) { + mOnCompleteListener.run(); + mOnCompleteListener = null; + } + } + + @VisibleForTesting + public List<ScrollCaptureTarget> getTargets() { + return new ArrayList<>(mTargets); + } + + /** + * Get the top ranked result out of all completed requests. + * + * @return the top ranked result + */ + public ScrollCaptureTarget getTopResult() { + ScrollCaptureTarget target = mTargets.isEmpty() ? null : mTargets.get(0); + return target != null && target.getScrollBounds() != null ? target : null; + } + + private class SearchRequest implements Consumer<Rect> { + private ScrollCaptureTarget mTarget; + + SearchRequest(ScrollCaptureTarget target) { + mTarget = target; + } + + @Override + public void accept(Rect scrollBounds) { + if (mTarget == null || mCancel.isCanceled()) { + return; + } + mExecutor.execute(() -> consume(scrollBounds)); + } + + private void consume(Rect scrollBounds) { + if (mTarget == null || mCancel.isCanceled()) { + return; + } + if (!nullOrEmpty(scrollBounds)) { + mTarget.setScrollBounds(scrollBounds); + mTarget.updatePositionInWindow(); + } + mCompleted++; + mTarget = null; + + // All done? + if (mCompleted == mTargets.size()) { + signalComplete(); + } + } + } + + private static final int AFTER = 1; + private static final int BEFORE = -1; + private static final int EQUAL = 0; + + static final Comparator<ScrollCaptureTarget> PRIORITY_ORDER = (a, b) -> { + if (a == null && b == null) { + return 0; + } else if (a == null || b == null) { + return (a == null) ? 1 : -1; + } + + boolean emptyScrollBoundsA = nullOrEmpty(a.getScrollBounds()); + boolean emptyScrollBoundsB = nullOrEmpty(b.getScrollBounds()); + if (emptyScrollBoundsA || emptyScrollBoundsB) { + if (emptyScrollBoundsA && emptyScrollBoundsB) { + return EQUAL; + } + // Prefer the one with a non-empty scroll bounds + if (emptyScrollBoundsA) { + return AFTER; + } + return BEFORE; + } + + final View viewA = a.getContainingView(); + final View viewB = b.getContainingView(); + + // Prefer any view with scrollCaptureHint="INCLUDE", over one without + // This is an escape hatch for the next rule (descendants first) + boolean hintIncludeA = hasIncludeHint(viewA); + boolean hintIncludeB = hasIncludeHint(viewB); + if (hintIncludeA != hintIncludeB) { + return (hintIncludeA) ? BEFORE : AFTER; + } + // If the views are relatives, prefer the descendant. This allows implementations to + // leverage nested scrolling APIs by interacting with the innermost scrollable view (as + // would happen with touch input). + if (isDescendant(viewA, viewB)) { + return BEFORE; + } + if (isDescendant(viewB, viewA)) { + return AFTER; + } + + // finally, prefer one with larger scroll bounds + int scrollAreaA = area(a.getScrollBounds()); + int scrollAreaB = area(b.getScrollBounds()); + return (scrollAreaA >= scrollAreaB) ? BEFORE : AFTER; + }; + + private static int area(Rect r) { + return r.width() * r.height(); + } + + private static boolean nullOrEmpty(Rect r) { + return r == null || r.isEmpty(); + } + + private static boolean hasIncludeHint(View view) { + return (view.getScrollCaptureHint() & View.SCROLL_CAPTURE_HINT_INCLUDE) != 0; + } + + /** + * Determines if {@code otherView} is a descendant of {@code view}. + * + * @param view a view + * @param otherView another view + * @return true if {@code view} is an ancestor of {@code otherView} + */ + private static boolean isDescendant(@NonNull View view, @NonNull View otherView) { + if (view == otherView) { + return false; + } + ViewParent otherParent = otherView.getParent(); + while (otherParent != view && otherParent != null) { + otherParent = otherParent.getParent(); + } + return otherParent == view; + } + + void dump(IndentingPrintWriter writer) { + writer.println("results:"); + writer.increaseIndent(); + writer.println("complete: " + isComplete()); + writer.println("cancelled: " + mCancel.isCanceled()); + writer.println("targets:"); + writer.increaseIndent(); + if (isEmpty()) { + writer.println("None"); + } else { + for (int i = 0; i < mTargets.size(); i++) { + writer.println("[" + i + "]"); + writer.increaseIndent(); + mTargets.get(i).dump(writer); + writer.decreaseIndent(); + } + writer.decreaseIndent(); + } + writer.decreaseIndent(); + } +} diff --git a/core/java/android/view/ScrollCaptureSession.java b/core/java/android/view/ScrollCaptureSession.java index 92617a325265..748e7ea56f41 100644 --- a/core/java/android/view/ScrollCaptureSession.java +++ b/core/java/android/view/ScrollCaptureSession.java @@ -16,18 +16,15 @@ package android.view; +import static java.util.Objects.requireNonNull; + import android.annotation.NonNull; -import android.annotation.Nullable; import android.graphics.Point; import android.graphics.Rect; /** * A session represents the scope of interaction between a {@link ScrollCaptureCallback} and the - * system during an active scroll capture operation. During the scope of a session, a callback - * will receive a series of requests for image data. Resources provided here are valid for use - * until {@link ScrollCaptureCallback#onScrollCaptureEnd(Runnable)}. - * - * @hide + * system during an active scroll capture operation. */ public class ScrollCaptureSession { @@ -35,22 +32,27 @@ public class ScrollCaptureSession { private final Rect mScrollBounds; private final Point mPositionInWindow; - @Nullable - private ScrollCaptureConnection mConnection; - - /** @hide */ - public ScrollCaptureSession(Surface surface, Rect scrollBounds, Point positionInWindow, - ScrollCaptureConnection connection) { - mSurface = surface; - mScrollBounds = scrollBounds; - mPositionInWindow = positionInWindow; - mConnection = connection; + /** + * Constructs a new session instance. + * + * @param surface the surface to consume generated images + * @param scrollBounds the bounds of the capture area within the containing view + * @param positionInWindow the offset of scrollBounds within the window + */ + public ScrollCaptureSession(@NonNull Surface surface, @NonNull Rect scrollBounds, + @NonNull Point positionInWindow) { + mSurface = requireNonNull(surface); + mScrollBounds = requireNonNull(scrollBounds); + mPositionInWindow = requireNonNull(positionInWindow); } /** * Returns a * <a href="https://source.android.com/devices/graphics/arch-bq-gralloc">BufferQueue</a> in the * form of a {@link Surface} for transfer of image buffers. + * <p> + * The surface is guaranteed to remain {@link Surface#isValid() valid} until the session + * {@link ScrollCaptureCallback#onScrollCaptureEnd(Runnable) ends}. * * @return the surface for transferring image buffers * @throws IllegalStateException if the session has been closed @@ -80,26 +82,4 @@ public class ScrollCaptureSession { public Point getPositionInWindow() { return mPositionInWindow; } - - /** - * Notify the system that an a buffer has been posted via the getSurface() channel. - * - * @param frameNumber the frame number of the queued buffer - * @param capturedArea the area captured, relative to scroll bounds - */ - public void notifyBufferSent(long frameNumber, @NonNull Rect capturedArea) { - if (mConnection != null) { - mConnection.onRequestImageCompleted(frameNumber, capturedArea); - } - } - - /** - * @hide - */ - public void disconnect() { - mConnection = null; - if (mSurface.isValid()) { - mSurface.release(); - } - } } diff --git a/core/java/android/view/ScrollCaptureTarget.java b/core/java/android/view/ScrollCaptureTarget.java index f3fcabb26b31..4fd48892da70 100644 --- a/core/java/android/view/ScrollCaptureTarget.java +++ b/core/java/android/view/ScrollCaptureTarget.java @@ -22,14 +22,16 @@ import android.annotation.UiThread; import android.graphics.Matrix; import android.graphics.Point; import android.graphics.Rect; +import android.os.CancellationSignal; import com.android.internal.util.FastMath; +import java.io.PrintWriter; +import java.util.function.Consumer; + /** * A target collects the set of contextual information for a ScrollCaptureHandler discovered during * a {@link View#dispatchScrollCaptureSearch scroll capture search}. - * - * @hide */ public final class ScrollCaptureTarget { private final View mContainingView; @@ -41,7 +43,6 @@ public final class ScrollCaptureTarget { private final float[] mTmpFloatArr = new float[2]; private final Matrix mMatrixViewLocalToWindow = new Matrix(); - private final Rect mTmpRect = new Rect(); public ScrollCaptureTarget(@NonNull View scrollTarget, @NonNull Rect localVisibleRect, @NonNull Point positionInWindow, @NonNull ScrollCaptureCallback callback) { @@ -52,7 +53,10 @@ public final class ScrollCaptureTarget { mPositionInWindow = positionInWindow; } - /** @return the hint that the {@code containing view} had during the scroll capture search */ + /** + * @return the hint that the {@code containing view} had during the scroll capture search + * @see View#getScrollCaptureHint() + */ @View.ScrollCaptureHint public int getHint() { return mHint; @@ -71,8 +75,7 @@ public final class ScrollCaptureTarget { } /** - * Returns the un-clipped, visible bounds of the containing view during the scroll capture - * search. This is used to determine on-screen area to assist in selecting the primary target. + * Returns the visible bounds of the containing view. * * @return the visible bounds of the {@code containing view} in view-local coordinates */ @@ -81,13 +84,17 @@ public final class ScrollCaptureTarget { return mLocalVisibleRect; } - /** @return the position of the {@code containing view} within the window */ + /** @return the position of the visible bounds of the containing view within the window */ @NonNull public Point getPositionInWindow() { return mPositionInWindow; } - /** @return the {@code scroll bounds} for this {@link ScrollCaptureCallback callback} */ + /** + * @return the {@code scroll bounds} for this {@link ScrollCaptureCallback callback} + * + * @see ScrollCaptureCallback#onScrollCaptureSearch(CancellationSignal, Consumer) + */ @Nullable public Rect getScrollBounds() { return mScrollBounds; @@ -119,8 +126,8 @@ public final class ScrollCaptureTarget { } /** - * Refresh the value of {@link #mLocalVisibleRect} and {@link #mPositionInWindow} based on the - * current state of the {@code containing view}. + * Refresh the local visible bounds and it's offset within the window, based on the current + * state of the {@code containing view}. */ @UiThread public void updatePositionInWindow() { @@ -132,4 +139,27 @@ public final class ScrollCaptureTarget { roundIntoPoint(mPositionInWindow, mTmpFloatArr); } + public String toString() { + return "ScrollCaptureTarget{" + "view=" + mContainingView + + ", callback=" + mCallback + + ", scrollBounds=" + mScrollBounds + + ", localVisibleRect=" + mLocalVisibleRect + + ", positionInWindow=" + mPositionInWindow + + "}"; + } + + void dump(@NonNull PrintWriter writer) { + View view = getContainingView(); + writer.println("view: " + view); + writer.println("hint: " + mHint); + writer.println("callback: " + mCallback); + writer.println("scrollBounds: " + + (mScrollBounds == null ? "null" : mScrollBounds.toShortString())); + Point inWindow = getPositionInWindow(); + writer.println("positionInWindow: " + + ((inWindow == null) ? "null" : "[" + inWindow.x + "," + inWindow.y + "]")); + Rect localVisible = getLocalVisibleRect(); + writer.println("localVisibleRect: " + + (localVisible == null ? "null" : localVisible.toShortString())); + } } diff --git a/core/java/android/view/ScrollCaptureTargetResolver.java b/core/java/android/view/ScrollCaptureTargetResolver.java deleted file mode 100644 index e4316bbc9397..000000000000 --- a/core/java/android/view/ScrollCaptureTargetResolver.java +++ /dev/null @@ -1,337 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.view; - -import android.annotation.AnyThread; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.UiThread; -import android.graphics.Rect; -import android.os.Handler; -import android.os.Looper; -import android.os.SystemClock; -import android.util.Log; - -import com.android.internal.annotations.VisibleForTesting; - - -import java.util.Queue; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Consumer; - -/** - * Queries additional state from a list of {@link ScrollCaptureTarget targets} via asynchronous - * callbacks, then aggregates and reduces the target list to a single target, or null if no target - * is suitable. - * <p> - * The rules for selection are (in order): - * <ul> - * <li>prefer getScrollBounds(): non-empty - * <li>prefer View.getScrollCaptureHint == SCROLL_CAPTURE_HINT_INCLUDE - * <li>prefer descendants before parents - * <li>prefer larger area for getScrollBounds() (clipped to view bounds) - * </ul> - * - * <p> - * All calls to {@link ScrollCaptureCallback#onScrollCaptureSearch} are made on the main thread, - * with results are queued and consumed to the main thread as well. - * - * @see #start(Handler, long, Consumer) - * - * @hide - */ -@UiThread -public class ScrollCaptureTargetResolver { - private static final String TAG = "ScrollCaptureTargetRes"; - - private final Object mLock = new Object(); - - private final Queue<ScrollCaptureTarget> mTargets; - private Handler mHandler; - private long mTimeLimitMillis; - - private Consumer<ScrollCaptureTarget> mWhenComplete; - private int mPendingBoundsRequests; - private long mDeadlineMillis; - - private ScrollCaptureTarget mResult; - private boolean mFinished; - - private boolean mStarted; - - private static int area(Rect r) { - return r.width() * r.height(); - } - - private static boolean nullOrEmpty(Rect r) { - return r == null || r.isEmpty(); - } - - /** - * Binary operator which selects the best {@link ScrollCaptureTarget}. - */ - private static ScrollCaptureTarget chooseTarget(ScrollCaptureTarget a, ScrollCaptureTarget b) { - if (a == null && b == null) { - return null; - } else if (a == null || b == null) { - ScrollCaptureTarget c = (a == null) ? b : a; - return c; - } - - boolean emptyScrollBoundsA = nullOrEmpty(a.getScrollBounds()); - boolean emptyScrollBoundsB = nullOrEmpty(b.getScrollBounds()); - if (emptyScrollBoundsA || emptyScrollBoundsB) { - if (emptyScrollBoundsA && emptyScrollBoundsB) { - // Both have an empty or null scrollBounds - Log.d(TAG, "chooseTarget: (both have empty or null bounds) return " + null); - return null; - } - // Prefer the one with a non-empty scroll bounds - if (emptyScrollBoundsA) { - Log.d(TAG, "chooseTarget: (a has empty or null bounds) return " + b); - return b; - } - Log.d(TAG, "chooseTarget: (b has empty or null bounds) return " + a); - return a; - } - - final View viewA = a.getContainingView(); - final View viewB = b.getContainingView(); - - // Prefer any view with scrollCaptureHint="INCLUDE", over one without - // This is an escape hatch for the next rule (descendants first) - boolean hintIncludeA = hasIncludeHint(viewA); - boolean hintIncludeB = hasIncludeHint(viewB); - if (hintIncludeA != hintIncludeB) { - ScrollCaptureTarget c = (hintIncludeA) ? a : b; - Log.d(TAG, "chooseTarget: (has hint=INCLUDE) return " + c); - return c; - } - - // If the views are relatives, prefer the descendant. This allows implementations to - // leverage nested scrolling APIs by interacting with the innermost scrollable view (as - // would happen with touch input). - if (isDescendant(viewA, viewB)) { - Log.d(TAG, "chooseTarget: (b is descendant of a) return " + b); - return b; - } - if (isDescendant(viewB, viewA)) { - Log.d(TAG, "chooseTarget: (a is descendant of b) return " + a); - return a; - } - - // finally, prefer one with larger scroll bounds - int scrollAreaA = area(a.getScrollBounds()); - int scrollAreaB = area(b.getScrollBounds()); - ScrollCaptureTarget c = (scrollAreaA >= scrollAreaB) ? a : b; - Log.d(TAG, "chooseTarget: return " + c); - return c; - } - - /** - * Creates an instance to query and filter {@code target}. - * - * @param targets a list of {@link ScrollCaptureTarget} as collected by {@link - * View#dispatchScrollCaptureSearch}. - * @see #start(Handler, long, Consumer) - */ - public ScrollCaptureTargetResolver(Queue<ScrollCaptureTarget> targets) { - mTargets = targets; - } - - void checkThread() { - if (mHandler.getLooper() != Looper.myLooper()) { - throw new IllegalStateException("Called from wrong thread! (" - + Thread.currentThread().getName() + ")"); - } - } - - /** - * Blocks until a result is returned (after completion or timeout). - * <p> - * For testing only. Normal usage should receive a callback after calling {@link #start}. - */ - @VisibleForTesting - public ScrollCaptureTarget waitForResult() throws InterruptedException { - synchronized (mLock) { - while (!mFinished) { - mLock.wait(); - } - } - return mResult; - } - - private void supplyResult(ScrollCaptureTarget target) { - checkThread(); - if (mFinished) { - return; - } - mResult = chooseTarget(mResult, target); - boolean finish = mPendingBoundsRequests == 0 - || SystemClock.uptimeMillis() >= mDeadlineMillis; - if (finish) { - mPendingBoundsRequests = 0; - mWhenComplete.accept(mResult); - synchronized (mLock) { - mFinished = true; - mLock.notify(); - } - mWhenComplete = null; - } - } - - /** - * Asks all targets for {@link ScrollCaptureCallback#onScrollCaptureSearch(Consumer) - * scrollBounds}, and selects the primary target according to the {@link - * #chooseTarget} function. - * - * @param timeLimitMillis the amount of time to wait for all responses before delivering the top - * result - * @param resultConsumer the consumer to receive the primary target - */ - @AnyThread - public void start(Handler uiHandler, long timeLimitMillis, - Consumer<ScrollCaptureTarget> resultConsumer) { - synchronized (mLock) { - if (mStarted) { - throw new IllegalStateException("already started!"); - } - if (timeLimitMillis < 0) { - throw new IllegalArgumentException("Time limit must be positive"); - } - mHandler = uiHandler; - mTimeLimitMillis = timeLimitMillis; - mWhenComplete = resultConsumer; - if (mTargets.isEmpty()) { - mHandler.post(() -> supplyResult(null)); - return; - } - mStarted = true; - uiHandler.post(this::run); - } - } - - private void run() { - checkThread(); - - mPendingBoundsRequests = mTargets.size(); - for (ScrollCaptureTarget target : mTargets) { - queryTarget(target); - } - mDeadlineMillis = SystemClock.uptimeMillis() + mTimeLimitMillis; - mHandler.postAtTime(mTimeoutRunnable, mDeadlineMillis); - } - - private final Runnable mTimeoutRunnable = () -> { - checkThread(); - supplyResult(null); - }; - - /** - * Adds a target to the list and requests {@link ScrollCaptureCallback#onScrollCaptureSearch} - * scrollBounds} from it. Results are returned by a call to {@link #onScrollBoundsProvided}. - * - * @param target the target to add - */ - @UiThread - private void queryTarget(@NonNull ScrollCaptureTarget target) { - checkThread(); - final ScrollCaptureCallback callback = target.getCallback(); - // from the UI thread, request scroll bounds - callback.onScrollCaptureSearch( - // allow only one callback to onReady.accept(): - new SingletonConsumer<Rect>( - // Queue and consume on the UI thread - ((scrollBounds) -> mHandler.post( - () -> onScrollBoundsProvided(target, scrollBounds))))); - } - - @UiThread - private void onScrollBoundsProvided(ScrollCaptureTarget target, @Nullable Rect scrollBounds) { - checkThread(); - if (mFinished) { - return; - } - - // Record progress. - mPendingBoundsRequests--; - - // Remove the timeout. - mHandler.removeCallbacks(mTimeoutRunnable); - - boolean doneOrTimedOut = mPendingBoundsRequests == 0 - || SystemClock.uptimeMillis() >= mDeadlineMillis; - - final View containingView = target.getContainingView(); - if (!nullOrEmpty(scrollBounds) && containingView.isAggregatedVisible()) { - target.updatePositionInWindow(); - target.setScrollBounds(scrollBounds); - supplyResult(target); - } - - if (!mFinished) { - // Reschedule the timeout. - mHandler.postAtTime(mTimeoutRunnable, mDeadlineMillis); - } - } - - private static boolean hasIncludeHint(View view) { - return (view.getScrollCaptureHint() & View.SCROLL_CAPTURE_HINT_INCLUDE) != 0; - } - - /** - * Determines if {@code otherView} is a descendant of {@code view}. - * - * @param view a view - * @param otherView another view - * @return true if {@code view} is an ancestor of {@code otherView} - */ - private static boolean isDescendant(@NonNull View view, @NonNull View otherView) { - if (view == otherView) { - return false; - } - ViewParent otherParent = otherView.getParent(); - while (otherParent != view && otherParent != null) { - otherParent = otherParent.getParent(); - } - return otherParent == view; - } - - /** - * A safe wrapper for a consumer callbacks intended to accept a single value. It ensures - * that the receiver of the consumer does not retain a reference to {@code target} after use nor - * cause race conditions by invoking {@link Consumer#accept accept} more than once. - */ - static class SingletonConsumer<T> implements Consumer<T> { - final AtomicReference<Consumer<T>> mAtomicRef; - - /** - * @param target the target consumer - **/ - SingletonConsumer(Consumer<T> target) { - mAtomicRef = new AtomicReference<>(target); - } - - @Override - public void accept(T t) { - final Consumer<T> consumer = mAtomicRef.getAndSet(null); - if (consumer != null) { - consumer.accept(t); - } - } - } -} diff --git a/core/java/android/view/SoundEffectConstants.java b/core/java/android/view/SoundEffectConstants.java index 8d891bbc2cfc..f177451783dc 100644 --- a/core/java/android/view/SoundEffectConstants.java +++ b/core/java/android/view/SoundEffectConstants.java @@ -16,12 +16,21 @@ package android.view; +import android.media.AudioManager; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.annotations.VisibleForTesting.Visibility; + +import java.util.Random; + /** - * Constants to be used to play sound effects via {@link View#playSoundEffect(int)} + * Constants to be used to play sound effects via {@link View#playSoundEffect(int)} */ public class SoundEffectConstants { private SoundEffectConstants() {} + private static final Random NAVIGATION_REPEAT_RANDOMIZER = new Random(); + private static int sLastNavigationRepeatSoundEffectId = -1; public static final int CLICK = 0; @@ -29,6 +38,14 @@ public class SoundEffectConstants { public static final int NAVIGATION_UP = 2; public static final int NAVIGATION_RIGHT = 3; public static final int NAVIGATION_DOWN = 4; + /** Sound effect for a repeatedly triggered navigation, e.g. due to long pressing a button */ + public static final int NAVIGATION_REPEAT_LEFT = 5; + /** @see #NAVIGATION_REPEAT_LEFT */ + public static final int NAVIGATION_REPEAT_UP = 6; + /** @see #NAVIGATION_REPEAT_LEFT */ + public static final int NAVIGATION_REPEAT_RIGHT = 7; + /** @see #NAVIGATION_REPEAT_LEFT */ + public static final int NAVIGATION_REPEAT_DOWN = 8; /** * Get the sonification constant for the focus directions. @@ -40,7 +57,7 @@ public class SoundEffectConstants { * @throws {@link IllegalArgumentException} when the passed direction is not one of the * documented values. */ - public static int getContantForFocusDirection(int direction) { + public static int getContantForFocusDirection(@View.FocusDirection int direction) { switch (direction) { case View.FOCUS_RIGHT: return SoundEffectConstants.NAVIGATION_RIGHT; @@ -56,4 +73,65 @@ public class SoundEffectConstants { throw new IllegalArgumentException("direction must be one of " + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, FOCUS_FORWARD, FOCUS_BACKWARD}."); } + + /** + * Get the sonification constant for the focus directions + * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN}, + * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, {@link View#FOCUS_FORWARD} + * or {@link View#FOCUS_BACKWARD} + * @param repeating True if the user long-presses a direction + * @return The appropriate sonification constant + * @throws IllegalArgumentException when the passed direction is not one of the + * documented values. + */ + public static int getConstantForFocusDirection(@View.FocusDirection int direction, + boolean repeating) { + if (repeating) { + switch (direction) { + case View.FOCUS_RIGHT: + return SoundEffectConstants.NAVIGATION_REPEAT_RIGHT; + case View.FOCUS_FORWARD: + case View.FOCUS_DOWN: + return SoundEffectConstants.NAVIGATION_REPEAT_DOWN; + case View.FOCUS_LEFT: + return SoundEffectConstants.NAVIGATION_REPEAT_LEFT; + case View.FOCUS_BACKWARD: + case View.FOCUS_UP: + return SoundEffectConstants.NAVIGATION_REPEAT_UP; + } + throw new IllegalArgumentException("direction must be one of {FOCUS_UP, FOCUS_DOWN, " + + "FOCUS_LEFT, FOCUS_RIGHT, FOCUS_FORWARD, FOCUS_BACKWARD}."); + } else { + return getContantForFocusDirection(direction); + } + } + + /** + * @param effectId any of the effect ids defined in {@link SoundEffectConstants} + * @return true if the given effect id is a navigation repeat one + * @hide + */ + @VisibleForTesting(visibility = Visibility.PACKAGE) + public static boolean isNavigationRepeat(int effectId) { + return effectId == SoundEffectConstants.NAVIGATION_REPEAT_DOWN + || effectId == SoundEffectConstants.NAVIGATION_REPEAT_LEFT + || effectId == SoundEffectConstants.NAVIGATION_REPEAT_RIGHT + || effectId == SoundEffectConstants.NAVIGATION_REPEAT_UP; + } + + /** + * @return The next navigation repeat sound effect id, chosen at random in a non-repeating + * fashion + * @hide + */ + @VisibleForTesting(visibility = Visibility.PACKAGE) + public static int nextNavigationRepeatSoundEffectId() { + int next = NAVIGATION_REPEAT_RANDOMIZER.nextInt( + AudioManager.NUM_NAVIGATION_REPEAT_SOUND_EFFECTS - 1); + if (next >= sLastNavigationRepeatSoundEffectId) { + next++; + } + sLastNavigationRepeatSoundEffectId = next; + return AudioManager.getNthNavigationRepeatSoundEffect(next); + } } diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 0832578d80c5..03dd10050724 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -3504,64 +3504,4 @@ public final class SurfaceControl implements Parcelable { public static Transaction getGlobalTransaction() { return sGlobalTransaction; } - - /** - * Wrapper for sending blur data to SurfaceFlinger. - * @hide - */ - public static final class BlurRegion { - public int blurRadius; - public float cornerRadiusTL; - public float cornerRadiusTR; - public float cornerRadiusBL; - public float cornerRadiusBR; - public float alpha = 1; - public boolean visible = true; - public final Rect rect = new Rect(); - - private final float[] mFloatArray = new float[10]; - - public BlurRegion() { - } - - public BlurRegion(BlurRegion other) { - rect.set(other.rect); - blurRadius = other.blurRadius; - alpha = other.alpha; - cornerRadiusTL = other.cornerRadiusTL; - cornerRadiusTR = other.cornerRadiusTR; - cornerRadiusBL = other.cornerRadiusBL; - cornerRadiusBR = other.cornerRadiusBR; - } - - /** - * Serializes this class into a float array that's more JNI friendly. - */ - public float[] toFloatArray() { - mFloatArray[0] = blurRadius; - mFloatArray[1] = alpha; - mFloatArray[2] = rect.left; - mFloatArray[3] = rect.top; - mFloatArray[4] = rect.right; - mFloatArray[5] = rect.bottom; - mFloatArray[6] = cornerRadiusTL; - mFloatArray[7] = cornerRadiusTR; - mFloatArray[8] = cornerRadiusBL; - mFloatArray[9] = cornerRadiusBR; - return mFloatArray; - } - - @Override - public String toString() { - return "BlurRegion{" - + "blurRadius=" + blurRadius - + ", corners={" + cornerRadiusTL - + "," + cornerRadiusTR - + "," + cornerRadiusBL - + "," + cornerRadiusBR - + "}, alpha=" + alpha - + ", rect=" + rect - + "}"; - } - } } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 3789324a038c..ebef4646b0d9 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -184,10 +184,10 @@ import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.Queue; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; import java.util.function.Predicate; /** @@ -1484,7 +1484,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @see #getScrollCaptureHint() * @see #setScrollCaptureHint(int) - * @hide */ public static final int SCROLL_CAPTURE_HINT_AUTO = 0; @@ -1495,7 +1494,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @see #getScrollCaptureHint() * @see #setScrollCaptureHint(int) - * @hide */ public static final int SCROLL_CAPTURE_HINT_EXCLUDE = 0x1; @@ -1506,7 +1504,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @see #getScrollCaptureHint() * @see #setScrollCaptureHint(int) - * @hide */ public static final int SCROLL_CAPTURE_HINT_INCLUDE = 0x2; @@ -1517,7 +1514,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @see #getScrollCaptureHint() * @see #setScrollCaptureHint(int) - * @hide */ public static final int SCROLL_CAPTURE_HINT_EXCLUDE_DESCENDANTS = 0x4; @@ -30053,8 +30049,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * Returns the current scroll capture hint for this view. * * @return the current scroll capture hint - * - * @hide */ @ScrollCaptureHint public int getScrollCaptureHint() { @@ -30067,8 +30061,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * scroll capture targets. * * @param hint the scrollCaptureHint flags value to set - * - * @hide */ public void setScrollCaptureHint(@ScrollCaptureHint int hint) { mPrivateFlags4 &= ~PFLAG4_SCROLL_CAPTURE_HINT_MASK; @@ -30088,10 +30080,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * setting a custom callback to help ensure it is selected as the target. * * @param callback the new callback to assign - * - * @hide */ - public void setScrollCaptureCallback(@Nullable ScrollCaptureCallback callback) { + public final void setScrollCaptureCallback(@Nullable ScrollCaptureCallback callback) { getListenerInfo().mScrollCaptureCallback = callback; } @@ -30110,29 +30100,54 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Dispatch a scroll capture search request down the view hierarchy. + * + * @param localVisibleRect the visible area of this ViewGroup in local coordinates, according to + * the parent + * @param windowOffset the offset of this view within the window + * @param targets accepts potential scroll capture targets; {@link Consumer#accept + * results.accept} may be called zero or more times on the calling + * thread before onScrollCaptureSearch returns + */ + public void dispatchScrollCaptureSearch( + @NonNull Rect localVisibleRect, @NonNull Point windowOffset, + @NonNull Consumer<ScrollCaptureTarget> targets) { + onScrollCaptureSearch(localVisibleRect, windowOffset, targets); + } + + /** * Called when scroll capture is requested, to search for appropriate content to scroll. If * applicable, this view adds itself to the provided list for consideration, subject to the * flags set by {@link #setScrollCaptureHint}. * * @param localVisibleRect the local visible rect of this view * @param windowOffset the offset of localVisibleRect within the window - * @param targets a queue which collects potential targets - * + * @param targets accepts potential scroll capture targets; {@link Consumer#accept + * results.accept} may be called zero or more times on the calling + * thread before onScrollCaptureSearch returns * @throws IllegalStateException if this view is not attached to a window - * @hide */ - public void dispatchScrollCaptureSearch(@NonNull Rect localVisibleRect, - @NonNull Point windowOffset, @NonNull Queue<ScrollCaptureTarget> targets) { + public void onScrollCaptureSearch(@NonNull Rect localVisibleRect, + @NonNull Point windowOffset, @NonNull Consumer<ScrollCaptureTarget> targets) { int hint = getScrollCaptureHint(); if ((hint & SCROLL_CAPTURE_HINT_EXCLUDE) != 0) { return; } + boolean rectIsVisible = true; + + // Apply clipBounds if present. + if (mClipBounds != null) { + rectIsVisible = localVisibleRect.intersect(mClipBounds); + } + if (!rectIsVisible) { + return; + } // Get a callback provided by the framework, library or application. ScrollCaptureCallback callback = (mListenerInfo == null) ? null : mListenerInfo.mScrollCaptureCallback; - // Try internal support for standard scrolling containers. + // Try framework support for standard scrolling containers. if (callback == null) { callback = createScrollCaptureCallbackInternal(localVisibleRect, windowOffset); } @@ -30142,7 +30157,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // Add to the list for consideration Point offset = new Point(windowOffset.x, windowOffset.y); Rect rect = new Rect(localVisibleRect); - targets.add(new ScrollCaptureTarget(this, rect, offset, callback)); + targets.accept(new ScrollCaptureTarget(this, rect, offset, callback)); } } diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 37bea5821e42..38a59373554c 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -77,7 +77,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Queue; +import java.util.function.Consumer; import java.util.function.Predicate; /** @@ -7463,92 +7463,73 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } /** - * Offsets the given rectangle in parent's local coordinates into child's coordinate space - * and clips the result to the child View's bounds, padding and clipRect if appropriate. If the - * resulting rectangle is not empty, the request is forwarded to the child. - * <p> - * Note: This method does not account for any static View transformations which may be - * applied to the child view. - * - * @param child the child to dispatch to - * @param localVisibleRect the visible (clipped) area of this ViewGroup, in local coordinates - * @param windowOffset the offset of localVisibleRect within the window - * @param targets a queue to collect located targets - */ - private void dispatchTransformedScrollCaptureSearch(View child, Rect localVisibleRect, - Point windowOffset, Queue<ScrollCaptureTarget> targets) { - - // copy local visible rect for modification and dispatch - final Rect childVisibleRect = getTempRect(); - childVisibleRect.set(localVisibleRect); - - // transform to child coords - final Point childWindowOffset = getTempPoint(); - childWindowOffset.set(windowOffset.x, windowOffset.y); - - final int dx = child.mLeft - mScrollX; - final int dy = child.mTop - mScrollY; - - childVisibleRect.offset(-dx, -dy); - childWindowOffset.offset(dx, dy); - - boolean rectIsVisible = true; - final int width = mRight - mLeft; - final int height = mBottom - mTop; - - // Clip to child bounds - if (getClipChildren()) { - rectIsVisible = childVisibleRect.intersect(0, 0, child.getWidth(), child.getHeight()); - } - - // Clip to child padding. - if (rectIsVisible && (child instanceof ViewGroup) - && ((ViewGroup) child).getClipToPadding()) { - rectIsVisible = childVisibleRect.intersect( - child.mPaddingLeft, child.mPaddingTop, - child.getWidth() - child.mPaddingRight, - child.getHeight() - child.mPaddingBottom); - } - // Clip to child clipBounds. - if (rectIsVisible && child.mClipBounds != null) { - rectIsVisible = childVisibleRect.intersect(child.mClipBounds); - } - if (rectIsVisible) { - child.dispatchScrollCaptureSearch(childVisibleRect, childWindowOffset, targets); - } - } - - /** * Handle the scroll capture search request by checking this view if applicable, then to each * child view. * * @param localVisibleRect the visible area of this ViewGroup in local coordinates, according to * the parent * @param windowOffset the offset of this view within the window - * @param targets the collected list of scroll capture targets - * - * @hide + * @param targets accepts potential scroll capture targets; {@link Consumer#accept + * results.accept} may be called zero or more times on the calling + * thread before onScrollCaptureSearch returns */ @Override public void dispatchScrollCaptureSearch( @NonNull Rect localVisibleRect, @NonNull Point windowOffset, - @NonNull Queue<ScrollCaptureTarget> targets) { + @NonNull Consumer<ScrollCaptureTarget> targets) { + + // copy local visible rect for modification and dispatch + final Rect rect = getTempRect(); + rect.set(localVisibleRect); + + if (getClipToPadding()) { + rect.inset(mPaddingLeft, mPaddingTop, mPaddingRight, mPaddingBottom); + } // Dispatch to self first. super.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, targets); - // Then dispatch to children, if not excluding descendants. - if ((getScrollCaptureHint() & SCROLL_CAPTURE_HINT_EXCLUDE_DESCENDANTS) == 0) { - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - // Only visible views can be captured. - if (child.getVisibility() != View.VISIBLE) { - continue; - } - // Transform to child coords and dispatch - dispatchTransformedScrollCaptureSearch(child, localVisibleRect, windowOffset, - targets); + // Skip children if descendants excluded. + if ((getScrollCaptureHint() & SCROLL_CAPTURE_HINT_EXCLUDE_DESCENDANTS) != 0) { + return; + } + + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + View child = getChildAt(i); + // Only visible views can be captured. + if (child.getVisibility() != View.VISIBLE) { + continue; + } + // Offset the given rectangle (in parent's local coordinates) into child's coordinate + // space and clip the result to the child View's bounds, padding and clipRect as needed. + // If the resulting rectangle is not empty, the request is forwarded to the child. + + // copy local visible rect for modification and dispatch + final Rect childVisibleRect = getTempRect(); + childVisibleRect.set(localVisibleRect); + + // transform to child coords + final Point childWindowOffset = getTempPoint(); + childWindowOffset.set(windowOffset.x, windowOffset.y); + + final int dx = child.mLeft - mScrollX; + final int dy = child.mTop - mScrollY; + + childVisibleRect.offset(-dx, -dy); + childWindowOffset.offset(dx, dy); + + boolean rectIsVisible = true; + + // Clip to child bounds + if (getClipChildren()) { + rectIsVisible = childVisibleRect.intersect(0, 0, child.getWidth(), + child.getHeight()); + } + + // Clip to child padding. + if (rectIsVisible) { + child.dispatchScrollCaptureSearch(childVisibleRect, childWindowOffset, targets); } } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 144691d3eaa0..f8e65bd0d056 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -141,6 +141,7 @@ import android.util.AndroidRuntimeException; import android.util.ArraySet; import android.util.DisplayMetrics; import android.util.EventLog; +import android.util.IndentingPrintWriter; import android.util.Log; import android.util.LongArray; import android.util.MergedConfiguration; @@ -202,6 +203,7 @@ import com.android.internal.view.SurfaceCallbackHelper; import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; +import java.io.StringWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.HashSet; @@ -274,6 +276,11 @@ public final class ViewRootImpl implements ViewParent, */ private static final int CONTENT_CAPTURE_ENABLED_FALSE = 2; + /** + * Maximum time to wait for {@link View#dispatchScrollCaptureSearch} to complete. + */ + private static final int SCROLL_CAPTURE_REQUEST_TIMEOUT_MILLIS = 2500; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) static final ThreadLocal<HandlerActionQueue> sRunQueues = new ThreadLocal<HandlerActionQueue>(); @@ -323,6 +330,8 @@ public final class ViewRootImpl implements ViewParent, private boolean mForceDisableBLAST; private boolean mEnableTripleBuffering; + private boolean mFastScrollSoundEffectsEnabled; + /** * Signals that compatibility booleans have been initialized according to * target SDK versions. @@ -666,8 +675,6 @@ public final class ViewRootImpl implements ViewParent, private final InsetsController mInsetsController; private final ImeFocusController mImeFocusController; - private ScrollCaptureConnection mScrollCaptureConnection; - private boolean mIsSurfaceOpaque; private final BackgroundBlurDrawable.Aggregator mBlurRegionAggregator = @@ -681,12 +688,6 @@ public final class ViewRootImpl implements ViewParent, return mImeFocusController; } - /** @return The current {@link ScrollCaptureConnection} for this instance, if any is active. */ - @Nullable - public ScrollCaptureConnection getScrollCaptureConnection() { - return mScrollCaptureConnection; - } - private final GestureExclusionTracker mGestureExclusionTracker = new GestureExclusionTracker(); private IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection; @@ -728,6 +729,8 @@ public final class ViewRootImpl implements ViewParent, private HashSet<ScrollCaptureCallback> mRootScrollCaptureCallbacks; + private long mScrollCaptureRequestTimeout = SCROLL_CAPTURE_REQUEST_TIMEOUT_MILLIS; + /** * Increment this value when the surface has been replaced. */ @@ -813,6 +816,10 @@ public final class ViewRootImpl implements ViewParent, loadSystemProperties(); mImeFocusController = new ImeFocusController(this); + AudioManager audioManager = mContext.getSystemService(AudioManager.class); + mFastScrollSoundEffectsEnabled = audioManager.areNavigationRepeatSoundEffectsEnabled(); + + mScrollCaptureRequestTimeout = SCROLL_CAPTURE_REQUEST_TIMEOUT_MILLIS; } public static void addFirstDrawHandler(Runnable callback) { @@ -3962,11 +3969,12 @@ public final class ViewRootImpl implements ViewParent, } private void addFrameCallbackIfNeeded() { - boolean nextDrawUseBlastSync = mNextDrawUseBlastSync; - boolean hasBlur = mBlurRegionAggregator.hasRegions(); - boolean reportNextDraw = mReportNextDraw; + final boolean nextDrawUseBlastSync = mNextDrawUseBlastSync; + final boolean reportNextDraw = mReportNextDraw; + final boolean hasBlurUpdates = mBlurRegionAggregator.hasUpdates(); + final boolean needsCallbackForBlur = hasBlurUpdates || mBlurRegionAggregator.hasRegions(); - if (!nextDrawUseBlastSync && !reportNextDraw && !hasBlur) { + if (!nextDrawUseBlastSync && !reportNextDraw && !needsCallbackForBlur) { return; } @@ -3974,18 +3982,22 @@ public final class ViewRootImpl implements ViewParent, Log.d(mTag, "Creating frameDrawingCallback" + " nextDrawUseBlastSync=" + nextDrawUseBlastSync + " reportNextDraw=" + reportNextDraw - + " hasBlur=" + hasBlur); + + " hasBlurUpdates=" + hasBlurUpdates); } - // The callback will run on a worker thread pool from the render thread. + final BackgroundBlurDrawable.BlurRegion[] blurRegionsForFrame = + needsCallbackForBlur ? mBlurRegionAggregator.getBlurRegionsCopyForRT() : null; + + // The callback will run on the render thread. HardwareRenderer.FrameDrawingCallback frameDrawingCallback = frame -> { if (DEBUG_BLAST) { Log.d(mTag, "Received frameDrawingCallback frameNum=" + frame + "." + " Creating transactionCompleteCallback=" + nextDrawUseBlastSync); } - if (hasBlur) { - mBlurRegionAggregator.dispatchBlurTransactionIfNeeded(frame); + if (needsCallbackForBlur) { + mBlurRegionAggregator + .dispatchBlurTransactionIfNeeded(frame, blurRegionsForFrame, hasBlurUpdates); } if (mBlastBufferQueue == null) { @@ -6081,8 +6093,10 @@ public final class ViewRootImpl implements ViewParent, v, mTempRect); } if (v.requestFocus(direction, mTempRect)) { - playSoundEffect(SoundEffectConstants - .getContantForFocusDirection(direction)); + boolean isFastScrolling = event.getRepeatCount() > 0; + playSoundEffect( + SoundEffectConstants.getConstantForFocusDirection(direction, + isFastScrolling)); return true; } } @@ -7743,20 +7757,31 @@ public final class ViewRootImpl implements ViewParent, try { final AudioManager audioManager = getAudioManager(); + if (mFastScrollSoundEffectsEnabled + && SoundEffectConstants.isNavigationRepeat(effectId)) { + audioManager.playSoundEffect( + SoundEffectConstants.nextNavigationRepeatSoundEffectId()); + return; + } + switch (effectId) { case SoundEffectConstants.CLICK: audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK); return; case SoundEffectConstants.NAVIGATION_DOWN: + case SoundEffectConstants.NAVIGATION_REPEAT_DOWN: audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_DOWN); return; case SoundEffectConstants.NAVIGATION_LEFT: + case SoundEffectConstants.NAVIGATION_REPEAT_LEFT: audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_LEFT); return; case SoundEffectConstants.NAVIGATION_RIGHT: + case SoundEffectConstants.NAVIGATION_REPEAT_RIGHT: audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_RIGHT); return; case SoundEffectConstants.NAVIGATION_UP: + case SoundEffectConstants.NAVIGATION_REPEAT_UP: audioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_UP); return; default: @@ -9206,9 +9231,9 @@ public final class ViewRootImpl implements ViewParent, * Collect and include any ScrollCaptureCallback instances registered with the window. * * @see #addScrollCaptureCallback(ScrollCaptureCallback) - * @param targets the search queue for targets + * @param results an object to collect the results of the search */ - private void collectRootScrollCaptureTargets(Queue<ScrollCaptureTarget> targets) { + private void collectRootScrollCaptureTargets(ScrollCaptureSearchResults results) { if (mRootScrollCaptureCallbacks == null) { return; } @@ -9216,26 +9241,45 @@ public final class ViewRootImpl implements ViewParent, // Add to the list for consideration Point offset = new Point(mView.getLeft(), mView.getTop()); Rect rect = new Rect(0, 0, mView.getWidth(), mView.getHeight()); - targets.add(new ScrollCaptureTarget(mView, rect, offset, cb)); + results.addTarget(new ScrollCaptureTarget(mView, rect, offset, cb)); } } /** - * Handles an inbound request for scroll capture from the system. If a client is not already - * active, a search will be dispatched through the view tree to locate scrolling content. + * Update the timeout for scroll capture requests. Only affects this view root. + * The default value is {@link #SCROLL_CAPTURE_REQUEST_TIMEOUT_MILLIS}. + * + * @param timeMillis the new timeout in milliseconds + */ + public void setScrollCaptureRequestTimeout(int timeMillis) { + mScrollCaptureRequestTimeout = timeMillis; + } + + /** + * Get the current timeout for scroll capture requests. + * + * @return the timeout in milliseconds + */ + public long getScrollCaptureRequestTimeout() { + return mScrollCaptureRequestTimeout; + } + + /** + * Handles an inbound request for scroll capture from the system. A search will be + * dispatched through the view tree to locate scrolling content. * <p> - * Either {@link IScrollCaptureCallbacks#onConnected(IScrollCaptureConnection, Rect, - * Point)} or {@link IScrollCaptureCallbacks#onUnavailable()} will be returned - * depending on the results of the search. + * A call to {@link IScrollCaptureCallbacks#onScrollCaptureResponse(ScrollCaptureResponse)} + * will follow. * * @param callbacks to receive responses - * @see ScrollCaptureTargetResolver + * @see ScrollCaptureTargetSelector */ public void handleScrollCaptureRequest(@NonNull IScrollCaptureCallbacks callbacks) { - LinkedList<ScrollCaptureTarget> targetList = new LinkedList<>(); + ScrollCaptureSearchResults results = + new ScrollCaptureSearchResults(mContext.getMainExecutor()); // Window (root) level callbacks - collectRootScrollCaptureTargets(targetList); + collectRootScrollCaptureTargets(results); // Search through View-tree View rootView = getView(); @@ -9243,58 +9287,70 @@ public final class ViewRootImpl implements ViewParent, Point point = new Point(); Rect rect = new Rect(0, 0, rootView.getWidth(), rootView.getHeight()); getChildVisibleRect(rootView, rect, point); - rootView.dispatchScrollCaptureSearch(rect, point, targetList); + rootView.dispatchScrollCaptureSearch(rect, point, results::addTarget); } - - // No-op path. Scroll capture not offered for this window. - if (targetList.isEmpty()) { - dispatchScrollCaptureSearchResult(callbacks, null); - return; + Runnable onComplete = () -> dispatchScrollCaptureSearchResult(callbacks, results); + results.setOnCompleteListener(onComplete); + if (!results.isComplete()) { + mHandler.postDelayed(results::finish, getScrollCaptureRequestTimeout()); } - - // Request scrollBounds from each of the targets. - // Continues with the consumer once all responses are consumed, or the timeout expires. - ScrollCaptureTargetResolver resolver = new ScrollCaptureTargetResolver(targetList); - resolver.start(mHandler, 1000, - (selected) -> dispatchScrollCaptureSearchResult(callbacks, selected)); } /** Called by {@link #handleScrollCaptureRequest} when a result is returned */ private void dispatchScrollCaptureSearchResult( @NonNull IScrollCaptureCallbacks callbacks, - @Nullable ScrollCaptureTarget selectedTarget) { + @NonNull ScrollCaptureSearchResults results) { + + ScrollCaptureTarget selectedTarget = results.getTopResult(); + + ScrollCaptureResponse.Builder response = new ScrollCaptureResponse.Builder(); + response.setWindowTitle(getTitle().toString()); + + StringWriter writer = new StringWriter(); + IndentingPrintWriter pw = new IndentingPrintWriter(writer); + results.dump(pw); + pw.flush(); + response.addMessage(writer.toString()); - // If timeout or no eligible targets found. if (selectedTarget == null) { + response.setDescription("No scrollable targets found in window"); try { - if (DEBUG_SCROLL_CAPTURE) { - Log.d(TAG, "scrollCaptureSearch returned no targets available."); - } - callbacks.onUnavailable(); + callbacks.onScrollCaptureResponse(response.build()); } catch (RemoteException e) { - if (DEBUG_SCROLL_CAPTURE) { - Log.w(TAG, "Failed to send scroll capture search result.", e); - } + Log.e(TAG, "Failed to send scroll capture search result", e); } return; } - // Create a client instance and return it to the caller - mScrollCaptureConnection = new ScrollCaptureConnection(selectedTarget, callbacks); + response.setDescription("Connected"); + + // Compute area covered by scrolling content within window + Rect boundsInWindow = new Rect(); + View containingView = selectedTarget.getContainingView(); + containingView.getLocationInWindow(mAttachInfo.mTmpLocation); + boundsInWindow.set(selectedTarget.getScrollBounds()); + boundsInWindow.offset(mAttachInfo.mTmpLocation[0], mAttachInfo.mTmpLocation[1]); + response.setBoundsInWindow(boundsInWindow); + + // Compute the area on screen covered by the window + Rect boundsOnScreen = new Rect(); + mView.getLocationOnScreen(mAttachInfo.mTmpLocation); + boundsOnScreen.set(0, 0, mView.getWidth(), mView.getHeight()); + boundsOnScreen.offset(mAttachInfo.mTmpLocation[0], mAttachInfo.mTmpLocation[1]); + response.setWindowBounds(boundsOnScreen); + + // Create a connection and return it to the caller + ScrollCaptureConnection connection = new ScrollCaptureConnection( + mView.getContext().getMainExecutor(), selectedTarget, callbacks); + response.setConnection(connection); + try { - if (DEBUG_SCROLL_CAPTURE) { - Log.d(TAG, "scrollCaptureSearch returning client: " + getScrollCaptureConnection()); - } - callbacks.onConnected( - mScrollCaptureConnection, - selectedTarget.getScrollBounds(), - selectedTarget.getPositionInWindow()); + callbacks.onScrollCaptureResponse(response.build()); } catch (RemoteException e) { if (DEBUG_SCROLL_CAPTURE) { - Log.w(TAG, "Failed to send scroll capture search result.", e); + Log.w(TAG, "Failed to send scroll capture search response.", e); } - mScrollCaptureConnection.disconnect(); - mScrollCaptureConnection = null; + connection.close(); } } diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index 4ecdd78f5a42..221b3346df58 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -2621,7 +2621,6 @@ public abstract class Window { * callback with the root view of the window. * * @param callback the callback to add - * @hide */ public void registerScrollCaptureCallback(@NonNull ScrollCaptureCallback callback) { } @@ -2630,7 +2629,6 @@ public abstract class Window { * Unregisters a {@link ScrollCaptureCallback} previously registered with this window. * * @param callback the callback to remove - * @hide */ public void unregisterScrollCaptureCallback(@NonNull ScrollCaptureCallback callback) { } diff --git a/core/java/android/window/ClientWindowFrames.java b/core/java/android/window/ClientWindowFrames.java index e22a5eb9fe7b..acf9882e6658 100644 --- a/core/java/android/window/ClientWindowFrames.java +++ b/core/java/android/window/ClientWindowFrames.java @@ -58,9 +58,9 @@ public class ClientWindowFrames implements Parcelable { /** Needed for AIDL out parameters. */ public void readFromParcel(Parcel in) { - frame.set(Rect.CREATOR.createFromParcel(in)); - displayFrame.set(Rect.CREATOR.createFromParcel(in)); - backdropFrame.set(Rect.CREATOR.createFromParcel(in)); + frame.readFromParcel(in); + displayFrame.readFromParcel(in); + backdropFrame.readFromParcel(in); } @Override diff --git a/core/java/com/android/internal/graphics/drawable/BackgroundBlurDrawable.java b/core/java/com/android/internal/graphics/drawable/BackgroundBlurDrawable.java index 96dac565eb3d..402d7fed90c5 100644 --- a/core/java/com/android/internal/graphics/drawable/BackgroundBlurDrawable.java +++ b/core/java/com/android/internal/graphics/drawable/BackgroundBlurDrawable.java @@ -19,6 +19,7 @@ package com.android.internal.graphics.drawable; import android.annotation.ColorInt; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UiThread; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; @@ -31,12 +32,14 @@ import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.RenderNode; import android.graphics.drawable.Drawable; -import android.util.ArrayMap; +import android.util.ArraySet; import android.util.Log; -import android.view.SurfaceControl; +import android.util.LongSparseArray; import android.view.ViewRootImpl; import com.android.internal.R; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; /** * A drawable that keeps track of a blur region, pokes a hole under it, and propagates its state @@ -52,26 +55,40 @@ public final class BackgroundBlurDrawable extends Drawable { private final Paint mPaint = new Paint(); private final Path mRectPath = new Path(); private final float[] mTmpRadii = new float[8]; - private final SurfaceControl.BlurRegion mBlurRegion = new SurfaceControl.BlurRegion(); - // This will be called from a thread pool. - private final RenderNode.PositionUpdateListener mPositionUpdateListener = + private boolean mVisible = true; + + // Confined to UiThread. The values are copied into a BlurRegion, which lives on + // RenderThread to avoid interference with UiThread updates. + private int mBlurRadius; + private float mCornerRadiusTL; + private float mCornerRadiusTR; + private float mCornerRadiusBL; + private float mCornerRadiusBR; + private float mAlpha = 1; + + // Do not update from UiThread. This holds the latest position for this drawable. It is used + // by the Aggregator from RenderThread to get the final position of the blur region sent to SF + private final Rect mRect = new Rect(); + // This is called from a thread pool. The callbacks might come out of order w.r.t. the frame + // number, so we send a Runnable holding the actual update to the Aggregator. The Aggregator + // can apply the update on RenderThread when processing that same frame. + @VisibleForTesting + public final RenderNode.PositionUpdateListener mPositionUpdateListener = new RenderNode.PositionUpdateListener() { @Override public void positionChanged(long frameNumber, int left, int top, int right, int bottom) { - synchronized (mAggregator) { - mBlurRegion.rect.set(left, top, right, bottom); - mAggregator.onBlurRegionUpdated(BackgroundBlurDrawable.this, mBlurRegion); - } + mAggregator.onRenderNodePositionChanged(frameNumber, () -> { + mRect.set(left, top, right, bottom); + }); } @Override public void positionLost(long frameNumber) { - synchronized (mAggregator) { - mBlurRegion.rect.setEmpty(); - mAggregator.onBlurRegionUpdated(BackgroundBlurDrawable.this, mBlurRegion); - } + mAggregator.onRenderNodePositionChanged(frameNumber, () -> { + mRect.setEmpty(); + }); } }; @@ -79,6 +96,7 @@ public final class BackgroundBlurDrawable extends Drawable { mAggregator = aggregator; mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC)); mPaint.setColor(Color.TRANSPARENT); + mPaint.setAntiAlias(true); mRenderNode = new RenderNode("BackgroundBlurDrawable"); mRenderNode.addPositionUpdateListener(mPositionUpdateListener); } @@ -104,23 +122,30 @@ public final class BackgroundBlurDrawable extends Drawable { public boolean setVisible(boolean visible, boolean restart) { boolean changed = super.setVisible(visible, restart); if (changed) { - mBlurRegion.visible = visible; + mVisible = visible; + mAggregator.onBlurDrawableUpdated(this); } return changed; } @Override public void setAlpha(int alpha) { - mBlurRegion.alpha = alpha / 255f; - invalidateSelf(); + if (mAlpha != alpha / 255f) { + mAlpha = alpha / 255f; + invalidateSelf(); + mAggregator.onBlurDrawableUpdated(this); + } } /** * Blur radius in pixels. */ public void setBlurRadius(int blurRadius) { - mBlurRegion.blurRadius = blurRadius; - invalidateSelf(); + if (mBlurRadius != blurRadius) { + mBlurRadius = blurRadius; + invalidateSelf(); + mAggregator.onBlurDrawableUpdated(this); + } } /** @@ -139,14 +164,18 @@ public final class BackgroundBlurDrawable extends Drawable { */ public void setCornerRadius(float cornerRadiusTL, float cornerRadiusTR, float cornerRadiusBL, float cornerRadiusBR) { - synchronized (mAggregator) { - mBlurRegion.cornerRadiusTL = cornerRadiusTL; - mBlurRegion.cornerRadiusTR = cornerRadiusTR; - mBlurRegion.cornerRadiusBL = cornerRadiusBL; - mBlurRegion.cornerRadiusBR = cornerRadiusBR; + if (mCornerRadiusTL != cornerRadiusTL + || mCornerRadiusTR != cornerRadiusTR + || mCornerRadiusBL != cornerRadiusBL + || mCornerRadiusBR != cornerRadiusBR) { + mCornerRadiusTL = cornerRadiusTL; + mCornerRadiusTR = cornerRadiusTR; + mCornerRadiusBL = cornerRadiusBL; + mCornerRadiusBR = cornerRadiusBR; + updatePath(); + invalidateSelf(); + mAggregator.onBlurDrawableUpdated(this); } - updatePath(); - invalidateSelf(); } @Override @@ -157,12 +186,10 @@ public final class BackgroundBlurDrawable extends Drawable { } private void updatePath() { - synchronized (mAggregator) { - mTmpRadii[0] = mTmpRadii[1] = mBlurRegion.cornerRadiusTL; - mTmpRadii[2] = mTmpRadii[3] = mBlurRegion.cornerRadiusTR; - mTmpRadii[4] = mTmpRadii[5] = mBlurRegion.cornerRadiusBL; - mTmpRadii[6] = mTmpRadii[7] = mBlurRegion.cornerRadiusBR; - } + mTmpRadii[0] = mTmpRadii[1] = mCornerRadiusTL; + mTmpRadii[2] = mTmpRadii[3] = mCornerRadiusTR; + mTmpRadii[4] = mTmpRadii[5] = mCornerRadiusBL; + mTmpRadii[6] = mTmpRadii[7] = mCornerRadiusBR; mRectPath.reset(); if (getAlpha() == 0 || !isVisible()) { return; @@ -182,17 +209,32 @@ public final class BackgroundBlurDrawable extends Drawable { return PixelFormat.TRANSLUCENT; } + @Override + public String toString() { + return "BackgroundBlurDrawable{" + + "blurRadius=" + mBlurRadius + + ", corners={" + mCornerRadiusTL + + "," + mCornerRadiusTR + + "," + mCornerRadiusBL + + "," + mCornerRadiusBR + + "}, alpha=" + mAlpha + + ", visible=" + mVisible + + "}"; + } + /** * Responsible for keeping track of all blur regions of a {@link ViewRootImpl} and posting a * message when it's time to propagate them. */ public static final class Aggregator { - - private final ArrayMap<BackgroundBlurDrawable, SurfaceControl.BlurRegion> mBlurRegions = - new ArrayMap<>(); + private final Object mRtLock = new Object(); + // Maintains a list of all *visible* blur drawables. Confined to UI thread + private final ArraySet<BackgroundBlurDrawable> mDrawables = new ArraySet(); + @GuardedBy("mRtLock") + private final LongSparseArray<ArraySet<Runnable>> mFrameRtUpdates = new LongSparseArray(); private final ViewRootImpl mViewRoot; - private float[][] mTmpBlurRegionsArray; - private boolean mNeedsUpdate; + private BlurRegion[] mTmpBlurRegionsForFrame = new BlurRegion[0]; + private boolean mHasUiUpdates; public Aggregator(ViewRootImpl viewRoot) { mViewRoot = viewRoot; @@ -209,60 +251,191 @@ public final class BackgroundBlurDrawable extends Drawable { } /** - * Called from RenderThread only, already locked. - * @param drawable - * @param blurRegion + * Called when a BackgroundBlurDrawable has been updated */ - void onBlurRegionUpdated(BackgroundBlurDrawable drawable, - SurfaceControl.BlurRegion blurRegion) { - if (blurRegion.rect.isEmpty() || blurRegion.alpha == 0 || blurRegion.blurRadius == 0 - || !blurRegion.visible) { - mBlurRegions.remove(drawable); - mNeedsUpdate = true; - if (DEBUG) { - Log.d(TAG, "Remove " + blurRegion); + @UiThread + void onBlurDrawableUpdated(BackgroundBlurDrawable drawable) { + final boolean shouldBeDrawn = + drawable.mAlpha != 0 && drawable.mBlurRadius > 0 && drawable.mVisible; + final boolean isDrawn = mDrawables.contains(drawable); + if (shouldBeDrawn) { + mHasUiUpdates = true; + if (!isDrawn) { + mDrawables.add(drawable); + if (DEBUG) { + Log.d(TAG, "Add " + drawable); + } + } else { + if (DEBUG) { + Log.d(TAG, "Update " + drawable); + } } - } else { - mBlurRegions.put(drawable, blurRegion); - mNeedsUpdate = true; + } else if (!shouldBeDrawn && isDrawn) { + mHasUiUpdates = true; + mDrawables.remove(drawable); if (DEBUG) { - Log.d(TAG, "Update " + blurRegion); + Log.d(TAG, "Remove " + drawable); } } } + // Called from a thread pool + void onRenderNodePositionChanged(long frameNumber, Runnable update) { + // One of the blur region's position has changed, so we have to send an updated list + // of blur regions to SurfaceFlinger for this frame. + synchronized (mRtLock) { + ArraySet<Runnable> frameRtUpdates = mFrameRtUpdates.get(frameNumber); + if (frameRtUpdates == null) { + frameRtUpdates = new ArraySet<>(); + mFrameRtUpdates.put(frameNumber, frameRtUpdates); + } + frameRtUpdates.add(update); + } + } + + /** + * @return true if there are any updates that need to be sent to SF + */ + @UiThread + public boolean hasUpdates() { + return mHasUiUpdates; + } + /** - * If there are any blur regions visible on the screen at the moment. + * @return true if there are any visible blur regions */ + @UiThread public boolean hasRegions() { - return mBlurRegions.size() > 0; + return mDrawables.size() > 0; + } + + /** + * @return an array of BlurRegions, which are holding a copy of the information in + * all the currently visible BackgroundBlurDrawables + */ + @UiThread + public BlurRegion[] getBlurRegionsCopyForRT() { + if (mHasUiUpdates) { + mTmpBlurRegionsForFrame = new BlurRegion[mDrawables.size()]; + for (int i = 0; i < mDrawables.size(); i++) { + mTmpBlurRegionsForFrame[i] = new BlurRegion(mDrawables.valueAt(i)); + } + mHasUiUpdates = false; + } + + return mTmpBlurRegionsForFrame; } /** - * Dispatch blur updates, if there were any. - * @param frameNumber Frame where the update should happen. + * Called on RenderThread. + * + * @return all blur regions if there are any ui or position updates for this frame, + * null otherwise */ - public void dispatchBlurTransactionIfNeeded(long frameNumber) { - synchronized (this) { - if (!mNeedsUpdate) { - return; + @VisibleForTesting + public float[][] getBlurRegionsToDispatchToSf(long frameNumber, + BlurRegion[] blurRegionsForFrame, boolean hasUiUpdatesForFrame) { + synchronized (mRtLock) { + if (!hasUiUpdatesForFrame && (mFrameRtUpdates.size() == 0 + || mFrameRtUpdates.keyAt(0) > frameNumber)) { + return null; } - mNeedsUpdate = false; - if (mTmpBlurRegionsArray == null - || mTmpBlurRegionsArray.length != mBlurRegions.size()) { - mTmpBlurRegionsArray = new float[mBlurRegions.size()][]; + // mFrameRtUpdates holds position updates coming from a thread pool span from + // RenderThread. At this point, all position updates for frame frameNumber should + // have been added to mFrameRtUpdates. + // Here, we apply all updates for frames <= frameNumber in case some previous update + // has been missed. This also protects mFrameRtUpdates from memory leaks. + while (mFrameRtUpdates.size() != 0 && mFrameRtUpdates.keyAt(0) <= frameNumber) { + final ArraySet<Runnable> frameUpdates = mFrameRtUpdates.valueAt(0); + mFrameRtUpdates.removeAt(0); + for (int i = 0; i < frameUpdates.size(); i++) { + frameUpdates.valueAt(i).run(); + } } + } + + if (DEBUG) { + Log.d(TAG, "Dispatching " + blurRegionsForFrame.length + " blur regions:"); + } + + final float[][] blurRegionsArray = new float[blurRegionsForFrame.length][]; + for (int i = 0; i < blurRegionsArray.length; i++) { + blurRegionsArray[i] = blurRegionsForFrame[i].toFloatArray(); if (DEBUG) { - Log.d(TAG, "onBlurRegionUpdated will dispatch " + mTmpBlurRegionsArray.length - + " regions for frame " + frameNumber); - } - for (int i = 0; i < mTmpBlurRegionsArray.length; i++) { - mTmpBlurRegionsArray[i] = mBlurRegions.valueAt(i).toFloatArray(); + Log.d(TAG, blurRegionsForFrame[i].toString()); } + } + return blurRegionsArray; + } - mViewRoot.dispatchBlurRegions(mTmpBlurRegionsArray, frameNumber); + /** + * Called on RenderThread in FrameDrawingCallback. + * Dispatch all blur regions if there are any ui or position updates. + */ + public void dispatchBlurTransactionIfNeeded(long frameNumber, + BlurRegion[] blurRegionsForFrame, boolean hasUiUpdatesForFrame) { + final float[][] blurRegionsArray = getBlurRegionsToDispatchToSf(frameNumber, + blurRegionsForFrame, hasUiUpdatesForFrame); + if (blurRegionsArray != null) { + mViewRoot.dispatchBlurRegions(blurRegionsArray, frameNumber); } } + + } + + /** + * Wrapper for sending blur data to SurfaceFlinger + * Confined to RenderThread. + */ + public static final class BlurRegion { + public final int blurRadius; + public final float cornerRadiusTL; + public final float cornerRadiusTR; + public final float cornerRadiusBL; + public final float cornerRadiusBR; + public final float alpha; + public final Rect rect; + + BlurRegion(BackgroundBlurDrawable drawable) { + alpha = drawable.mAlpha; + blurRadius = drawable.mBlurRadius; + cornerRadiusTL = drawable.mCornerRadiusTL; + cornerRadiusTR = drawable.mCornerRadiusTR; + cornerRadiusBL = drawable.mCornerRadiusBL; + cornerRadiusBR = drawable.mCornerRadiusBR; + rect = drawable.mRect; + } + + /** + * Serializes this class into a float array that's more JNI friendly. + */ + float[] toFloatArray() { + final float[] floatArray = new float[10]; + floatArray[0] = blurRadius; + floatArray[1] = alpha; + floatArray[2] = rect.left; + floatArray[3] = rect.top; + floatArray[4] = rect.right; + floatArray[5] = rect.bottom; + floatArray[6] = cornerRadiusTL; + floatArray[7] = cornerRadiusTR; + floatArray[8] = cornerRadiusBL; + floatArray[9] = cornerRadiusBR; + return floatArray; + } + + @Override + public String toString() { + return "BlurRegion{" + + "blurRadius=" + blurRadius + + ", corners={" + cornerRadiusTL + + "," + cornerRadiusTR + + "," + cornerRadiusBL + + "," + cornerRadiusBR + + "}, alpha=" + alpha + + ", rect=" + rect + + "}"; + } } } diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 54095bd86887..62e1ee19b25e 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -7965,16 +7965,14 @@ public class BatteryStatsImpl extends BatteryStats { /** Adds the given energy to the given standard energy bucket for this uid. */ private void addEnergyToStandardBucketLocked(long energyDeltaUJ, - @StandardEnergyBucket int energyBucket, boolean accumulate) { + @StandardEnergyBucket int energyBucket) { getOrCreateMeasuredEnergyStatsLocked() - .updateStandardBucket(energyBucket, energyDeltaUJ, accumulate); + .updateStandardBucket(energyBucket, energyDeltaUJ); } /** Adds the given energy to the given custom energy bucket for this uid. */ - private void addEnergyToCustomBucketLocked(long energyDeltaUJ, int energyBucket, - boolean accumulate) { - getOrCreateMeasuredEnergyStatsLocked() - .updateCustomBucket(energyBucket, energyDeltaUJ, accumulate); + private void addEnergyToCustomBucketLocked(long energyDeltaUJ, int energyBucket) { + getOrCreateMeasuredEnergyStatsLocked().updateCustomBucket(energyBucket, energyDeltaUJ); } /** @@ -12468,7 +12466,7 @@ public class BatteryStatsImpl extends BatteryStats { return; } - mGlobalMeasuredEnergyStats.updateStandardBucket(energyBucket, energyUJ, true); + mGlobalMeasuredEnergyStats.updateStandardBucket(energyBucket, energyUJ); // Now we blame individual apps, but only if the display was ON. if (energyBucket != MeasuredEnergyStats.ENERGY_BUCKET_SCREEN_ON) { @@ -12506,7 +12504,7 @@ public class BatteryStatsImpl extends BatteryStats { final long appDisplayEnergyMJ = (totalDisplayEnergyMJ * fgTimeMs + (totalFgTimeMs / 2)) / totalFgTimeMs; - uid.addEnergyToStandardBucketLocked(appDisplayEnergyMJ * 1000, energyBucket, true); + uid.addEnergyToStandardBucketLocked(appDisplayEnergyMJ * 1000, energyBucket); // To mitigate round-off errors, remove this app from numerator & denominator totals totalDisplayEnergyMJ -= appDisplayEnergyMJ; @@ -12533,7 +12531,7 @@ public class BatteryStatsImpl extends BatteryStats { if (mGlobalMeasuredEnergyStats == null) return; if (!mOnBatteryInternal || mIgnoreNextExternalStats || totalEnergyUJ <= 0) return; - mGlobalMeasuredEnergyStats.updateCustomBucket(customEnergyBucket, totalEnergyUJ, true); + mGlobalMeasuredEnergyStats.updateCustomBucket(customEnergyBucket, totalEnergyUJ); if (uidEnergies == null) return; final int numUids = uidEnergies.size(); @@ -12543,7 +12541,7 @@ public class BatteryStatsImpl extends BatteryStats { if (uidEnergyUJ == 0) continue; final Uid uidObj = getAvailableUidStatsLocked(uidInt); if (uidObj != null) { - uidObj.addEnergyToCustomBucketLocked(uidEnergyUJ, customEnergyBucket, true); + uidObj.addEnergyToCustomBucketLocked(uidEnergyUJ, customEnergyBucket); } else { // Ignore any uid not already known to BatteryStats, rather than creating a new Uid. // Otherwise we could end up reviving dead Uids. Note that the CPU data is updated diff --git a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java index eef9fa74e83a..0163acc295fb 100644 --- a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java +++ b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java @@ -22,10 +22,12 @@ import android.os.BatteryStats; import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; import android.os.Bundle; -import android.os.SystemClock; +import android.os.UidBatteryConsumer; import android.os.UserHandle; import android.util.SparseArray; +import com.android.internal.annotations.VisibleForTesting; + import java.util.ArrayList; import java.util.List; @@ -85,7 +87,7 @@ public class BatteryUsageStatsProvider { } /** - * Returns a snapshot of battery attribution data. + * Returns snapshots of battery attribution data, one per supplied query. */ public List<BatteryUsageStats> getBatteryUsageStats(List<BatteryUsageStatsQuery> queries) { @@ -112,12 +114,19 @@ public class BatteryUsageStatsProvider { return results; } - private BatteryUsageStats getBatteryUsageStats(BatteryUsageStatsQuery query) { + /** + * Returns a snapshot of battery attribution data. + */ + @VisibleForTesting + public BatteryUsageStats getBatteryUsageStats(BatteryUsageStatsQuery query) { + final long realtimeUs = mStats.mClocks.elapsedRealtime() * 1000; + final long uptimeUs = mStats.mClocks.uptimeMillis() * 1000; + final long[] customMeasuredEnergiesMicroJoules = mStats.getCustomMeasuredEnergiesMicroJoules(); final int customPowerComponentCount = customMeasuredEnergiesMicroJoules != null - ? customMeasuredEnergiesMicroJoules.length - : 0; + ? customMeasuredEnergiesMicroJoules.length + : 0; // TODO(b/174186358): read extra time component number from configuration final int customTimeComponentCount = 0; @@ -128,12 +137,14 @@ public class BatteryUsageStatsProvider { SparseArray<? extends BatteryStats.Uid> uidStats = mStats.getUidStats(); for (int i = uidStats.size() - 1; i >= 0; i--) { - batteryUsageStatsBuilder.getOrCreateUidBatteryConsumerBuilder(uidStats.valueAt(i)); + final BatteryStats.Uid uid = uidStats.valueAt(i); + batteryUsageStatsBuilder.getOrCreateUidBatteryConsumerBuilder(uid) + .setTimeInStateMs(UidBatteryConsumer.STATE_BACKGROUND, + getProcessBackgroundTimeMs(uid, realtimeUs)) + .setTimeInStateMs(UidBatteryConsumer.STATE_FOREGROUND, + getProcessForegroundTimeMs(uid, realtimeUs)); } - final long realtimeUs = SystemClock.elapsedRealtime() * 1000; - final long uptimeUs = SystemClock.uptimeMillis() * 1000; - final List<PowerCalculator> powerCalculators = getPowerCalculators(); for (int i = 0, count = powerCalculators.size(); i < count; i++) { PowerCalculator powerCalculator = powerCalculators.get(i); @@ -143,4 +154,35 @@ public class BatteryUsageStatsProvider { return batteryUsageStatsBuilder.build(); } + + private long getProcessForegroundTimeMs(BatteryStats.Uid uid, long realtimeUs) { + final long topStateDurationMs = uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_TOP, + realtimeUs, BatteryStats.STATS_SINCE_CHARGED) / 1000; + + long foregroundActivityDurationMs = 0; + final BatteryStats.Timer foregroundActivityTimer = uid.getForegroundActivityTimer(); + if (foregroundActivityTimer != null) { + foregroundActivityDurationMs = foregroundActivityTimer.getTotalTimeLocked(realtimeUs, + BatteryStats.STATS_SINCE_CHARGED) / 1000; + } + + // Use the min value of STATE_TOP time and foreground activity time, since both of these + // times are imprecise + final long foregroundDurationMs = Math.min(topStateDurationMs, + foregroundActivityDurationMs); + + long foregroundServiceDurationMs = 0; + final BatteryStats.Timer foregroundServiceTimer = uid.getForegroundServiceTimer(); + if (foregroundServiceTimer != null) { + foregroundServiceDurationMs = foregroundServiceTimer.getTotalTimeLocked(realtimeUs, + BatteryStats.STATS_SINCE_CHARGED) / 1000; + } + + return foregroundDurationMs + foregroundServiceDurationMs; + } + + private long getProcessBackgroundTimeMs(BatteryStats.Uid uid, long realtimeUs) { + return uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_BACKGROUND, realtimeUs, + BatteryStats.STATS_SINCE_CHARGED) / 1000; + } } diff --git a/core/java/com/android/internal/os/TEST_MAPPING b/core/java/com/android/internal/os/TEST_MAPPING index 9698f190a419..791e9ad5ef9d 100644 --- a/core/java/com/android/internal/os/TEST_MAPPING +++ b/core/java/com/android/internal/os/TEST_MAPPING @@ -1,6 +1,23 @@ { "presubmit": [ { + "file_patterns": ["Battery[^/]*\\.java"], + "name": "FrameworksCoreTests", + "options": [ + { "include-filter": "com.android.internal.os.BatteryStatsTests" }, + { "exclude-annotation": "com.android.internal.os.SkipPresubmit" } + ] + }, + { + "file_patterns": ["Battery[^/]*\\.java"], + "name": "FrameworksServicesTests", + "options": [ + { "include-filter": "com.android.server.am.BatteryStatsServiceTest" }, + { "include-filter": "com.android.server.am.MeasuredEnergySnapshotTest" }, + { "include-filter": "com.android.server.am.BatteryExternalStatsWorkerTest" } + ] + }, + { "name": "FrameworksCoreTests", "options": [ { diff --git a/core/java/com/android/internal/os/UsageBasedPowerEstimator.java b/core/java/com/android/internal/os/UsageBasedPowerEstimator.java index 5910b61f0f6f..fd5201431eab 100644 --- a/core/java/com/android/internal/os/UsageBasedPowerEstimator.java +++ b/core/java/com/android/internal/os/UsageBasedPowerEstimator.java @@ -32,6 +32,10 @@ public class UsageBasedPowerEstimator { mAveragePowerMahPerMs = averagePowerMilliAmp / MILLIS_IN_HOUR; } + public boolean isSupported() { + return mAveragePowerMahPerMs != 0; + } + /** * Given a {@link BatteryStats.Timer}, returns the accumulated duration. */ diff --git a/core/java/com/android/internal/os/WifiPowerCalculator.java b/core/java/com/android/internal/os/WifiPowerCalculator.java index 63763f76c6ed..98f613fc1c40 100644 --- a/core/java/com/android/internal/os/WifiPowerCalculator.java +++ b/core/java/com/android/internal/os/WifiPowerCalculator.java @@ -15,8 +15,13 @@ */ package com.android.internal.os; +import android.os.BatteryConsumer; import android.os.BatteryStats; +import android.os.BatteryUsageStats; +import android.os.BatteryUsageStatsQuery; import android.os.Process; +import android.os.SystemBatteryConsumer; +import android.os.UidBatteryConsumer; import android.os.UserHandle; import android.util.Log; import android.util.SparseArray; @@ -30,25 +35,93 @@ import java.util.List; public class WifiPowerCalculator extends PowerCalculator { private static final boolean DEBUG = BatteryStatsHelper.DEBUG; private static final String TAG = "WifiPowerCalculator"; - private final double mIdleCurrentMa; - private final double mTxCurrentMa; - private final double mRxCurrentMa; - private final PowerProfile mPowerProfile; + private final UsageBasedPowerEstimator mIdlePowerEstimator; + private final UsageBasedPowerEstimator mTxPowerEstimator; + private final UsageBasedPowerEstimator mRxPowerEstimator; + private final UsageBasedPowerEstimator mPowerOnPowerEstimator; + private final UsageBasedPowerEstimator mScanPowerEstimator; + private final UsageBasedPowerEstimator mBatchScanPowerEstimator; private final boolean mHasWifiPowerController; - private double mTotalAppPowerDrain = 0; - private long mTotalAppRunningTime = 0; - private WifiPowerEstimator mWifiPowerEstimator; - private boolean mHasWifiPowerReporting; + private final double mWifiPowerPerPacket; + + private static class PowerDurationAndTraffic { + public double powerMah; + public long durationMs; + + public long wifiRxPackets; + public long wifiTxPackets; + public long wifiRxBytes; + public long wifiTxBytes; + } public WifiPowerCalculator(PowerProfile profile) { - mPowerProfile = profile; + mPowerOnPowerEstimator = new UsageBasedPowerEstimator( + profile.getAveragePower(PowerProfile.POWER_WIFI_ON)); + mScanPowerEstimator = new UsageBasedPowerEstimator( + profile.getAveragePower(PowerProfile.POWER_WIFI_SCAN)); + mBatchScanPowerEstimator = new UsageBasedPowerEstimator( + profile.getAveragePower(PowerProfile.POWER_WIFI_BATCHED_SCAN)); + mIdlePowerEstimator = new UsageBasedPowerEstimator( + profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_IDLE)); + mTxPowerEstimator = new UsageBasedPowerEstimator( + profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_TX)); + mRxPowerEstimator = new UsageBasedPowerEstimator( + profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_RX)); + + mWifiPowerPerPacket = getWifiPowerPerPacket(profile); + + mHasWifiPowerController = + mIdlePowerEstimator.isSupported() && mTxPowerEstimator.isSupported() + && mRxPowerEstimator.isSupported(); + } + + @Override + public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats, + long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) { + + // batteryStats.hasWifiActivityReporting can change if we get energy data at a later point, + // so always check this field. + final boolean hasWifiPowerReporting = + mHasWifiPowerController && batteryStats.hasWifiActivityReporting(); + + final SystemBatteryConsumer.Builder systemBatteryConsumerBuilder = + builder.getOrCreateSystemBatteryConsumerBuilder( + SystemBatteryConsumer.DRAIN_TYPE_WIFI); - mIdleCurrentMa = profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_IDLE); - mTxCurrentMa = profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_TX); - mRxCurrentMa = profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_RX); + long totalAppDurationMs = 0; + double totalAppPowerMah = 0; + final PowerDurationAndTraffic powerDurationAndTraffic = new PowerDurationAndTraffic(); + final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders = + builder.getUidBatteryConsumerBuilders(); + for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) { + final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i); + calculateApp(powerDurationAndTraffic, app.getBatteryStatsUid(), rawRealtimeUs, + BatteryStats.STATS_SINCE_CHARGED, + hasWifiPowerReporting); - mHasWifiPowerController = mIdleCurrentMa != 0 && mTxCurrentMa != 0 && mRxCurrentMa != 0; - mWifiPowerEstimator = new WifiPowerEstimator(mPowerProfile); + totalAppDurationMs += powerDurationAndTraffic.durationMs; + totalAppPowerMah += powerDurationAndTraffic.powerMah; + + app.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_WIFI, + powerDurationAndTraffic.durationMs); + app.setConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI, + powerDurationAndTraffic.powerMah); + + if (app.getUid() == Process.WIFI_UID) { + systemBatteryConsumerBuilder.addUidBatteryConsumer(app); + app.excludeFromBatteryUsageStats(); + } + } + + calculateRemaining(powerDurationAndTraffic, batteryStats, rawRealtimeUs, + BatteryStats.STATS_SINCE_CHARGED, + hasWifiPowerReporting, totalAppDurationMs, totalAppPowerMah); + + systemBatteryConsumerBuilder + .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_WIFI, + powerDurationAndTraffic.durationMs) + .setConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI, + powerDurationAndTraffic.powerMah); } /** @@ -64,100 +137,151 @@ public class WifiPowerCalculator extends PowerCalculator { // batteryStats.hasWifiActivityReporting can change if we get energy data at a later point, // so always check this field. - mHasWifiPowerReporting = mHasWifiPowerController && batteryStats.hasWifiActivityReporting(); - - super.calculate(sippers, batteryStats, rawRealtimeUs, rawUptimeUs, statsType, asUsers); + final boolean hasWifiPowerReporting = + mHasWifiPowerController && batteryStats.hasWifiActivityReporting(); - BatterySipper bs = new BatterySipper(BatterySipper.DrainType.WIFI, null, 0); - calculateRemaining(bs, batteryStats, rawRealtimeUs, rawUptimeUs, statsType); + final BatterySipper bs = new BatterySipper(BatterySipper.DrainType.WIFI, null, 0); + long totalAppDurationMs = 0; + double totalAppPowerMah = 0; + final PowerDurationAndTraffic powerDurationAndTraffic = new PowerDurationAndTraffic(); for (int i = sippers.size() - 1; i >= 0; i--) { - BatterySipper app = sippers.get(i); - if (app.getUid() == Process.WIFI_UID) { - if (DEBUG) Log.d(TAG, "WiFi adding sipper " + app + ": cpu=" + app.cpuTimeMs); - app.isAggregated = true; - bs.add(app); + final BatterySipper app = sippers.get(i); + if (app.drainType == BatterySipper.DrainType.APP) { + calculateApp(powerDurationAndTraffic, app.uidObj, rawRealtimeUs, statsType, + hasWifiPowerReporting); + + totalAppDurationMs += powerDurationAndTraffic.durationMs; + totalAppPowerMah += powerDurationAndTraffic.powerMah; + + app.wifiPowerMah = powerDurationAndTraffic.powerMah; + app.wifiRunningTimeMs = powerDurationAndTraffic.durationMs; + app.wifiRxBytes = powerDurationAndTraffic.wifiRxBytes; + app.wifiRxPackets = powerDurationAndTraffic.wifiRxPackets; + app.wifiTxBytes = powerDurationAndTraffic.wifiTxBytes; + app.wifiTxPackets = powerDurationAndTraffic.wifiTxPackets; + if (app.getUid() == Process.WIFI_UID) { + if (DEBUG) Log.d(TAG, "WiFi adding sipper " + app + ": cpu=" + app.cpuTimeMs); + app.isAggregated = true; + bs.add(app); + } } } + + calculateRemaining(powerDurationAndTraffic, batteryStats, rawRealtimeUs, statsType, + hasWifiPowerReporting, totalAppDurationMs, totalAppPowerMah); + + bs.wifiRunningTimeMs += powerDurationAndTraffic.durationMs; + bs.wifiPowerMah += powerDurationAndTraffic.powerMah; + if (bs.sumPower() > 0) { sippers.add(bs); } } - @Override - protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs, - long rawUptimeUs, int statsType) { - if (!mHasWifiPowerReporting) { - mWifiPowerEstimator.calculateApp(app, u, rawRealtimeUs, rawUptimeUs, statsType); - return; - } - - final BatteryStats.ControllerActivityCounter counter = u.getWifiControllerActivity(); - if (counter == null) { - return; - } - - final long idleTime = counter.getIdleTimeCounter().getCountLocked(statsType); - final long txTime = counter.getTxTimeCounters()[0].getCountLocked(statsType); - final long rxTime = counter.getRxTimeCounter().getCountLocked(statsType); - app.wifiRunningTimeMs = idleTime + rxTime + txTime; - mTotalAppRunningTime += app.wifiRunningTimeMs; - - app.wifiPowerMah = - ((idleTime * mIdleCurrentMa) + (txTime * mTxCurrentMa) + (rxTime * mRxCurrentMa)) - / (1000 * 60 * 60); - mTotalAppPowerDrain += app.wifiPowerMah; - - app.wifiRxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_WIFI_RX_DATA, + private void calculateApp(PowerDurationAndTraffic powerDurationAndTraffic, BatteryStats.Uid u, + long rawRealtimeUs, + int statsType, boolean hasWifiPowerReporting) { + powerDurationAndTraffic.wifiRxPackets = u.getNetworkActivityPackets( + BatteryStats.NETWORK_WIFI_RX_DATA, statsType); - app.wifiTxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_WIFI_TX_DATA, + powerDurationAndTraffic.wifiTxPackets = u.getNetworkActivityPackets( + BatteryStats.NETWORK_WIFI_TX_DATA, statsType); - app.wifiRxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_WIFI_RX_DATA, + powerDurationAndTraffic.wifiRxBytes = u.getNetworkActivityBytes( + BatteryStats.NETWORK_WIFI_RX_DATA, statsType); - app.wifiTxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_WIFI_TX_DATA, + powerDurationAndTraffic.wifiTxBytes = u.getNetworkActivityBytes( + BatteryStats.NETWORK_WIFI_TX_DATA, statsType); - if (DEBUG && app.wifiPowerMah != 0) { - Log.d(TAG, "UID " + u.getUid() + ": idle=" + idleTime + "ms rx=" + rxTime + "ms tx=" + - txTime + "ms power=" + formatCharge(app.wifiPowerMah)); - } - } + if (hasWifiPowerReporting) { + final BatteryStats.ControllerActivityCounter counter = u.getWifiControllerActivity(); + if (counter != null) { + final long idleTime = counter.getIdleTimeCounter().getCountLocked(statsType); + final long txTime = counter.getTxTimeCounters()[0].getCountLocked(statsType); + final long rxTime = counter.getRxTimeCounter().getCountLocked(statsType); + + powerDurationAndTraffic.durationMs = idleTime + rxTime + txTime; + powerDurationAndTraffic.powerMah = mIdlePowerEstimator.calculatePower(idleTime) + + mTxPowerEstimator.calculatePower(txTime) + + mRxPowerEstimator.calculatePower(rxTime); - private void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs, - long rawUptimeUs, int statsType) { - if (!mHasWifiPowerReporting) { - mWifiPowerEstimator.calculateRemaining(app, stats, rawRealtimeUs, rawUptimeUs, - statsType); - return; + if (DEBUG && powerDurationAndTraffic.powerMah != 0) { + Log.d(TAG, "UID " + u.getUid() + ": idle=" + idleTime + "ms rx=" + rxTime + + "ms tx=" + txTime + "ms power=" + formatCharge( + powerDurationAndTraffic.powerMah)); + } + } + } else { + final double wifiPacketPower = ( + powerDurationAndTraffic.wifiRxPackets + powerDurationAndTraffic.wifiTxPackets) + * mWifiPowerPerPacket; + final long wifiRunningTime = u.getWifiRunningTime(rawRealtimeUs, statsType) / 1000; + final long wifiScanTimeMs = u.getWifiScanTime(rawRealtimeUs, statsType) / 1000; + long batchScanTimeMs = 0; + for (int bin = 0; bin < BatteryStats.Uid.NUM_WIFI_BATCHED_SCAN_BINS; bin++) { + batchScanTimeMs += u.getWifiBatchedScanTime(bin, rawRealtimeUs, statsType) / 1000; + } + + powerDurationAndTraffic.durationMs = wifiRunningTime; + powerDurationAndTraffic.powerMah = wifiPacketPower + + mPowerOnPowerEstimator.calculatePower(wifiRunningTime) + + mScanPowerEstimator.calculatePower(wifiScanTimeMs) + + mBatchScanPowerEstimator.calculatePower(batchScanTimeMs); + + if (DEBUG && powerDurationAndTraffic.powerMah != 0) { + Log.d(TAG, "UID " + u.getUid() + ": power=" + formatCharge( + powerDurationAndTraffic.powerMah)); + } } + } - final BatteryStats.ControllerActivityCounter counter = stats.getWifiControllerActivity(); + private void calculateRemaining(PowerDurationAndTraffic powerDurationAndTraffic, + BatteryStats stats, long rawRealtimeUs, + int statsType, boolean hasWifiPowerReporting, long totalAppDurationMs, + double totalAppPowerMah) { + long totalDurationMs; + double totalPowerMah; + if (hasWifiPowerReporting) { + final BatteryStats.ControllerActivityCounter counter = + stats.getWifiControllerActivity(); - final long idleTimeMs = counter.getIdleTimeCounter().getCountLocked(statsType); - final long txTimeMs = counter.getTxTimeCounters()[0].getCountLocked(statsType); - final long rxTimeMs = counter.getRxTimeCounter().getCountLocked(statsType); + final long idleTimeMs = counter.getIdleTimeCounter().getCountLocked(statsType); + final long txTimeMs = counter.getTxTimeCounters()[0].getCountLocked(statsType); + final long rxTimeMs = counter.getRxTimeCounter().getCountLocked(statsType); - app.wifiRunningTimeMs = Math.max(0, - (idleTimeMs + rxTimeMs + txTimeMs) - mTotalAppRunningTime); + totalDurationMs = idleTimeMs + rxTimeMs + txTimeMs; - double powerDrainMah = counter.getPowerCounter().getCountLocked(statsType) - / (double) (1000 * 60 * 60); - if (powerDrainMah == 0) { - // Some controllers do not report power drain, so we can calculate it here. - powerDrainMah = ((idleTimeMs * mIdleCurrentMa) + (txTimeMs * mTxCurrentMa) - + (rxTimeMs * mRxCurrentMa)) / (1000 * 60 * 60); + totalPowerMah = + counter.getPowerCounter().getCountLocked(statsType) / (double) (1000 * 60 * 60); + if (totalPowerMah == 0) { + // Some controllers do not report power drain, so we can calculate it here. + totalPowerMah = mIdlePowerEstimator.calculatePower(idleTimeMs) + + mTxPowerEstimator.calculatePower(txTimeMs) + + mRxPowerEstimator.calculatePower(rxTimeMs); + } + } else { + totalDurationMs = stats.getGlobalWifiRunningTime(rawRealtimeUs, statsType) / 1000; + totalPowerMah = mPowerOnPowerEstimator.calculatePower(totalDurationMs); } - app.wifiPowerMah = Math.max(0, powerDrainMah - mTotalAppPowerDrain); + + powerDurationAndTraffic.durationMs = Math.max(0, totalDurationMs - totalAppDurationMs); + powerDurationAndTraffic.powerMah = Math.max(0, totalPowerMah - totalAppPowerMah); if (DEBUG) { - Log.d(TAG, "left over WiFi power: " + formatCharge(app.wifiPowerMah)); + Log.d(TAG, "left over WiFi power: " + formatCharge(powerDurationAndTraffic.powerMah)); } } - @Override - public void reset() { - mTotalAppPowerDrain = 0; - mTotalAppRunningTime = 0; - mWifiPowerEstimator.reset(); + /** + * Return estimated power per Wi-Fi packet in mAh/packet where 1 packet = 2 KB. + */ + private static double getWifiPowerPerPacket(PowerProfile profile) { + // TODO(b/179392913): Extract average bit rates from system + final long wifiBps = 1000000; + final double averageWifiActivePower = + profile.getAveragePower(PowerProfile.POWER_WIFI_ACTIVE) / 3600; + return averageWifiActivePower / (((double) wifiBps) / 8 / 2048); } } diff --git a/core/java/com/android/internal/os/WifiPowerEstimator.java b/core/java/com/android/internal/os/WifiPowerEstimator.java deleted file mode 100644 index d0a105cfd958..000000000000 --- a/core/java/com/android/internal/os/WifiPowerEstimator.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.internal.os; - -import android.os.BatteryStats; -import android.os.Process; -import android.os.UserHandle; -import android.util.Log; -import android.util.SparseArray; - -import java.util.List; - -/** - * Estimates WiFi power usage based on timers in BatteryStats. - */ -public class WifiPowerEstimator extends PowerCalculator { - private static final boolean DEBUG = BatteryStatsHelper.DEBUG; - private static final String TAG = "WifiPowerEstimator"; - private final double mWifiPowerPerPacket; - private final double mWifiPowerOn; - private final double mWifiPowerScan; - private final double mWifiPowerBatchScan; - private long mTotalAppWifiRunningTimeMs = 0; - - public WifiPowerEstimator(PowerProfile profile) { - mWifiPowerPerPacket = getWifiPowerPerPacket(profile); - mWifiPowerOn = profile.getAveragePower(PowerProfile.POWER_WIFI_ON); - mWifiPowerScan = profile.getAveragePower(PowerProfile.POWER_WIFI_SCAN); - mWifiPowerBatchScan = profile.getAveragePower(PowerProfile.POWER_WIFI_BATCHED_SCAN); - } - - @Override - public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats, - long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) { - super.calculate(sippers, batteryStats, rawRealtimeUs, rawUptimeUs, statsType, asUsers); - - BatterySipper bs = new BatterySipper(BatterySipper.DrainType.WIFI, null, 0); - calculateRemaining(bs, batteryStats, rawRealtimeUs, rawUptimeUs, statsType); - - for (int i = sippers.size() - 1; i >= 0; i--) { - BatterySipper app = sippers.get(i); - if (app.getUid() == Process.WIFI_UID) { - if (DEBUG) Log.d(TAG, "WiFi adding sipper " + app + ": cpu=" + app.cpuTimeMs); - app.isAggregated = true; - bs.add(app); - } - } - if (bs.sumPower() > 0) { - sippers.add(bs); - } - } - - /** - * Return estimated power per Wi-Fi packet in mAh/packet where 1 packet = 2 KB. - */ - private static double getWifiPowerPerPacket(PowerProfile profile) { - final long WIFI_BPS = 1000000; // TODO: Extract average bit rates from system - final double WIFI_POWER = profile.getAveragePower(PowerProfile.POWER_WIFI_ACTIVE) - / 3600; - return WIFI_POWER / (((double)WIFI_BPS) / 8 / 2048); - } - - @Override - protected void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs, - long rawUptimeUs, int statsType) { - app.wifiRxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_WIFI_RX_DATA, - statsType); - app.wifiTxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_WIFI_TX_DATA, - statsType); - app.wifiRxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_WIFI_RX_DATA, - statsType); - app.wifiTxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_WIFI_TX_DATA, - statsType); - - final double wifiPacketPower = (app.wifiRxPackets + app.wifiTxPackets) - * mWifiPowerPerPacket; - - app.wifiRunningTimeMs = u.getWifiRunningTime(rawRealtimeUs, statsType) / 1000; - mTotalAppWifiRunningTimeMs += app.wifiRunningTimeMs; - final double wifiLockPower = (app.wifiRunningTimeMs * mWifiPowerOn) / (1000*60*60); - - final long wifiScanTimeMs = u.getWifiScanTime(rawRealtimeUs, statsType) / 1000; - final double wifiScanPower = (wifiScanTimeMs * mWifiPowerScan) / (1000*60*60); - - double wifiBatchScanPower = 0; - for (int bin = 0; bin < BatteryStats.Uid.NUM_WIFI_BATCHED_SCAN_BINS; bin++) { - final long batchScanTimeMs = - u.getWifiBatchedScanTime(bin, rawRealtimeUs, statsType) / 1000; - final double batchScanPower = (batchScanTimeMs * mWifiPowerBatchScan) / (1000*60*60); - wifiBatchScanPower += batchScanPower; - } - - app.wifiPowerMah = wifiPacketPower + wifiLockPower + wifiScanPower + wifiBatchScanPower; - if (DEBUG && app.wifiPowerMah != 0) { - Log.d(TAG, "UID " + u.getUid() + ": power=" + formatCharge(app.wifiPowerMah)); - } - } - - void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs, - long rawUptimeUs, int statsType) { - final long totalRunningTimeMs = stats.getGlobalWifiRunningTime(rawRealtimeUs, statsType) - / 1000; - final double powerDrain = ((totalRunningTimeMs - mTotalAppWifiRunningTimeMs) * mWifiPowerOn) - / (1000*60*60); - app.wifiRunningTimeMs = totalRunningTimeMs; - app.wifiPowerMah = Math.max(0, powerDrain); - } - - @Override - public void reset() { - mTotalAppWifiRunningTimeMs = 0; - } -} diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index b99c953fa4e8..6b1d408bee9a 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -18,8 +18,8 @@ package com.android.internal.os; import static android.system.OsConstants.O_CLOEXEC; -import static com.android.internal.os.ZygoteConnectionConstants.MAX_ZYGOTE_ARGC; - +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.pm.ApplicationInfo; import android.net.Credentials; import android.net.LocalServerSocket; @@ -36,17 +36,16 @@ import android.util.Log; import com.android.internal.net.NetworkUtilsInternal; +import dalvik.annotation.optimization.CriticalNative; import dalvik.annotation.optimization.FastNative; import dalvik.system.ZygoteHooks; import libcore.io.IoUtils; -import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.FileDescriptor; import java.io.IOException; -import java.io.InputStreamReader; /** @hide */ public final class Zygote { @@ -238,6 +237,8 @@ public final class Zygote { */ public static final String CHILD_ZYGOTE_UID_RANGE_END = "--uid-range-end="; + private static final String TAG = "Zygote"; + /** Prefix prepended to socket names created by init */ private static final String ANDROID_SOCKET_PREFIX = "ANDROID_SOCKET_"; @@ -408,6 +409,10 @@ public final class Zygote { // Note that this event ends at the end of handleChildProc. Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork"); + if (gids != null && gids.length > 0) { + NetworkUtilsInternal.setAllowNetworkingForProcess(containsInetGid(gids)); + } + // Set the Java Language thread priority to the default value for new apps. Thread.currentThread().setPriority(Thread.NORM_PRIORITY); @@ -580,114 +585,163 @@ public final class Zygote { private static native int nativeGetUsapPoolEventFD(); /** - * Fork a new unspecialized app process from the zygote + * Fork a new unspecialized app process from the zygote. Adds the Usap to the native + * Usap table. * * @param usapPoolSocket The server socket the USAP will call accept on - * @param sessionSocketRawFDs Anonymous session sockets that are currently open - * @param isPriorityFork Value controlling the process priority level until accept is called - * @return In the Zygote process this function will always return null; in unspecialized app - * processes this function will return a Runnable object representing the new - * application that is passed up from usapMain. + * @param sessionSocketRawFDs Anonymous session sockets that are currently open. + * These are closed in the child. + * @param isPriorityFork Raise the initial process priority level because this is on the + * critical path for application startup. + * @return In the child process, this returns a Runnable that waits for specialization + * info to start an app process. In the sygote/parent process this returns null. */ - static Runnable forkUsap(LocalServerSocket usapPoolSocket, - int[] sessionSocketRawFDs, - boolean isPriorityFork) { - FileDescriptor[] pipeFDs; + static @Nullable Runnable forkUsap(LocalServerSocket usapPoolSocket, + int[] sessionSocketRawFDs, + boolean isPriorityFork) { + FileDescriptor readFD; + FileDescriptor writeFD; try { - pipeFDs = Os.pipe2(O_CLOEXEC); + FileDescriptor[] pipeFDs = Os.pipe2(O_CLOEXEC); + readFD = pipeFDs[0]; + writeFD = pipeFDs[1]; } catch (ErrnoException errnoEx) { throw new IllegalStateException("Unable to create USAP pipe.", errnoEx); } - int pid = - nativeForkUsap(pipeFDs[0].getInt$(), pipeFDs[1].getInt$(), - sessionSocketRawFDs, isPriorityFork); - + int pid = nativeForkApp(readFD.getInt$(), writeFD.getInt$(), + sessionSocketRawFDs, /*argsKnown=*/ false, isPriorityFork); if (pid == 0) { - IoUtils.closeQuietly(pipeFDs[0]); - return usapMain(usapPoolSocket, pipeFDs[1]); + IoUtils.closeQuietly(readFD); + return childMain(null, usapPoolSocket, writeFD); + } else if (pid == -1) { + // Fork failed. + return null; } else { - // The read-end of the pipe will be closed by the native code. - // See removeUsapTableEntry(); - IoUtils.closeQuietly(pipeFDs[1]); + // readFD will be closed by the native code. See removeUsapTableEntry(); + IoUtils.closeQuietly(writeFD); + nativeAddUsapTableEntry(pid, readFD.getInt$()); return null; } } - private static native int nativeForkUsap(int readPipeFD, - int writePipeFD, - int[] sessionSocketRawFDs, - boolean isPriorityFork); + private static native int nativeForkApp(int readPipeFD, + int writePipeFD, + int[] sessionSocketRawFDs, + boolean argsKnown, + boolean isPriorityFork); + + /** + * Add an entry for a new Usap to the table maintained in native code. + */ + @CriticalNative + private static native void nativeAddUsapTableEntry(int pid, int readPipeFD); + + /** + * Fork a new app process from the zygote. argBuffer contains a fork command that + * request neither a child zygote, nor a wrapped process. Continue to accept connections + * on the specified socket, use those to refill argBuffer, and continue to process + * sufficiently simple fork requests. We presume that the only open file descriptors + * requiring special treatment are the session socket embedded in argBuffer, and + * zygoteSocket. + * @param argBuffer containing initial command and the connected socket from which to + * read more + * @param zygoteSocket socket from which to obtain new connections when current argBuffer + * one is disconnected + * @param expectedUId Uid of peer for initial requests. Subsequent requests from a different + * peer will cause us to return rather than perform the requested fork. + * @param minUid Minimum Uid enforced for all but first fork request. The caller checks + * the Uid policy for the initial request. + * @param firstNiceName name of first created process. Used for error reporting only. + * @return A Runnable in each child process, null in the parent. + * If this returns in then argBuffer still contains a command needing to be executed. + */ + static @Nullable Runnable forkSimpleApps(@NonNull ZygoteCommandBuffer argBuffer, + @NonNull FileDescriptor zygoteSocket, + int expectedUid, + int minUid, + @Nullable String firstNiceName) { + boolean in_child = + argBuffer.forkRepeatedly(zygoteSocket, expectedUid, minUid, firstNiceName); + if (in_child) { + return childMain(argBuffer, /*usapPoolSocket=*/null, /*writePipe=*/null); + } else { + return null; + } + } /** - * This function is used by unspecialized app processes to wait for specialization requests from - * the system server. + * Specialize the current process into one described by argBuffer or the command read from + * usapPoolSocket. Exactly one of those must be null. If we are given an argBuffer, we close + * it. Used both for a specializing a USAP process, and for process creation without USAPs. + * In both cases, we specialize the process after first returning to Java code. * * @param writePipe The write end of the reporting pipe used to communicate with the poll loop * of the ZygoteServer. * @return A runnable oject representing the new application. */ - private static Runnable usapMain(LocalServerSocket usapPoolSocket, - FileDescriptor writePipe) { + private static Runnable childMain(@Nullable ZygoteCommandBuffer argBuffer, + @Nullable LocalServerSocket usapPoolSocket, + FileDescriptor writePipe) { final int pid = Process.myPid(); - Process.setArgV0(Process.is64Bit() ? "usap64" : "usap32"); - LocalSocket sessionSocket = null; DataOutputStream usapOutputStream = null; - Credentials peerCredentials = null; ZygoteArguments args = null; - // Change the priority to max before calling accept so we can respond to new specialization - // requests as quickly as possible. This will be reverted to the default priority in the - // native specialization code. - boostUsapPriority(); - - while (true) { - try { - sessionSocket = usapPoolSocket.accept(); - - // Block SIGTERM so we won't be killed if the Zygote flushes the USAP pool. - blockSigTerm(); - - BufferedReader usapReader = - new BufferedReader(new InputStreamReader(sessionSocket.getInputStream())); - usapOutputStream = - new DataOutputStream(sessionSocket.getOutputStream()); + // Block SIGTERM so we won't be killed if the Zygote flushes the USAP pool. + blockSigTerm(); - peerCredentials = sessionSocket.getPeerCredentials(); + LocalSocket sessionSocket = null; + if (argBuffer == null) { + // Read arguments from usapPoolSocket instead. - String[] argStrings = readArgumentList(usapReader); + Process.setArgV0(Process.is64Bit() ? "usap64" : "usap32"); - if (argStrings != null) { - args = new ZygoteArguments(argStrings); + // Change the priority to max before calling accept so we can respond to new + // specialization requests as quickly as possible. This will be reverted to the + // default priority in the native specialization code. + boostUsapPriority(); + while (true) { + ZygoteCommandBuffer tmpArgBuffer = null; + try { + sessionSocket = usapPoolSocket.accept(); + + usapOutputStream = + new DataOutputStream(sessionSocket.getOutputStream()); + Credentials peerCredentials = sessionSocket.getPeerCredentials(); + tmpArgBuffer = new ZygoteCommandBuffer(sessionSocket); + args = ZygoteArguments.getInstance(argBuffer); + applyUidSecurityPolicy(args, peerCredentials); // TODO (chriswailes): Should this only be run for debug builds? validateUsapCommand(args); break; - } else { - Log.e("USAP", "Truncated command received."); - IoUtils.closeQuietly(sessionSocket); - - // Re-enable SIGTERM so the USAP can be flushed from the pool if necessary. - unblockSigTerm(); + } catch (Exception ex) { + Log.e("USAP", ex.getMessage()); } - } catch (Exception ex) { - Log.e("USAP", ex.getMessage()); - IoUtils.closeQuietly(sessionSocket); - // Re-enable SIGTERM so the USAP can be flushed from the pool if necessary. unblockSigTerm(); + IoUtils.closeQuietly(sessionSocket); + IoUtils.closeQuietly(tmpArgBuffer); + blockSigTerm(); } + } else { + try { + args = ZygoteArguments.getInstance(argBuffer); + } catch (Exception ex) { + Log.e("AppStartup", ex.getMessage()); + throw new AssertionError("Failed to parse application start command", ex); + } + // peerCredentials were checked in parent. + } + if (args == null) { + throw new AssertionError("Empty command line"); } - try { - // SIGTERM is blocked on loop exit. This prevents a USAP that is specializing from - // being killed during a pool flush. - - setAppProcessName(args, "USAP"); + // SIGTERM is blocked here. This prevents a USAP that is specializing from being + // killed during a pool flush. - applyUidSecurityPolicy(args, peerCredentials); applyDebuggerSystemProperty(args); int[][] rlimits = null; @@ -696,53 +750,57 @@ public final class Zygote { rlimits = args.mRLimits.toArray(INT_ARRAY_2D); } - // This must happen before the SELinux policy for this process is - // changed when specializing. - try { - // Used by ZygoteProcess.zygoteSendArgsAndGetResult to fill in a - // Process.ProcessStartResult object. - usapOutputStream.writeInt(pid); - } catch (IOException ioEx) { - Log.e("USAP", "Failed to write response to session socket: " - + ioEx.getMessage()); - throw new RuntimeException(ioEx); - } finally { - IoUtils.closeQuietly(sessionSocket); - + if (argBuffer == null) { + // This must happen before the SELinux policy for this process is + // changed when specializing. try { - // This socket is closed using Os.close due to an issue with the implementation - // of LocalSocketImp.close(). Because the raw FD is created by init and then - // loaded from an environment variable (as opposed to being created by the - // LocalSocketImpl itself) the current implementation will not actually close - // the underlying FD. - // - // See b/130309968 for discussion of this issue. - Os.close(usapPoolSocket.getFileDescriptor()); - } catch (ErrnoException ex) { - Log.e("USAP", "Failed to close USAP pool socket"); - throw new RuntimeException(ex); + // Used by ZygoteProcess.zygoteSendArgsAndGetResult to fill in a + // Process.ProcessStartResult object. + usapOutputStream.writeInt(pid); + } catch (IOException ioEx) { + Log.e("USAP", "Failed to write response to session socket: " + + ioEx.getMessage()); + throw new RuntimeException(ioEx); + } finally { + try { + // Since the raw FD is created by init and then loaded from an environment + // variable (as opposed to being created by the LocalSocketImpl itself), + // the LocalSocket/LocalSocketImpl does not own the Os-level socket. See + // the spec for LocalSocket.createConnectedLocalSocket(FileDescriptor fd). + // Thus closing the LocalSocket does not suffice. See b/130309968 for more + // discussion. + FileDescriptor fd = usapPoolSocket.getFileDescriptor(); + usapPoolSocket.close(); + Os.close(fd); + } catch (ErrnoException | IOException ex) { + Log.e("USAP", "Failed to close USAP pool socket"); + throw new RuntimeException(ex); + } } } - try { - ByteArrayOutputStream buffer = - new ByteArrayOutputStream(Zygote.USAP_MANAGEMENT_MESSAGE_BYTES); - DataOutputStream outputStream = new DataOutputStream(buffer); - - // This is written as a long so that the USAP reporting pipe and USAP pool event FD - // handlers in ZygoteServer.runSelectLoop can be unified. These two cases should - // both send/receive 8 bytes. - outputStream.writeLong(pid); - outputStream.flush(); - - Os.write(writePipe, buffer.toByteArray(), 0, buffer.size()); - } catch (Exception ex) { - Log.e("USAP", - String.format("Failed to write PID (%d) to pipe (%d): %s", - pid, writePipe.getInt$(), ex.getMessage())); - throw new RuntimeException(ex); - } finally { - IoUtils.closeQuietly(writePipe); + if (writePipe != null) { + try { + ByteArrayOutputStream buffer = + new ByteArrayOutputStream(Zygote.USAP_MANAGEMENT_MESSAGE_BYTES); + DataOutputStream outputStream = new DataOutputStream(buffer); + + // This is written as a long so that the USAP reporting pipe and USAP pool + // event FD handlers in ZygoteServer.runSelectLoop can be unified. These two + // cases should both send/receive 8 bytes. + // TODO: Needs tweaking to handle the non-Usap invoke-with case, which expects + // a different format. + outputStream.writeLong(pid); + outputStream.flush(); + Os.write(writePipe, buffer.toByteArray(), 0, buffer.size()); + } catch (Exception ex) { + Log.e("USAP", + String.format("Failed to write PID (%d) to pipe (%d): %s", + pid, writePipe.getInt$(), ex.getMessage())); + throw new RuntimeException(ex); + } finally { + IoUtils.closeQuietly(writePipe); + } } specializeAppProcess(args.mUid, args.mGid, args.mGids, @@ -849,13 +907,29 @@ public final class Zygote { return nativeRemoveUsapTableEntry(usapPID); } + @CriticalNative private static native boolean nativeRemoveUsapTableEntry(int usapPID); /** - * uid 1000 (Process.SYSTEM_UID) may specify any uid > 1000 in normal + * Return the minimum child uid that the given peer is allowed to create. + * uid 1000 (Process.SYSTEM_UID) may specify any uid ≥ 1000 in normal * operation. It may also specify any gid and setgroups() list it chooses. * In factory test mode, it may specify any UID. - * + */ + static int minChildUid(Credentials peer) { + if (peer.getUid() == Process.SYSTEM_UID + && FactoryTest.getMode() == FactoryTest.FACTORY_TEST_OFF) { + /* In normal operation, SYSTEM_UID can only specify a restricted + * set of UIDs. In factory test mode, SYSTEM_UID may specify any uid. + */ + return Process.SYSTEM_UID; + } else { + return 0; + } + } + + /* + * Adjust uid and gid arguments, ensuring that the security policy is satisfied. * @param args non-null; zygote spawner arguments * @param peer non-null; peer credentials * @throws ZygoteSecurityException Indicates a security issue when applying the UID based @@ -864,17 +938,10 @@ public final class Zygote { static void applyUidSecurityPolicy(ZygoteArguments args, Credentials peer) throws ZygoteSecurityException { - if (peer.getUid() == Process.SYSTEM_UID) { - /* In normal operation, SYSTEM_UID can only specify a restricted - * set of UIDs. In factory test mode, SYSTEM_UID may specify any uid. - */ - boolean uidRestricted = FactoryTest.getMode() == FactoryTest.FACTORY_TEST_OFF; - - if (uidRestricted && args.mUidSpecified && (args.mUid < Process.SYSTEM_UID)) { - throw new ZygoteSecurityException( - "System UID may not launch process with UID < " - + Process.SYSTEM_UID); - } + if (args.mUidSpecified && (args.mUid < minChildUid(peer))) { + throw new ZygoteSecurityException( + "System UID may not launch process with UID < " + + Process.SYSTEM_UID); } // If not otherwise specified, uid and gid are inherited from peer @@ -960,45 +1027,6 @@ public final class Zygote { } /** - * Reads an argument list from the provided socket - * @return Argument list or null if EOF is reached - * @throws IOException passed straight through - */ - static String[] readArgumentList(BufferedReader socketReader) throws IOException { - int argc; - - try { - String argc_string = socketReader.readLine(); - - if (argc_string == null) { - // EOF reached. - return null; - } - argc = Integer.parseInt(argc_string); - - } catch (NumberFormatException ex) { - Log.e("Zygote", "Invalid Zygote wire format: non-int at argc"); - throw new IOException("Invalid wire format"); - } - - // See bug 1092107: large argc can be used for a DOS attack - if (argc > MAX_ZYGOTE_ARGC) { - throw new IOException("Max arg count exceeded"); - } - - String[] args = new String[argc]; - for (int arg_index = 0; arg_index < argc; arg_index++) { - args[arg_index] = socketReader.readLine(); - if (args[arg_index] == null) { - // We got an unexpected EOF. - throw new IOException("Truncated request"); - } - } - - return args; - } - - /** * Creates a managed LocalServerSocket object using a file descriptor * created by an init.rc script. The init scripts that specify the * sockets name can be found in system/core/rootdir. The socket is bound diff --git a/core/java/com/android/internal/os/ZygoteArguments.java b/core/java/com/android/internal/os/ZygoteArguments.java index 32b808ab2528..65b454d47db2 100644 --- a/core/java/com/android/internal/os/ZygoteArguments.java +++ b/core/java/com/android/internal/os/ZygoteArguments.java @@ -16,8 +16,8 @@ package com.android.internal.os; +import java.io.EOFException; import java.util.ArrayList; -import java.util.Arrays; /** * Handles argument parsing for args related to the zygote spawner. @@ -245,20 +245,34 @@ class ZygoteArguments { /** * Constructs instance and parses args * - * @param args zygote command-line args + * @param args zygote command-line args as ZygoteCommandBuffer, positioned after argument count. */ - ZygoteArguments(String[] args) throws IllegalArgumentException { - parseArgs(args); + private ZygoteArguments(ZygoteCommandBuffer args, int argCount) + throws IllegalArgumentException, EOFException { + parseArgs(args, argCount); + } + + /** + * Return a new ZygoteArguments reflecting the contents of the given ZygoteCommandBuffer. Return + * null if the ZygoteCommandBuffer was positioned at EOF. Assumes the buffer is initially + * positioned at the beginning of the command. + */ + public static ZygoteArguments getInstance(ZygoteCommandBuffer args) + throws IllegalArgumentException, EOFException { + int argCount = args.getCount(); + return argCount == 0 ? null : new ZygoteArguments(args, argCount); } /** * Parses the commandline arguments intended for the Zygote spawner (such as "--setuid=" and - * "--setgid=") and creates an array containing the remaining args. + * "--setgid=") and creates an array containing the remaining args. Return false if we were + * at EOF. * * Per security review bug #1112214, duplicate args are disallowed in critical cases to make * injection harder. */ - private void parseArgs(String[] args) throws IllegalArgumentException { + private void parseArgs(ZygoteCommandBuffer args, int argCount) + throws IllegalArgumentException, EOFException { /* * See android.os.ZygoteProcess.zygoteSendArgsAndGetResult() * Presently the wire format to the zygote process is: @@ -269,13 +283,13 @@ class ZygoteArguments { * the child or -1 on failure. */ - int curArg = 0; - + String unprocessedArg = null; + int curArg = 0; // Index of arg boolean seenRuntimeArgs = false; - boolean expectRuntimeArgs = true; - for ( /* curArg */ ; curArg < args.length; curArg++) { - String arg = args[curArg]; + + for ( /* curArg */ ; curArg < argCount; ++curArg) { + String arg = args.nextArg(); if (arg.equals("--")) { curArg++; @@ -367,7 +381,8 @@ class ZygoteArguments { "Duplicate arg specified"); } try { - mInvokeWith = args[++curArg]; + ++curArg; + mInvokeWith = args.nextArg(); } catch (IndexOutOfBoundsException ex) { throw new IllegalArgumentException( "--invoke-with requires argument"); @@ -397,12 +412,14 @@ class ZygoteArguments { } else if (arg.startsWith("--app-data-dir=")) { mAppDataDir = getAssignmentValue(arg); } else if (arg.equals("--preload-app")) { - mPreloadApp = args[++curArg]; + ++curArg; + mPreloadApp = args.nextArg(); } else if (arg.equals("--preload-package")) { - mPreloadPackage = args[++curArg]; - mPreloadPackageLibs = args[++curArg]; - mPreloadPackageLibFileName = args[++curArg]; - mPreloadPackageCacheKey = args[++curArg]; + curArg += 4; + mPreloadPackage = args.nextArg(); + mPreloadPackageLibs = args.nextArg(); + mPreloadPackageLibFileName = args.nextArg(); + mPreloadPackageCacheKey = args.nextArg(); } else if (arg.equals("--preload-default")) { mPreloadDefault = true; expectRuntimeArgs = false; @@ -411,8 +428,11 @@ class ZygoteArguments { } else if (arg.equals("--set-api-denylist-exemptions")) { // consume all remaining args; this is a stand-alone command, never included // with the regular fork command. - mApiDenylistExemptions = Arrays.copyOfRange(args, curArg + 1, args.length); - curArg = args.length; + mApiDenylistExemptions = new String[argCount - curArg - 1]; + ++curArg; + for (int i = 0; curArg < argCount; ++curArg, ++i) { + mApiDenylistExemptions[i] = args.nextArg(); + } expectRuntimeArgs = false; } else if (arg.startsWith("--hidden-api-log-sampling-rate=")) { String rateStr = getAssignmentValue(arg); @@ -462,35 +482,46 @@ class ZygoteArguments { } else if (arg.equals(Zygote.BIND_MOUNT_APP_DATA_DIRS)) { mBindMountAppDataDirs = true; } else { + unprocessedArg = arg; break; } } + // curArg is the index of the first unprocessed argument. That argument is either referenced + // by unprocessedArg or not read yet. if (mBootCompleted) { - if (args.length - curArg > 0) { + if (argCount > curArg) { throw new IllegalArgumentException("Unexpected arguments after --boot-completed"); } } else if (mAbiListQuery || mPidQuery) { - if (args.length - curArg > 0) { + if (argCount > curArg) { throw new IllegalArgumentException("Unexpected arguments after --query-abi-list."); } } else if (mPreloadPackage != null) { - if (args.length - curArg > 0) { + if (argCount > curArg) { throw new IllegalArgumentException( "Unexpected arguments after --preload-package."); } } else if (mPreloadApp != null) { - if (args.length - curArg > 0) { + if (argCount > curArg) { throw new IllegalArgumentException( "Unexpected arguments after --preload-app."); } } else if (expectRuntimeArgs) { if (!seenRuntimeArgs) { - throw new IllegalArgumentException("Unexpected argument : " + args[curArg]); + throw new IllegalArgumentException("Unexpected argument : " + + (unprocessedArg == null ? args.nextArg() : unprocessedArg)); } - mRemainingArgs = new String[args.length - curArg]; - System.arraycopy(args, curArg, mRemainingArgs, 0, mRemainingArgs.length); + mRemainingArgs = new String[argCount - curArg]; + int i = 0; + if (unprocessedArg != null) { + mRemainingArgs[0] = unprocessedArg; + ++i; + } + for (; i < argCount - curArg; ++i) { + mRemainingArgs[i] = args.nextArg(); + } } if (mStartChildZygote) { diff --git a/core/java/com/android/internal/os/ZygoteCommandBuffer.java b/core/java/com/android/internal/os/ZygoteCommandBuffer.java new file mode 100644 index 000000000000..b61ae7acfacd --- /dev/null +++ b/core/java/com/android/internal/os/ZygoteCommandBuffer.java @@ -0,0 +1,187 @@ +/* + * 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.internal.os; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.LocalSocket; + +import java.io.FileDescriptor; +import java.lang.ref.Reference; // For reachabilityFence. + +/** + * A native-accessible buffer for Zygote commands. Designed to support repeated forking + * of applications without intervening memory allocation, thus keeping zygote memory + * as stable as possible. + * A ZygoteCommandBuffer may have an associated socket from which it can be refilled. + * Otherwise the contents are explicitly set by getInstance(). + * + * NOT THREAD-SAFE. No methods may be called concurrently from multiple threads. + * + * Only one ZygoteCommandBuffer can exist at a time. + * Must be explicitly closed before being dropped. + * @hide + */ +class ZygoteCommandBuffer implements AutoCloseable { + private long mNativeBuffer; // Not final so that we can clear it in close(). + + /** + * The command socket. + * + * mSocket is retained in the child process in "peer wait" mode, so + * that it closes when the child process terminates. In other cases, + * it is closed in the peer. + */ + private final LocalSocket mSocket; + private final int mNativeSocket; + + /** + * Constructs instance from file descriptor from which the command will be read. + * Only a single instance may be live in a given process. The native code checks. + * + * @param fd file descriptor to read from. The setCommand() method may be used if and only if + * fd is null. + */ + ZygoteCommandBuffer(@Nullable LocalSocket socket) { + mSocket = socket; + if (socket == null) { + mNativeSocket = -1; + } else { + mNativeSocket = mSocket.getFileDescriptor().getInt$(); + } + mNativeBuffer = getNativeBuffer(mNativeSocket); + } + + /** + * Constructs an instance with explicitly supplied arguments and an invalid + * file descriptor. Can only be used for a single command. + */ + ZygoteCommandBuffer(@NonNull String[] args) { + this((LocalSocket) null); + setCommand(args); + } + + + private static native long getNativeBuffer(int fd); + + /** + * Deallocate native resources associated with the one and only command buffer, and prevent + * reuse. Subsequent calls to getInstance() will yield a new buffer. + * We do not close the associated socket, if any. + */ + @Override + public void close() { + freeNativeBuffer(mNativeBuffer); + mNativeBuffer = 0; + } + + private static native void freeNativeBuffer(long /* NativeCommandBuffer* */ nbuffer); + + /** + * Read at least the first line of the next command into the buffer, return the argument count + * from that line. Assumes we are initially positioned at the beginning of the first line of + * the command. Leave the buffer positioned at the beginning of the second command line, i.e. + * the first argument. If the buffer has no associated file descriptor, we just reposition to + * the beginning of the buffer, and reread existing contents. Returns zero if we started out + * at EOF. + */ + int getCount() { + try { + return nativeGetCount(mNativeBuffer); + } finally { + // Make sure the mNativeSocket doesn't get closed due to early finalization. + Reference.reachabilityFence(mSocket); + } + } + + private static native int nativeGetCount(long /* NativeCommandBuffer* */ nbuffer); + + + /* + * Set the buffer to contain the supplied sequence of arguments. + */ + private void setCommand(String[] command) { + int nArgs = command.length; + insert(mNativeBuffer, Integer.toString(nArgs)); + for (String s: command) { + insert(mNativeBuffer, s); + } + // Native code checks there is no socket; hence no reachabilityFence. + } + + private static native void insert(long /* NativeCommandBuffer* */ nbuffer, String s); + + /** + * Retrieve the next argument/line from the buffer, filling the buffer as necessary. + */ + String nextArg() { + try { + return nativeNextArg(mNativeBuffer); + } finally { + Reference.reachabilityFence(mSocket); + } + } + + private static native String nativeNextArg(long /* NativeCommandBuffer* */ nbuffer); + + void readFullyAndReset() { + try { + nativeReadFullyAndReset(mNativeBuffer); + } finally { + Reference.reachabilityFence(mSocket); + } + } + + private static native void nativeReadFullyAndReset(long /* NativeCommandBuffer* */ nbuffer); + + /** + * Fork a child as specified by the current command in the buffer, and repeat this process + * after refilling the buffer, so long as the buffer clearly contains another fork command. + * + * @param zygoteSocket socket from which to obtain new connections when current one is + * disconnected + * @param expectedUid Peer UID for current connection. We refuse to deal with requests from + * a different UID. + * @param minUid the smallest uid that may be request for the child process. + * @param firstNiceName The name for the initial process to be forked. Used only for error + * reporting. + * + * @return true in the child, false in the parent. In the parent case, the buffer is positioned + * at the beginning of a command that still needs to be processed. + */ + boolean forkRepeatedly(FileDescriptor zygoteSocket, int expectedUid, int minUid, + String firstNiceName) { + try { + return nativeForkRepeatedly(mNativeBuffer, zygoteSocket.getInt$(), + expectedUid, minUid, firstNiceName); + } finally { + Reference.reachabilityFence(mSocket); + Reference.reachabilityFence(zygoteSocket); + } + } + + /* + * Repeatedly fork children as above. It commonly does not return in the parent, but it may. + * @return true in the chaild, false in the parent if we encounter a command we couldn't handle. + */ + private static native boolean nativeForkRepeatedly(long /* NativeCommandBuffer* */ nbuffer, + int zygoteSocketRawFd, + int expectedUid, + int minUid, + String firstNiceName); + +} diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java index 5a576ebbc442..37c75907061c 100644 --- a/core/java/com/android/internal/os/ZygoteConnection.java +++ b/core/java/com/android/internal/os/ZygoteConnection.java @@ -36,16 +36,15 @@ import android.system.StructPollfd; import android.util.Log; import dalvik.system.VMRuntime; +import dalvik.system.ZygoteHooks; import libcore.io.IoUtils; -import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.FileDescriptor; import java.io.IOException; -import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.concurrent.TimeUnit; @@ -67,7 +66,6 @@ class ZygoteConnection { private final LocalSocket mSocket; @UnsupportedAppUsage private final DataOutputStream mSocketOutStream; - private final BufferedReader mSocketReader; @UnsupportedAppUsage private final Credentials peer; private final String abiList; @@ -85,9 +83,6 @@ class ZygoteConnection { this.abiList = abiList; mSocketOutStream = new DataOutputStream(socket.getOutputStream()); - mSocketReader = - new BufferedReader( - new InputStreamReader(socket.getInputStream()), Zygote.SOCKET_BUFFER_SIZE); mSocket.setSoTimeout(CONNECTION_TIMEOUT_MILLIS); @@ -111,178 +106,216 @@ class ZygoteConnection { } /** - * Reads one start command from the command socket. If successful, a child is forked and a + * Reads a command from the command socket. If a child is successfully forked, a * {@code Runnable} that calls the childs main method (or equivalent) is returned in the child * process. {@code null} is always returned in the parent process (the zygote). + * If multipleOK is set, we may keep processing additional fork commands before returning. * * If the client closes the socket, an {@code EOF} condition is set, which callers can test * for by calling {@code ZygoteConnection.isClosedByPeer}. */ - Runnable processOneCommand(ZygoteServer zygoteServer) { - String[] args; - - try { - args = Zygote.readArgumentList(mSocketReader); - } catch (IOException ex) { - throw new IllegalStateException("IOException on command socket", ex); - } - - // readArgumentList returns null only when it has reached EOF with no available - // data to read. This will only happen when the remote socket has disconnected. - if (args == null) { - isEof = true; - return null; - } - - int pid; - FileDescriptor childPipeFd = null; - FileDescriptor serverPipeFd = null; - - ZygoteArguments parsedArgs = new ZygoteArguments(args); - - if (parsedArgs.mBootCompleted) { - handleBootCompleted(); - return null; - } - - if (parsedArgs.mAbiListQuery) { - handleAbiListQuery(); - return null; - } - - if (parsedArgs.mPidQuery) { - handlePidQuery(); - return null; - } + Runnable processCommand(ZygoteServer zygoteServer, boolean multipleOK) { + ZygoteArguments parsedArgs; + + try (ZygoteCommandBuffer argBuffer = new ZygoteCommandBuffer(mSocket)) { + while (true) { + try { + parsedArgs = ZygoteArguments.getInstance(argBuffer); + // Keep argBuffer around, since we need it to fork. + } catch (IOException ex) { + throw new IllegalStateException("IOException on command socket", ex); + } + if (parsedArgs == null) { + isEof = true; + return null; + } - if (parsedArgs.mUsapPoolStatusSpecified) { - return handleUsapPoolStatusChange(zygoteServer, parsedArgs.mUsapPoolEnabled); - } + int pid; + FileDescriptor childPipeFd = null; + FileDescriptor serverPipeFd = null; - if (parsedArgs.mPreloadDefault) { - handlePreload(); - return null; - } + if (parsedArgs.mBootCompleted) { + handleBootCompleted(); + return null; + } - if (parsedArgs.mPreloadPackage != null) { - handlePreloadPackage(parsedArgs.mPreloadPackage, parsedArgs.mPreloadPackageLibs, - parsedArgs.mPreloadPackageLibFileName, parsedArgs.mPreloadPackageCacheKey); - return null; - } + if (parsedArgs.mAbiListQuery) { + handleAbiListQuery(); + return null; + } - if (canPreloadApp() && parsedArgs.mPreloadApp != null) { - byte[] rawParcelData = Base64.getDecoder().decode(parsedArgs.mPreloadApp); - Parcel appInfoParcel = Parcel.obtain(); - appInfoParcel.unmarshall(rawParcelData, 0, rawParcelData.length); - appInfoParcel.setDataPosition(0); - ApplicationInfo appInfo = ApplicationInfo.CREATOR.createFromParcel(appInfoParcel); - appInfoParcel.recycle(); - if (appInfo != null) { - handlePreloadApp(appInfo); - } else { - throw new IllegalArgumentException("Failed to deserialize --preload-app"); - } - return null; - } + if (parsedArgs.mPidQuery) { + handlePidQuery(); + return null; + } - if (parsedArgs.mApiDenylistExemptions != null) { - return handleApiDenylistExemptions(zygoteServer, parsedArgs.mApiDenylistExemptions); - } + if (parsedArgs.mUsapPoolStatusSpecified) { + // Handle this once we've released the argBuffer, to avoid opening a second one. + break; + } - if (parsedArgs.mHiddenApiAccessLogSampleRate != -1 - || parsedArgs.mHiddenApiAccessStatslogSampleRate != -1) { - return handleHiddenApiAccessLogSampleRate(zygoteServer, - parsedArgs.mHiddenApiAccessLogSampleRate, - parsedArgs.mHiddenApiAccessStatslogSampleRate); - } + if (parsedArgs.mPreloadDefault) { + handlePreload(); + return null; + } - if (parsedArgs.mPermittedCapabilities != 0 || parsedArgs.mEffectiveCapabilities != 0) { - throw new ZygoteSecurityException("Client may not specify capabilities: " - + "permitted=0x" + Long.toHexString(parsedArgs.mPermittedCapabilities) - + ", effective=0x" + Long.toHexString(parsedArgs.mEffectiveCapabilities)); - } + if (parsedArgs.mPreloadPackage != null) { + handlePreloadPackage(parsedArgs.mPreloadPackage, + parsedArgs.mPreloadPackageLibs, + parsedArgs.mPreloadPackageLibFileName, + parsedArgs.mPreloadPackageCacheKey); + return null; + } - Zygote.applyUidSecurityPolicy(parsedArgs, peer); - Zygote.applyInvokeWithSecurityPolicy(parsedArgs, peer); + if (canPreloadApp() && parsedArgs.mPreloadApp != null) { + byte[] rawParcelData = Base64.getDecoder().decode(parsedArgs.mPreloadApp); + Parcel appInfoParcel = Parcel.obtain(); + appInfoParcel.unmarshall(rawParcelData, 0, rawParcelData.length); + appInfoParcel.setDataPosition(0); + ApplicationInfo appInfo = + ApplicationInfo.CREATOR.createFromParcel(appInfoParcel); + appInfoParcel.recycle(); + if (appInfo != null) { + handlePreloadApp(appInfo); + } else { + throw new IllegalArgumentException("Failed to deserialize --preload-app"); + } + return null; + } - Zygote.applyDebuggerSystemProperty(parsedArgs); - Zygote.applyInvokeWithSystemProperty(parsedArgs); + if (parsedArgs.mApiDenylistExemptions != null) { + return handleApiDenylistExemptions(zygoteServer, + parsedArgs.mApiDenylistExemptions); + } - int[][] rlimits = null; + if (parsedArgs.mHiddenApiAccessLogSampleRate != -1 + || parsedArgs.mHiddenApiAccessStatslogSampleRate != -1) { + return handleHiddenApiAccessLogSampleRate(zygoteServer, + parsedArgs.mHiddenApiAccessLogSampleRate, + parsedArgs.mHiddenApiAccessStatslogSampleRate); + } - if (parsedArgs.mRLimits != null) { - rlimits = parsedArgs.mRLimits.toArray(Zygote.INT_ARRAY_2D); - } + if (parsedArgs.mPermittedCapabilities != 0 + || parsedArgs.mEffectiveCapabilities != 0) { + throw new ZygoteSecurityException("Client may not specify capabilities: " + + "permitted=0x" + Long.toHexString(parsedArgs.mPermittedCapabilities) + + ", effective=0x" + + Long.toHexString(parsedArgs.mEffectiveCapabilities)); + } - int[] fdsToIgnore = null; + Zygote.applyUidSecurityPolicy(parsedArgs, peer); + Zygote.applyInvokeWithSecurityPolicy(parsedArgs, peer); - if (parsedArgs.mInvokeWith != null) { - try { - FileDescriptor[] pipeFds = Os.pipe2(O_CLOEXEC); - childPipeFd = pipeFds[1]; - serverPipeFd = pipeFds[0]; - Os.fcntlInt(childPipeFd, F_SETFD, 0); - fdsToIgnore = new int[]{childPipeFd.getInt$(), serverPipeFd.getInt$()}; - } catch (ErrnoException errnoEx) { - throw new IllegalStateException("Unable to set up pipe for invoke-with", errnoEx); - } - } + Zygote.applyDebuggerSystemProperty(parsedArgs); + Zygote.applyInvokeWithSystemProperty(parsedArgs); - /* - * In order to avoid leaking descriptors to the Zygote child, - * the native code must close the two Zygote socket descriptors - * in the child process before it switches from Zygote-root to - * the UID and privileges of the application being launched. - * - * In order to avoid "bad file descriptor" errors when the - * two LocalSocket objects are closed, the Posix file - * descriptors are released via a dup2() call which closes - * the socket and substitutes an open descriptor to /dev/null. - */ + int[][] rlimits = null; - int [] fdsToClose = { -1, -1 }; + if (parsedArgs.mRLimits != null) { + rlimits = parsedArgs.mRLimits.toArray(Zygote.INT_ARRAY_2D); + } - FileDescriptor fd = mSocket.getFileDescriptor(); + int[] fdsToIgnore = null; + + if (parsedArgs.mInvokeWith != null) { + try { + FileDescriptor[] pipeFds = Os.pipe2(O_CLOEXEC); + childPipeFd = pipeFds[1]; + serverPipeFd = pipeFds[0]; + Os.fcntlInt(childPipeFd, F_SETFD, 0); + fdsToIgnore = new int[]{childPipeFd.getInt$(), serverPipeFd.getInt$()}; + } catch (ErrnoException errnoEx) { + throw new IllegalStateException("Unable to set up pipe for invoke-with", + errnoEx); + } + } - if (fd != null) { - fdsToClose[0] = fd.getInt$(); - } + /* + * In order to avoid leaking descriptors to the Zygote child, + * the native code must close the two Zygote socket descriptors + * in the child process before it switches from Zygote-root to + * the UID and privileges of the application being launched. + * + * In order to avoid "bad file descriptor" errors when the + * two LocalSocket objects are closed, the Posix file + * descriptors are released via a dup2() call which closes + * the socket and substitutes an open descriptor to /dev/null. + */ - fd = zygoteServer.getZygoteSocketFileDescriptor(); + int [] fdsToClose = { -1, -1 }; - if (fd != null) { - fdsToClose[1] = fd.getInt$(); - } + FileDescriptor fd = mSocket.getFileDescriptor(); - pid = Zygote.forkAndSpecialize(parsedArgs.mUid, parsedArgs.mGid, parsedArgs.mGids, - parsedArgs.mRuntimeFlags, rlimits, parsedArgs.mMountExternal, parsedArgs.mSeInfo, - parsedArgs.mNiceName, fdsToClose, fdsToIgnore, parsedArgs.mStartChildZygote, - parsedArgs.mInstructionSet, parsedArgs.mAppDataDir, parsedArgs.mIsTopApp, - parsedArgs.mPkgDataInfoList, parsedArgs.mWhitelistedDataInfoList, - parsedArgs.mBindMountAppDataDirs, parsedArgs.mBindMountAppStorageDirs); + if (fd != null) { + fdsToClose[0] = fd.getInt$(); + } - try { - if (pid == 0) { - // in child - zygoteServer.setForkChild(); + FileDescriptor zygoteFd = zygoteServer.getZygoteSocketFileDescriptor(); - zygoteServer.closeServerSocket(); - IoUtils.closeQuietly(serverPipeFd); - serverPipeFd = null; + if (zygoteFd != null) { + fdsToClose[1] = zygoteFd.getInt$(); + } - return handleChildProc(parsedArgs, childPipeFd, parsedArgs.mStartChildZygote); - } else { - // In the parent. A pid < 0 indicates a failure and will be handled in - // handleParentProc. - IoUtils.closeQuietly(childPipeFd); - childPipeFd = null; - handleParentProc(pid, serverPipeFd); - return null; + if (parsedArgs.mInvokeWith != null || parsedArgs.mStartChildZygote + || !multipleOK || peer.getUid() != Process.SYSTEM_UID) { + // Continue using old code for now. TODO: Handle these cases in the other path. + pid = Zygote.forkAndSpecialize(parsedArgs.mUid, parsedArgs.mGid, + parsedArgs.mGids, parsedArgs.mRuntimeFlags, rlimits, + parsedArgs.mMountExternal, parsedArgs.mSeInfo, parsedArgs.mNiceName, + fdsToClose, fdsToIgnore, parsedArgs.mStartChildZygote, + parsedArgs.mInstructionSet, parsedArgs.mAppDataDir, + parsedArgs.mIsTopApp, parsedArgs.mPkgDataInfoList, + parsedArgs.mWhitelistedDataInfoList, parsedArgs.mBindMountAppDataDirs, + parsedArgs.mBindMountAppStorageDirs); + + try { + if (pid == 0) { + // in child + zygoteServer.setForkChild(); + + zygoteServer.closeServerSocket(); + IoUtils.closeQuietly(serverPipeFd); + serverPipeFd = null; + + return handleChildProc(parsedArgs, childPipeFd, + parsedArgs.mStartChildZygote); + } else { + // In the parent. A pid < 0 indicates a failure and will be handled in + // handleParentProc. + IoUtils.closeQuietly(childPipeFd); + childPipeFd = null; + handleParentProc(pid, serverPipeFd); + return null; + } + } finally { + IoUtils.closeQuietly(childPipeFd); + IoUtils.closeQuietly(serverPipeFd); + } + } else { + ZygoteHooks.preFork(); + Runnable result = Zygote.forkSimpleApps(argBuffer, + zygoteServer.getZygoteSocketFileDescriptor(), + peer.getUid(), Zygote.minChildUid(peer), parsedArgs.mNiceName); + if (result == null) { + // parent; we finished some number of forks. Result is Boolean. + // We already did the equivalent of handleParentProc(). + ZygoteHooks.postForkCommon(); + // argBuffer contains a command not understood by forksimpleApps. + continue; + } else { + // child; result is a Runnable. + zygoteServer.setForkChild(); + Zygote.setAppProcessName(parsedArgs, TAG); // ??? Necessary? + return result; + } + } } - } finally { - IoUtils.closeQuietly(childPipeFd); - IoUtils.closeQuietly(serverPipeFd); } + if (parsedArgs.mUsapPoolStatusSpecified) { + // Now that we've released argBuffer: + return handleUsapPoolStatusChange(zygoteServer, parsedArgs.mUsapPoolEnabled); + } + throw new AssertionError("Shouldn't get here"); } private void handleAbiListQuery() { @@ -557,7 +590,7 @@ class ZygoteConnection { if (res > 0) { if ((fds[0].revents & POLLIN) != 0) { - // Only read one byte, so as not to block. + // Only read one byte, so as not to block. Really needed? int readBytes = android.system.Os.read(pipeFd, data, dataIndex, 1); if (readBytes < 0) { throw new RuntimeException("Some error"); diff --git a/core/java/com/android/internal/os/ZygoteConnectionConstants.java b/core/java/com/android/internal/os/ZygoteConnectionConstants.java index 506e39f30617..0c1cd6de1bb4 100644 --- a/core/java/com/android/internal/os/ZygoteConnectionConstants.java +++ b/core/java/com/android/internal/os/ZygoteConnectionConstants.java @@ -31,9 +31,6 @@ public class ZygoteConnectionConstants { */ public static final int CONNECTION_TIMEOUT_MILLIS = 1000; - /** max number of arguments that a connection can specify */ - public static final int MAX_ZYGOTE_ARGC = 1024; - /** * Wait time for a wrapped app to report back its pid. * diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index f2ed50ef6a07..d5b778e9c9e1 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -65,6 +65,7 @@ import dalvik.system.ZygoteHooks; import libcore.io.IoUtils; import java.io.BufferedReader; +import java.io.EOFException; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -782,7 +783,13 @@ public class ZygoteInit { int pid; try { - parsedArgs = new ZygoteArguments(args); + ZygoteCommandBuffer commandBuffer = new ZygoteCommandBuffer(args); + try { + parsedArgs = ZygoteArguments.getInstance(commandBuffer); + } catch (EOFException e) { + throw new AssertionError("Unexpected argument error for forking system server", e); + } + commandBuffer.close(); Zygote.applyDebuggerSystemProperty(parsedArgs); Zygote.applyInvokeWithSystemProperty(parsedArgs); @@ -861,7 +868,7 @@ public class ZygoteInit { * into new processes are required to either set the priority to the default value or terminate * before executing any non-system code. The native side of this occurs in SpecializeCommon, * while the Java Language priority is changed in ZygoteInit.handleSystemServerProcess, - * ZygoteConnection.handleChildProc, and Zygote.usapMain. + * ZygoteConnection.handleChildProc, and Zygote.childMain. * * @param argv Command line arguments used to specify the Zygote's configuration. */ diff --git a/core/java/com/android/internal/os/ZygoteServer.java b/core/java/com/android/internal/os/ZygoteServer.java index 585ddf6ddf98..f71b31493035 100644 --- a/core/java/com/android/internal/os/ZygoteServer.java +++ b/core/java/com/android/internal/os/ZygoteServer.java @@ -337,7 +337,7 @@ class ZygoteServer { * @param sessionSocketRawFDs Anonymous session sockets that are currently open * @return In the Zygote process this function will always return null; in unspecialized app * processes this function will return a Runnable object representing the new - * application that is passed up from usapMain. + * application that is passed up from childMain (the usap's main wait loop). */ Runnable fillUsapPool(int[] sessionSocketRawFDs, boolean isPriorityRefill) { @@ -420,6 +420,7 @@ class ZygoteServer { * Runs the zygote process's select loop. Accepts new connections as * they happen, and reads commands from connections one spawn-request's * worth at a time. + * @param abiList list of ABIs supported by this zygote. */ Runnable runSelectLoop(String abiList) { ArrayList<FileDescriptor> socketFDs = new ArrayList<>(); @@ -537,22 +538,23 @@ class ZygoteServer { if (pollIndex == 0) { // Zygote server socket - ZygoteConnection newPeer = acceptCommandPeer(abiList); peers.add(newPeer); socketFDs.add(newPeer.getFileDescriptor()); - } else if (pollIndex < usapPoolEventFDIndex) { // Session socket accepted from the Zygote server socket try { ZygoteConnection connection = peers.get(pollIndex); - final Runnable command = connection.processOneCommand(this); + boolean multipleForksOK = !isUsapPoolEnabled() + && ZygoteHooks.indefiniteThreadSuspensionOK(); + final Runnable command = + connection.processCommand(this, multipleForksOK); // TODO (chriswailes): Is this extra check necessary? if (mIsForkChild) { // We're in the child. We should always have a command to run at - // this stage if processOneCommand hasn't called "exec". + // this stage if processCommand hasn't called "exec". if (command == null) { throw new IllegalStateException("command == null"); } @@ -565,7 +567,7 @@ class ZygoteServer { } // We don't know whether the remote side of the socket was closed or - // not until we attempt to read from it from processOneCommand. This + // not until we attempt to read from it from processCommand. This // shows up as a regular POLLIN event in our regular processing // loop. if (connection.isClosedByPeer()) { diff --git a/core/java/com/android/internal/power/MeasuredEnergyStats.java b/core/java/com/android/internal/power/MeasuredEnergyStats.java index d7b4d78c56cf..d49203c731e9 100644 --- a/core/java/com/android/internal/power/MeasuredEnergyStats.java +++ b/core/java/com/android/internal/power/MeasuredEnergyStats.java @@ -193,34 +193,30 @@ public class MeasuredEnergyStats { return mAccumulatedEnergiesMicroJoules.length; } - // TODO: Get rid of the 'accumulate' boolean. It's always true. /** Updates the given standard energy bucket with the given energy if accumulate is true. */ - public void updateStandardBucket(@StandardEnergyBucket int bucket, long energyDeltaUJ, - boolean accumulate) { + public void updateStandardBucket(@StandardEnergyBucket int bucket, long energyDeltaUJ) { checkValidStandardBucket(bucket); - updateEntry(bucket, energyDeltaUJ, accumulate); + updateEntry(bucket, energyDeltaUJ); } /** Updates the given custom energy bucket with the given energy if accumulate is true. */ - public void updateCustomBucket(int customBucket, long energyDeltaUJ, boolean accumulate) { + public void updateCustomBucket(int customBucket, long energyDeltaUJ) { if (!isValidCustomBucket(customBucket)) { Slog.e(TAG, "Attempted to update invalid custom bucket " + customBucket); return; } final int index = customBucketToIndex(customBucket); - updateEntry(index, energyDeltaUJ, accumulate); + updateEntry(index, energyDeltaUJ); } /** Updates the given index with the given energy if accumulate is true. */ - private void updateEntry(int index, long energyDeltaUJ, boolean accumulate) { - if (accumulate) { - if (mAccumulatedEnergiesMicroJoules[index] >= 0L) { - mAccumulatedEnergiesMicroJoules[index] += energyDeltaUJ; - } else { - Slog.wtf(TAG, "Attempting to add " + energyDeltaUJ + " to unavailable bucket " - + getBucketName(index) + " whose value was " - + mAccumulatedEnergiesMicroJoules[index]); - } + private void updateEntry(int index, long energyDeltaUJ) { + if (mAccumulatedEnergiesMicroJoules[index] >= 0L) { + mAccumulatedEnergiesMicroJoules[index] += energyDeltaUJ; + } else { + Slog.wtf(TAG, "Attempting to add " + energyDeltaUJ + " to unavailable bucket " + + getBucketName(index) + " whose value was " + + mAccumulatedEnergiesMicroJoules[index]); } } diff --git a/core/java/com/android/internal/power/TEST_MAPPING b/core/java/com/android/internal/power/TEST_MAPPING new file mode 100644 index 000000000000..96f31bcbe5b2 --- /dev/null +++ b/core/java/com/android/internal/power/TEST_MAPPING @@ -0,0 +1,19 @@ +{ + "presubmit": [ + { + "name": "FrameworksCoreTests", + "options": [ + { "include-filter": "com.android.internal.os.BatteryStatsTests" }, + { "exclude-annotation": "com.android.internal.os.SkipPresubmit" } + ] + }, + { + "name": "FrameworksServicesTests", + "options": [ + { "include-filter": "com.android.server.am.BatteryStatsServiceTest" }, + { "include-filter": "com.android.server.am.MeasuredEnergySnapshotTest" }, + { "include-filter": "com.android.server.am.BatteryExternalStatsWorkerTest" } + ] + } + ] +} diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl index f7d440d9fd95..95e0a3b524c5 100644 --- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl +++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl @@ -94,6 +94,6 @@ interface ITelephonyRegistry { void notifyBarringInfoChanged(int slotIndex, int subId, in BarringInfo barringInfo); void notifyPhysicalChannelConfigForSubscriber(in int subId, in List<PhysicalChannelConfig> configs); - void notifyDataEnabled(boolean enabled, int reason); + void notifyDataEnabled(in int phoneId, int subId, boolean enabled, int reason); void notifyAllowedNetworkTypesChanged(in int phoneId, in int subId, in Map allowedNetworkTypeList); } diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java index ef2275d4218c..ab0149fce0a0 100644 --- a/core/java/com/android/internal/view/BaseIWindow.java +++ b/core/java/com/android/internal/view/BaseIWindow.java @@ -30,6 +30,7 @@ import android.view.IWindowSession; import android.view.InsetsSourceControl; import android.view.InsetsState; import android.view.PointerIcon; +import android.view.ScrollCaptureResponse; import android.view.WindowInsets.Type.InsetsType; import android.window.ClientWindowFrames; @@ -160,7 +161,9 @@ public class BaseIWindow extends IWindow.Stub { @Override public void requestScrollCapture(IScrollCaptureCallbacks callbacks) { try { - callbacks.onUnavailable(); + callbacks.onScrollCaptureResponse( + new ScrollCaptureResponse.Builder().setDescription("Not Implemented").build()); + } catch (RemoteException ex) { // ignore } diff --git a/core/java/com/android/internal/view/OWNERS b/core/java/com/android/internal/view/OWNERS index 851d1f37522a..eb2478f6550a 100644 --- a/core/java/com/android/internal/view/OWNERS +++ b/core/java/com/android/internal/view/OWNERS @@ -18,3 +18,7 @@ per-file AppearanceRegion = file:/services/core/java/com/android/server/wm/OWNER per-file BaseIWIndow.java = file:/services/core/java/com/android/server/wm/OWNERS per-file RotationPolicy.java = file:/services/core/java/com/android/server/wm/OWNERS per-file WindowManagerPolicyThread.java = file:/services/core/java/com/android/server/wm/OWNERS + +# Scroll Capture +per-file *ScrollCapture*.java = file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS +per-file *CaptureHelper*.java = file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS diff --git a/core/java/com/android/internal/view/ScrollCaptureViewSupport.java b/core/java/com/android/internal/view/ScrollCaptureViewSupport.java index 85fa791b429c..a41511b74a7d 100644 --- a/core/java/com/android/internal/view/ScrollCaptureViewSupport.java +++ b/core/java/com/android/internal/view/ScrollCaptureViewSupport.java @@ -16,15 +16,19 @@ package com.android.internal.view; +import android.annotation.UiThread; +import android.content.Context; +import android.content.pm.ActivityInfo; import android.graphics.HardwareRenderer; import android.graphics.Matrix; import android.graphics.RecordingCanvas; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.RenderNode; -import android.os.Handler; +import android.os.CancellationSignal; import android.util.DisplayMetrics; import android.util.Log; +import android.view.Display.ColorMode; import android.view.ScrollCaptureCallback; import android.view.ScrollCaptureSession; import android.view.Surface; @@ -44,32 +48,42 @@ import java.util.function.Consumer; * @param <V> the specific View subclass handled * @see ScrollCaptureViewHelper */ +@UiThread public class ScrollCaptureViewSupport<V extends View> implements ScrollCaptureCallback { - public static final long NO_FRAME_PRODUCED = -1; - private static final String TAG = "ScrollCaptureViewSupport"; private static final boolean WAIT_FOR_ANIMATION = true; private final WeakReference<V> mWeakView; private final ScrollCaptureViewHelper<V> mViewHelper; - private ViewRenderer mRenderer; - private Handler mUiHandler; + private final ViewRenderer mRenderer; private boolean mStarted; private boolean mEnded; ScrollCaptureViewSupport(V containingView, ScrollCaptureViewHelper<V> viewHelper) { mWeakView = new WeakReference<>(containingView); mRenderer = new ViewRenderer(); - mUiHandler = containingView.getHandler(); + // TODO(b/177649144): provide access to color space from android.media.Image mViewHelper = viewHelper; } - // Base implementation of ScrollCaptureCallback + /** Based on ViewRootImpl#updateColorModeIfNeeded */ + @ColorMode + private static int getColorMode(View containingView) { + Context context = containingView.getContext(); + int colorMode = containingView.getViewRootImpl().mWindowAttributes.getColorMode(); + if (!context.getResources().getConfiguration().isScreenWideColorGamut()) { + colorMode = ActivityInfo.COLOR_MODE_DEFAULT; + } + return colorMode; + } @Override - public final void onScrollCaptureSearch(Consumer<Rect> onReady) { + public final void onScrollCaptureSearch(CancellationSignal signal, Consumer<Rect> onReady) { + if (signal.isCanceled()) { + return; + } V view = mWeakView.get(); mStarted = false; mEnded = false; @@ -82,7 +96,11 @@ public class ScrollCaptureViewSupport<V extends View> implements ScrollCaptureCa } @Override - public final void onScrollCaptureStart(ScrollCaptureSession session, Runnable onReady) { + public final void onScrollCaptureStart(ScrollCaptureSession session, CancellationSignal signal, + Runnable onReady) { + if (signal.isCanceled()) { + return; + } V view = mWeakView.get(); mEnded = false; @@ -99,18 +117,22 @@ public class ScrollCaptureViewSupport<V extends View> implements ScrollCaptureCa } @Override - public final void onScrollCaptureImageRequest(ScrollCaptureSession session, Rect requestRect) { + public final void onScrollCaptureImageRequest(ScrollCaptureSession session, + CancellationSignal signal, Rect requestRect, Consumer<Rect> onComplete) { + if (signal.isCanceled()) { + return; + } V view = mWeakView.get(); if (view == null || !view.isVisibleToUser()) { // Signal to the controller that we have a problem and can't continue. - session.notifyBufferSent(NO_FRAME_PRODUCED, new Rect()); + onComplete.accept(new Rect()); return; } // Ask the view to scroll as needed to bring this area into view. ScrollResult scrollResult = mViewHelper.onScrollRequested(view, session.getScrollBounds(), requestRect); if (scrollResult.availableArea.isEmpty()) { - session.notifyBufferSent(NO_FRAME_PRODUCED, scrollResult.availableArea); + onComplete.accept(scrollResult.availableArea); return; } view.invalidate(); // don't wait for vsync @@ -121,17 +143,13 @@ public class ScrollCaptureViewSupport<V extends View> implements ScrollCaptureCa viewCaptureArea.offset(0, -scrollResult.scrollDelta); if (WAIT_FOR_ANIMATION) { - Log.d(TAG, "render: delaying until animation"); view.postOnAnimation(() -> { - Log.d(TAG, "postOnAnimation(): rendering now"); - long resultFrame = mRenderer.renderView(view, viewCaptureArea); - Log.d(TAG, "notifyBufferSent: " + scrollResult.availableArea); - - session.notifyBufferSent(resultFrame, new Rect(scrollResult.availableArea)); + mRenderer.renderView(view, viewCaptureArea); + onComplete.accept(new Rect(scrollResult.availableArea)); }); } else { - long resultFrame = mRenderer.renderView(view, viewCaptureArea); - session.notifyBufferSent(resultFrame, new Rect(scrollResult.availableArea)); + mRenderer.renderView(view, viewCaptureArea); + onComplete.accept(new Rect(scrollResult.availableArea)); } } @@ -239,7 +257,7 @@ public class ScrollCaptureViewSupport<V extends View> implements ScrollCaptureCa mCaptureRenderNode.endRecording(); } - public long renderView(View view, Rect sourceRect) { + public void renderView(View view, Rect sourceRect) { if (updateForView(view)) { setupLighting(view); } @@ -258,7 +276,7 @@ public class ScrollCaptureViewSupport<V extends View> implements ScrollCaptureCa switch (request.syncAndDraw()) { case HardwareRenderer.SYNC_OK: case HardwareRenderer.SYNC_REDRAW_REQUESTED: - return frameNumber; + return; case HardwareRenderer.SYNC_FRAME_DROPPED: Log.e(TAG, "syncAndDraw(): SYNC_FRAME_DROPPED !"); @@ -270,7 +288,6 @@ public class ScrollCaptureViewSupport<V extends View> implements ScrollCaptureCa Log.e(TAG, "syncAndDraw(): SYNC_CONTEXT_IS_STOPPED !"); break; } - return NO_FRAME_PRODUCED; } public void trimMemory() { @@ -289,5 +306,17 @@ public class ScrollCaptureViewSupport<V extends View> implements ScrollCaptureCa mTempMatrix.mapRect(mTempRectF); mTempRectF.round(outRect); } + + public void setColorMode(@ColorMode int colorMode) { + mRenderer.setColorMode(colorMode); + } + } + + @Override + public String toString() { + return "ScrollCaptureViewSupport{" + + "view=" + mWeakView.get() + + ", helper=" + mViewHelper + + '}'; } } diff --git a/core/java/com/android/server/OWNERS b/core/java/com/android/server/OWNERS new file mode 100644 index 000000000000..1262925447b9 --- /dev/null +++ b/core/java/com/android/server/OWNERS @@ -0,0 +1 @@ +per-file SystemConfig.java = toddke@google.com diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java index 671cb8481dd9..b40ffb0136f2 100644 --- a/core/java/com/android/server/SystemConfig.java +++ b/core/java/com/android/server/SystemConfig.java @@ -24,6 +24,7 @@ import android.app.ActivityManager; import android.content.ComponentName; import android.content.pm.FeatureInfo; import android.content.pm.PackageManager; +import android.hardware.SensorPrivacyManager; import android.os.Build; import android.os.CarrierAssociatedAppEntry; import android.os.Environment; @@ -1252,6 +1253,14 @@ public class SystemConfig { addFeature(PackageManager.FEATURE_CROSS_LAYER_BLUR, 0); } + if (SensorPrivacyManager.USE_MICROPHONE_TOGGLE) { + addFeature(PackageManager.FEATURE_MICROPHONE_TOGGLE, 0); + } + + if (SensorPrivacyManager.USE_CAMERA_TOGGLE) { + addFeature(PackageManager.FEATURE_CAMERA_TOGGLE, 0); + } + for (String featureName : mUnavailableFeatures) { removeFeature(featureName); } diff --git a/core/jni/Android.bp b/core/jni/Android.bp index b485f0f4fb62..2287900795a5 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -211,6 +211,7 @@ cc_library_shared { "com_android_internal_os_KernelSingleProcessCpuThreadReader.cpp", "com_android_internal_os_KernelSingleUidTimeReader.cpp", "com_android_internal_os_Zygote.cpp", + "com_android_internal_os_ZygoteCommandBuffer.cpp", "com_android_internal_os_ZygoteInit.cpp", "hwbinder/EphemeralStorage.cpp", "fd_utils.cpp", diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index dc77bba44607..ddd861380fab 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -198,6 +198,7 @@ extern int register_com_android_internal_os_KernelCpuUidBpfMapReader(JNIEnv *env extern int register_com_android_internal_os_KernelSingleProcessCpuThreadReader(JNIEnv* env); extern int register_com_android_internal_os_KernelSingleUidTimeReader(JNIEnv *env); extern int register_com_android_internal_os_Zygote(JNIEnv *env); +extern int register_com_android_internal_os_ZygoteCommandBuffer(JNIEnv *env); extern int register_com_android_internal_os_ZygoteInit(JNIEnv *env); extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv *env); @@ -1531,6 +1532,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_com_android_internal_net_NetworkUtilsInternal), REG_JNI(register_com_android_internal_os_ClassLoaderFactory), REG_JNI(register_com_android_internal_os_Zygote), + REG_JNI(register_com_android_internal_os_ZygoteCommandBuffer), REG_JNI(register_com_android_internal_os_ZygoteInit), REG_JNI(register_com_android_internal_util_VirtualRefBasePtr), REG_JNI(register_android_hardware_Camera), diff --git a/core/jni/OWNERS b/core/jni/OWNERS index 35d1d7bd7946..19c6a625646e 100644 --- a/core/jni/OWNERS +++ b/core/jni/OWNERS @@ -42,6 +42,9 @@ per-file android_os_HwParcel* = file:platform/system/libhwbinder:/OWNERS per-file android_os_HwRemoteBinder* = file:platform/system/libhwbinder:/OWNERS per-file EphemeralStorage* = file:platform/system/libhwbinder:/OWNERS +# Sensor +per-file android_hardware_SensorManager* = arthuri@google.com, bduddie@google.com, stange@google.com + per-file *Zygote* = file:/ZYGOTE_OWNERS per-file Android.bp = file:platform/build/soong:/OWNERS per-file android_animation_* = file:/core/java/android/animation/OWNERS diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index e8017253fc29..c9062d8a50bc 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -17,6 +17,8 @@ #define LOG_TAG "Zygote" #define ATRACE_TAG ATRACE_TAG_DALVIK +#include "com_android_internal_os_Zygote.h" + #include <async_safe/log.h> // sys/mount.h has to come before linux/fs.h due to redefinition of MS_RDONLY, MS_BIND, etc @@ -91,19 +93,6 @@ #include "nativebridge/native_bridge.h" -/* Functions in the callchain during the fork shall not be protected with - Armv8.3-A Pointer Authentication, otherwise child will not be able to return. */ -#ifdef __ARM_FEATURE_PAC_DEFAULT -#ifdef __ARM_FEATURE_BTI_DEFAULT -#define NO_PAC_FUNC __attribute__((target("branch-protection=bti"))) -#else -#define NO_PAC_FUNC __attribute__((target("branch-protection=none"))) -#endif /* __ARM_FEATURE_BTI_DEFAULT */ -#else /* !__ARM_FEATURE_PAC_DEFAULT */ -#define NO_PAC_FUNC -#endif /* __ARM_FEATURE_PAC_DEFAULT */ - - namespace { // TODO (chriswailes): Add a function to initialize native Zygote data. @@ -118,8 +107,7 @@ using android::base::StringPrintf; using android::base::WriteStringToFile; using android::base::GetBoolProperty; -#define CREATE_ERROR(...) StringPrintf("%s:%d: ", __FILE__, __LINE__). \ - append(StringPrintf(__VA_ARGS__)) +using android::zygote::ZygoteFailure; // This type is duplicated in fd_utils.h typedef const std::function<void(std::string)>& fail_fn_t; @@ -214,7 +202,7 @@ class UsapTableEntry { static constexpr EntryStorage INVALID_ENTRY_VALUE = {-1, -1}; std::atomic<EntryStorage> mStorage; - static_assert(decltype(mStorage)::is_always_lock_free); + static_assert(decltype(mStorage)::is_always_lock_free); // Accessed from signal handler. public: constexpr UsapTableEntry() : mStorage(INVALID_ENTRY_VALUE) {} @@ -917,36 +905,6 @@ void SetThreadName(const std::string& thread_name) { } /** - * A failure function used to report fatal errors to the managed runtime. This - * function is often curried with the process name information and then passed - * to called functions. - * - * @param env Managed runtime environment - * @param process_name A native representation of the process name - * @param managed_process_name A managed representation of the process name - * @param msg The error message to be reported - */ -[[noreturn]] -static void ZygoteFailure(JNIEnv* env, - const char* process_name, - jstring managed_process_name, - const std::string& msg) { - std::unique_ptr<ScopedUtfChars> scoped_managed_process_name_ptr = nullptr; - if (managed_process_name != nullptr) { - scoped_managed_process_name_ptr.reset(new ScopedUtfChars(env, managed_process_name)); - if (scoped_managed_process_name_ptr->c_str() != nullptr) { - process_name = scoped_managed_process_name_ptr->c_str(); - } - } - - const std::string& error_msg = - (process_name == nullptr) ? msg : StringPrintf("(%s) %s", process_name, msg.c_str()); - - env->FatalError(error_msg.c_str()); - __builtin_unreachable(); -} - -/** * A helper method for converting managed strings to native strings. A fatal * error is generated if a problem is encountered in extracting a non-null * string. @@ -1073,86 +1031,6 @@ static void PAuthKeyChange(JNIEnv* env) { #endif } -// Utility routine to fork a process from the zygote. -NO_PAC_FUNC -static pid_t ForkCommon(JNIEnv* env, bool is_system_server, - const std::vector<int>& fds_to_close, - const std::vector<int>& fds_to_ignore, - bool is_priority_fork) { - SetSignalHandlers(); - - // Curry a failure function. - auto fail_fn = std::bind(ZygoteFailure, env, is_system_server ? "system_server" : "zygote", - nullptr, _1); - - // Temporarily block SIGCHLD during forks. The SIGCHLD handler might - // log, which would result in the logging FDs we close being reopened. - // This would cause failures because the FDs are not allowlisted. - // - // Note that the zygote process is single threaded at this point. - BlockSignal(SIGCHLD, fail_fn); - - // Close any logging related FDs before we start evaluating the list of - // file descriptors. - __android_log_close(); - AStatsSocket_close(); - - // If this is the first fork for this zygote, create the open FD table. If - // it isn't, we just need to check whether the list of open files has changed - // (and it shouldn't in the normal case). - if (gOpenFdTable == nullptr) { - gOpenFdTable = FileDescriptorTable::Create(fds_to_ignore, fail_fn); - } else { - gOpenFdTable->Restat(fds_to_ignore, fail_fn); - } - - android_fdsan_error_level fdsan_error_level = android_fdsan_get_error_level(); - - // Purge unused native memory in an attempt to reduce the amount of false - // sharing with the child process. By reducing the size of the libc_malloc - // region shared with the child process we reduce the number of pages that - // transition to the private-dirty state when malloc adjusts the meta-data - // on each of the pages it is managing after the fork. - mallopt(M_PURGE, 0); - - pid_t pid = fork(); - - if (pid == 0) { - if (is_priority_fork) { - setpriority(PRIO_PROCESS, 0, PROCESS_PRIORITY_MAX); - } else { - setpriority(PRIO_PROCESS, 0, PROCESS_PRIORITY_MIN); - } - - // The child process. - PAuthKeyChange(env); - PreApplicationInit(); - - // Clean up any descriptors which must be closed immediately - DetachDescriptors(env, fds_to_close, fail_fn); - - // Invalidate the entries in the USAP table. - ClearUsapTable(); - - // Re-open all remaining open file descriptors so that they aren't shared - // with the zygote across a fork. - gOpenFdTable->ReopenOrDetach(fail_fn); - - // Turn fdsan back on. - android_fdsan_set_error_level(fdsan_error_level); - - // Reset the fd to the unsolicited zygote socket - gSystemServerSocketFd = -1; - } else { - ALOGD("Forked child process %d", pid); - } - - // We blocked SIGCHLD prior to a fork, we unblock it here. - UnblockSignal(SIGCHLD, fail_fn); - - return pid; -} - // Create an app data directory over tmpfs overlayed CE / DE storage, and bind mount it // from the actual app data directory in data mirror. static bool createAndMountAppData(std::string_view package_name, @@ -1973,9 +1851,10 @@ static void AddUsapTableEntry(pid_t usap_pid, int read_pipe_fd) { static int sUsapTableInsertIndex = 0; int search_index = sUsapTableInsertIndex; - do { if (gUsapTable[search_index].SetIfInvalid(usap_pid, read_pipe_fd)) { + ++gUsapPoolCount; + // Start our next search right after where we finished this one. sUsapTableInsertIndex = (search_index + 1) % gUsapTable.size(); @@ -1993,7 +1872,7 @@ static void AddUsapTableEntry(pid_t usap_pid, int read_pipe_fd) { /** * Invalidates the entry in the USAPTable corresponding to the provided * process ID if it is present. If an entry was removed the USAP pool - * count is decremented. + * count is decremented. May be called from signal handler. * * @param usap_pid Process ID of the USAP entry to invalidate * @return True if an entry was invalidated; false otherwise @@ -2069,6 +1948,121 @@ static void UnmountStorageOnInit(JNIEnv* env) { namespace android { +/** + * A failure function used to report fatal errors to the managed runtime. This + * function is often curried with the process name information and then passed + * to called functions. + * + * @param env Managed runtime environment + * @param process_name A native representation of the process name + * @param managed_process_name A managed representation of the process name + * @param msg The error message to be reported + */ +[[noreturn]] +void zygote::ZygoteFailure(JNIEnv* env, + const char* process_name, + jstring managed_process_name, + const std::string& msg) { + std::unique_ptr<ScopedUtfChars> scoped_managed_process_name_ptr = nullptr; + if (managed_process_name != nullptr) { + scoped_managed_process_name_ptr.reset(new ScopedUtfChars(env, managed_process_name)); + if (scoped_managed_process_name_ptr->c_str() != nullptr) { + process_name = scoped_managed_process_name_ptr->c_str(); + } + } + + const std::string& error_msg = + (process_name == nullptr || process_name[0] == '\0') ? + msg : StringPrintf("(%s) %s", process_name, msg.c_str()); + + env->FatalError(error_msg.c_str()); + __builtin_unreachable(); +} + +// Utility routine to fork a process from the zygote. +NO_PAC_FUNC +pid_t zygote::ForkCommon(JNIEnv* env, bool is_system_server, + const std::vector<int>& fds_to_close, + const std::vector<int>& fds_to_ignore, + bool is_priority_fork, + bool purge) { + SetSignalHandlers(); + + // Curry a failure function. + auto fail_fn = std::bind(zygote::ZygoteFailure, env, + is_system_server ? "system_server" : "zygote", + nullptr, _1); + + // Temporarily block SIGCHLD during forks. The SIGCHLD handler might + // log, which would result in the logging FDs we close being reopened. + // This would cause failures because the FDs are not allowlisted. + // + // Note that the zygote process is single threaded at this point. + BlockSignal(SIGCHLD, fail_fn); + + // Close any logging related FDs before we start evaluating the list of + // file descriptors. + __android_log_close(); + AStatsSocket_close(); + + // If this is the first fork for this zygote, create the open FD table. If + // it isn't, we just need to check whether the list of open files has changed + // (and it shouldn't in the normal case). + if (gOpenFdTable == nullptr) { + gOpenFdTable = FileDescriptorTable::Create(fds_to_ignore, fail_fn); + } else { + gOpenFdTable->Restat(fds_to_ignore, fail_fn); + } + + android_fdsan_error_level fdsan_error_level = android_fdsan_get_error_level(); + + if (purge) { + // Purge unused native memory in an attempt to reduce the amount of false + // sharing with the child process. By reducing the size of the libc_malloc + // region shared with the child process we reduce the number of pages that + // transition to the private-dirty state when malloc adjusts the meta-data + // on each of the pages it is managing after the fork. + mallopt(M_PURGE, 0); + } + + pid_t pid = fork(); + + if (pid == 0) { + if (is_priority_fork) { + setpriority(PRIO_PROCESS, 0, PROCESS_PRIORITY_MAX); + } else { + setpriority(PRIO_PROCESS, 0, PROCESS_PRIORITY_MIN); + } + + // The child process. + PAuthKeyChange(env); + PreApplicationInit(); + + // Clean up any descriptors which must be closed immediately + DetachDescriptors(env, fds_to_close, fail_fn); + + // Invalidate the entries in the USAP table. + ClearUsapTable(); + + // Re-open all remaining open file descriptors so that they aren't shared + // with the zygote across a fork. + gOpenFdTable->ReopenOrDetach(fail_fn); + + // Turn fdsan back on. + android_fdsan_set_error_level(fdsan_error_level); + + // Reset the fd to the unsolicited zygote socket + gSystemServerSocketFd = -1; + } else { + ALOGD("Forked child process %d", pid); + } + + // We blocked SIGCHLD prior to a fork, we unblock it here. + UnblockSignal(SIGCHLD, fail_fn); + + return pid; +} + static void com_android_internal_os_Zygote_nativePreApplicationInit(JNIEnv*, jclass) { PreApplicationInit(); } @@ -2085,7 +2079,8 @@ static jint com_android_internal_os_Zygote_nativeForkAndSpecialize( jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote); if (UNLIKELY(managed_fds_to_close == nullptr)) { - ZygoteFailure(env, "zygote", nice_name, "Zygote received a null fds_to_close vector."); + zygote::ZygoteFailure(env, "zygote", nice_name, + "Zygote received a null fds_to_close vector."); } std::vector<int> fds_to_close = @@ -2111,7 +2106,7 @@ static jint com_android_internal_os_Zygote_nativeForkAndSpecialize( fds_to_ignore.push_back(gSystemServerSocketFd); } - pid_t pid = ForkCommon(env, false, fds_to_close, fds_to_ignore, true); + pid_t pid = zygote::ForkCommon(env, false, fds_to_close, fds_to_ignore, true); if (pid == 0) { SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits, @@ -2146,10 +2141,10 @@ static jint com_android_internal_os_Zygote_nativeForkSystemServer( fds_to_ignore.push_back(gSystemServerSocketFd); } - pid_t pid = ForkCommon(env, true, - fds_to_close, - fds_to_ignore, - true); + pid_t pid = zygote::ForkCommon(env, true, + fds_to_close, + fds_to_ignore, + true); if (pid == 0) { // System server prcoess does not need data isolation so no need to // know pkg_data_info_list. @@ -2189,58 +2184,74 @@ static jint com_android_internal_os_Zygote_nativeForkSystemServer( * ensuring proper file descriptor hygiene. * * @param env Managed runtime environment - * @param read_pipe_fd The read FD for the USAP reporting pipe. Manually closed by blastlas - * in managed code. + * @param read_pipe_fd The read FD for the USAP reporting pipe. Manually closed by the child + * in managed code. -1 indicates none. * @param write_pipe_fd The write FD for the USAP reporting pipe. Manually closed by the - * zygote in managed code. + * zygote in managed code. -1 indicates none. * @param managed_session_socket_fds A list of anonymous session sockets that must be ignored by * the FD hygiene code and automatically "closed" in the new USAP. + * @param args_known Arguments for specialization are available; no need to read from a socket * @param is_priority_fork Controls the nice level assigned to the newly created process - * @return + * @return child pid in the parent, 0 in the child */ NO_PAC_FUNC -static jint com_android_internal_os_Zygote_nativeForkUsap(JNIEnv* env, - jclass, - jint read_pipe_fd, - jint write_pipe_fd, - jintArray managed_session_socket_fds, - jboolean is_priority_fork) { - std::vector<int> fds_to_close(MakeUsapPipeReadFDVector()), - fds_to_ignore(fds_to_close); - +static jint com_android_internal_os_Zygote_nativeForkApp(JNIEnv* env, + jclass, + jint read_pipe_fd, + jint write_pipe_fd, + jintArray managed_session_socket_fds, + jboolean args_known, + jboolean is_priority_fork) { std::vector<int> session_socket_fds = ExtractJIntArray(env, "USAP", nullptr, managed_session_socket_fds) .value_or(std::vector<int>()); + return zygote::forkApp(env, read_pipe_fd, write_pipe_fd, session_socket_fds, + args_known == JNI_TRUE, is_priority_fork == JNI_TRUE, true); +} - // The USAP Pool Event FD is created during the initialization of the - // USAP pool and should always be valid here. +NO_PAC_FUNC +int zygote::forkApp(JNIEnv* env, + int read_pipe_fd, + int write_pipe_fd, + const std::vector<int>& session_socket_fds, + bool args_known, + bool is_priority_fork, + bool purge) { + + std::vector<int> fds_to_close(MakeUsapPipeReadFDVector()), + fds_to_ignore(fds_to_close); fds_to_close.push_back(gZygoteSocketFD); - fds_to_close.push_back(gUsapPoolEventFD); - fds_to_close.insert(fds_to_close.end(), session_socket_fds.begin(), session_socket_fds.end()); if (gSystemServerSocketFd != -1) { fds_to_close.push_back(gSystemServerSocketFd); } + if (args_known) { + fds_to_close.push_back(gUsapPoolSocketFD); + } + fds_to_close.insert(fds_to_close.end(), session_socket_fds.begin(), session_socket_fds.end()); - fds_to_ignore.push_back(gZygoteSocketFD); fds_to_ignore.push_back(gUsapPoolSocketFD); - fds_to_ignore.push_back(gUsapPoolEventFD); - fds_to_ignore.push_back(read_pipe_fd); - fds_to_ignore.push_back(write_pipe_fd); + fds_to_ignore.push_back(gZygoteSocketFD); + if (read_pipe_fd != -1) { + fds_to_ignore.push_back(read_pipe_fd); + } + if (write_pipe_fd != -1) { + fds_to_ignore.push_back(write_pipe_fd); + } fds_to_ignore.insert(fds_to_ignore.end(), session_socket_fds.begin(), session_socket_fds.end()); + + if (gUsapPoolEventFD != -1) { + fds_to_close.push_back(gUsapPoolEventFD); + fds_to_ignore.push_back(gUsapPoolEventFD); + } if (gSystemServerSocketFd != -1) { + if (args_known) { + fds_to_close.push_back(gSystemServerSocketFd); + } fds_to_ignore.push_back(gSystemServerSocketFd); } - - pid_t usap_pid = ForkCommon(env, /* is_system_server= */ false, fds_to_close, fds_to_ignore, - is_priority_fork == JNI_TRUE); - - if (usap_pid != 0) { - ++gUsapPoolCount; - AddUsapTableEntry(usap_pid, read_pipe_fd); - } - - return usap_pid; + return zygote::ForkCommon(env, /* is_system_server= */ false, fds_to_close, + fds_to_ignore, is_priority_fork == JNI_TRUE, purge); } static void com_android_internal_os_Zygote_nativeAllowFileAcrossFork( @@ -2354,7 +2365,7 @@ static void com_android_internal_os_Zygote_nativeInitNativeState(JNIEnv* env, jc */ if (!SetTaskProfiles(0, {})) { - ZygoteFailure(env, "zygote", nullptr, "Zygote SetTaskProfiles failed"); + zygote::ZygoteFailure(env, "zygote", nullptr, "Zygote SetTaskProfiles failed"); } } @@ -2372,15 +2383,21 @@ static jintArray com_android_internal_os_Zygote_nativeGetUsapPipeFDs(JNIEnv* env return managed_usap_fds; } +/* + * Add the given pid and file descriptor to the Usap table. CriticalNative method. + */ +static void com_android_internal_os_Zygote_nativeAddUsapTableEntry(jint pid, jint read_pipe_fd) { + AddUsapTableEntry(pid, read_pipe_fd); +} + /** - * A JNI wrapper around RemoveUsapTableEntry. + * A JNI wrapper around RemoveUsapTableEntry. CriticalNative method. * * @param env Managed runtime environment * @param usap_pid Process ID of the USAP entry to invalidate * @return True if an entry was invalidated; false otherwise. */ -static jboolean com_android_internal_os_Zygote_nativeRemoveUsapTableEntry(JNIEnv* env, jclass, - jint usap_pid) { +static jboolean com_android_internal_os_Zygote_nativeRemoveUsapTableEntry(jint usap_pid) { return RemoveUsapTableEntry(usap_pid); } @@ -2395,7 +2412,8 @@ static jboolean com_android_internal_os_Zygote_nativeRemoveUsapTableEntry(JNIEnv static jint com_android_internal_os_Zygote_nativeGetUsapPoolEventFD(JNIEnv* env, jclass) { if (gUsapPoolEventFD == -1) { if ((gUsapPoolEventFD = eventfd(0, 0)) == -1) { - ZygoteFailure(env, "zygote", nullptr, StringPrintf("Unable to create eventfd: %s", strerror(errno))); + zygote::ZygoteFailure(env, "zygote", nullptr, + StringPrintf("Unable to create eventfd: %s", strerror(errno))); } } @@ -2438,12 +2456,12 @@ static void com_android_internal_os_Zygote_nativeEmptyUsapPool(JNIEnv* env, jcla } static void com_android_internal_os_Zygote_nativeBlockSigTerm(JNIEnv* env, jclass) { - auto fail_fn = std::bind(ZygoteFailure, env, "usap", nullptr, _1); + auto fail_fn = std::bind(zygote::ZygoteFailure, env, "usap", nullptr, _1); BlockSignal(SIGTERM, fail_fn); } static void com_android_internal_os_Zygote_nativeUnblockSigTerm(JNIEnv* env, jclass) { - auto fail_fn = std::bind(ZygoteFailure, env, "usap", nullptr, _1); + auto fail_fn = std::bind(zygote::ZygoteFailure, env, "usap", nullptr, _1); UnblockSignal(SIGTERM, fail_fn); } @@ -2549,7 +2567,10 @@ static const JNINativeMethod gMethods[] = { (void*)com_android_internal_os_Zygote_nativePreApplicationInit}, {"nativeInstallSeccompUidGidFilter", "(II)V", (void*)com_android_internal_os_Zygote_nativeInstallSeccompUidGidFilter}, - {"nativeForkUsap", "(II[IZ)I", (void*)com_android_internal_os_Zygote_nativeForkUsap}, + {"nativeForkApp", "(II[IZZ)I", (void*)com_android_internal_os_Zygote_nativeForkApp}, + // @CriticalNative + {"nativeAddUsapTableEntry", "(II)V", + (void*)com_android_internal_os_Zygote_nativeAddUsapTableEntry}, {"nativeSpecializeAppProcess", "(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/" "String;Z[Ljava/lang/String;[Ljava/lang/String;ZZ)V", @@ -2558,6 +2579,10 @@ static const JNINativeMethod gMethods[] = { (void*)com_android_internal_os_Zygote_nativeInitNativeState}, {"nativeGetUsapPipeFDs", "()[I", (void*)com_android_internal_os_Zygote_nativeGetUsapPipeFDs}, + // @CriticalNative + {"nativeAddUsapTableEntry", "(II)V", + (void*)com_android_internal_os_Zygote_nativeAddUsapTableEntry}, + // @CriticalNative {"nativeRemoveUsapTableEntry", "(I)Z", (void*)com_android_internal_os_Zygote_nativeRemoveUsapTableEntry}, {"nativeGetUsapPoolEventFD", "()I", diff --git a/core/jni/com_android_internal_os_Zygote.h b/core/jni/com_android_internal_os_Zygote.h new file mode 100644 index 000000000000..d2da91476bc7 --- /dev/null +++ b/core/jni/com_android_internal_os_Zygote.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _COM_ANDROID_INTERNAL_OS_ZYGOTE_H +#define _COM_ANDROID_INTERNAL_OS_ZYGOTE_H + +#define LOG_TAG "Zygote" +#define ATRACE_TAG ATRACE_TAG_DALVIK + +/* Functions in the callchain during the fork shall not be protected with + Armv8.3-A Pointer Authentication, otherwise child will not be able to return. */ +#ifdef __ARM_FEATURE_PAC_DEFAULT +#ifdef __ARM_FEATURE_BTI_DEFAULT +#define NO_PAC_FUNC __attribute__((target("branch-protection=bti"))) +#else +#define NO_PAC_FUNC __attribute__((target("branch-protection=none"))) +#endif /* __ARM_FEATURE_BTI_DEFAULT */ +#else /* !__ARM_FEATURE_PAC_DEFAULT */ +#define NO_PAC_FUNC +#endif /* __ARM_FEATURE_PAC_DEFAULT */ + +#include <jni.h> +#include <vector> +#include <android-base/stringprintf.h> + +#define CREATE_ERROR(...) StringPrintf("%s:%d: ", __FILE__, __LINE__). \ + append(StringPrintf(__VA_ARGS__)) + +namespace android { +namespace zygote { + +NO_PAC_FUNC +pid_t ForkCommon(JNIEnv* env,bool is_system_server, + const std::vector<int>& fds_to_close, + const std::vector<int>& fds_to_ignore, + bool is_priority_fork, + bool purge = true); + +/** + * Fork a process. The pipe fds are used for usap communication, or -1 in + * other cases. Session_socket_fds are FDs used for zygote communication that must be dealt + * with hygienically, but are not otherwise used here. Args_known indicates that the process + * will be immediately specialized with arguments that are already known, so no usap + * communication is required. Is_priority_fork should be true if this is on the app startup + * critical path. Purge specifies that unused pages should be purged before the fork. + */ +NO_PAC_FUNC +int forkApp(JNIEnv* env, + int read_pipe_fd, + int write_pipe_fd, + const std::vector<int>& session_socket_fds, + bool args_known, + bool is_priority_fork, + bool purge); + +[[noreturn]] +void ZygoteFailure(JNIEnv* env, + const char* process_name, + jstring managed_process_name, + const std::string& msg); + +} // namespace zygote +} // namespace android + +#endif // _COM_ANDROID_INTERNAL_OS_ZYGOTE_ diff --git a/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp b/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp new file mode 100644 index 000000000000..011e8f8f1b8c --- /dev/null +++ b/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp @@ -0,0 +1,512 @@ +/* + * 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 "com_android_internal_os_Zygote.h" + +#include <algorithm> +#include <android-base/logging.h> +#include <async_safe/log.h> +#include <cctype> +#include <chrono> +#include <core_jni_helpers.h> +#include <errno.h> +#include <fcntl.h> +#include <jni.h> +#include <nativehelper/JNIHelp.h> +#include <optional> +#include <poll.h> +#include <unistd.h> +#include <utility> +#include <utils/misc.h> +#include <sys/mman.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <vector> + +namespace android { + +using namespace std::placeholders; +using android::base::StringPrintf; +using android::zygote::ZygoteFailure; + +// WARNING: Knows a little about the wire protocol used to communicate with Zygote. +// TODO: Fix error handling. + +constexpr size_t MAX_COMMAND_BYTES = 12200; +constexpr size_t NICE_NAME_BYTES = 50; + +// A buffer optionally bundled with a file descriptor from which we can fill it. +// Does not own the file descriptor; destroying a NativeCommandBuffer does not +// close the descriptor. +class NativeCommandBuffer { + public: + NativeCommandBuffer(int sourceFd): mEnd(0), mNext(0), mLinesLeft(0), mFd(sourceFd) {} + + // Read mNext line from mFd, filling mBuffer from file descriptor, as needed. + // Return a pair of pointers pointing to the first character, and one past the + // mEnd of the line, i.e. at the newline. Returns nothing on failure. + template<class FailFn> + std::optional<std::pair<char*, char*>> readLine(FailFn fail_fn) { + char* result = mBuffer + mNext; + while (true) { + if (mNext == mEnd) { + if (mEnd == MAX_COMMAND_BYTES) { + return {}; + } + if (mFd == -1) { + fail_fn("ZygoteCommandBuffer.readLine attempted to read from mFd -1"); + } + ssize_t nread = TEMP_FAILURE_RETRY(read(mFd, mBuffer + mEnd, MAX_COMMAND_BYTES - mEnd)); + if (nread <= 0) { + if (nread == 0) { + return {}; + } + fail_fn(CREATE_ERROR("session socket read failed: %s", strerror(errno))); + } else if (nread == MAX_COMMAND_BYTES - mEnd) { + // This is pessimistic by one character, but close enough. + fail_fn("ZygoteCommandBuffer overflowed: command too long"); + } + mEnd += nread; + } + // UTF-8 does not allow newline to occur as part of a multibyte character. + char* nl = static_cast<char *>(memchr(mBuffer + mNext, '\n', mEnd - mNext)); + if (nl == nullptr) { + mNext = mEnd; + } else { + mNext = nl - mBuffer + 1; + if (--mLinesLeft < 0) { + fail_fn("ZygoteCommandBuffer.readLine attempted to read past mEnd of command"); + } + return std::make_pair(result, nl); + } + } + } + + void reset() { + mNext = 0; + } + + // Make sure the current command is fully buffered, without reading past the current command. + template<class FailFn> + void readAllLines(FailFn fail_fn) { + while (mLinesLeft > 0) { + readLine(fail_fn); + } + } + + void clear() { + // Don't bother to actually clear the buffer; it'll be unmapped in the child anyway. + reset(); + mNiceName[0] = '\0'; + mEnd = 0; + } + + // Insert line into the mBuffer. Checks that the mBuffer is not associated with an mFd. + // Implicitly adds newline separators. Allows mBuffer contents to be explicitly set. + void insert(const char* line, size_t lineLen) { + DCHECK(mFd == -1); + CHECK(mEnd + lineLen < MAX_COMMAND_BYTES); + strncpy(mBuffer + mEnd, line, lineLen); + mBuffer[mEnd + lineLen] = '\n'; + mEnd += lineLen + 1; + } + + // Clear mBuffer, start reading new command, return the number of arguments, leaving mBuffer + // positioned at the beginning of first argument. Return 0 on EOF. + template<class FailFn> + int getCount(FailFn fail_fn) { + mLinesLeft = 1; + auto line = readLine(fail_fn); + if (!line.has_value()) { + return 0; + } + char* countString = line.value().first; // Newline terminated. + long nArgs = atol(countString); + if (nArgs <= 0 || nArgs >= MAX_COMMAND_BYTES / 2) { + fail_fn(CREATE_ERROR("Unreasonable argument count %ld", nArgs)); + } + mLinesLeft = nArgs; + return static_cast<int>(nArgs); + } + + // Is the mBuffer a simple fork command? + // We disallow request to wrap the child process, child zygotes, anything that + // mentions capabilities or requests uid < minUid. + // We insist that --setuid and --setgid arguments are explicitly included and that the + // command starts with --runtime-args. + // Assumes we are positioned at the beginning of the command after the argument count, + // and leaves the position at some indeterminate position in the buffer. + // As a side effect, this sets mNiceName to a non-empty string, if possible. + template<class FailFn> + bool isSimpleForkCommand(int minUid, FailFn fail_fn) { + if (mLinesLeft <= 0 || mLinesLeft >= MAX_COMMAND_BYTES / 2) { + return false; + } + static const char* RUNTIME_ARGS = "--runtime-args"; + static const char* INVOKE_WITH = "--invoke-with"; + static const char* CHILD_ZYGOTE = "--start-child-zygote"; + static const char* SETUID = "--setuid="; + static const char* SETGID = "--setgid="; + static const char* CAPABILITIES = "--capabilities"; + static const char* NICE_NAME = "--nice-name="; + static const size_t RA_LENGTH = strlen(RUNTIME_ARGS); + static const size_t IW_LENGTH = strlen(INVOKE_WITH); + static const size_t CZ_LENGTH = strlen(CHILD_ZYGOTE); + static const size_t SU_LENGTH = strlen(SETUID); + static const size_t SG_LENGTH = strlen(SETGID); + static const size_t CA_LENGTH = strlen(CAPABILITIES); + static const size_t NN_LENGTH = strlen(NICE_NAME); + + bool saw_setuid = false, saw_setgid = false; + bool saw_runtime_args = false; + + while (mLinesLeft > 0) { + auto read_result = readLine(fail_fn); + if (!read_result.has_value()) { + return false; + } + auto [arg_start, arg_end] = read_result.value(); + if (arg_end - arg_start == RA_LENGTH + && strncmp(arg_start, RUNTIME_ARGS, RA_LENGTH) == 0) { + saw_runtime_args = true; + continue; + } + if (arg_end - arg_start >= NN_LENGTH + && strncmp(arg_start, NICE_NAME, NN_LENGTH) == 0) { + size_t name_len = arg_end - (arg_start + NN_LENGTH); + size_t copy_len = std::min(name_len, NICE_NAME_BYTES - 1); + memcpy(mNiceName, arg_start + NN_LENGTH, copy_len); + mNiceName[copy_len] = '\0'; + continue; + } + if (arg_end - arg_start == IW_LENGTH + && strncmp(arg_start, INVOKE_WITH, IW_LENGTH) == 0) { + // This also removes the need for invoke-with security checks here. + return false; + } + if (arg_end - arg_start == CZ_LENGTH + && strncmp(arg_start, CHILD_ZYGOTE, CZ_LENGTH) == 0) { + return false; + } + if (arg_end - arg_start >= CA_LENGTH + && strncmp(arg_start, CAPABILITIES, CA_LENGTH) == 0) { + return false; + } + if (arg_end - arg_start >= SU_LENGTH + && strncmp(arg_start, SETUID, SU_LENGTH) == 0) { + int uid = digitsVal(arg_start + SU_LENGTH, arg_end); + if (uid < minUid) { + return false; + } + saw_setuid = true; + continue; + } + if (arg_end - arg_start >= SG_LENGTH + && strncmp(arg_start, SETGID, SG_LENGTH) == 0) { + int gid = digitsVal(arg_start + SG_LENGTH, arg_end); + if (gid == -1) { + return false; + } + saw_setgid = true; + } + } + return saw_runtime_args && saw_setuid && saw_setgid; + } + + void setFd(int new_fd) { + mFd = new_fd; + } + + int getFd() const { + return mFd; + } + + const char* niceNameAddr() const { + return mNiceName; + } + + // Debug only: + void logState() const { + ALOGD("mbuffer starts with %c%c, nice name is %s, " + "mEnd = %u, mNext = %u, mLinesLeft = %d, mFd = %d", + mBuffer[0], (mBuffer[1] == '\n' ? ' ' : mBuffer[1]), + niceNameAddr(), + static_cast<unsigned>(mEnd), static_cast<unsigned>(mNext), + static_cast<int>(mLinesLeft), mFd); + } + + private: + // Picky version of atoi(). No sign or unexpected characters allowed. Return -1 on failure. + static int digitsVal(char* start, char* end) { + int result = 0; + if (end - start > 6) { + return -1; + } + for (char* dp = start; dp < end; ++dp) { + if (*dp < '0' || *dp > '9') { + ALOGW("Argument failed integer format check"); + return -1; + } + result = 10 * result + (*dp - '0'); + } + return result; + } + + uint32_t mEnd; // Index of first empty byte in the mBuffer. + uint32_t mNext; // Index of first character past last line returned by readLine. + int32_t mLinesLeft; // Lines in current command that haven't yet been read. + int mFd; // Open file descriptor from which we can read more. -1 if none. + char mNiceName[NICE_NAME_BYTES]; + char mBuffer[MAX_COMMAND_BYTES]; +}; + +static_assert(sizeof(NativeCommandBuffer) < 3 * 4096); + +static int buffersAllocd(0); + +// Get a new NativeCommandBuffer. Can only be called once between freeNativeBuffer calls, +// so that only one buffer exists at a time. +jlong com_android_internal_os_ZygoteCommandBuffer_getNativeBuffer(JNIEnv* env, jclass, jint fd) { + CHECK(buffersAllocd == 0); + ++buffersAllocd; + // MMap explicitly to get it page aligned. + void *bufferMem = mmap(NULL, sizeof(NativeCommandBuffer), PROT_READ | PROT_WRITE, + MAP_ANONYMOUS | MAP_PRIVATE | MAP_POPULATE, -1, 0); + // Currently we mmap and unmap one for every request handled by the Java code. + // That could be improved, but unclear it matters. + if (bufferMem == MAP_FAILED) { + ZygoteFailure(env, nullptr, nullptr, "Failed to map argument buffer"); + } + return (jlong) new(bufferMem) NativeCommandBuffer(fd); +} + +// Delete native command buffer. +void com_android_internal_os_ZygoteCommandBuffer_freeNativeBuffer(JNIEnv* env, jclass, + jlong j_buffer) { + CHECK(buffersAllocd == 1); + NativeCommandBuffer* n_buffer = reinterpret_cast<NativeCommandBuffer*>(j_buffer); + n_buffer->~NativeCommandBuffer(); + if (munmap(n_buffer, sizeof(NativeCommandBuffer)) != 0) { + ZygoteFailure(env, nullptr, nullptr, "Failed to unmap argument buffer"); + } + --buffersAllocd; +} + +// Clear the buffer, read the line containing the count, and return the count. +jint com_android_internal_os_ZygoteCommandBuffer_nativeGetCount(JNIEnv* env, jclass, + jlong j_buffer) { + NativeCommandBuffer* n_buffer = reinterpret_cast<NativeCommandBuffer*>(j_buffer); + auto fail_fn = std::bind(ZygoteFailure, env, nullptr, nullptr, _1); + return n_buffer->getCount(fail_fn); +} + +// Explicitly insert a string as the last line (argument) of the buffer. +void com_android_internal_os_ZygoteCommandBuffer_insert(JNIEnv* env, jclass, jlong j_buffer, + jstring line) { + NativeCommandBuffer* n_buffer = reinterpret_cast<NativeCommandBuffer*>(j_buffer); + size_t lineLen = static_cast<size_t>(env->GetStringUTFLength(line)); + const char* cstring = env->GetStringUTFChars(line, NULL); + n_buffer->insert(cstring, lineLen); + env->ReleaseStringUTFChars(line, cstring); +} + +// Read a line from the buffer, refilling as necessary. +jstring com_android_internal_os_ZygoteCommandBuffer_nativeNextArg(JNIEnv* env, jclass, + jlong j_buffer) { + NativeCommandBuffer* n_buffer = reinterpret_cast<NativeCommandBuffer*>(j_buffer); + auto fail_fn = std::bind(ZygoteFailure, env, n_buffer->niceNameAddr(), nullptr, _1); + auto line = n_buffer->readLine(fail_fn); + if (!line.has_value()) { + fail_fn("Incomplete zygote command"); + } + auto [cresult, endp] = line.value(); + // OK to temporarily clobber the buffer, since this is not thread safe, and we're modifying + // the buffer anyway. + *endp = '\0'; + jstring result = env->NewStringUTF(cresult); + *endp = '\n'; + return result; +} + +// Read all lines from the current command into the buffer, and then reset the buffer, so +// we will start reading again at the beginning of the command, starting with the argument +// count. And we don't need access to the fd to do so. +void com_android_internal_os_ZygoteCommandBuffer_nativeReadFullyAndReset(JNIEnv* env, jclass, + jlong j_buffer) { + NativeCommandBuffer* n_buffer = reinterpret_cast<NativeCommandBuffer*>(j_buffer); + auto fail_fn = std::bind(ZygoteFailure, env, n_buffer->niceNameAddr(), nullptr, _1); + n_buffer->readAllLines(fail_fn); + n_buffer->reset(); +} + +// Fork a child as specified by the current command buffer, and refill the command +// buffer from the given socket. So long as the result is another simple fork command, +// repeat this process. +// It must contain a fork command, which is currently restricted not to fork another +// zygote or involve a wrapper process. +// The initial buffer should be partially or entirely read; we read it fully and reset it. +// When we return, the buffer contains the command we couldn't handle, and has been reset(). +// We return false in the parent when we see a command we didn't understand, and thus the +// command in the buffer still needs to be executed. +// We return true in each child. +// We only process fork commands if the peer uid matches expected_uid. +// For every fork command after the first, we check that the requested uid is at +// least minUid. +NO_PAC_FUNC +jboolean com_android_internal_os_ZygoteCommandBuffer_nativeForkRepeatedly( + JNIEnv* env, + jclass, + jlong j_buffer, + jint zygote_socket_fd, + jint expected_uid, + jint minUid, + jstring managed_nice_name) { + + NativeCommandBuffer* n_buffer = reinterpret_cast<NativeCommandBuffer*>(j_buffer); + int session_socket = n_buffer->getFd(); + std::vector<int> session_socket_fds {session_socket}; + auto fail_fn_1 = std::bind(ZygoteFailure, env, static_cast<const char*>(nullptr), + static_cast<jstring>(managed_nice_name), _1); + // This binds to the nice name address; the actual names are updated by isSimpleForkCommand: + auto fail_fn_n = std::bind(ZygoteFailure, env, n_buffer->niceNameAddr(), + static_cast<jstring>(nullptr), _1); + auto fail_fn_z = std::bind(ZygoteFailure, env, "zygote", nullptr, _1); + + struct pollfd fd_structs[2]; + static const int ZYGOTE_IDX = 0; + static const int SESSION_IDX = 1; + fd_structs[ZYGOTE_IDX].fd = zygote_socket_fd; + fd_structs[ZYGOTE_IDX].events = POLLIN; + fd_structs[SESSION_IDX].fd = session_socket; + fd_structs[SESSION_IDX].events = POLLIN; + + struct timeval timeout; + socklen_t timeout_size = sizeof timeout; + if (getsockopt(session_socket, SOL_SOCKET, SO_RCVTIMEO, &timeout, &timeout_size) != 0) { + fail_fn_z("Failed to retrieve session socket timeout"); + } + + struct ucred credentials; + socklen_t cred_size = sizeof credentials; + if (getsockopt(n_buffer->getFd(), SOL_SOCKET, SO_PEERCRED, &credentials, &cred_size) == -1 + || cred_size != sizeof credentials) { + fail_fn_1("ForkMany failed to get initial credentials, %s", strerror(errno)); + } + + bool first_time = true; + do { + if (credentials.uid != expected_uid) { + return JNI_FALSE; + } + n_buffer->readAllLines(first_time ? fail_fn_1 : fail_fn_n); + n_buffer->reset(); + int pid = zygote::forkApp(env, /* no pipe FDs */ -1, -1, session_socket_fds, + /*args_known=*/ true, /*is_priority_fork=*/ true, + /*purge=*/ first_time); + if (pid == 0) { + return JNI_TRUE; + } + // We're in the parent. Write big-endian pid, followed by a boolean. + char pid_buf[5]; + int tmp_pid = pid; + for (int i = 3; i >= 0; --i) { + pid_buf[i] = tmp_pid & 0xff; + tmp_pid >>= 8; + } + pid_buf[4] = 0; // Process is not wrapped. + int res = write(session_socket, pid_buf, 5); + if (res != 5) { + if (res == -1) { + (first_time ? fail_fn_1 : fail_fn_n) + (CREATE_ERROR("Pid write error %d: %s", errno, strerror(errno))); + } else { + (first_time ? fail_fn_1 : fail_fn_n) + (CREATE_ERROR("Write unexpectedly returned short: %d < 5", res)); + } + } + // Clear buffer and get count from next command. + n_buffer->clear(); + for (;;) { + // Poll isn't strictly necessary for now. But without it, disconnect is hard to detect. + int poll_res = TEMP_FAILURE_RETRY(poll(fd_structs, 2, -1 /* infinite timeout */)); + if ((fd_structs[SESSION_IDX].revents & POLLIN) != 0) { + if (n_buffer->getCount(fail_fn_z) != 0) { + break; + } // else disconnected; + } else if (poll_res == 0 || (fd_structs[ZYGOTE_IDX].revents & POLLIN) == 0) { + fail_fn_z( + CREATE_ERROR("Poll returned with no descriptors ready! Poll returned %d", poll_res)); + } + // We've now seen either a disconnect or connect request. + close(session_socket); + int new_fd = accept(zygote_socket_fd, nullptr, nullptr); + if (new_fd == -1) { + fail_fn_z("Accept(%d) failed: %s", zygote_socket_fd, strerror(errno)); + } + if (new_fd != session_socket) { + // Move new_fd back to the old value, so that we don't have to change Java-level data + // structures to reflect a change. This implicitly closes the old one. + if (dup2(new_fd, session_socket) != session_socket) { + fail_fn_z(CREATE_ERROR("Failed to move fd %d to %d: %s", + new_fd, session_socket, strerror(errno))); + } + close(new_fd); + } + // If we ever return, we effectively reuse the old Java ZygoteConnection. + // None of its state needs to change. + if (setsockopt(session_socket, SOL_SOCKET, SO_RCVTIMEO, &timeout, timeout_size) != 0) { + fail_fn_z(CREATE_ERROR("Failed to set receive timeout for socket %d: %s", + session_socket, strerror(errno))); + } + if (setsockopt(session_socket, SOL_SOCKET, SO_SNDTIMEO, &timeout, timeout_size) != 0) { + fail_fn_z(CREATE_ERROR("Failed to set send timeout for socket %d: %s", + session_socket, strerror(errno))); + } + if (getsockopt(session_socket, SOL_SOCKET, SO_PEERCRED, &credentials, &cred_size) == -1) { + fail_fn_z(CREATE_ERROR("ForkMany failed to get credentials: %s", strerror(errno))); + } + if (cred_size != sizeof credentials) { + fail_fn_z(CREATE_ERROR("ForkMany credential size = %d, should be %d", + cred_size, static_cast<int>(sizeof credentials))); + } + } + first_time = false; + } while (n_buffer->isSimpleForkCommand(minUid, fail_fn_n)); + ALOGW("forkRepeatedly terminated due to non-simple command"); + n_buffer->logState(); + n_buffer->reset(); + return JNI_FALSE; +} + +#define METHOD_NAME(m) com_android_internal_os_ZygoteCommandBuffer_ ## m + +static const JNINativeMethod gMethods[] = { + {"getNativeBuffer", "(I)J", (void *) METHOD_NAME(getNativeBuffer)}, + {"freeNativeBuffer", "(J)V", (void *) METHOD_NAME(freeNativeBuffer)}, + {"insert", "(JLjava/lang/String;)V", (void *) METHOD_NAME(insert)}, + {"nativeNextArg", "(J)Ljava/lang/String;", (void *) METHOD_NAME(nativeNextArg)}, + {"nativeReadFullyAndReset", "(J)V", (void *) METHOD_NAME(nativeReadFullyAndReset)}, + {"nativeGetCount", "(J)I", (void *) METHOD_NAME(nativeGetCount)}, + {"nativeForkRepeatedly", "(JIIILjava/lang/String;)Z", + (void *) METHOD_NAME(nativeForkRepeatedly)}, +}; + +int register_com_android_internal_os_ZygoteCommandBuffer(JNIEnv* env) { + return RegisterMethodsOrDie(env, "com/android/internal/os/ZygoteCommandBuffer", gMethods, + NELEM(gMethods)); +} + +} // namespace android diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto index d3c2d31779db..ec41a47a8798 100644 --- a/core/proto/android/server/activitymanagerservice.proto +++ b/core/proto/android/server/activitymanagerservice.proto @@ -627,6 +627,7 @@ message ActivityManagerServiceDumpProcessesProto { optional int64 duration_ms = 2; optional string tag = 3; optional int32 type = 4; + optional int32 reason_code = 5; } repeated PendingTempWhitelist pending_temp_whitelist = 26; diff --git a/core/proto/android/server/powerstatsservice.proto b/core/proto/android/server/powerstatsservice.proto index 4b1ee02a6c30..f64e146f87b7 100644 --- a/core/proto/android/server/powerstatsservice.proto +++ b/core/proto/android/server/powerstatsservice.proto @@ -144,7 +144,7 @@ message StateResidencyProto { */ optional int64 total_state_entry_count = 3; /** - * Last time this state was entered. Time in milliseconds since boot + * Last time this state was entered. Walltime in milliseconds since Unix epoch. */ optional int64 last_entry_timestamp_ms = 4; } @@ -208,7 +208,7 @@ message EnergyConsumerResultProto { /** Unique index identifying the energy consumer. */ optional int32 id = 1; - /** Time since device boot(CLOCK_BOOTTIME) in milli-seconds */ + /** Walltime in milliseconds since Unix epoch */ optional int64 timestamp_ms = 2; /** Accumulated energy since device boot in microwatt-seconds (uWs) */ @@ -247,7 +247,7 @@ message EnergyMeasurementProto { */ optional int32 id = 1; - /** Time since device boot(CLOCK_BOOTTIME) in milli-seconds */ + /** Walltime in milliseconds since Unix epoch */ optional int64 timestamp_ms = 2; /** Accumulated energy since device boot in microwatt-seconds (uWs) */ diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto index a1be865c0c16..ec502c3c272f 100644 --- a/core/proto/android/server/windowmanagerservice.proto +++ b/core/proto/android/server/windowmanagerservice.proto @@ -174,10 +174,10 @@ message DisplayContentProto { // Use root_display_area instead optional WindowContainerProto window_container = 1 [deprecated=true]; optional int32 id = 2; - reserved 3; // stacks - optional DockedStackDividerControllerProto docked_stack_divider_controller = 4 [deprecated=true]; + reserved 3; // RootTasks + optional DockedTaskDividerControllerProto docked_task_divider_controller = 4 [deprecated=true]; // Will be removed soon. - optional PinnedStackControllerProto pinned_stack_controller = 5 [deprecated=true]; + optional PinnedTaskControllerProto pinned_task_controller = 5 [deprecated=true]; /* non app windows */ repeated WindowTokenProto above_app_windows = 6 [deprecated=true]; repeated WindowTokenProto below_app_windows = 7 [deprecated=true]; @@ -256,15 +256,15 @@ message DisplayRotationProto { optional int32 last_orientation = 5 [(.android.typedef) = "android.content.pm.ActivityInfo.ScreenOrientation"]; } -/* represents DockedStackDividerController */ -message DockedStackDividerControllerProto { +/* represents DockedTaskDividerController */ +message DockedTaskDividerControllerProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; optional bool minimized_dock = 1 [deprecated=true]; } -/* represents PinnedStackController */ -message PinnedStackControllerProto { +/* represents PinnedTaskController */ +message PinnedTaskControllerProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; optional .android.graphics.RectProto default_bounds = 1 [deprecated=true]; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 5dd85805cfc1..d5f5d28aa7a2 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1284,15 +1284,15 @@ android:backgroundPermission="android.permission.RECORD_BACKGROUND_AUDIO" android:protectionLevel="dangerous|instant" /> - <!-- Allows an application to record audio while in the background. - <p>Protection level: dangerous - --> + <!-- @SystemApi @TestApi Allows an application to record audio while in the background. + This permission is not intended to be held by apps. + <p>Protection level: internal + @hide --> <permission android:name="android.permission.RECORD_BACKGROUND_AUDIO" android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_recordBackgroundAudio" android:description="@string/permdesc_recordBackgroundAudio" - android:permissionFlags="hardRestricted|installerExemptIgnored" - android:protectionLevel="dangerous" /> + android:protectionLevel="internal" /> <!-- ====================================================================== --> <!-- Permissions for activity recognition --> @@ -1368,15 +1368,15 @@ android:backgroundPermission="android.permission.BACKGROUND_CAMERA" android:protectionLevel="dangerous|instant" /> - <!-- Required to be able to access the camera device in the background. - <p>Protection level: dangerous - --> + <!-- @SystemApi @TestApi Required to be able to access the camera device in the background. + This permission is not intended to be held by apps. + <p>Protection level: internal + @hide --> <permission android:name="android.permission.BACKGROUND_CAMERA" android:permissionGroup="android.permission-group.UNDEFINED" android:label="@string/permlab_backgroundCamera" android:description="@string/permdesc_backgroundCamera" - android:permissionFlags="hardRestricted|installerExemptIgnored" - android:protectionLevel="dangerous" /> + android:protectionLevel="internal" /> <!-- @SystemApi Required in addition to android.permission.CAMERA to be able to access system only camera devices. @@ -2641,6 +2641,16 @@ android:label="@string/permlab_manageProfileAndDeviceOwners" android:description="@string/permdesc_manageProfileAndDeviceOwners" /> + <!-- @TestApi @hide Allows an application to reset the record of previous system update freeze + periods. --> + <permission android:name="android.permission.CLEAR_FREEZE_PERIOD" + android:protectionLevel="signature" /> + + <!-- @TestApi @hide Allows an application to force available DevicePolicyManager logs to + DPC. --> + <permission android:name="android.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS" + android:protectionLevel="signature" /> + <!-- Allows an application to get full detailed information about recently running tasks, with full fidelity to the real state. @hide --> @@ -3972,17 +3982,6 @@ <permission android:name="com.android.permission.INSTALL_EXISTING_PACKAGES" android:protectionLevel="signature|privileged" /> - <!-- Allows an application to use the package installer v2 APIs. - <p>The package installer v2 APIs are still a work in progress and we're - currently validating they work in all scenarios. - <p>Not for use by third-party applications. - TODO(b/152310230): use this permission to protect only Incremental installations - once the APIs are confirmed to be sufficient. - @hide - --> - <permission android:name="com.android.permission.USE_INSTALLER_V2" - android:protectionLevel="signature|verifier" /> - <!-- Allows an application to use System Data Loaders. <p>Not for use by third-party applications. @hide @@ -5421,6 +5420,12 @@ @hide --> <permission android:name="android.permission.MANAGE_SENSOR_PRIVACY" android:protectionLevel="signature" /> + + <!-- @SystemApi Allows sensor privacy changes to be observed. + @hide --> + <permission android:name="android.permission.OBSERVE_SENSOR_PRIVACY" + android:protectionLevel="signature|installer" /> + <!-- @SystemApi Permission that protects the {@link Intent#ACTION_REVIEW_ACCESSIBILITY_SERVICES} intent. @hide --> diff --git a/core/res/res/layout/splash_screen_view.xml b/core/res/res/layout/splash_screen_view.xml index 513da5e431e5..e6d724f1ecb2 100644 --- a/core/res/res/layout/splash_screen_view.xml +++ b/core/res/res/layout/splash_screen_view.xml @@ -21,14 +21,16 @@ android:orientation="vertical"> <View android:id="@+id/splashscreen_icon_view" - android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:layout_gravity="center"/> + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:layout_gravity="center" + android:contentDescription="@string/splash_screen_view_icon_description"/> <View android:id="@+id/splashscreen_branding_view" android:layout_height="wrap_content" android:layout_width="wrap_content" android:layout_gravity="center_horizontal|bottom" - android:layout_marginBottom="60dp"/> + android:layout_marginBottom="60dp" + android:contentDescription="@string/splash_screen_view_branding_description"/> </android.window.SplashScreenView>
\ No newline at end of file diff --git a/core/res/res/values-night/colors.xml b/core/res/res/values-night/colors.xml index 29f2b6f14b57..4410e944db39 100644 --- a/core/res/res/values-night/colors.xml +++ b/core/res/res/values-night/colors.xml @@ -26,6 +26,10 @@ <color name="notification_default_color_dark">#ddffffff</color> + <color name="notification_primary_text_color_current">@color/notification_primary_text_color_dark</color> + <color name="notification_secondary_text_color_current">@color/notification_secondary_text_color_dark</color> + <color name="notification_default_color_current">@color/notification_default_color_dark</color> + <color name="chooser_row_divider">@color/list_divider_color_dark</color> <color name="chooser_gradient_background">@color/loading_gradient_background_color_dark</color> <color name="chooser_gradient_highlight">@color/loading_gradient_highlight_color_dark</color> diff --git a/core/res/res/values-night/values.xml b/core/res/res/values-night/values.xml index b1bcf7285bb0..3fd82b874a84 100644 --- a/core/res/res/values-night/values.xml +++ b/core/res/res/values-night/values.xml @@ -28,9 +28,4 @@ </style> <style name="Theme.DeviceDefault.QuickSettings.Dialog" parent="Theme.DeviceDefault.Dialog" /> - - <style name="TextAppearance.Material.Notification"> - <item name="textColor">@color/notification_secondary_text_color_dark</item> - <item name="textSize">@dimen/notification_text_size</item> - </style> </resources>
\ No newline at end of file diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml index 59c260cecfbf..d79c01faaa36 100644 --- a/core/res/res/values/colors.xml +++ b/core/res/res/values/colors.xml @@ -143,6 +143,10 @@ <color name="notification_default_color_dark">@color/primary_text_default_material_light</color> <color name="notification_default_color_light">#a3202124</color> + <color name="notification_primary_text_color_current">@color/notification_primary_text_color_light</color> + <color name="notification_secondary_text_color_current">@color/notification_secondary_text_color_light</color> + <color name="notification_default_color_current">@color/notification_default_color_light</color> + <color name="notification_default_color">#757575</color> <!-- Gray 600 --> <color name="notification_action_button_text_color">@color/notification_default_color</color> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 592a3a1ff083..12cb3980f785 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1953,6 +1953,8 @@ <string name="config_systemShell" translatable="false">com.android.shell</string> <!-- The name of the package that will hold the system contacts role. --> <string name="config_systemContacts" translatable="false">com.android.contacts</string> + <!-- The name of the package that will hold the speech recognizer role by default. --> + <string name="config_systemSpeechRecognizer" translatable="false"></string> <!-- The name of the package that will be allowed to change its components' label/icon. --> <string name="config_overrideComponentUiPackage" translatable="false"></string> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 9b2573f3d62f..2004d0a8d15c 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -3166,6 +3166,8 @@ <public name="config_customMediaKeyDispatcher" /> <!-- @hide @SystemApi --> <public name="config_customMediaSessionPolicyProvider" /> + <!-- @hide @SystemApi --> + <public name="config_systemSpeechRecognizer" /> </public-group> <public-group type="id" first-id="0x01020055"> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 3505dee134c0..9eb2f145da38 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1644,7 +1644,7 @@ <!-- Message shown during acqusition when the user's face is turned too far left or right [CHAR LIMIT=50] --> <string name="face_acquired_pan_too_extreme">Turn your head a little less.</string> <!-- Message shown during acqusition when the user's face is tilted too high or too low [CHAR LIMIT=50] --> - <string name="face_acquired_tilt_too_extreme">Turn your head a little less.</string> + <string name="face_acquired_tilt_too_extreme">Tilt your head a little less.</string> <!-- Message shown during acquisiton when the user's face is tilted too far left or right [CHAR LIMIT=50] --> <string name="face_acquired_roll_too_extreme">Turn your head a little less.</string> <!-- Message shown during acquisition when the user's face is obscured [CHAR LIMIT=50] --> @@ -4538,7 +4538,7 @@ <string name="color_correction_feature_name">Color Correction</string> <!-- Title of Reduce Brightness feature, shown in the warning dialog about the accessibility shortcut. [CHAR LIMIT=none] --> - <string name="reduce_bright_colors_feature_name">Reduce Brightness</string> + <string name="reduce_bright_colors_feature_name">Reduce brightness</string> <!-- Text in toast to alert the user that the accessibility shortcut turned on an accessibility service. [CHAR LIMIT=none] --> <string name="accessibility_shortcut_enabling_service">Held volume keys. <xliff:g id="service_name" example="TalkBack">%1$s</xliff:g> turned on.</string> @@ -5849,4 +5849,8 @@ ul.</string> <!--- Label for notification channel for all sensor privacy related notifications. [CHAR LIMIT=NONE] --> <string name="sensor_privacy_notification_channel_label">Sensor Privacy</string> + <!-- Content description for the icon on the splash screen. [CHAR LIMIT=50] --> + <string name="splash_screen_view_icon_description">Application icon</string> + <!-- Content description for the branding image on the splash screen. [CHAR LIMIT=50] --> + <string name="splash_screen_view_branding_description">Application branding image</string> </resources> diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml index 158289a39d5c..3c4a5d4aab73 100644 --- a/core/res/res/values/styles_material.xml +++ b/core/res/res/values/styles_material.xml @@ -466,18 +466,20 @@ please see styles_device_defaults.xml. </style> <style name="TextAppearance.Material.Notification"> - <item name="textColor">@color/notification_secondary_text_color_light</item> + <item name="textColor">@color/notification_secondary_text_color_current</item> <item name="textSize">@dimen/notification_text_size</item> </style> <style name="TextAppearance.Material.Notification.Reply" /> <style name="TextAppearance.Material.Notification.Title"> + <item name="textColor">@color/notification_primary_text_color_current</item> <item name="fontFamily">sans-serif-medium</item> <item name="textSize">@dimen/notification_title_text_size</item> </style> <style name="TextAppearance.Material.Notification.BigTitle"> + <item name="textColor">@color/notification_primary_text_color_current</item> <item name="fontFamily">sans-serif-medium</item> <item name="textSize">@dimen/notification_big_title_text_size</item> </style> @@ -492,9 +494,8 @@ please see styles_device_defaults.xml. <style name="TextAppearance.Material.Notification.Time" parent="TextAppearance.Material.Notification.Info" /> - <style name="TextAppearance.Material.Notification.Emphasis"> - <item name="textColor">#66000000</item> - </style> + <!-- unused; keep identical to parent --> + <style name="TextAppearance.Material.Notification.Emphasis"/> <style name="TextAppearance.Material.Notification.Conversation.AppName" parent="TextAppearance.Material.Notification.Title"> <item name="android:textSize">16sp</item> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 29b8e6e08947..7ad05de1bdd0 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -4204,4 +4204,6 @@ <java-symbol type="bool" name="config_telephony5gNonStandalone" /> <java-symbol type="bool" name="config_voice_data_sms_auto_fallback" /> + + <java-symbol type="bool" name="config_enableOneHandedKeyguard" /> </resources> diff --git a/core/tests/benchmarks/src/android/os/ParcelableBenchmark.java b/core/tests/benchmarks/src/android/os/ParcelableBenchmark.java new file mode 100644 index 000000000000..1cf430205627 --- /dev/null +++ b/core/tests/benchmarks/src/android/os/ParcelableBenchmark.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +import android.annotation.SuppressLint; +import android.graphics.Point; +import android.graphics.Rect; +import android.util.MergedConfiguration; +import android.view.InsetsSource; +import android.view.InsetsState; + +import com.google.caliper.AfterExperiment; +import com.google.caliper.BeforeExperiment; + +/** + * Benchmark of read/write large Parcelable class. This also shows the performance of different + * implementations for nested Parcelable class: + * <ul> + * <li>Well-written read/writeFromParcel (direct access)</li> + * <li>read/writeTypedObject (object creation + addition int to indicate nullity)</li> + * <li>read/writeParcelable (object creation + addition type String)</li> + * </ul> + */ +public class ParcelableBenchmark { + private Parcel mParcel; + + @BeforeExperiment + protected void setUp() { + mParcel = Parcel.obtain(); + } + + @AfterExperiment + protected void tearDown() { + mParcel.recycle(); + mParcel = null; + } + + public void timeReadWriteMergedConfiguration(int reps) { + final MergedConfiguration mergedConfiguration = new MergedConfiguration(); + for (int i = 0; i < reps; i++) { + mergedConfiguration.writeToParcel(mParcel, 0); + mParcel.setDataPosition(0); + mergedConfiguration.readFromParcel(mParcel); + } + } + + public void timeReadWriteInsetsState(int reps) { + final InsetsState insetsState = new InsetsState(); + for (int i = 0; i < InsetsState.SIZE; i++) { + insetsState.addSource(new InsetsSource(i)); + } + for (int i = 0; i < reps; i++) { + insetsState.writeToParcel(mParcel, 0); + mParcel.setDataPosition(0); + insetsState.readFromParcel(mParcel); + } + } + + public void timeReadWritePointArray(int reps) { + final PointArray pointArray = new PointArray(); + for (int i = 0; i < reps; i++) { + pointArray.writeToParcel(mParcel, 0); + mParcel.setDataPosition(0); + pointArray.readFromParcel(mParcel); + } + } + + public void timeReadWritePointArrayFast(int reps) { + final PointArrayFast pointArray = new PointArrayFast(); + for (int i = 0; i < reps; i++) { + pointArray.writeToParcel(mParcel, 0); + mParcel.setDataPosition(0); + pointArray.readFromParcel(mParcel); + } + } + + @SuppressLint("ParcelCreator") + private static class PointArray implements Parcelable { + Rect mBounds = new Rect(); + Point[] mPoints = new Point[10]; + { + for (int i = 0; i < mPoints.length; i++) { + mPoints[i] = new Point(); + } + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(mBounds, flags); + dest.writeParcelableArray(mPoints, flags); + } + + void readFromParcel(Parcel in) { + mBounds = in.readParcelable(Rect.class.getClassLoader()); + mPoints = in.readParcelableArray(Point.class.getClassLoader(), Point.class); + } + + @Override + public int describeContents() { + return 0; + } + } + + @SuppressLint("ParcelCreator") + private static class PointArrayFast extends PointArray { + + @Override + public void writeToParcel(Parcel dest, int flags) { + mBounds.writeToParcel(dest, flags); + dest.writeTypedArray(mPoints, flags); + } + + @Override + void readFromParcel(Parcel in) { + mBounds.readFromParcel(in); + in.readTypedArray(mPoints, Point.CREATOR); + } + } +} diff --git a/core/tests/coretests/src/android/app/people/PeopleSpaceTileTest.java b/core/tests/coretests/src/android/app/people/PeopleSpaceTileTest.java index 1573c19b89a3..7cb680499d98 100644 --- a/core/tests/coretests/src/android/app/people/PeopleSpaceTileTest.java +++ b/core/tests/coretests/src/android/app/people/PeopleSpaceTileTest.java @@ -37,6 +37,7 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.net.Uri; import android.os.Parcel; +import android.os.UserHandle; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; @@ -124,13 +125,13 @@ public class PeopleSpaceTileTest { } @Test - public void testUid() { + public void testUserHandle() { PeopleSpaceTile tile = new PeopleSpaceTile .Builder(new ShortcutInfo.Builder(mContext, "123").build(), mLauncherApps) - .setUid(42) + .setUserHandle(new UserHandle(0)) .build(); - assertThat(tile.getUid()).isEqualTo(42); + assertThat(tile.getUserHandle()).isEqualTo(new UserHandle(0)); } @Test @@ -227,7 +228,7 @@ public class PeopleSpaceTileTest { .setUserName("name") .setUserIcon(mIcon) .setContactUri(Uri.parse("contact")) - .setUid(42) + .setUserHandle(new UserHandle(1)) .setPackageName("package.name") .setLastInteractionTimestamp(7L) .setIsImportantConversation(true) @@ -246,7 +247,7 @@ public class PeopleSpaceTileTest { assertThat(readTile.getUserName()).isEqualTo(tile.getUserName()); assertThat(readTile.getUserIcon().toString()).isEqualTo(tile.getUserIcon().toString()); assertThat(readTile.getContactUri()).isEqualTo(tile.getContactUri()); - assertThat(readTile.getUid()).isEqualTo(tile.getUid()); + assertThat(readTile.getUserHandle()).isEqualTo(tile.getUserHandle()); assertThat(readTile.getPackageName()).isEqualTo(tile.getPackageName()); assertThat(readTile.getLastInteractionTimestamp()).isEqualTo( tile.getLastInteractionTimestamp()); diff --git a/core/tests/coretests/src/android/view/BlurAggregatorTest.java b/core/tests/coretests/src/android/view/BlurAggregatorTest.java new file mode 100644 index 000000000000..b01f2755efdd --- /dev/null +++ b/core/tests/coretests/src/android/view/BlurAggregatorTest.java @@ -0,0 +1,318 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import static androidx.test.InstrumentationRegistry.getInstrumentation; + +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertFalse; +import static junit.framework.TestCase.assertNotNull; +import static junit.framework.TestCase.assertNull; +import static junit.framework.TestCase.assertTrue; + +import android.content.Context; +import android.platform.test.annotations.Presubmit; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.internal.graphics.drawable.BackgroundBlurDrawable; +import com.android.internal.graphics.drawable.BackgroundBlurDrawable.Aggregator; +import com.android.internal.graphics.drawable.BackgroundBlurDrawable.BlurRegion; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@Presubmit +@RunWith(AndroidJUnit4.class) +public class BlurAggregatorTest { + private static final int TEST_BLUR_RADIUS = 30; + private static final int TEST_FRAME_NUMBER = 1; + + private Context mContext; + + private Aggregator mAggregator; + private BackgroundBlurDrawable mDrawable; + + private ViewRootImpl mViewRoot; + + @Before + public void setUp() { + mContext = getInstrumentation().getTargetContext(); + getInstrumentation().runOnMainSync(() -> { + mViewRoot = new ViewRootImpl(mContext, mContext.getDisplayNoVerify()); + }); + mAggregator = new Aggregator(mViewRoot); + mDrawable = createTestBackgroundBlurDrawable(); + } + + private BackgroundBlurDrawable createTestBackgroundBlurDrawable() { + final BackgroundBlurDrawable drawable = mAggregator.createBackgroundBlurDrawable(mContext); + drawable.setBlurRadius(TEST_BLUR_RADIUS); + final boolean hasUpdates = mAggregator.hasUpdates(); + final BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT(); + mAggregator.getBlurRegionsToDispatchToSf(TEST_FRAME_NUMBER, blurRegions, hasUpdates); + return drawable; + } + + @Test + public void testBlurRadiusUpdatePropagatesToRenderThreadIfNeeded() { + mDrawable.setBlurRadius(TEST_BLUR_RADIUS); + assertFalse(mAggregator.hasUpdates()); + + mDrawable.setBlurRadius(0); + assertTrue(mAggregator.hasUpdates()); + BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT(); + assertEquals(0, blurRegions.length); + assertFalse(mAggregator.hasUpdates()); + + mDrawable.setBlurRadius(TEST_BLUR_RADIUS); + assertTrue(mAggregator.hasUpdates()); + blurRegions = mAggregator.getBlurRegionsCopyForRT(); + assertEquals(1, blurRegions.length); + assertEquals(TEST_BLUR_RADIUS, blurRegions[0].blurRadius); + assertFalse(mAggregator.hasUpdates()); + + } + + @Test + public void testAlphaUpdatePropagatesToRenderThreadIfNeeded() { + mDrawable.setAlpha(20); + assertTrue(mAggregator.hasUpdates()); + BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT(); + assertEquals(1, blurRegions.length); + assertEquals(20 / 255f, blurRegions[0].alpha); + assertFalse(mAggregator.hasUpdates()); + + mDrawable.setAlpha(20); + assertFalse(mAggregator.hasUpdates()); + + mDrawable.setAlpha(0); + assertTrue(mAggregator.hasUpdates()); + blurRegions = mAggregator.getBlurRegionsCopyForRT(); + assertEquals(0, blurRegions.length); + assertFalse(mAggregator.hasUpdates()); + } + + @Test + public void testCornerRadiusUpdatePropagatesToRenderThreadIfNeeded() { + mDrawable.setCornerRadius(1f, 2f, 3f, 4f); + assertTrue(mAggregator.hasUpdates()); + final BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT(); + assertEquals(1, blurRegions.length); + assertEquals(1f, blurRegions[0].cornerRadiusTL); + assertEquals(2f, blurRegions[0].cornerRadiusTR); + assertEquals(3f, blurRegions[0].cornerRadiusBL); + assertEquals(4f, blurRegions[0].cornerRadiusBR); + assertFalse(mAggregator.hasUpdates()); + } + + @Test + public void testVisibleUpdatePropagatesToRenderThreadIfNeeded() { + mDrawable.setVisible(false, /* restart= */false); + assertTrue(mAggregator.hasUpdates()); + BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT(); + assertEquals(0, blurRegions.length); + assertFalse(mAggregator.hasUpdates()); + + mDrawable.setVisible(true, /* restart= */ false); + assertTrue(mAggregator.hasUpdates()); + blurRegions = mAggregator.getBlurRegionsCopyForRT(); + assertEquals(1, blurRegions.length); + assertEquals(TEST_BLUR_RADIUS, blurRegions[0].blurRadius); + assertFalse(mAggregator.hasUpdates()); + } + + @Test + public void testBlurRegionCopyForRtIsSameIfNoUiUpdates() { + mDrawable.setBlurRadius(30); + BlurRegion[] blurRegions1 = mAggregator.getBlurRegionsCopyForRT(); + assertEquals(1, blurRegions1.length); + assertEquals(30, blurRegions1[0].blurRadius); + + BlurRegion[] blurRegions2 = mAggregator.getBlurRegionsCopyForRT(); + assertEquals(blurRegions1, blurRegions2); + } + + @Test + public void testPositionUpdateAppearsInBlurRegion() { + BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT(); + assertEquals(1, blurRegions.length); + + mDrawable.mPositionUpdateListener.positionChanged(TEST_FRAME_NUMBER, 1, 2, 3, 4); + mAggregator.getBlurRegionsToDispatchToSf(TEST_FRAME_NUMBER, blurRegions, + mAggregator.hasUpdates()); + assertEquals(1, blurRegions[0].rect.left); + assertEquals(2, blurRegions[0].rect.top); + assertEquals(3, blurRegions[0].rect.right); + assertEquals(4, blurRegions[0].rect.bottom); + } + + @Test + public void testNoBlurRegionsDispatchedWhenNoUpdates() { + final boolean hasUpdates = mAggregator.hasUpdates(); + assertFalse(hasUpdates); + final BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT(); + assertEquals(1, blurRegions.length); + + float[][] blurRegionsForSf = mAggregator.getBlurRegionsToDispatchToSf( + TEST_FRAME_NUMBER, blurRegions, hasUpdates); + assertNull(blurRegionsForSf); + } + + @Test + public void testBlurRegionDispatchedIfOnlyDrawableUpdated() { + mDrawable.setBlurRadius(50); + final boolean hasUpdates = mAggregator.hasUpdates(); + assertTrue(hasUpdates); + final BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT(); + assertEquals(1, blurRegions.length); + + float[][] blurRegionsForSf = mAggregator.getBlurRegionsToDispatchToSf( + TEST_FRAME_NUMBER, blurRegions, hasUpdates); + assertNotNull(blurRegionsForSf); + assertEquals(1, blurRegionsForSf.length); + assertEquals(50f, blurRegionsForSf[0][0]); + } + + @Test + public void testBlurRegionDispatchedIfOnlyPositionUpdated() { + final boolean hasUpdates = mAggregator.hasUpdates(); + assertFalse(hasUpdates); + final BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT(); + assertEquals(1, blurRegions.length); + + mDrawable.mPositionUpdateListener.positionChanged(TEST_FRAME_NUMBER, 1, 2, 3, 4); + float[][] blurRegionsForSf = mAggregator.getBlurRegionsToDispatchToSf( + TEST_FRAME_NUMBER, blurRegions, hasUpdates); + assertNotNull(blurRegionsForSf); + assertEquals(1, blurRegionsForSf.length); + assertEquals((float) TEST_BLUR_RADIUS, blurRegionsForSf[0][0]); + assertEquals(1f, blurRegionsForSf[0][2]); + assertEquals(2f, blurRegionsForSf[0][3]); + assertEquals(3f, blurRegionsForSf[0][4]); + assertEquals(4f, blurRegionsForSf[0][5]); + } + + @Test + public void testPositionUpdateIsAppliedInNextFrameIfMissed() { + final boolean hasUpdates = mAggregator.hasUpdates(); + assertFalse(hasUpdates); + final BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT(); + assertEquals(1, blurRegions.length); + + mDrawable.mPositionUpdateListener.positionChanged(TEST_FRAME_NUMBER, 1, 2, 3, 4); + float[][] blurRegionsForSf = mAggregator.getBlurRegionsToDispatchToSf( + TEST_FRAME_NUMBER + 1, blurRegions, hasUpdates); + assertNotNull(blurRegionsForSf); + assertEquals(1, blurRegionsForSf.length); + assertEquals((float) TEST_BLUR_RADIUS, blurRegionsForSf[0][0]); + assertEquals(1f, blurRegionsForSf[0][2]); + assertEquals(2f, blurRegionsForSf[0][3]); + assertEquals(3f, blurRegionsForSf[0][4]); + assertEquals(4f, blurRegionsForSf[0][5]); + } + + @Test + public void testMultipleDrawablesDispatchedToSfIfOneIsUpdated() { + final BackgroundBlurDrawable drawable2 = createTestBackgroundBlurDrawable(); + drawable2.setBlurRadius(50); + final boolean hasUpdates = mAggregator.hasUpdates(); + assertTrue(hasUpdates); + final BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT(); + assertEquals(2, blurRegions.length); + + // Check that an update in one of the drawables triggers a dispatch of all blur regions + float[][] blurRegionsForSf = mAggregator.getBlurRegionsToDispatchToSf( + TEST_FRAME_NUMBER, blurRegions, hasUpdates); + assertNotNull(blurRegionsForSf); + assertEquals(2, blurRegionsForSf.length); + + // Check that the Aggregator deleted all position updates for frame TEST_FRAME_NUMBER + blurRegionsForSf = mAggregator.getBlurRegionsToDispatchToSf( + TEST_FRAME_NUMBER, blurRegions, /* hasUiUpdates= */ false); + assertNull(blurRegionsForSf); + + // Check that a position update triggers a dispatch of all blur regions + drawable2.mPositionUpdateListener.positionChanged(TEST_FRAME_NUMBER, 1, 2, 3, 4); + blurRegionsForSf = mAggregator.getBlurRegionsToDispatchToSf( + TEST_FRAME_NUMBER + 1, blurRegions, hasUpdates); + assertNotNull(blurRegionsForSf); + assertEquals(2, blurRegionsForSf.length); + } + + @Test + public void testUiThreadUpdatesDoNotChangeStateOnRenderThread() { + // Updates for frame N + mDrawable.setBlurRadius(50); + mDrawable.setCornerRadius(1, 2, 3, 4); + mDrawable.setAlpha(20); + + final BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT(); + assertEquals(1, blurRegions.length); + assertEquals(50, blurRegions[0].blurRadius); + assertEquals(20 / 255f, blurRegions[0].alpha); + assertEquals(1f, blurRegions[0].cornerRadiusTL); + assertEquals(2f, blurRegions[0].cornerRadiusTR); + assertEquals(3f, blurRegions[0].cornerRadiusBL); + assertEquals(4f, blurRegions[0].cornerRadiusBR); + + // Updates for frame N+1 + mDrawable.setBlurRadius(60); + mDrawable.setCornerRadius(10, 20, 30, 40); + mDrawable.setAlpha(40); + + // Assert state for frame N is untouched + assertEquals(50, blurRegions[0].blurRadius); + assertEquals(20 / 255f, blurRegions[0].alpha); + assertEquals(1f, blurRegions[0].cornerRadiusTL); + assertEquals(2f, blurRegions[0].cornerRadiusTR); + assertEquals(3f, blurRegions[0].cornerRadiusBL); + assertEquals(4f, blurRegions[0].cornerRadiusBR); + } + + @Test + public void testPositionUpdatesForFutureFramesAreNotAppliedForCurrentFrame() { + final BlurRegion[] blurRegions = mAggregator.getBlurRegionsCopyForRT(); + + mDrawable.mPositionUpdateListener.positionChanged(TEST_FRAME_NUMBER, 1, 2, 3, 4); + mDrawable.mPositionUpdateListener.positionChanged(TEST_FRAME_NUMBER + 1, 5, 6, 7, 8); + + final float[][] blurRegionsForSf = mAggregator.getBlurRegionsToDispatchToSf( + TEST_FRAME_NUMBER, blurRegions, /* hasUiUpdates= */ false); + assertNotNull(blurRegionsForSf); + assertEquals(1, blurRegionsForSf.length); + // Assert state for first frame is not affected by update for second frame + assertEquals((float) TEST_BLUR_RADIUS, blurRegionsForSf[0][0]); + assertEquals(1f, blurRegionsForSf[0][2]); + assertEquals(2f, blurRegionsForSf[0][3]); + assertEquals(3f, blurRegionsForSf[0][4]); + assertEquals(4f, blurRegionsForSf[0][5]); + + final float[][] blurRegionsForSfForNextFrame = mAggregator.getBlurRegionsToDispatchToSf( + TEST_FRAME_NUMBER + 1, blurRegions, /* hasUiUpdates= */ false); + assertNotNull(blurRegionsForSfForNextFrame); + assertEquals(1, blurRegionsForSfForNextFrame.length); + // Assert second frame updates are applied normally + assertEquals((float) TEST_BLUR_RADIUS, blurRegionsForSfForNextFrame[0][0]); + assertEquals(5f, blurRegionsForSfForNextFrame[0][2]); + assertEquals(6f, blurRegionsForSfForNextFrame[0][3]); + assertEquals(7f, blurRegionsForSfForNextFrame[0][4]); + assertEquals(8f, blurRegionsForSfForNextFrame[0][5]); + } + +} diff --git a/core/tests/coretests/src/android/view/OWNERS b/core/tests/coretests/src/android/view/OWNERS index 80165f065995..fa1aa5eab26c 100644 --- a/core/tests/coretests/src/android/view/OWNERS +++ b/core/tests/coretests/src/android/view/OWNERS @@ -9,3 +9,6 @@ per-file *Focus* = file:/services/core/java/com/android/server/wm/OWNERS per-file *Insets* = file:/services/core/java/com/android/server/wm/OWNERS per-file *View* = file:/services/core/java/com/android/server/wm/OWNERS per-file *Visibility* = file:/services/core/java/com/android/server/wm/OWNERS + +# Scroll Capture +per-file *ScrollCapture*.java = file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS diff --git a/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java b/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java index b9cf1e4a234c..516fb76eeaf7 100644 --- a/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java +++ b/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java @@ -16,16 +16,12 @@ package android.view; -import static androidx.test.InstrumentationRegistry.getInstrumentation; import static androidx.test.InstrumentationRegistry.getTargetContext; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -35,6 +31,7 @@ import static org.mockito.Mockito.when; import android.graphics.Point; import android.graphics.Rect; import android.os.Handler; +import android.os.ICancellationSignal; import androidx.test.runner.AndroidJUnit4; @@ -42,9 +39,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import org.mockito.stubbing.Answer; /** * Tests of {@link ScrollCaptureConnection}. @@ -56,261 +51,138 @@ public class ScrollCaptureConnectionTest { private final Point mPositionInWindow = new Point(1, 2); private final Rect mLocalVisibleRect = new Rect(2, 3, 4, 5); private final Rect mScrollBounds = new Rect(3, 4, 5, 6); + private final TestScrollCaptureCallback mCallback = new TestScrollCaptureCallback(); + + private ScrollCaptureTarget mTarget; + private ScrollCaptureConnection mConnection; private Handler mHandler; - private ScrollCaptureTarget mTarget1; @Mock private Surface mSurface; @Mock - private IScrollCaptureCallbacks mConnectionCallbacks; - @Mock - private View mMockView1; + private IScrollCaptureCallbacks mRemote; @Mock - private ScrollCaptureCallback mCallback1; + private View mView; @Before public void setUp() { MockitoAnnotations.initMocks(this); mHandler = new Handler(getTargetContext().getMainLooper()); + when(mSurface.isValid()).thenReturn(true); + when(mView.getScrollCaptureHint()).thenReturn(View.SCROLL_CAPTURE_HINT_INCLUDE); - when(mMockView1.getHandler()).thenReturn(mHandler); - when(mMockView1.getScrollCaptureHint()).thenReturn(View.SCROLL_CAPTURE_HINT_INCLUDE); - - mTarget1 = new ScrollCaptureTarget( - mMockView1, mLocalVisibleRect, mPositionInWindow, mCallback1); - mTarget1.setScrollBounds(mScrollBounds); - } - - /** Test the DelayedAction timeout helper class works as expected. */ - @Test - public void testDelayedAction() { - Runnable action = Mockito.mock(Runnable.class); - ScrollCaptureConnection.DelayedAction delayed = - new ScrollCaptureConnection.DelayedAction(mHandler, 100, action); - try { - Thread.sleep(200); - } catch (InterruptedException ex) { - /* ignore */ - } - getInstrumentation().waitForIdleSync(); - assertFalse(delayed.cancel()); - assertFalse(delayed.timeoutNow()); - verify(action, times(1)).run(); - } - - /** Test the DelayedAction cancel() */ - @Test - public void testDelayedAction_cancel() { - Runnable action = Mockito.mock(Runnable.class); - ScrollCaptureConnection.DelayedAction delayed = - new ScrollCaptureConnection.DelayedAction(mHandler, 100, action); - try { - Thread.sleep(50); - } catch (InterruptedException ex) { - /* ignore */ - } - assertTrue(delayed.cancel()); - assertFalse(delayed.timeoutNow()); - try { - Thread.sleep(200); - } catch (InterruptedException ex) { - /* ignore */ - } - getInstrumentation().waitForIdleSync(); - verify(action, never()).run(); - } - - /** Test the DelayedAction timeoutNow() - for testing only */ - @Test - public void testDelayedAction_timeoutNow() { - Runnable action = Mockito.mock(Runnable.class); - ScrollCaptureConnection.DelayedAction delayed = - new ScrollCaptureConnection.DelayedAction(mHandler, 100, action); - try { - Thread.sleep(50); - } catch (InterruptedException ex) { - /* ignore */ - } - assertTrue(delayed.timeoutNow()); - assertFalse(delayed.cancel()); - getInstrumentation().waitForIdleSync(); - verify(action, times(1)).run(); + mTarget = new ScrollCaptureTarget(mView, mLocalVisibleRect, mPositionInWindow, mCallback); + mTarget.setScrollBounds(mScrollBounds); + mConnection = new ScrollCaptureConnection(Runnable::run, mTarget, mRemote); } /** Test creating a client with valid info */ @Test public void testConstruction() { - new ScrollCaptureConnection(mTarget1, mConnectionCallbacks); + ScrollCaptureTarget target = new ScrollCaptureTarget( + mView, mLocalVisibleRect, mPositionInWindow, mCallback); + target.setScrollBounds(new Rect(1, 2, 3, 4)); + new ScrollCaptureConnection(Runnable::run, target, mRemote); } /** Test creating a client fails if arguments are not valid. */ @Test public void testConstruction_requiresScrollBounds() { try { - mTarget1.setScrollBounds(null); - new ScrollCaptureConnection(mTarget1, mConnectionCallbacks); + mTarget.setScrollBounds(null); + new ScrollCaptureConnection(Runnable::run, mTarget, mRemote); fail("An exception was expected."); } catch (RuntimeException ex) { // Ignore, expected. } } - @SuppressWarnings("SameParameterValue") - private static Answer<Void> runRunnable(int arg) { - return invocation -> { - Runnable r = invocation.getArgument(arg); - r.run(); - return null; - }; - } - - @SuppressWarnings("SameParameterValue") - private static Answer<Void> reportBufferSent(int sessionArg, long frameNum, Rect capturedArea) { - return invocation -> { - ScrollCaptureSession session = invocation.getArgument(sessionArg); - session.notifyBufferSent(frameNum, capturedArea); - return null; - }; - } - /** @see ScrollCaptureConnection#startCapture(Surface) */ @Test public void testStartCapture() throws Exception { - final ScrollCaptureConnection connection = new ScrollCaptureConnection(mTarget1, - mConnectionCallbacks); - - // Have the session start accepted immediately - doAnswer(runRunnable(1)).when(mCallback1) - .onScrollCaptureStart(any(ScrollCaptureSession.class), any(Runnable.class)); - connection.startCapture(mSurface); - getInstrumentation().waitForIdleSync(); - - verify(mCallback1, times(1)) - .onScrollCaptureStart(any(ScrollCaptureSession.class), any(Runnable.class)); - verify(mConnectionCallbacks, times(1)).onCaptureStarted(); - verifyNoMoreInteractions(mConnectionCallbacks); + mConnection.startCapture(mSurface); + + mCallback.completeStartRequest(); + assertTrue(mConnection.isStarted()); + + verify(mRemote, times(1)).onCaptureStarted(); + verifyNoMoreInteractions(mRemote); } @Test - public void testStartCaptureTimeout() throws Exception { - final ScrollCaptureConnection connection = new ScrollCaptureConnection(mTarget1, - mConnectionCallbacks); - connection.startCapture(mSurface); + public void testStartCapture_cancellation() throws Exception { + ICancellationSignal signal = mConnection.startCapture(mSurface); + signal.cancel(); - // Force timeout to fire - connection.getTimeoutAction().timeoutNow(); + mCallback.completeStartRequest(); + assertFalse(mConnection.isStarted()); - getInstrumentation().waitForIdleSync(); - verify(mCallback1, times(1)).onScrollCaptureEnd(any(Runnable.class)); - } - - private void startCapture(ScrollCaptureConnection connection) throws Exception { - doAnswer(runRunnable(1)).when(mCallback1) - .onScrollCaptureStart(any(ScrollCaptureSession.class), any(Runnable.class)); - connection.startCapture(mSurface); - getInstrumentation().waitForIdleSync(); - reset(mCallback1, mConnectionCallbacks); + verifyNoMoreInteractions(mRemote); } /** @see ScrollCaptureConnection#requestImage(Rect) */ @Test public void testRequestImage() throws Exception { - final ScrollCaptureConnection connection = new ScrollCaptureConnection(mTarget1, - mConnectionCallbacks); - startCapture(connection); - - // Stub the callback to complete the request immediately - doAnswer(reportBufferSent(/* sessionArg */ 0, /* frameNum */ 1L, new Rect(1, 2, 3, 4))) - .when(mCallback1) - .onScrollCaptureImageRequest(any(ScrollCaptureSession.class), any(Rect.class)); - - // Make the inbound binder call - connection.requestImage(new Rect(1, 2, 3, 4)); + mConnection.startCapture(mSurface); + mCallback.completeStartRequest(); + reset(mRemote); - // Wait for handler thread dispatch - getInstrumentation().waitForIdleSync(); - verify(mCallback1, times(1)).onScrollCaptureImageRequest( - any(ScrollCaptureSession.class), eq(new Rect(1, 2, 3, 4))); + mConnection.requestImage(new Rect(1, 2, 3, 4)); + mCallback.completeImageRequest(new Rect(1, 2, 3, 4)); - // Wait for binder thread dispatch - getInstrumentation().waitForIdleSync(); - verify(mConnectionCallbacks, times(1)) - .onCaptureBufferSent(eq(1L), eq(new Rect(1, 2, 3, 4))); - - verifyNoMoreInteractions(mCallback1, mConnectionCallbacks); + verify(mRemote, times(1)) + .onImageRequestCompleted(eq(0), eq(new Rect(1, 2, 3, 4))); + verifyNoMoreInteractions(mRemote); } @Test - public void testRequestImageTimeout() throws Exception { - final ScrollCaptureConnection connection = new ScrollCaptureConnection(mTarget1, - mConnectionCallbacks); - startCapture(connection); - - // Make the inbound binder call - connection.requestImage(new Rect(1, 2, 3, 4)); - - // Wait for handler thread dispatch - getInstrumentation().waitForIdleSync(); - verify(mCallback1, times(1)).onScrollCaptureImageRequest( - any(ScrollCaptureSession.class), eq(new Rect(1, 2, 3, 4))); - - // Force timeout to fire - connection.getTimeoutAction().timeoutNow(); - getInstrumentation().waitForIdleSync(); - - // (callback not stubbed, does nothing) - // Timeout triggers request to end capture - verify(mCallback1, times(1)).onScrollCaptureEnd(any(Runnable.class)); - verifyNoMoreInteractions(mCallback1, mConnectionCallbacks); + public void testRequestImage_cancellation() throws Exception { + mConnection.startCapture(mSurface); + mCallback.completeStartRequest(); + reset(mRemote); + + ICancellationSignal signal = mConnection.requestImage(new Rect(1, 2, 3, 4)); + signal.cancel(); + mCallback.completeImageRequest(new Rect(1, 2, 3, 4)); + + verifyNoMoreInteractions(mRemote); } /** @see ScrollCaptureConnection#endCapture() */ @Test public void testEndCapture() throws Exception { - final ScrollCaptureConnection connection = new ScrollCaptureConnection(mTarget1, - mConnectionCallbacks); - startCapture(connection); - - // Stub the callback to complete the request immediately - doAnswer(runRunnable(0)) - .when(mCallback1) - .onScrollCaptureEnd(any(Runnable.class)); - - // Make the inbound binder call - connection.endCapture(); + mConnection.startCapture(mSurface); + mCallback.completeStartRequest(); + reset(mRemote); - // Wait for handler thread dispatch - getInstrumentation().waitForIdleSync(); - verify(mCallback1, times(1)).onScrollCaptureEnd(any(Runnable.class)); + mConnection.endCapture(); + mCallback.completeEndRequest(); - // Wait for binder thread dispatch - getInstrumentation().waitForIdleSync(); - verify(mConnectionCallbacks, times(1)).onConnectionClosed(); - - verifyNoMoreInteractions(mCallback1, mConnectionCallbacks); + // And the reply is sent + verify(mRemote, times(1)).onCaptureEnded(); + verifyNoMoreInteractions(mRemote); } + /** @see ScrollCaptureConnection#endCapture() */ @Test - public void testEndCaptureTimeout() throws Exception { - final ScrollCaptureConnection connection = new ScrollCaptureConnection(mTarget1, - mConnectionCallbacks); - startCapture(connection); + public void testEndCapture_cancellation() throws Exception { + mConnection.startCapture(mSurface); + mCallback.completeStartRequest(); + reset(mRemote); - // Make the inbound binder call - connection.endCapture(); + ICancellationSignal signal = mConnection.endCapture(); + signal.cancel(); + mCallback.completeEndRequest(); - // Wait for handler thread dispatch - getInstrumentation().waitForIdleSync(); - verify(mCallback1, times(1)).onScrollCaptureEnd(any(Runnable.class)); - - // Force timeout to fire - connection.getTimeoutAction().timeoutNow(); - - // Wait for binder thread dispatch - getInstrumentation().waitForIdleSync(); - verify(mConnectionCallbacks, times(1)).onConnectionClosed(); + verifyNoMoreInteractions(mRemote); + } - verifyNoMoreInteractions(mCallback1, mConnectionCallbacks); + @Test + public void testClose() throws Exception { + mConnection.close(); + assertFalse(mConnection.isConnected()); + verifyNoMoreInteractions(mRemote); } + } diff --git a/core/tests/coretests/src/android/view/ScrollCaptureSearchResultsTest.java b/core/tests/coretests/src/android/view/ScrollCaptureSearchResultsTest.java new file mode 100644 index 000000000000..cc229e11dcf2 --- /dev/null +++ b/core/tests/coretests/src/android/view/ScrollCaptureSearchResultsTest.java @@ -0,0 +1,415 @@ +/* + * 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.view; + +import static androidx.test.InstrumentationRegistry.getTargetContext; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.annotation.Nullable; +import android.content.Context; +import android.graphics.Point; +import android.graphics.Rect; +import android.os.CancellationSignal; +import android.os.SystemClock; + +import androidx.annotation.NonNull; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +/** + * Tests of {@link ScrollCaptureTargetSelector}. + */ +@RunWith(AndroidJUnit4.class) +public class ScrollCaptureSearchResultsTest { + + + private static final Rect EMPTY_RECT = new Rect(); + private static final String TAG = "Test"; + + private final Executor mDirectExec = Runnable::run; + private Executor mBgExec; + + @Before + public void setUp() { + mBgExec = Executors.newSingleThreadExecutor(); + } + + @Test + public void testNoTargets() { + ScrollCaptureSearchResults results = new ScrollCaptureSearchResults(mDirectExec); + assertTrue(results.isComplete()); + + assertNull("Expected null due to empty queue", results.getTopResult()); + } + + @Test + public void testNoValidTargets() { + ScrollCaptureSearchResults results = new ScrollCaptureSearchResults(mDirectExec); + + FakeScrollCaptureCallback callback1 = new FakeScrollCaptureCallback(mDirectExec); + callback1.setScrollBounds(EMPTY_RECT); + ScrollCaptureTarget target1 = createTarget(callback1, new Rect(20, 30, 40, 50), + new Point(0, 0), View.SCROLL_CAPTURE_HINT_AUTO); + + // Supplies scrollBounds = empty rect + FakeScrollCaptureCallback callback2 = new FakeScrollCaptureCallback(mDirectExec); + callback2.setScrollBounds(EMPTY_RECT); + ScrollCaptureTarget target2 = createTarget(callback2, new Rect(20, 30, 40, 50), + new Point(0, 20), View.SCROLL_CAPTURE_HINT_INCLUDE); + + results.addTarget(target1); + results.addTarget(target2); + + assertTrue(results.isComplete()); + assertNull("Expected null due to no valid targets", results.getTopResult()); + } + + @Test + public void testSingleTarget() { + ScrollCaptureSearchResults results = new ScrollCaptureSearchResults(mDirectExec); + FakeScrollCaptureCallback callback = new FakeScrollCaptureCallback(mDirectExec); + ScrollCaptureTarget target = createTarget(callback, + new Rect(20, 30, 40, 50), new Point(10, 10), + View.SCROLL_CAPTURE_HINT_AUTO); + callback.setScrollBounds(new Rect(2, 2, 18, 18)); + + results.addTarget(target); + assertTrue(results.isComplete()); + + ScrollCaptureTarget result = results.getTopResult(); + assertSame("Excepted the same target as a result", target, result); + assertEquals("result has wrong scroll bounds", + new Rect(2, 2, 18, 18), result.getScrollBounds()); + } + + @Test + public void testSingleTarget_backgroundThread() throws InterruptedException { + FakeScrollCaptureCallback callback1 = new FakeScrollCaptureCallback(mBgExec); + ScrollCaptureTarget target1 = createTarget(callback1, + new Rect(20, 30, 40, 50), new Point(10, 10), + View.SCROLL_CAPTURE_HINT_AUTO); + callback1.setDelay(100); + callback1.setScrollBounds(new Rect(2, 2, 18, 18)); + + ScrollCaptureSearchResults results = new ScrollCaptureSearchResults(mDirectExec); + results.addTarget(target1); + + CountDownLatch latch = new CountDownLatch(1); + results.setOnCompleteListener(latch::countDown); + if (!latch.await(200, TimeUnit.MILLISECONDS)) { + fail("onComplete listener was expected"); + } + + ScrollCaptureTarget result = results.getTopResult(); + assertSame("Excepted the single target1 as a result", target1, result); + assertEquals("Result has wrong scroll bounds", + new Rect(2, 2, 18, 18), result.getScrollBounds()); + } + + @Test + public void testRanking() { + + // 1 - Empty + FakeScrollCaptureCallback callback1 = new FakeScrollCaptureCallback(mDirectExec); + callback1.setScrollBounds(EMPTY_RECT); + ViewGroup targetView1 = new FakeView(getTargetContext(), 0, 0, 60, 60, 1); + ScrollCaptureTarget target1 = createTargetWithView(targetView1, callback1, + new Rect(0, 0, 60, 60), new Point(0, 0), View.SCROLL_CAPTURE_HINT_AUTO); + + // 2 - 10x10 + HINT_INCLUDE + FakeScrollCaptureCallback callback2 = new FakeScrollCaptureCallback(mDirectExec); + callback2.setScrollBounds(new Rect(0, 0, 10, 10)); + ViewGroup targetView2 = new FakeView(getTargetContext(), 0, 0, 60, 60, 2); + ScrollCaptureTarget target2 = createTargetWithView(targetView2, callback2, + new Rect(0, 0, 60, 60), new Point(0, 0), View.SCROLL_CAPTURE_HINT_INCLUDE); + + // 3 - 20x20 + AUTO + FakeScrollCaptureCallback callback3 = new FakeScrollCaptureCallback(mDirectExec); + callback3.setScrollBounds(new Rect(0, 0, 20, 20)); + ViewGroup targetView3 = new FakeView(getTargetContext(), 0, 0, 60, 60, 3); + ScrollCaptureTarget target3 = createTargetWithView(targetView3, callback3, + new Rect(0, 0, 60, 60), new Point(0, 0), View.SCROLL_CAPTURE_HINT_AUTO); + + // 4 - 30x30 + AUTO + FakeScrollCaptureCallback callback4 = new FakeScrollCaptureCallback(mDirectExec); + callback4.setScrollBounds(new Rect(0, 0, 10, 10)); + ViewGroup targetView4 = new FakeView(getTargetContext(), 0, 0, 60, 60, 4); + ScrollCaptureTarget target4 = createTargetWithView(targetView4, callback4, + new Rect(0, 0, 60, 60), new Point(0, 0), View.SCROLL_CAPTURE_HINT_AUTO); + + // 5 - 10x10 + child of #4 + FakeScrollCaptureCallback callback5 = new FakeScrollCaptureCallback(mDirectExec); + callback5.setScrollBounds(new Rect(0, 0, 10, 10)); + ViewGroup targetView5 = new FakeView(getTargetContext(), 0, 0, 60, 60, 5); + ScrollCaptureTarget target5 = createTargetWithView(targetView5, callback5, + new Rect(0, 0, 60, 60), new Point(0, 0), View.SCROLL_CAPTURE_HINT_AUTO); + targetView4.addView(targetView5); + + // 6 - 20x20 + child of #4 + FakeScrollCaptureCallback callback6 = new FakeScrollCaptureCallback(mDirectExec); + callback6.setScrollBounds(new Rect(0, 0, 20, 20)); + ViewGroup targetView6 = new FakeView(getTargetContext(), 0, 0, 60, 60, 6); + ScrollCaptureTarget target6 = createTargetWithView(targetView6, callback6, + new Rect(0, 0, 60, 60), new Point(0, 0), View.SCROLL_CAPTURE_HINT_AUTO); + targetView4.addView(targetView6); + + ScrollCaptureSearchResults results = new ScrollCaptureSearchResults(mDirectExec); + results.addTarget(target1); + results.addTarget(target2); + results.addTarget(target3); + results.addTarget(target4); + results.addTarget(target5); + results.addTarget(target6); + assertTrue(results.isComplete()); + + // Verify "top" result + assertEquals(target2, results.getTopResult()); + + // Verify priority ("best" first) + assertThat(results.getTargets()) + .containsExactly( + target2, + target6, + target5, + target4, + target3, + target1); + } + + /** + * If a timeout expires, late results are ignored. + */ + @Test + public void testTimeout() { + ScrollCaptureSearchResults results = new ScrollCaptureSearchResults(mDirectExec); + + // callback 1, 10x10, hint=AUTO, responds after 100ms from bg thread + FakeScrollCaptureCallback callback1 = new FakeScrollCaptureCallback(mBgExec); + callback1.setScrollBounds(new Rect(5, 5, 15, 15)); + callback1.setDelay(100); + ScrollCaptureTarget target1 = createTarget( + callback1, new Rect(20, 30, 40, 50), new Point(10, 10), + View.SCROLL_CAPTURE_HINT_AUTO); + results.addTarget(target1); + + // callback 2, 20x20, hint=AUTO, responds after 5s from bg thread + FakeScrollCaptureCallback callback2 = new FakeScrollCaptureCallback(mBgExec); + callback2.setScrollBounds(new Rect(0, 0, 20, 20)); + callback2.setDelay(1000); + ScrollCaptureTarget target2 = createTarget( + callback2, new Rect(20, 30, 40, 50), new Point(10, 10), + View.SCROLL_CAPTURE_HINT_AUTO); + results.addTarget(target2); + + // callback 3, 20x20, hint=INCLUDE, responds after 10s from bg thread + FakeScrollCaptureCallback callback3 = new FakeScrollCaptureCallback(mBgExec); + callback3.setScrollBounds(new Rect(0, 0, 20, 20)); + callback3.setDelay(1500); + ScrollCaptureTarget target3 = createTarget( + callback3, new Rect(20, 30, 40, 50), new Point(10, 10), + View.SCROLL_CAPTURE_HINT_INCLUDE); + results.addTarget(target3); + + // callback 1 will be received + // callback 2 & 3 will be ignored due to timeout + SystemClock.sleep(500); + results.finish(); + + ScrollCaptureTarget result = results.getTopResult(); + assertSame("Expected target1 as the result, due to timeouts of others", target1, result); + assertEquals("callback1 should have been called", + 1, callback1.getOnScrollCaptureSearchCount()); + assertEquals("callback2 should have been called", + 1, callback2.getOnScrollCaptureSearchCount()); + assertEquals("callback3 should have been called", + 1, callback3.getOnScrollCaptureSearchCount()); + + assertEquals("result has wrong scroll bounds", + new Rect(5, 5, 15, 15), result.getScrollBounds()); + assertNull("target2 should not have been updated", + target2.getScrollBounds()); + assertNull("target3 should not have been updated", + target3.getScrollBounds()); + } + + @Test + public void testWithCallbackMultipleReplies() { + // Calls response methods 3 times each + ScrollCaptureCallback callback1 = new CallbackStub() { + @Override + public void onScrollCaptureSearch(@NonNull CancellationSignal signal, + @NonNull Consumer<Rect> onReady) { + onReady.accept(new Rect(1, 2, 3, 4)); + onReady.accept(new Rect(9, 10, 11, 12)); + } + }; + + ScrollCaptureTarget target1 = createTarget(callback1, new Rect(20, 30, 40, 50), + new Point(10, 10), View.SCROLL_CAPTURE_HINT_AUTO); + + ScrollCaptureSearchResults results = new ScrollCaptureSearchResults(mDirectExec); + results.addTarget(target1); + assertTrue(results.isComplete()); + + ScrollCaptureTarget result = results.getTopResult(); + assertSame("Expected target1", target1, result); + assertEquals("result has wrong scroll bounds", + new Rect(1, 2, 3, 4), result.getScrollBounds()); + } + + private void setupTargetView(View view, Rect localVisibleRect, int scrollCaptureHint) { + view.setScrollCaptureHint(scrollCaptureHint); + view.onVisibilityAggregated(true); + // Treat any offset as padding, outset localVisibleRect on all sides and use this as + // child bounds + Rect bounds = new Rect(localVisibleRect); + bounds.inset(-bounds.left, -bounds.top, bounds.left, bounds.top); + view.layout(bounds.left, bounds.top, bounds.right, bounds.bottom); + view.onVisibilityAggregated(true); + } + + private ScrollCaptureTarget createTarget(ScrollCaptureCallback callback, Rect localVisibleRect, + Point positionInWindow, int scrollCaptureHint) { + View mockView = new View(getTargetContext()); + return createTargetWithView(mockView, callback, localVisibleRect, positionInWindow, + scrollCaptureHint); + } + + private ScrollCaptureTarget createTargetWithView(View view, ScrollCaptureCallback callback, + Rect localVisibleRect, Point positionInWindow, int scrollCaptureHint) { + setupTargetView(view, localVisibleRect, scrollCaptureHint); + return new ScrollCaptureTarget(view, localVisibleRect, positionInWindow, callback); + } + + + static class FakeView extends ViewGroup implements ViewParent { + FakeView(Context context, int l, int t, int r, int b, int id) { + super(context); + layout(l, t, r, b); + setId(id); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + } + } + + static class FakeScrollCaptureCallback implements ScrollCaptureCallback { + private final Executor mExecutor; + private Rect mScrollBounds; + private long mDelayMillis; + private int mOnScrollCaptureSearchCount; + FakeScrollCaptureCallback(Executor executor) { + mExecutor = executor; + } + public int getOnScrollCaptureSearchCount() { + return mOnScrollCaptureSearchCount; + } + + @Override + public void onScrollCaptureSearch(CancellationSignal signal, Consumer<Rect> onReady) { + mOnScrollCaptureSearchCount++; + run(() -> { + Rect b = getScrollBounds(); + onReady.accept(b); + }); + } + + @Override + public void onScrollCaptureStart(ScrollCaptureSession session, CancellationSignal signal, + Runnable onReady) { + run(onReady); + } + + @Override + public void onScrollCaptureImageRequest(ScrollCaptureSession session, + CancellationSignal signal, Rect captureArea, Consumer<Rect> onReady) { + run(() -> onReady.accept(captureArea)); + } + + @Override + public void onScrollCaptureEnd(Runnable onReady) { + run(onReady); + } + + public void setScrollBounds(@Nullable Rect scrollBounds) { + mScrollBounds = scrollBounds; + } + + public void setDelay(long delayMillis) { + mDelayMillis = delayMillis; + } + + protected Rect getScrollBounds() { + return mScrollBounds; + } + + protected void run(Runnable r) { + mExecutor.execute(() -> { + delay(); + r.run(); + }); + } + + protected void delay() { + if (mDelayMillis > 0) { + try { + Thread.sleep(mDelayMillis); + } catch (InterruptedException e) { + // Ignore + } + } + } + } + static class CallbackStub implements ScrollCaptureCallback { + @Override + public void onScrollCaptureSearch(@NonNull CancellationSignal signal, + @NonNull Consumer<Rect> onReady) { + } + + @Override + public void onScrollCaptureStart(@NonNull ScrollCaptureSession session, + @NonNull CancellationSignal signal, @NonNull Runnable onReady) { + } + + @Override + public void onScrollCaptureImageRequest(@NonNull ScrollCaptureSession session, + @NonNull CancellationSignal signal, @NonNull Rect captureArea, + Consumer<Rect> onReady) { + } + + @Override + public void onScrollCaptureEnd(@NonNull Runnable onReady) { + } + } +} diff --git a/core/tests/coretests/src/android/view/ScrollCaptureTargetResolverTest.java b/core/tests/coretests/src/android/view/ScrollCaptureTargetResolverTest.java deleted file mode 100644 index 8b21b8ecee89..000000000000 --- a/core/tests/coretests/src/android/view/ScrollCaptureTargetResolverTest.java +++ /dev/null @@ -1,498 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.view; - -import static androidx.test.InstrumentationRegistry.getTargetContext; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; - -import android.annotation.Nullable; -import android.content.Context; -import android.graphics.Point; -import android.graphics.Rect; -import android.os.Handler; - -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.LinkedList; -import java.util.function.Consumer; - -/** - * Tests of {@link ScrollCaptureTargetResolver}. - */ -@RunWith(AndroidJUnit4.class) -public class ScrollCaptureTargetResolverTest { - - private static final long TEST_TIMEOUT_MS = 2000; - private static final long RESOLVER_TIMEOUT_MS = 1000; - - private Handler mHandler; - private TargetConsumer mTargetConsumer; - - @Before - public void setUp() { - mTargetConsumer = new TargetConsumer(); - mHandler = new Handler(getTargetContext().getMainLooper()); - } - - @Test(timeout = TEST_TIMEOUT_MS) - public void testEmptyQueue() throws InterruptedException { - ScrollCaptureTargetResolver resolver = new ScrollCaptureTargetResolver(new LinkedList<>()); - resolver.start(mHandler, RESOLVER_TIMEOUT_MS, mTargetConsumer); - - // Test only - resolver.waitForResult(); - - ScrollCaptureTarget result = mTargetConsumer.getLastValue(); - assertNull("Expected null due to empty queue", result); - } - - @Test(timeout = TEST_TIMEOUT_MS) - public void testNoValidTargets() throws InterruptedException { - LinkedList<ScrollCaptureTarget> targetQueue = new LinkedList<>(); - - // Supplies scrollBounds = null - FakeScrollCaptureCallback callback1 = new FakeScrollCaptureCallback(); - callback1.setScrollBounds(null); - ScrollCaptureTarget target1 = createTarget(callback1, new Rect(20, 30, 40, 50), - new Point(0, 0), View.SCROLL_CAPTURE_HINT_AUTO); - - // Supplies scrollBounds = empty rect - FakeScrollCaptureCallback callback2 = new FakeScrollCaptureCallback(); - callback2.setScrollBounds(new Rect()); - ScrollCaptureTarget target2 = createTarget(callback2, new Rect(20, 30, 40, 50), - new Point(0, 20), View.SCROLL_CAPTURE_HINT_INCLUDE); - - targetQueue.add(target1); - targetQueue.add(target2); - - ScrollCaptureTargetResolver resolver = new ScrollCaptureTargetResolver(targetQueue); - resolver.start(mHandler, RESOLVER_TIMEOUT_MS, mTargetConsumer); - - // Test only - resolver.waitForResult(); - - ScrollCaptureTarget result = mTargetConsumer.getLastValue(); - assertNull("Expected null due to no valid targets", result); - } - - @Test(timeout = TEST_TIMEOUT_MS) - public void testSingleTarget() throws InterruptedException { - FakeScrollCaptureCallback callback = new FakeScrollCaptureCallback(); - ScrollCaptureTarget target = createTarget(callback, - new Rect(20, 30, 40, 50), new Point(10, 10), - View.SCROLL_CAPTURE_HINT_AUTO); - callback.setScrollBounds(new Rect(2, 2, 18, 18)); - - LinkedList<ScrollCaptureTarget> targetQueue = new LinkedList<>(); - targetQueue.add(target); - ScrollCaptureTargetResolver resolver = new ScrollCaptureTargetResolver(targetQueue); - resolver.start(mHandler, RESOLVER_TIMEOUT_MS, mTargetConsumer); - - // Test only - resolver.waitForResult(); - - ScrollCaptureTarget result = mTargetConsumer.getLastValue(); - assertSame("Excepted the same target as a result", target, result); - assertEquals("result has wrong scroll bounds", - new Rect(2, 2, 18, 18), result.getScrollBounds()); - } - - @Test(timeout = TEST_TIMEOUT_MS) - public void testSingleTarget_backgroundThread() throws InterruptedException { - BackgroundTestCallback callback1 = new BackgroundTestCallback(); - ScrollCaptureTarget target1 = createTarget(callback1, - new Rect(20, 30, 40, 50), new Point(10, 10), - View.SCROLL_CAPTURE_HINT_AUTO); - callback1.setDelay(100); - callback1.setScrollBounds(new Rect(2, 2, 18, 18)); - - LinkedList<ScrollCaptureTarget> targetQueue = new LinkedList<>(); - targetQueue.add(target1); - - ScrollCaptureTargetResolver resolver = new ScrollCaptureTargetResolver(targetQueue); - resolver.start(mHandler, RESOLVER_TIMEOUT_MS, mTargetConsumer); - - // Test only - resolver.waitForResult(); - - ScrollCaptureTarget result = mTargetConsumer.getLastValue(); - assertSame("Excepted the single target1 as a result", target1, result); - assertEquals("Result has wrong scroll bounds", - new Rect(2, 2, 18, 18), result.getScrollBounds()); - } - - @Test(timeout = TEST_TIMEOUT_MS) - public void testPreferNonEmptyBounds() throws InterruptedException { - LinkedList<ScrollCaptureTarget> targetQueue = new LinkedList<>(); - - FakeScrollCaptureCallback callback1 = new FakeScrollCaptureCallback(); - callback1.setScrollBounds(new Rect()); - ScrollCaptureTarget target1 = createTarget(callback1, new Rect(20, 30, 40, 50), - new Point(0, 0), View.SCROLL_CAPTURE_HINT_AUTO); - - FakeScrollCaptureCallback callback2 = new FakeScrollCaptureCallback(); - callback2.setScrollBounds(new Rect(0, 0, 20, 20)); - ScrollCaptureTarget target2 = createTarget(callback2, new Rect(20, 30, 40, 50), - new Point(0, 20), View.SCROLL_CAPTURE_HINT_INCLUDE); - - FakeScrollCaptureCallback callback3 = new FakeScrollCaptureCallback(); - callback3.setScrollBounds(null); - ScrollCaptureTarget target3 = createTarget(callback3, new Rect(20, 30, 40, 50), - new Point(0, 40), View.SCROLL_CAPTURE_HINT_AUTO); - - targetQueue.add(target1); - targetQueue.add(target2); // scrollBounds not null or empty() - targetQueue.add(target3); - - ScrollCaptureTargetResolver resolver = new ScrollCaptureTargetResolver(targetQueue); - resolver.start(mHandler, RESOLVER_TIMEOUT_MS, mTargetConsumer); - resolver.waitForResult(); - - ScrollCaptureTarget result = mTargetConsumer.getLastValue(); - assertEquals("Expected " + target2 + " as a result", target2, result); - assertEquals("result has wrong scroll bounds", - new Rect(0, 0, 20, 20), result.getScrollBounds()); - } - - @Test(timeout = TEST_TIMEOUT_MS) - public void testPreferHintInclude() throws InterruptedException { - LinkedList<ScrollCaptureTarget> targetQueue = new LinkedList<>(); - - FakeScrollCaptureCallback callback1 = new FakeScrollCaptureCallback(); - callback1.setScrollBounds(new Rect(0, 0, 20, 20)); - ScrollCaptureTarget target1 = createTarget(callback1, new Rect(20, 30, 40, 50), - new Point(0, 0), View.SCROLL_CAPTURE_HINT_AUTO); - - FakeScrollCaptureCallback callback2 = new FakeScrollCaptureCallback(); - callback2.setScrollBounds(new Rect(1, 1, 19, 19)); - ScrollCaptureTarget target2 = createTarget(callback2, new Rect(20, 30, 40, 50), - new Point(0, 20), View.SCROLL_CAPTURE_HINT_INCLUDE); - - FakeScrollCaptureCallback callback3 = new FakeScrollCaptureCallback(); - callback3.setScrollBounds(new Rect(2, 2, 18, 18)); - ScrollCaptureTarget target3 = createTarget(callback3, new Rect(20, 30, 40, 50), - new Point(0, 40), View.SCROLL_CAPTURE_HINT_AUTO); - - targetQueue.add(target1); - targetQueue.add(target2); // * INCLUDE > AUTO - targetQueue.add(target3); - - ScrollCaptureTargetResolver resolver = new ScrollCaptureTargetResolver(targetQueue); - resolver.start(mHandler, RESOLVER_TIMEOUT_MS, mTargetConsumer); - - resolver.waitForResult(); - - ScrollCaptureTarget result = mTargetConsumer.getLastValue(); - assertEquals("input = " + targetQueue + " Expected " + target2 - + " as the result, due to hint=INCLUDE", target2, result); - assertEquals("result has wrong scroll bounds", - new Rect(1, 1, 19, 19), result.getScrollBounds()); - } - - @Test(timeout = TEST_TIMEOUT_MS) - public void testDescendantPreferred() throws InterruptedException { - LinkedList<ScrollCaptureTarget> targetQueue = new LinkedList<>(); - - ViewGroup targetView1 = new FakeRootView(getTargetContext(), 0, 0, 60, 60); // 60x60 - ViewGroup targetView2 = new FakeRootView(getTargetContext(), 20, 30, 40, 50); // 20x20 - ViewGroup targetView3 = new FakeRootView(getTargetContext(), 5, 5, 15, 15); // 10x10 - - targetView1.addView(targetView2); - targetView2.addView(targetView3); - - // Create first target with an unrelated parent - FakeScrollCaptureCallback callback1 = new FakeScrollCaptureCallback(); - callback1.setScrollBounds(new Rect(0, 0, 60, 60)); - ScrollCaptureTarget target1 = createTargetWithView(targetView1, callback1, - new Rect(0, 0, 60, 60), - new Point(0, 0), View.SCROLL_CAPTURE_HINT_AUTO); - - // Create second target associated with a view within parent2 - FakeScrollCaptureCallback callback2 = new FakeScrollCaptureCallback(); - callback2.setScrollBounds(new Rect(0, 0, 20, 20)); - ScrollCaptureTarget target2 = createTargetWithView(targetView2, callback2, - new Rect(0, 0, 20, 20), - new Point(20, 30), View.SCROLL_CAPTURE_HINT_AUTO); - - // Create third target associated with a view within parent3 - FakeScrollCaptureCallback callback3 = new FakeScrollCaptureCallback(); - callback3.setScrollBounds(new Rect(0, 0, 15, 15)); - ScrollCaptureTarget target3 = createTargetWithView(targetView3, callback3, - new Rect(0, 0, 15, 15), - new Point(25, 35), View.SCROLL_CAPTURE_HINT_AUTO); - - targetQueue.add(target1); // auto, 60x60 - targetQueue.add(target2); // auto, 20x20 - targetQueue.add(target3); // auto, 15x15 <- innermost scrollable - - ScrollCaptureTargetResolver resolver = new ScrollCaptureTargetResolver(targetQueue); - resolver.start(mHandler, RESOLVER_TIMEOUT_MS, mTargetConsumer); - - // Test only - resolver.waitForResult(); - - ScrollCaptureTarget result = mTargetConsumer.getLastValue(); - assertSame("Expected target3 as the result, due to relation", target3, result); - assertEquals("result has wrong scroll bounds", - new Rect(0, 0, 15, 15), result.getScrollBounds()); - } - - /** - * If a timeout expires, late results are ignored. - */ - @Test(timeout = TEST_TIMEOUT_MS) - public void testTimeout() throws InterruptedException { - LinkedList<ScrollCaptureTarget> targetQueue = new LinkedList<>(); - - // callback 1, 10x10, hint=AUTO, responds immediately from bg thread - BackgroundTestCallback callback1 = new BackgroundTestCallback(); - callback1.setScrollBounds(new Rect(5, 5, 15, 15)); - ScrollCaptureTarget target1 = createTarget( - callback1, new Rect(20, 30, 40, 50), new Point(10, 10), - View.SCROLL_CAPTURE_HINT_AUTO); - targetQueue.add(target1); - - // callback 2, 20x20, hint=AUTO, responds after 5s from bg thread - BackgroundTestCallback callback2 = new BackgroundTestCallback(); - callback2.setScrollBounds(new Rect(0, 0, 20, 20)); - callback2.setDelay(5000); - ScrollCaptureTarget target2 = createTarget( - callback2, new Rect(20, 30, 40, 50), new Point(10, 10), - View.SCROLL_CAPTURE_HINT_AUTO); - targetQueue.add(target2); - - // callback 3, 20x20, hint=INCLUDE, responds after 10s from bg thread - BackgroundTestCallback callback3 = new BackgroundTestCallback(); - callback3.setScrollBounds(new Rect(0, 0, 20, 20)); - callback3.setDelay(10000); - ScrollCaptureTarget target3 = createTarget( - callback3, new Rect(20, 30, 40, 50), new Point(10, 10), - View.SCROLL_CAPTURE_HINT_INCLUDE); - targetQueue.add(target3); - - // callback 1 will be received - // callback 2 & 3 will be ignored due to timeout - - ScrollCaptureTargetResolver resolver = new ScrollCaptureTargetResolver(targetQueue); - resolver.start(mHandler, RESOLVER_TIMEOUT_MS, mTargetConsumer); - - resolver.waitForResult(); - - ScrollCaptureTarget result = mTargetConsumer.getLastValue(); - assertSame("Expected target1 as the result, due to timeouts of others", target1, result); - assertEquals("result has wrong scroll bounds", - new Rect(5, 5, 15, 15), result.getScrollBounds()); - assertEquals("callback1 should have been called", - 1, callback1.getOnScrollCaptureSearchCount()); - assertEquals("callback2 should have been called", - 1, callback2.getOnScrollCaptureSearchCount()); - assertEquals("callback3 should have been called", - 1, callback3.getOnScrollCaptureSearchCount()); - } - - @Test(timeout = TEST_TIMEOUT_MS) - public void testWithCallbackMultipleReplies() throws InterruptedException { - // Calls response methods 3 times each - RepeatingCaptureCallback callback1 = new RepeatingCaptureCallback(3); - callback1.setScrollBounds(new Rect(2, 2, 18, 18)); - ScrollCaptureTarget target1 = createTarget(callback1, new Rect(20, 30, 40, 50), - new Point(10, 10), View.SCROLL_CAPTURE_HINT_AUTO); - - FakeScrollCaptureCallback callback2 = new FakeScrollCaptureCallback(); - callback2.setScrollBounds(new Rect(0, 0, 20, 20)); - ScrollCaptureTarget target2 = createTarget(callback2, new Rect(20, 30, 40, 50), - new Point(10, 10), View.SCROLL_CAPTURE_HINT_AUTO); - - LinkedList<ScrollCaptureTarget> targetQueue = new LinkedList<>(); - targetQueue.add(target1); - targetQueue.add(target2); - - ScrollCaptureTargetResolver resolver = new ScrollCaptureTargetResolver(targetQueue); - resolver.start(mHandler, RESOLVER_TIMEOUT_MS, mTargetConsumer); - - resolver.waitForResult(); - - ScrollCaptureTarget result = mTargetConsumer.getLastValue(); - assertSame("Expected target2 as the result, due to hint=INCLUDE", target2, result); - assertEquals("result has wrong scroll bounds", - new Rect(0, 0, 20, 20), result.getScrollBounds()); - assertEquals("callback1 should have been called once", - 1, callback1.getOnScrollCaptureSearchCount()); - assertEquals("callback2 should have been called once", - 1, callback2.getOnScrollCaptureSearchCount()); - } - - private static class TargetConsumer implements Consumer<ScrollCaptureTarget> { - volatile ScrollCaptureTarget mResult; - int mAcceptCount; - - ScrollCaptureTarget getLastValue() { - return mResult; - } - - int acceptCount() { - return mAcceptCount; - } - - @Override - public void accept(@Nullable ScrollCaptureTarget t) { - mAcceptCount++; - mResult = t; - } - } - - private void setupTargetView(View view, Rect localVisibleRect, int scrollCaptureHint) { - view.setScrollCaptureHint(scrollCaptureHint); - view.onVisibilityAggregated(true); - // Treat any offset as padding, outset localVisibleRect on all sides and use this as - // child bounds - Rect bounds = new Rect(localVisibleRect); - bounds.inset(-bounds.left, -bounds.top, bounds.left, bounds.top); - view.layout(bounds.left, bounds.top, bounds.right, bounds.bottom); - view.onVisibilityAggregated(true); - } - - private ScrollCaptureTarget createTarget(ScrollCaptureCallback callback, Rect localVisibleRect, - Point positionInWindow, int scrollCaptureHint) { - View mockView = new View(getTargetContext()); - return createTargetWithView(mockView, callback, localVisibleRect, positionInWindow, - scrollCaptureHint); - } - - private ScrollCaptureTarget createTargetWithView(View view, ScrollCaptureCallback callback, - Rect localVisibleRect, Point positionInWindow, int scrollCaptureHint) { - setupTargetView(view, localVisibleRect, scrollCaptureHint); - return new ScrollCaptureTarget(view, localVisibleRect, positionInWindow, callback); - } - - - static class FakeRootView extends ViewGroup implements ViewParent { - FakeRootView(Context context, int l, int t, int r, int b) { - super(context); - layout(l, t, r, b); - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - } - } - - static class FakeScrollCaptureCallback implements ScrollCaptureCallback { - private Rect mScrollBounds; - private long mDelayMillis; - private int mOnScrollCaptureSearchCount; - - public int getOnScrollCaptureSearchCount() { - return mOnScrollCaptureSearchCount; - } - - @Override - public void onScrollCaptureSearch(Consumer<Rect> onReady) { - mOnScrollCaptureSearchCount++; - run(() -> { - Rect b = getScrollBounds(); - onReady.accept(b); - }); - } - - @Override - public void onScrollCaptureStart(ScrollCaptureSession session, Runnable onReady) { - run(onReady); - } - - @Override - public void onScrollCaptureImageRequest(ScrollCaptureSession session, Rect captureArea) { - run(() -> session.notifyBufferSent(0, captureArea)); - } - - @Override - public void onScrollCaptureEnd(Runnable onReady) { - run(onReady); - } - - public void setScrollBounds(@Nullable Rect scrollBounds) { - mScrollBounds = scrollBounds; - } - - public void setDelay(long delayMillis) { - mDelayMillis = delayMillis; - } - - protected Rect getScrollBounds() { - return mScrollBounds; - } - - protected void run(Runnable r) { - delay(); - r.run(); - } - - protected void delay() { - if (mDelayMillis > 0) { - try { - Thread.sleep(mDelayMillis); - } catch (InterruptedException e) { - // Ignore - } - } - } - } - - static class RepeatingCaptureCallback extends FakeScrollCaptureCallback { - private int mRepeatCount; - - RepeatingCaptureCallback(int repeatCount) { - mRepeatCount = repeatCount; - } - - protected void run(Runnable r) { - delay(); - for (int i = 0; i < mRepeatCount; i++) { - r.run(); - } - } - } - - /** Response to async calls on an arbitrary background thread */ - static class BackgroundTestCallback extends FakeScrollCaptureCallback { - static int sCount = 0; - private void runOnBackgroundThread(Runnable r) { - final Runnable target = () -> { - delay(); - r.run(); - }; - Thread t = new Thread(target); - synchronized (BackgroundTestCallback.this) { - sCount++; - } - t.setName("Background-Thread-" + sCount); - t.start(); - } - - @Override - protected void run(Runnable r) { - runOnBackgroundThread(r); - } - } -} diff --git a/core/tests/coretests/src/android/view/SoundEffectConstantsTest.java b/core/tests/coretests/src/android/view/SoundEffectConstantsTest.java new file mode 100644 index 000000000000..45ffb1221515 --- /dev/null +++ b/core/tests/coretests/src/android/view/SoundEffectConstantsTest.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for {@link SoundEffectConstants} + * + * Build/Install/Run: + * atest FrameworksCoreTests:SoundEffectConstantsTest + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class SoundEffectConstantsTest { + + @Test + public void testIsNavigationRepeat() { + assertTrue(SoundEffectConstants.isNavigationRepeat( + SoundEffectConstants.NAVIGATION_REPEAT_RIGHT)); + assertTrue(SoundEffectConstants.isNavigationRepeat( + SoundEffectConstants.NAVIGATION_REPEAT_LEFT)); + assertTrue( + SoundEffectConstants.isNavigationRepeat(SoundEffectConstants.NAVIGATION_REPEAT_UP)); + assertTrue(SoundEffectConstants.isNavigationRepeat( + SoundEffectConstants.NAVIGATION_REPEAT_DOWN)); + assertFalse(SoundEffectConstants.isNavigationRepeat(SoundEffectConstants.NAVIGATION_RIGHT)); + assertFalse(SoundEffectConstants.isNavigationRepeat(SoundEffectConstants.NAVIGATION_LEFT)); + assertFalse(SoundEffectConstants.isNavigationRepeat(SoundEffectConstants.NAVIGATION_UP)); + assertFalse(SoundEffectConstants.isNavigationRepeat(SoundEffectConstants.NAVIGATION_DOWN)); + assertFalse(SoundEffectConstants.isNavigationRepeat(-1)); + } +} diff --git a/core/tests/coretests/src/android/view/TestScrollCaptureCallback.java b/core/tests/coretests/src/android/view/TestScrollCaptureCallback.java new file mode 100644 index 000000000000..1520c6e34a95 --- /dev/null +++ b/core/tests/coretests/src/android/view/TestScrollCaptureCallback.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import static org.junit.Assert.*; + +import android.graphics.Rect; +import android.os.CancellationSignal; + +import androidx.annotation.NonNull; + +import java.util.function.Consumer; + +class TestScrollCaptureCallback implements ScrollCaptureCallback { + private Consumer<Rect> mSearchConsumer; + private Runnable mStartOnReady; + private Consumer<Rect> mImageOnComplete; + private Runnable mOnEndReady; + private volatile int mModCount; + + @Override + public void onScrollCaptureSearch(@NonNull CancellationSignal signal, + @NonNull Consumer<Rect> onReady) { + mSearchConsumer = onReady; + mModCount++; + } + + @Override + public void onScrollCaptureStart(@NonNull ScrollCaptureSession session, + @NonNull CancellationSignal signal, @NonNull Runnable onReady) { + mStartOnReady = onReady; + mModCount++; + } + + @Override + public void onScrollCaptureImageRequest(@NonNull ScrollCaptureSession session, + @NonNull CancellationSignal signal, @NonNull Rect captureArea, + @NonNull Consumer<Rect> onComplete) { + mImageOnComplete = onComplete; + mModCount++; + } + + @Override + public void onScrollCaptureEnd(@NonNull Runnable onReady) { + mOnEndReady = onReady; + } + + void completeSearchRequest(Rect scrollBounds) { + assertNotNull("Did not receive search request", mSearchConsumer); + mSearchConsumer.accept(scrollBounds); + mModCount++; + } + + void verifyZeroInteractions() { + assertEquals("Expected zero interactions", 0, mModCount); + } + + void completeStartRequest() { + assertNotNull("Did not receive start request", mStartOnReady); + mStartOnReady.run(); + } + + void completeImageRequest(Rect captured) { + assertNotNull("Did not receive image request", mImageOnComplete); + mImageOnComplete.accept(captured); + } + + void completeEndRequest() { + assertNotNull("Did not receive end request", mOnEndReady); + mOnEndReady.run(); + } +} diff --git a/core/tests/coretests/src/android/view/ViewGroupScrollCaptureTest.java b/core/tests/coretests/src/android/view/ViewGroupScrollCaptureTest.java index 3af0533e763c..41cd4c562bd8 100644 --- a/core/tests/coretests/src/android/view/ViewGroupScrollCaptureTest.java +++ b/core/tests/coretests/src/android/view/ViewGroupScrollCaptureTest.java @@ -19,7 +19,9 @@ package android.view; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.testng.AssertJUnit.assertSame; @@ -27,19 +29,20 @@ import android.annotation.Nullable; import android.content.Context; import android.graphics.Point; import android.graphics.Rect; +import android.os.CancellationSignal; import android.platform.test.annotations.Presubmit; +import androidx.annotation.NonNull; import androidx.test.filters.FlakyTest; import androidx.test.filters.MediumTest; import androidx.test.filters.SmallTest; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; -import java.util.LinkedList; -import java.util.Queue; +import java.util.concurrent.Executor; +import java.util.function.Consumer; /** * Exercises Scroll Capture search in {@link ViewGroup}. @@ -50,10 +53,7 @@ import java.util.Queue; @RunWith(MockitoJUnitRunner.class) public class ViewGroupScrollCaptureTest { - @Mock - ScrollCaptureCallback mMockCallback; - @Mock - ScrollCaptureCallback mMockCallback2; + private static final Executor DIRECT_EXECUTOR = Runnable::run; /** Make sure the hint flags are saved and loaded correctly. */ @Test @@ -103,25 +103,24 @@ public class ViewGroupScrollCaptureTest { public void testDispatchScrollCaptureSearch_noCallback_hintAuto() throws Exception { final Context context = getInstrumentation().getContext(); final MockViewGroup viewGroup = new MockViewGroup(context, 0, 0, 200, 200); + TestScrollCaptureCallback callback = new TestScrollCaptureCallback(); // When system internal scroll capture is requested, this callback is returned. - viewGroup.setScrollCaptureCallbackInternalForTest(mMockCallback); + viewGroup.setScrollCaptureCallbackInternalForTest(callback); Rect localVisibleRect = new Rect(0, 0, 200, 200); Point windowOffset = new Point(); - LinkedList<ScrollCaptureTarget> targetList = new LinkedList<>(); + ScrollCaptureSearchResults results = new ScrollCaptureSearchResults(DIRECT_EXECUTOR); // Dispatch - viewGroup.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, targetList); - - // Verify the system checked for fallback support - viewGroup.assertDispatchScrollCaptureCount(1); - viewGroup.assertLastDispatchScrollCaptureArgs(localVisibleRect, windowOffset); + viewGroup.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, results::addTarget); + callback.completeSearchRequest(new Rect(1, 2, 3, 4)); + assertTrue(results.isComplete()); // Verify the target is as expected. - assertEquals(1, targetList.size()); - ScrollCaptureTarget target = targetList.get(0); - assertSame("Target has the wrong callback", mMockCallback, target.getCallback()); + ScrollCaptureTarget target = results.getTopResult(); + assertNotNull("Target not found", target); + assertSame("Target has the wrong callback", callback, target.getCallback()); assertSame("Target has the wrong View", viewGroup, target.getContainingView()); assertEquals("Target hint is incorrect", View.SCROLL_CAPTURE_HINT_AUTO, target.getContainingView().getScrollCaptureHint()); @@ -139,18 +138,22 @@ public class ViewGroupScrollCaptureTest { final MockViewGroup viewGroup = new MockViewGroup(context, 0, 0, 200, 200, View.SCROLL_CAPTURE_HINT_EXCLUDE); + TestScrollCaptureCallback callback = new TestScrollCaptureCallback(); + // When system internal scroll capture is requested, this callback is returned. - viewGroup.setScrollCaptureCallbackInternalForTest(mMockCallback); + viewGroup.setScrollCaptureCallbackInternalForTest(callback); Rect localVisibleRect = new Rect(0, 0, 200, 200); Point windowOffset = new Point(); - LinkedList<ScrollCaptureTarget> targetList = new LinkedList<>(); + ScrollCaptureSearchResults results = new ScrollCaptureSearchResults(DIRECT_EXECUTOR); + assertTrue(results.isComplete()); // Dispatch - viewGroup.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, targetList); + viewGroup.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, results::addTarget); + callback.verifyZeroInteractions(); // Verify the results. - assertEquals("Target list size should be zero.", 0, targetList.size()); + assertTrue("Results should be empty.", results.isEmpty()); } /** @@ -164,27 +167,34 @@ public class ViewGroupScrollCaptureTest { final Context context = getInstrumentation().getContext(); MockViewGroup viewGroup = new MockViewGroup(context, 0, 0, 200, 200); + TestScrollCaptureCallback callback = new TestScrollCaptureCallback(); + TestScrollCaptureCallback callback2 = new TestScrollCaptureCallback(); + // With an already provided scroll capture callback - viewGroup.setScrollCaptureCallback(mMockCallback); + viewGroup.setScrollCaptureCallback(callback); // When system internal scroll capture is requested, this callback is returned. - viewGroup.setScrollCaptureCallbackInternalForTest(mMockCallback); + viewGroup.setScrollCaptureCallbackInternalForTest(callback2); Rect localVisibleRect = new Rect(0, 0, 200, 200); Point windowOffset = new Point(); - LinkedList<ScrollCaptureTarget> targetList = new LinkedList<>(); + ScrollCaptureSearchResults results = new ScrollCaptureSearchResults(DIRECT_EXECUTOR); // Dispatch to the ViewGroup - viewGroup.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, targetList); - - // Confirm that framework support was not requested, - // because this view already had a callback set. - viewGroup.assertCreateScrollCaptureCallbackInternalCount(0); + viewGroup.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, results::addTarget); + callback.completeSearchRequest(new Rect(1, 2, 3, 4)); // Verify the target is as expected. - assertEquals(1, targetList.size()); - ScrollCaptureTarget target = targetList.get(0); - assertSame("Target has the wrong callback", mMockCallback, target.getCallback()); + assertFalse(results.isEmpty()); + assertTrue(results.isComplete()); + + // internal framework callback was not requested + callback2.verifyZeroInteractions(); + + ScrollCaptureTarget target = results.getTopResult(); + + assertNotNull("Target not found", target); + assertSame("Target has the wrong callback", callback, target.getCallback()); assertSame("Target has the wrong View", viewGroup, target.getContainingView()); assertEquals("Target hint is incorrect", View.SCROLL_CAPTURE_HINT_AUTO, target.getContainingView().getScrollCaptureHint()); @@ -201,22 +211,22 @@ public class ViewGroupScrollCaptureTest { final Context context = getInstrumentation().getContext(); MockViewGroup viewGroup = new MockViewGroup(context, 0, 0, 200, 200, View.SCROLL_CAPTURE_HINT_EXCLUDE); + + TestScrollCaptureCallback callback = new TestScrollCaptureCallback(); + // With an already provided scroll capture callback - viewGroup.setScrollCaptureCallback(mMockCallback); + viewGroup.setScrollCaptureCallback(callback); Rect localVisibleRect = new Rect(0, 0, 200, 200); Point windowOffset = new Point(); - LinkedList<ScrollCaptureTarget> targetList = new LinkedList<>(); + ScrollCaptureSearchResults results = new ScrollCaptureSearchResults(DIRECT_EXECUTOR); // Dispatch to the ViewGroup itself - viewGroup.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, targetList); - - // Confirm that framework support was not requested, because this view is excluded. - // (And because this view has a callback set.) - viewGroup.assertCreateScrollCaptureCallbackInternalCount(0); + viewGroup.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, results::addTarget); + callback.verifyZeroInteractions(); // Has callback, but hint=excluded, so excluded. - assertTrue(targetList.isEmpty()); + assertNull(results.getTopResult()); } /** @@ -252,37 +262,43 @@ public class ViewGroupScrollCaptureTest { // | | | // +---------------+----------+ (200,200) - // View 1 is clipped and not visible. + // View 1 is fully clipped and not visible. final MockView view1 = new MockView(context, 0, 0, 200, 25); viewGroup.addView(view1); - // View 2 is partially visible. + // View 2 is partially visible. (75x75) final MockView view2 = new MockView(context, 0, 25, 150, 100); viewGroup.addView(view2); - // View 3 is partially visible. + TestScrollCaptureCallback callback1 = new TestScrollCaptureCallback(); + + // View 3 is partially visible (175x50) // Pretend View3 can scroll by having framework provide fallback support final MockView view3 = new MockView(context, 0, 100, 200, 200); // When system internal scroll capture is requested for this view, return this callback. - view3.setScrollCaptureCallbackInternalForTest(mMockCallback); + view3.setScrollCaptureCallbackInternalForTest(callback1); viewGroup.addView(view3); // View 4 is invisible and should be ignored. final MockView view4 = new MockView(context, 150, 25, 200, 100, View.INVISIBLE); viewGroup.addView(view4); - // View 4 is invisible and should be ignored. + TestScrollCaptureCallback callback2 = new TestScrollCaptureCallback(); + + // View 5 is partially visible and explicitly included via flag. (25x50) final MockView view5 = new MockView(context, 150, 100, 200, 200); - // When system internal scroll capture is requested for this view, return this callback. - view5.setScrollCaptureCallback(mMockCallback2); + view5.setScrollCaptureCallback(callback2); view5.setScrollCaptureHint(View.SCROLL_CAPTURE_HINT_INCLUDE); viewGroup.addView(view5); // Where targets are added - final LinkedList<ScrollCaptureTarget> targetList = new LinkedList<>(); + final ScrollCaptureSearchResults results = new ScrollCaptureSearchResults(DIRECT_EXECUTOR); // Dispatch to the ViewGroup - viewGroup.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, targetList); + viewGroup.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, results::addTarget); + callback1.completeSearchRequest(new Rect(0, 0, 200, 100)); + callback2.completeSearchRequest(new Rect(0, 0, 50, 100)); + assertTrue(results.isComplete()); // View 1 is entirely clipped by the parent and not visible, dispatch // skips this view entirely. @@ -317,18 +333,14 @@ public class ViewGroupScrollCaptureTest { view5.assertCreateScrollCaptureCallbackInternalCount(0); // 2 views should have been returned, view3 & view5 - assertEquals(2, targetList.size()); - - ScrollCaptureTarget target = targetList.get(0); - assertSame("First target has the wrong View", view3, target.getContainingView()); - assertSame("First target has the wrong callback", mMockCallback, target.getCallback()); - assertEquals("First target hint is incorrect", View.SCROLL_CAPTURE_HINT_AUTO, - target.getContainingView().getScrollCaptureHint()); - - target = targetList.get(1); - assertSame("Second target has the wrong View", view5, target.getContainingView()); - assertSame("Second target has the wrong callback", mMockCallback2, target.getCallback()); - assertEquals("Second target hint is incorrect", View.SCROLL_CAPTURE_HINT_INCLUDE, + assertFalse(results.isEmpty()); + assertTrue(results.isComplete()); + + ScrollCaptureTarget target = results.getTopResult(); + assertNotNull("Target not found", target); + assertSame("Result is the wrong View", view5, target.getContainingView()); + assertSame("Result is the wrong callback", callback2, target.getCallback()); + assertEquals("First target hint is incorrect", View.SCROLL_CAPTURE_HINT_INCLUDE, target.getContainingView().getScrollCaptureHint()); } @@ -371,7 +383,7 @@ public class ViewGroupScrollCaptureTest { } void assertCreateScrollCaptureCallbackInternalCount(int count) { - assertEquals("Unexpected number of calls to createScrollCaptureCallackInternal", + assertEquals("Unexpected number of calls to createScrollCaptureCallbackInternal", count, mCreateScrollCaptureCallbackInternalCount); } @@ -385,11 +397,11 @@ public class ViewGroupScrollCaptureTest { @Override public void dispatchScrollCaptureSearch(Rect localVisibleRect, Point windowOffset, - Queue<ScrollCaptureTarget> targets) { + Consumer<ScrollCaptureTarget> results) { mDispatchScrollCaptureSearchNumCalls++; mDispatchScrollCaptureSearchLastLocalVisibleRect = new Rect(localVisibleRect); mDispatchScrollCaptureSearchLastWindowOffset = new Point(windowOffset); - super.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, targets); + super.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, results); } @Override @@ -401,13 +413,31 @@ public class ViewGroupScrollCaptureTest { } } + static class CallbackStub implements ScrollCaptureCallback { + + @Override + public void onScrollCaptureSearch(@NonNull CancellationSignal signal, + @NonNull Consumer<Rect> onReady) { + } + + @Override + public void onScrollCaptureStart(@NonNull ScrollCaptureSession session, + @NonNull CancellationSignal signal, @NonNull Runnable onReady) { + } + + @Override + public void onScrollCaptureImageRequest(@NonNull ScrollCaptureSession session, + @NonNull CancellationSignal signal, @NonNull Rect captureArea, + Consumer<Rect> onComplete) { + } + + @Override + public void onScrollCaptureEnd(@NonNull Runnable onReady) { + } + }; + public static final class MockViewGroup extends ViewGroup { private ScrollCaptureCallback mInternalCallback; - private int mDispatchScrollCaptureSearchNumCalls; - private Rect mDispatchScrollCaptureSearchLastLocalVisibleRect; - private Point mDispatchScrollCaptureSearchLastWindowOffset; - private int mCreateScrollCaptureCallbackInternalCount; - MockViewGroup(Context context) { this(context, /* left */ 0, /* top */0, /* right */ 0, /* bottom */0); @@ -428,16 +458,10 @@ public class ViewGroupScrollCaptureTest { mInternalCallback = internal; } - void assertDispatchScrollCaptureSearchCount(int count) { - assertEquals("Unexpected number of calls to dispatchScrollCaptureSearch", - count, mDispatchScrollCaptureSearchNumCalls); - } - @Override @Nullable public ScrollCaptureCallback createScrollCaptureCallbackInternal(Rect localVisibleRect, Point offsetInWindow) { - mCreateScrollCaptureCallbackInternalCount++; return mInternalCallback; } @@ -445,36 +469,5 @@ public class ViewGroupScrollCaptureTest { protected void onLayout(boolean changed, int l, int t, int r, int b) { // We don't layout this view. } - - void assertDispatchScrollCaptureCount(int count) { - assertEquals(count, mDispatchScrollCaptureSearchNumCalls); - } - - void assertLastDispatchScrollCaptureArgs(Rect localVisibleRect, Point windowOffset) { - assertEquals("arg localVisibleRect to dispatchScrollCaptureCallback was incorrect.", - localVisibleRect, mDispatchScrollCaptureSearchLastLocalVisibleRect); - assertEquals("arg windowOffset to dispatchScrollCaptureCallback was incorrect.", - windowOffset, mDispatchScrollCaptureSearchLastWindowOffset); - } - void assertCreateScrollCaptureCallbackInternalCount(int count) { - assertEquals("Unexpected number of calls to createScrollCaptureCallackInternal", - count, mCreateScrollCaptureCallbackInternalCount); - } - - void reset() { - mDispatchScrollCaptureSearchNumCalls = 0; - mDispatchScrollCaptureSearchLastWindowOffset = null; - mDispatchScrollCaptureSearchLastLocalVisibleRect = null; - mCreateScrollCaptureCallbackInternalCount = 0; - } - - @Override - public void dispatchScrollCaptureSearch(Rect localVisibleRect, Point windowOffset, - Queue<ScrollCaptureTarget> targets) { - mDispatchScrollCaptureSearchNumCalls++; - mDispatchScrollCaptureSearchLastLocalVisibleRect = new Rect(localVisibleRect); - mDispatchScrollCaptureSearchLastWindowOffset = new Point(windowOffset); - super.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, targets); - } } } diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java index c67174f0ae1e..7746bc2e273a 100644 --- a/core/tests/coretests/src/android/view/ViewRootImplTest.java +++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java @@ -207,7 +207,7 @@ public class ViewRootImplTest { final CountDownLatch latch = new CountDownLatch(1); mViewRootImpl.handleScrollCaptureRequest(new IScrollCaptureCallbacks.Default() { @Override - public void onUnavailable() { + public void onScrollCaptureResponse(ScrollCaptureResponse response) { latch.countDown(); } }); @@ -220,6 +220,37 @@ public class ViewRootImplTest { } /** + * Ensure scroll capture request handles a ViewRootImpl with no view tree. + */ + @Test + public void requestScrollCapture_timeout() { + final View view = new View(mContext); + view.setScrollCaptureCallback(new TestScrollCaptureCallback()); // Does nothing + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + WindowManager.LayoutParams wmlp = + new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY); + // Set a fake token to bypass 'is your activity running' check + wmlp.token = new Binder(); + view.setLayoutParams(wmlp); + mViewRootImpl.setView(view, wmlp, null); + }); + + final CountDownLatch latch = new CountDownLatch(1); + mViewRootImpl.setScrollCaptureRequestTimeout(100); + mViewRootImpl.handleScrollCaptureRequest(new IScrollCaptureCallbacks.Default() { + @Override + public void onScrollCaptureResponse(ScrollCaptureResponse response) { + latch.countDown(); + } + }); + try { + if (!latch.await(2500, TimeUnit.MILLISECONDS)) { + fail("requestScrollCapture timeout did not occur"); + } + } catch (InterruptedException e) { /* ignore */ } + } + + /** * When window doesn't have focus, keys should be dropped. */ @Test diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java index 9cac7e794965..ff728d651067 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java @@ -77,6 +77,7 @@ import java.util.Arrays; * bit FrameworksCoreTests:com.android.internal.os.BatteryStatsCpuTimesTest */ @SmallTest +@SkipPresubmit("b/180015146") @RunWith(AndroidJUnit4.class) public class BatteryStatsCpuTimesTest { @Mock diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java index 4b37dd226e69..24baa93337ba 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java @@ -73,6 +73,7 @@ public class BatteryStatsImplTest { } @Test + @SkipPresubmit("b/180015146") public void testUpdateProcStateCpuTimes() { mBatteryStatsImpl.setOnBatteryInternal(true); mBatteryStatsImpl.updateTimeBasesLocked(false, Display.STATE_ON, 0, 0); @@ -230,6 +231,7 @@ public class BatteryStatsImplTest { } @Test + @SkipPresubmit("b/180015146") public void testCopyFromAllUidsCpuTimes() { mBatteryStatsImpl.setOnBatteryInternal(false); mBatteryStatsImpl.updateTimeBasesLocked(false, Display.STATE_ON, 0, 0); diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java index 6652c64c4344..931611ea7478 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java @@ -295,6 +295,7 @@ public class BatteryStatsNoteTest extends TestCase { } @SmallTest + @SkipPresubmit("b/180015146") public void testAlarmStartAndFinishLocked() throws Exception { final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks); @@ -332,6 +333,7 @@ public class BatteryStatsNoteTest extends TestCase { } @SmallTest + @SkipPresubmit("b/180015146") public void testAlarmStartAndFinishLocked_workSource() throws Exception { final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks); diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsSamplingTimerTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsSamplingTimerTest.java index 3b27f1897bd2..dd814e651ede 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsSamplingTimerTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsSamplingTimerTest.java @@ -56,6 +56,7 @@ public class BatteryStatsSamplingTimerTest extends TestCase { } @SmallTest + @SkipPresubmit("b/180015146") public void testEndSampleAndContinueWhenTimeOrCountDecreases() throws Exception { final MockClocks clocks = new MockClocks(); final BatteryStatsImpl.TimeBase timeBase = Mockito.mock(BatteryStatsImpl.TimeBase.class); diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java index b819d9edb2a8..d276bc34d05a 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java @@ -40,6 +40,7 @@ import org.junit.runners.Suite; BatteryStatsTimeBaseTest.class, BatteryStatsTimerTest.class, BatteryStatsUidTest.class, + BatteryUsageStatsProviderTest.class, BatteryUsageStatsTest.class, BatteryStatsUserLifecycleTests.class, BluetoothPowerCalculatorTest.class, @@ -71,9 +72,9 @@ import org.junit.runners.Suite; UserPowerCalculatorTest.class, VideoPowerCalculatorTest.class, WakelockPowerCalculatorTest.class, + WifiPowerCalculatorTest.class, com.android.internal.power.MeasuredEnergyStatsTest.class }) public class BatteryStatsTests { -} - +}
\ No newline at end of file diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsUserLifecycleTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsUserLifecycleTests.java index e7a1bcae459a..e90bcb76e457 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsUserLifecycleTests.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsUserLifecycleTests.java @@ -78,6 +78,7 @@ public class BatteryStatsUserLifecycleTests { } @Test + @SkipPresubmit("b/180015146") public void testNoCpuDataForRemovedUser() throws Exception { mIam.startUserInBackground(mTestUserId); waitUntilTrue("No uids for started user " + mTestUserId, diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java new file mode 100644 index 000000000000..c4b7796b49cf --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.os; + +import static com.google.common.truth.Truth.assertThat; + +import android.app.ActivityManager; +import android.content.Context; +import android.os.BatteryUsageStats; +import android.os.BatteryUsageStatsQuery; +import android.os.Process; +import android.os.UidBatteryConsumer; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.List; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class BatteryUsageStatsProviderTest { + private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42; + private static final long MINUTE_IN_MS = 60 * 1000; + + @Rule + public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule(); + + @Test + public void test_getBatteryUsageStats() { + BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats(); + + batteryStats.noteActivityResumedLocked(APP_UID, + 10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS); + batteryStats.noteUidProcessStateLocked(APP_UID, ActivityManager.PROCESS_STATE_TOP, + 10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS); + batteryStats.noteActivityPausedLocked(APP_UID, + 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS); + batteryStats.noteUidProcessStateLocked(APP_UID, ActivityManager.PROCESS_STATE_SERVICE, + 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS); + batteryStats.noteUidProcessStateLocked(APP_UID, ActivityManager.PROCESS_STATE_CACHED_EMPTY, + 40 * MINUTE_IN_MS, 40 * MINUTE_IN_MS); + + Context context = InstrumentationRegistry.getContext(); + BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, batteryStats); + + final BatteryUsageStats batteryUsageStats = + provider.getBatteryUsageStats(BatteryUsageStatsQuery.DEFAULT); + + final List<UidBatteryConsumer> uidBatteryConsumers = + batteryUsageStats.getUidBatteryConsumers(); + final UidBatteryConsumer uidBatteryConsumer = uidBatteryConsumers.get(0); + assertThat(uidBatteryConsumer.getTimeInStateMs(UidBatteryConsumer.STATE_FOREGROUND)) + .isEqualTo(20 * MINUTE_IN_MS); + assertThat(uidBatteryConsumer.getTimeInStateMs(UidBatteryConsumer.STATE_BACKGROUND)) + .isEqualTo(10 * MINUTE_IN_MS); + } +} diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java index 355ac6dbcc00..23ea508d19d3 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java @@ -35,6 +35,7 @@ import org.junit.runner.RunWith; import java.util.List; @SmallTest +@SkipPresubmit("b/180015146") @RunWith(AndroidJUnit4.class) public class BatteryUsageStatsTest { @@ -66,33 +67,36 @@ public class BatteryUsageStatsTest { final MockBatteryStatsImpl batteryStats = new MockBatteryStatsImpl(clocks); final BatteryStatsImpl.Uid batteryStatsUid = batteryStats.getUidStatsLocked(2000); - final BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(1, 1); - builder.setDischargePercentage(20); - builder.setDischargedPowerRange(1000, 2000); - - final UidBatteryConsumer.Builder uidBatteryConsumerBuilder = - builder.getOrCreateUidBatteryConsumerBuilder(batteryStatsUid); - uidBatteryConsumerBuilder.setPackageWithHighestDrain("foo"); - uidBatteryConsumerBuilder.setConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE, 300); - uidBatteryConsumerBuilder.setConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, 400); - uidBatteryConsumerBuilder.setConsumedPowerForCustomComponent( - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 500); - uidBatteryConsumerBuilder.setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_CPU, 600); - uidBatteryConsumerBuilder.setUsageDurationMillis( - BatteryConsumer.TIME_COMPONENT_CPU_FOREGROUND, 700); - uidBatteryConsumerBuilder.setUsageDurationForCustomComponentMillis( - BatteryConsumer.FIRST_CUSTOM_TIME_COMPONENT_ID, 800); - - final SystemBatteryConsumer.Builder systemBatteryConsumerBuilder = - builder.getOrCreateSystemBatteryConsumerBuilder( - SystemBatteryConsumer.DRAIN_TYPE_CAMERA); - systemBatteryConsumerBuilder.setConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, 10100); - systemBatteryConsumerBuilder.setConsumedPowerForCustomComponent( - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 10200); - systemBatteryConsumerBuilder.setUsageDurationMillis( - BatteryConsumer.TIME_COMPONENT_CPU, 10300); - systemBatteryConsumerBuilder.setUsageDurationForCustomComponentMillis( - BatteryConsumer.FIRST_CUSTOM_TIME_COMPONENT_ID, 10400); + final BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(1, 1) + .setDischargePercentage(20) + .setDischargedPowerRange(1000, 2000); + + builder.getOrCreateUidBatteryConsumerBuilder(batteryStatsUid) + .setPackageWithHighestDrain("foo") + .setTimeInStateMs(UidBatteryConsumer.STATE_FOREGROUND, 1000) + .setTimeInStateMs(UidBatteryConsumer.STATE_BACKGROUND, 2000) + .setConsumedPower( + BatteryConsumer.POWER_COMPONENT_USAGE, 300) + .setConsumedPower( + BatteryConsumer.POWER_COMPONENT_CPU, 400) + .setConsumedPowerForCustomComponent( + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 500) + .setUsageDurationMillis( + BatteryConsumer.TIME_COMPONENT_CPU, 600) + .setUsageDurationMillis( + BatteryConsumer.TIME_COMPONENT_CPU_FOREGROUND, 700) + .setUsageDurationForCustomComponentMillis( + BatteryConsumer.FIRST_CUSTOM_TIME_COMPONENT_ID, 800); + + builder.getOrCreateSystemBatteryConsumerBuilder(SystemBatteryConsumer.DRAIN_TYPE_CAMERA) + .setConsumedPower( + BatteryConsumer.POWER_COMPONENT_CPU, 10100) + .setConsumedPowerForCustomComponent( + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 10200) + .setUsageDurationMillis( + BatteryConsumer.TIME_COMPONENT_CPU, 10300) + .setUsageDurationForCustomComponentMillis( + BatteryConsumer.FIRST_CUSTOM_TIME_COMPONENT_ID, 10400); return builder.build(); } @@ -108,6 +112,10 @@ public class BatteryUsageStatsTest { for (UidBatteryConsumer uidBatteryConsumer : uidBatteryConsumers) { if (uidBatteryConsumer.getUid() == 2000) { assertThat(uidBatteryConsumer.getPackageWithHighestDrain()).isEqualTo("foo"); + assertThat(uidBatteryConsumer.getTimeInStateMs( + UidBatteryConsumer.STATE_FOREGROUND)).isEqualTo(1000); + assertThat(uidBatteryConsumer.getTimeInStateMs( + UidBatteryConsumer.STATE_BACKGROUND)).isEqualTo(2000); assertThat(uidBatteryConsumer.getConsumedPower( BatteryConsumer.POWER_COMPONENT_USAGE)).isEqualTo(300); assertThat(uidBatteryConsumer.getConsumedPower( diff --git a/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java index e5594712db10..f6aa08bf0645 100644 --- a/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java @@ -43,6 +43,7 @@ public class BluetoothPowerCalculatorTest { .setAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_TX, 100.0); @Test + @SkipPresubmit("b/180015146") public void testTimerBasedModel() { setDurationsAndPower(mStatsRule.getUidStats(Process.BLUETOOTH_UID) .getOrCreateBluetoothControllerActivityLocked(), @@ -73,6 +74,7 @@ public class BluetoothPowerCalculatorTest { } @Test + @SkipPresubmit("b/180015146") public void testReportedPowerBasedModel() { setDurationsAndPower(mStatsRule.getUidStats(Process.BLUETOOTH_UID) .getOrCreateBluetoothControllerActivityLocked(), diff --git a/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java b/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java index a80f5a03ee4e..4fe7d70e86ff 100644 --- a/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java @@ -382,6 +382,7 @@ public class BstatsCpuTimesValidationTest { } @Test + @SkipPresubmit("b/180015146 flakey") public void testCpuFreqTimes_stateFgService() throws Exception { if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) { Log.w(TAG, "Skipping " + testName.getMethodName() @@ -514,6 +515,7 @@ public class BstatsCpuTimesValidationTest { } @Test + @SkipPresubmit("b/180015146") public void testCpuFreqTimes_trackingDisabled() throws Exception { if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) { Log.w(TAG, "Skipping " + testName.getMethodName() diff --git a/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java index 9cf0d375ff51..e691beb09a70 100644 --- a/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java +++ b/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java @@ -92,6 +92,7 @@ public class CpuPowerCalculatorTest { } @Test + @SkipPresubmit("b/180015146") public void testTimerBasedModel() { when(mMockUserInfoProvider.exists(anyInt())).thenReturn(true); diff --git a/core/tests/coretests/src/com/android/internal/os/CustomMeasuredPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/CustomMeasuredPowerCalculatorTest.java index a4ea8923794a..f298f5988fc3 100644 --- a/core/tests/coretests/src/com/android/internal/os/CustomMeasuredPowerCalculatorTest.java +++ b/core/tests/coretests/src/com/android/internal/os/CustomMeasuredPowerCalculatorTest.java @@ -42,6 +42,7 @@ public class CustomMeasuredPowerCalculatorTest { public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule(); @Test + @SkipPresubmit("b/180015146") public void testMeasuredEnergyCopiedIntoBatteryConsumers() { final BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats(); SparseLongArray uidEnergies = new SparseLongArray(); diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidUserSysTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidUserSysTimeReaderTest.java index 7dca0cb92f9d..177f34875894 100644 --- a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidUserSysTimeReaderTest.java +++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidUserSysTimeReaderTest.java @@ -87,6 +87,7 @@ public class KernelCpuUidUserSysTimeReaderTest { } @Test + @SkipPresubmit("b/180015146") public void testThrottler() throws Exception { mReader = new KernelCpuUidUserSysTimeReader( new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), true); diff --git a/core/tests/coretests/src/com/android/internal/os/SkipPresubmit.java b/core/tests/coretests/src/com/android/internal/os/SkipPresubmit.java new file mode 100644 index 000000000000..d03ed663cc89 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/os/SkipPresubmit.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.os; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** Annotation to skip a test from TEST_MAPPING presubmit. */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +public @interface SkipPresubmit { + /** The optional reason why the test is ignored. */ + String value() default ""; +} diff --git a/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java index dfbf28b286c6..b5282e9a625a 100644 --- a/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java +++ b/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java @@ -78,6 +78,7 @@ public class SystemServicePowerCalculatorTest { } @Test + @SkipPresubmit("b/180015146") public void testPowerProfileBasedModel() { when(mMockUserInfoProvider.exists(anyInt())).thenReturn(true); diff --git a/core/tests/coretests/src/com/android/internal/os/WifiPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/WifiPowerCalculatorTest.java new file mode 100644 index 000000000000..e1005457c289 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/os/WifiPowerCalculatorTest.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.os; + + +import static com.google.common.truth.Truth.assertThat; + +import android.net.NetworkCapabilities; +import android.net.NetworkStats; +import android.os.BatteryConsumer; +import android.os.Process; +import android.os.SystemBatteryConsumer; +import android.os.UidBatteryConsumer; +import android.os.WorkSource; +import android.os.connectivity.WifiActivityEnergyInfo; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class WifiPowerCalculatorTest { + private static final double PRECISION = 0.00001; + + private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42; + + @Rule + public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule() + .setAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_IDLE, 360.0) + .setAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_RX, 480.0) + .setAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_TX, 720.0) + .setAveragePower(PowerProfile.POWER_WIFI_ON, 360.0) + .setAveragePower(PowerProfile.POWER_WIFI_SCAN, 480.0) + .setAveragePower(PowerProfile.POWER_WIFI_BATCHED_SCAN, 720.0) + .setAveragePower(PowerProfile.POWER_WIFI_ACTIVE, 1080.0); + + @Test + public void testPowerControllerBasedModel() { + BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats(); + + batteryStats.noteNetworkInterfaceForTransports("wifi", + new int[]{NetworkCapabilities.TRANSPORT_WIFI}); + + NetworkStats networkStats = new NetworkStats(10000, 1) + .insertEntry("wifi", APP_UID, 0, 0, 1000, 100, 2000, 20, 100) + .insertEntry("wifi", Process.WIFI_UID, 0, 0, 1111, 111, 2222, 22, 111); + mStatsRule.setNetworkStats(networkStats); + + WifiActivityEnergyInfo energyInfo = new WifiActivityEnergyInfo(10000, + WifiActivityEnergyInfo.STACK_STATE_STATE_ACTIVE, 1000, 2000, 3000, 4000); + + batteryStats.updateWifiState(energyInfo, 1000, 1000); + + WifiPowerCalculator calculator = new WifiPowerCalculator(mStatsRule.getPowerProfile()); + mStatsRule.apply(calculator); + + UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID); + assertThat(uidConsumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_WIFI)) + .isEqualTo(1423); + assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI)) + .isWithin(PRECISION).of(0.2214666); + + SystemBatteryConsumer systemConsumer = + mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_WIFI); + assertThat(systemConsumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_WIFI)) + .isEqualTo(5577); + assertThat(systemConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI)) + .isWithin(PRECISION).of(0.645200); + } + + @Test + public void testTimerBasedModel() { + BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats(); + + batteryStats.noteNetworkInterfaceForTransports("wifi", + new int[]{NetworkCapabilities.TRANSPORT_WIFI}); + + NetworkStats networkStats = new NetworkStats(10000, 1) + .insertEntry("wifi", APP_UID, 0, 0, 1000, 100, 2000, 20, 100) + .insertEntry("wifi", Process.WIFI_UID, 0, 0, 1111, 111, 2222, 22, 111); + mStatsRule.setNetworkStats(networkStats); + + batteryStats.noteWifiScanStartedLocked(APP_UID, 1000, 1000); + batteryStats.noteWifiScanStoppedLocked(APP_UID, 2000, 2000); + batteryStats.noteWifiRunningLocked(new WorkSource(APP_UID), 3000, 3000); + batteryStats.noteWifiStoppedLocked(new WorkSource(APP_UID), 4000, 4000); + batteryStats.noteWifiRunningLocked(new WorkSource(Process.WIFI_UID), 1111, 2222); + batteryStats.noteWifiStoppedLocked(new WorkSource(Process.WIFI_UID), 3333, 4444); + + // Don't pass WifiActivityEnergyInfo, making WifiPowerCalculator rely exclusively + // on the packet counts. + batteryStats.updateWifiState(/* energyInfo */ null, 1000, 1000); + + WifiPowerCalculator calculator = new WifiPowerCalculator(mStatsRule.getPowerProfile()); + mStatsRule.apply(calculator); + + UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID); + assertThat(uidConsumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_WIFI)) + .isEqualTo(1000); + assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI)) + .isWithin(PRECISION).of(0.8231573); + + SystemBatteryConsumer systemConsumer = + mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_WIFI); + assertThat(systemConsumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_WIFI)) + .isEqualTo(2222); + assertThat(systemConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI)) + .isWithin(PRECISION).of(0.8759216); + } +} diff --git a/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java b/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java index 5fd5a7838c3a..d217bce24b9c 100644 --- a/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java +++ b/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java @@ -81,11 +81,11 @@ public class MeasuredEnergyStatsTest { final MeasuredEnergyStats stats = new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10, true); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5, true); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true); - stats.updateCustomBucket(0, 50, true); - stats.updateCustomBucket(1, 60, true); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40); + stats.updateCustomBucket(0, 50); + stats.updateCustomBucket(1, 60); final MeasuredEnergyStats newStats = MeasuredEnergyStats.createFromTemplate(stats); @@ -114,11 +114,11 @@ public class MeasuredEnergyStatsTest { final MeasuredEnergyStats stats = new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10, true); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5, true); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true); - stats.updateCustomBucket(0, 50, true); - stats.updateCustomBucket(1, 60, true); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40); + stats.updateCustomBucket(0, 50); + stats.updateCustomBucket(1, 60); final Parcel parcel = Parcel.obtain(); stats.writeToParcel(parcel); @@ -149,11 +149,11 @@ public class MeasuredEnergyStatsTest { final MeasuredEnergyStats stats = new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10, true); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5, true); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true); - stats.updateCustomBucket(0, 50, true); - stats.updateCustomBucket(1, 60, true); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40); + stats.updateCustomBucket(0, 50); + stats.updateCustomBucket(1, 60); final Parcel parcel = Parcel.obtain(); MeasuredEnergyStats.writeSummaryToParcel(stats, parcel, false); @@ -185,17 +185,17 @@ public class MeasuredEnergyStatsTest { final MeasuredEnergyStats template = new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets); - template.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10, true); - template.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5, true); - template.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true); - template.updateCustomBucket(0, 50, true); + template.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10); + template.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5); + template.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40); + template.updateCustomBucket(0, 50); final MeasuredEnergyStats stats = MeasuredEnergyStats.createFromTemplate(template); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 200, true); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 7, true); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 63, true); - stats.updateCustomBucket(0, 315, true); - stats.updateCustomBucket(1, 316, true); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 200); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 7); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 63); + stats.updateCustomBucket(0, 315); + stats.updateCustomBucket(1, 316); final Parcel parcel = Parcel.obtain(); MeasuredEnergyStats.writeSummaryToParcel(stats, parcel, false); @@ -243,8 +243,8 @@ public class MeasuredEnergyStatsTest { final MeasuredEnergyStats stats = new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets); // Accumulate energy in one bucket and one custom bucket, the rest should be zero - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 200, true); - stats.updateCustomBucket(1, 60, true); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 200); + stats.updateCustomBucket(1, 60); // Let's try parcelling with including zeros final Parcel includeZerosParcel = Parcel.obtain(); @@ -305,11 +305,11 @@ public class MeasuredEnergyStatsTest { final MeasuredEnergyStats stats = new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10, true); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5, true); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true); - stats.updateCustomBucket(0, 50, true); - stats.updateCustomBucket(1, 60, true); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40); + stats.updateCustomBucket(0, 50); + stats.updateCustomBucket(1, 60); final Parcel parcel = Parcel.obtain(); MeasuredEnergyStats.writeSummaryToParcel(stats, parcel, false); @@ -331,14 +331,14 @@ public class MeasuredEnergyStatsTest { final MeasuredEnergyStats template = new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets); - template.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10, true); - template.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5, true); - template.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true); - template.updateCustomBucket(0, 50, true); + template.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10); + template.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5); + template.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40); + template.updateCustomBucket(0, 50); final MeasuredEnergyStats stats = MeasuredEnergyStats.createFromTemplate(template); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 0L, true); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 7L, true); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 0L); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 7L); final Parcel parcel = Parcel.obtain(); MeasuredEnergyStats.writeSummaryToParcel(stats, parcel, false); @@ -369,14 +369,14 @@ public class MeasuredEnergyStatsTest { final MeasuredEnergyStats stats = new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10, true); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_DOZE, 30, true); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5, true); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_DOZE, 30); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5); - stats.updateCustomBucket(0, 50, true); - stats.updateCustomBucket(1, 60, true); - stats.updateCustomBucket(0, 3, true); + stats.updateCustomBucket(0, 50); + stats.updateCustomBucket(1, 60); + stats.updateCustomBucket(0, 3); assertEquals(15, stats.getAccumulatedStandardBucketEnergy(ENERGY_BUCKET_SCREEN_ON)); assertEquals(ENERGY_DATA_UNAVAILABLE, @@ -409,10 +409,10 @@ public class MeasuredEnergyStatsTest { final MeasuredEnergyStats stats = new MeasuredEnergyStats(new boolean[NUMBER_STANDARD_ENERGY_BUCKETS], 3); - stats.updateCustomBucket(0, 50, true); - stats.updateCustomBucket(1, 60, true); - stats.updateCustomBucket(2, 13, true); - stats.updateCustomBucket(1, 70, true); + stats.updateCustomBucket(0, 50); + stats.updateCustomBucket(1, 60); + stats.updateCustomBucket(2, 13); + stats.updateCustomBucket(1, 70); final long[] output = stats.getAccumulatedCustomBucketEnergies(); assertEquals(3, output.length); @@ -449,11 +449,11 @@ public class MeasuredEnergyStatsTest { final MeasuredEnergyStats stats = new MeasuredEnergyStats(supportedStandardBuckets, numCustomBuckets); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10, true); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5, true); - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40, true); - stats.updateCustomBucket(0, 50, true); - stats.updateCustomBucket(1, 60, true); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 10); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 5); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_OTHER, 40); + stats.updateCustomBucket(0, 50); + stats.updateCustomBucket(1, 60); MeasuredEnergyStats.resetIfNotNull(stats); // All energy should be reset to 0 @@ -471,10 +471,10 @@ public class MeasuredEnergyStatsTest { } // Values should increase as usual. - stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 70, true); + stats.updateStandardBucket(ENERGY_BUCKET_SCREEN_ON, 70); assertEquals(70L, stats.getAccumulatedStandardBucketEnergy(ENERGY_BUCKET_SCREEN_ON)); - stats.updateCustomBucket(1, 12, true); + stats.updateCustomBucket(1, 12); assertEquals(12L, stats.getAccumulatedCustomBucketEnergy(1)); } diff --git a/core/tests/coretests/src/com/android/internal/view/OWNERS b/core/tests/coretests/src/com/android/internal/view/OWNERS new file mode 100644 index 000000000000..1dad10de5ac7 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/view/OWNERS @@ -0,0 +1,3 @@ +# Scroll Capture +per-file *ScrollCapture*.java = file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS +per-file *CaptureHelper*.java = file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 8fd5d804adc0..3900d7e674ca 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -406,8 +406,6 @@ applications that come with the platform <permission name="android.permission.SET_WALLPAPER" /> <permission name="android.permission.SET_WALLPAPER_COMPONENT" /> <permission name="android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE" /> - <!-- Permissions required for Incremental CTS tests --> - <permission name="com.android.permission.USE_INSTALLER_V2"/> <permission name="android.permission.LOADER_USAGE_STATS"/> <!-- Permission required to test system only camera devices. --> <permission name="android.permission.SYSTEM_CAMERA" /> diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java index f1f9a5fc92ea..e22257071dd2 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -153,6 +153,13 @@ public class Paint { * resource bitmaps often are) the filtering will already have been * done.</p> * + * <p>On devices running {@link Build.VERSION_CODES#O} and below, hardware + * accelerated drawing always uses bilinear sampling on scaled bitmaps, + * regardless of this flag. On devices running {@link Build.VERSION_CODES#Q} + * and above, this flag defaults to being set on a new {@code Paint}. It can + * be cleared with {@link #setFlags} or {@link #setFilterBitmap}.</p> + * + * @see #Paint() * @see #Paint(int) * @see #setFlags(int) */ @@ -558,6 +565,12 @@ public class Paint { /** * Create a new paint with default settings. + * + * <p>On devices running {@link Build.VERSION_CODES#O} and below, hardware + * accelerated drawing always acts as if {@link #FILTER_BITMAP_FLAG} is set. + * On devices running {@link Build.VERSION_CODES#Q} and above, + * {@code FILTER_BITMAP_FLAG} is set by this constructor, and it can be + * cleared with {@link #setFlags} or {@link #setFilterBitmap}.</p> */ public Paint() { this(0); @@ -567,6 +580,13 @@ public class Paint { * Create a new paint with the specified flags. Use setFlags() to change * these after the paint is created. * + * <p>On devices running {@link Build.VERSION_CODES#O} and below, hardware + * accelerated drawing always acts as if {@link #FILTER_BITMAP_FLAG} is set. + * On devices running {@link Build.VERSION_CODES#Q} and above, + * {@code FILTER_BITMAP_FLAG} is always set by this constructor, regardless + * of the value of {@code flags}. It can be cleared with {@link #setFlags} or + * {@link #setFilterBitmap}.</p> + * * @param flags initial flag bits, as if they were passed via setFlags(). */ public Paint(int flags) { @@ -991,6 +1011,7 @@ public class Paint { * device pixels. That is dependent on dithering and xfermodes. * * @see #setFilterBitmap(boolean) setFilterBitmap() + * @see #FILTER_BITMAP_FLAG */ public final boolean isFilterBitmap() { return (getFlags() & FILTER_BITMAP_FLAG) != 0; @@ -1004,6 +1025,7 @@ public class Paint { * * @param filter true to set the FILTER_BITMAP_FLAG bit in the paint's * flags, false to clear it. + * @see #FILTER_BITMAP_FLAG */ public void setFilterBitmap(boolean filter) { nSetFilterBitmap(mNativePaint, filter); diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index 32c777cf498c..c80788269c24 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -172,9 +172,9 @@ public class Typeface { * @hide */ @UnsupportedAppUsage - public long native_instance; + public final long native_instance; - private Runnable mCleaner; + private final Runnable mCleaner; /** @hide */ @IntDef(value = {NORMAL, BOLD, ITALIC, BOLD_ITALIC}) @@ -189,9 +189,9 @@ public class Typeface { /** @hide */ public static final int STYLE_MASK = 0x03; @UnsupportedAppUsage - private @Style int mStyle = 0; + private @Style final int mStyle; - private @IntRange(from = 0, to = FontStyle.FONT_WEIGHT_MAX) int mWeight = 0; + private @IntRange(from = 0, to = FontStyle.FONT_WEIGHT_MAX) final int mWeight; // Value for weight and italic. Indicates the value is resolved by font metadata. // Must be the same as the C++ constant in core/jni/android/graphics/FontFamily.cpp @@ -207,6 +207,7 @@ public class Typeface { private static final int STYLE_NORMAL = 0; private static final int STYLE_ITALIC = 1; + @GuardedBy("this") private int[] mSupportedAxes; private static final int[] EMPTY_AXES = {}; diff --git a/keystore/java/android/security/IKeyChainService.aidl b/keystore/java/android/security/IKeyChainService.aidl index d00f5f669594..684eebe6ffde 100644 --- a/keystore/java/android/security/IKeyChainService.aidl +++ b/keystore/java/android/security/IKeyChainService.aidl @@ -63,6 +63,7 @@ interface IKeyChainService { AppUriAuthenticationPolicy getCredentialManagementAppPolicy(); String getPredefinedAliasForPackageAndUri(String packageName, in Uri uri); void removeCredentialManagementApp(); + boolean isCredentialManagementApp(String packageName); // APIs used by KeyChainActivity void setGrant(int uid, String alias, boolean value); diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java index f0bcfe52686d..65a81cd57f41 100644 --- a/keystore/java/android/security/KeyChain.java +++ b/keystore/java/android/security/KeyChain.java @@ -423,6 +423,15 @@ public final class KeyChain { * credentials. This is limited to unmanaged devices. The authentication policy must be * provided to be able to make this request successfully. * + * <p> This intent should be started using {@link Activity#startActivityForResult(Intent, int)} + * to verify whether the request was successful and whether the user accepted or denied the + * request. If the user successfully receives and accepts the request, the result code will be + * {@link Activity#RESULT_OK}, otherwise the result code will be + * {@link Activity#RESULT_CANCELED}. + * + * <p> {@link KeyChain#isCredentialManagementApp(Context)} should be used to determine whether + * an app is already the credential management app. + * * @param policy The authentication policy determines which alias for a private key and * certificate pair should be used for authentication. */ @@ -591,6 +600,55 @@ public final class KeyChain { } /** + * Check whether the caller is the credential management app {@link CredentialManagementApp}. + * The credential management app has the ability to manage the user's KeyChain credentials + * on unmanaged devices. + * + * <p> {@link KeyChain#createManageCredentialsIntent} should be used by an app to request to + * become the credential management app. The user must approve this request before the app can + * manage the user's credentials. There can only be one credential management on the device. + * + * @return {@code true} if the caller is the credential management app. + */ + public static boolean isCredentialManagementApp(@NonNull Context context) { + boolean isCredentialManagementApp = false; + try (KeyChainConnection keyChainConnection = KeyChain.bind(context)) { + isCredentialManagementApp = keyChainConnection.getService() + .isCredentialManagementApp(context.getPackageName()); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } catch (InterruptedException e) { + throw new RuntimeException("Interrupted while checking whether the caller is the " + + "credential management app.", e); + } catch (SecurityException e) { + isCredentialManagementApp = false; + } + return isCredentialManagementApp; + } + + /** + * Called by the credential management app to get the authentication policy + * {@link AppUriAuthenticationPolicy}. + * + * @return the credential management app's authentication policy. + * @throws SecurityException if the caller is not the credential management app. + */ + @NonNull + public static AppUriAuthenticationPolicy getCredentialManagementAppPolicy( + @NonNull Context context) throws SecurityException { + AppUriAuthenticationPolicy policy = null; + try (KeyChainConnection keyChainConnection = KeyChain.bind(context)) { + policy = keyChainConnection.getService().getCredentialManagementAppPolicy(); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } catch (InterruptedException e) { + throw new RuntimeException( + "Interrupted while getting credential management app policy.", e); + } + return policy; + } + + /** * Set a credential management app. The credential management app has the ability to manage * the user's KeyChain credentials on unmanaged devices. * diff --git a/keystore/java/android/security/KeyStore2.java b/keystore/java/android/security/KeyStore2.java index 476e4d7b7b18..6ac3821d0f9c 100644 --- a/keystore/java/android/security/KeyStore2.java +++ b/keystore/java/android/security/KeyStore2.java @@ -24,6 +24,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceSpecificException; import android.security.keymaster.KeymasterDefs; +import android.system.keystore2.Domain; import android.system.keystore2.IKeystoreService; import android.system.keystore2.KeyDescriptor; import android.system.keystore2.KeyEntryResponse; @@ -157,6 +158,50 @@ public class KeyStore2 { } /** + * Grant string prefix as used by the keystore boringssl engine. Must be kept in sync + * with system/security/keystore-engine. Note: The prefix here includes the 0x which + * std::stringstream used in keystore-engine needs to identify the number as hex represented. + * Here we include it in the prefix, because Long#parseUnsignedLong does not understand it + * and gets the radix as explicit argument. + * @hide + */ + private static final String KEYSTORE_ENGINE_GRANT_ALIAS_PREFIX = + "ks2_keystore-engine_grant_id:0x"; + + /** + * This function turns a grant identifier into a specific string that is understood by the + * keystore-engine in system/security/keystore-engine. Is only used by VPN and WI-FI components + * to allow certain system components like racoon or vendor components like WPA supplicant + * to use keystore keys with boring ssl. + * + * @param grantId the grant id as returned by {@link #grant} in the {@code nspace} filed of + * the resulting {@code KeyDescriptor}. + * @return The grant descriptor string. + * @hide + */ + public static String makeKeystoreEngineGrantString(long grantId) { + return String.format("%s%016X", KEYSTORE_ENGINE_GRANT_ALIAS_PREFIX, grantId); + } + + /** + * Convenience function to turn a keystore engine grant string as returned by + * {@link #makeKeystoreEngineGrantString(long)} back into a grant KeyDescriptor. + * + * @param grantString As string returned by {@link #makeKeystoreEngineGrantString(long)} + * @return The grant key descriptor. + * @hide + */ + public static KeyDescriptor keystoreEngineGrantString2KeyDescriptor(String grantString) { + KeyDescriptor key = new KeyDescriptor(); + key.domain = Domain.GRANT; + key.nspace = Long.parseUnsignedLong( + grantString.substring(KEYSTORE_ENGINE_GRANT_ALIAS_PREFIX.length()), 16); + key.alias = null; + key.blob = null; + return key; + } + + /** * Create a grant that allows the grantee identified by {@code granteeUid} to use * the key specified by {@code descriptor} withint the restrictions given by * {@code accessVectore}. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java index 59271e9fb63c..e6e6d4a1934f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/WindowManagerShellWrapper.java @@ -22,7 +22,7 @@ import android.os.RemoteException; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.pip.PinnedStackListenerForwarder; -import com.android.wm.shell.pip.PinnedStackListenerForwarder.PinnedStackListener; +import com.android.wm.shell.pip.PinnedStackListenerForwarder.PinnedTaskListener; /** * The singleton wrapper to communicate between WindowManagerService and WMShell features @@ -46,7 +46,7 @@ public class WindowManagerShellWrapper { * Adds a pinned stack listener, which will receive updates from the window manager service * along with any other pinned stack listeners that were added via this method. */ - public void addPinnedStackListener(PinnedStackListener listener) + public void addPinnedStackListener(PinnedTaskListener listener) throws RemoteException { mPinnedStackListenerForwarder.addListener(listener); mPinnedStackListenerForwarder.register(DEFAULT_DISPLAY); @@ -55,7 +55,7 @@ public class WindowManagerShellWrapper { /** * Removes a pinned stack listener. */ - public void removePinnedStackListener(PinnedStackListener listener) { + public void removePinnedStackListener(PinnedTaskListener listener) { mPinnedStackListenerForwarder.removeListener(listener); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 047df5ba7ca9..1320780bfb8f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -1213,7 +1213,7 @@ public class BubbleController { /** PinnedStackListener that dispatches IME visibility updates to the stack. */ //TODO(b/170442945): Better way to do this / insets listener? - private class BubblesImeListener extends PinnedStackListenerForwarder.PinnedStackListener { + private class BubblesImeListener extends PinnedStackListenerForwarder.PinnedTaskListener { @Override public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) { if (mStackView != null) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java index 19c3cf9c462a..7d5c9f07f86c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java @@ -227,24 +227,29 @@ public class BubbleFlyoutView extends FrameLayout { /* * Fade animation for consecutive flyouts. */ - void animateUpdate(Bubble.FlyoutMessage flyoutMessage, float parentWidth, float stackY) { + void animateUpdate(Bubble.FlyoutMessage flyoutMessage, float parentWidth, PointF stackPos) { final Runnable afterFadeOut = () -> { updateFlyoutMessage(flyoutMessage, parentWidth); // Wait for TextViews to layout with updated height. post(() -> { - mFlyoutY = stackY + (mBubbleSize - mFlyoutTextContainer.getHeight()) / 2f; - fade(true /* in */, () -> {} /* after */); + fade(true /* in */, stackPos, () -> {} /* after */); } /* after */ ); }; - fade(false /* in */, afterFadeOut); + fade(false /* in */, stackPos, afterFadeOut); } /* * Fade-out above or fade-in from below. */ - private void fade(boolean in, Runnable afterFade) { + private void fade(boolean in, PointF stackPos, Runnable afterFade) { + mFlyoutY = stackPos.y + (mBubbleSize - mFlyoutTextContainer.getHeight()) / 2f; + setAlpha(in ? 0f : 1f); setTranslationY(in ? mFlyoutY + FLYOUT_FADE_Y : mFlyoutY); + mRestingTranslationX = mArrowPointingLeft + ? stackPos.x + mBubbleSize + mFlyoutSpaceFromBubble + : stackPos.x - getWidth() - mFlyoutSpaceFromBubble; + setTranslationX(mRestingTranslationX); animate() .alpha(in ? 1f : 0f) .setDuration(in ? FLYOUT_FADE_IN_DURATION : FLYOUT_FADE_OUT_DURATION) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index a8ab4064455c..e99669f5b5e0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -2431,7 +2431,7 @@ public class BubbleStackView extends FrameLayout if (mFlyout.getVisibility() == View.VISIBLE) { mFlyout.animateUpdate(bubble.getFlyoutMessage(), getWidth(), - mStackAnimationController.getStackPosition().y); + mStackAnimationController.getStackPosition()); } else { mFlyout.setVisibility(INVISIBLE); mFlyout.setupFlyoutStartingAsDot(bubble.getFlyoutMessage(), diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java index 4564af438f32..f118b1e0b7a3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java @@ -50,6 +50,7 @@ import com.android.internal.R; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Objects; /** * Contains information about the layout-properties of a display. This refers to internal layout @@ -82,6 +83,31 @@ public class DisplayLayout { private boolean mHasStatusBar = false; private int mNavBarFrameHeight = 0; + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof DisplayLayout)) return false; + final DisplayLayout other = (DisplayLayout) o; + return mUiMode == other.mUiMode + && mWidth == other.mWidth + && mHeight == other.mHeight + && Objects.equals(mCutout, other.mCutout) + && mRotation == other.mRotation + && mDensityDpi == other.mDensityDpi + && Objects.equals(mNonDecorInsets, other.mNonDecorInsets) + && Objects.equals(mStableInsets, other.mStableInsets) + && mHasNavigationBar == other.mHasNavigationBar + && mHasStatusBar == other.mHasStatusBar + && mNavBarFrameHeight == other.mNavBarFrameHeight; + } + + @Override + public int hashCode() { + return Objects.hash(mUiMode, mWidth, mHeight, mCutout, mRotation, mDensityDpi, + mNonDecorInsets, mStableInsets, mHasNavigationBar, mHasStatusBar, + mNavBarFrameHeight); + } + /** * Create empty layout. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java index fb70cbe502b0..4bb8e9b6581f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java @@ -39,12 +39,12 @@ import android.view.IWindowSession; import android.view.IWindowSessionCallback; import android.view.InsetsSourceControl; import android.view.InsetsState; +import android.view.ScrollCaptureResponse; import android.view.SurfaceControl; -import android.view.SurfaceSession; import android.view.SurfaceControlViewHost; +import android.view.SurfaceSession; import android.view.View; import android.view.ViewGroup; -import android.view.ViewRootImpl; import android.view.WindowManager; import android.view.WindowlessWindowManager; import android.window.ClientWindowFrames; @@ -371,7 +371,11 @@ public class SystemWindows { @Override public void requestScrollCapture(IScrollCaptureCallbacks callbacks) { try { - callbacks.onUnavailable(); + callbacks.onScrollCaptureResponse( + new ScrollCaptureResponse.Builder() + .setDescription("Not Implemented") + .build()); + } catch (RemoteException ex) { // ignore } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java index e94080aa8db7..3b670057cb1a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java @@ -227,7 +227,7 @@ public class TaskStackListenerImpl extends TaskStackListener implements Handler. } @Override - public void onActivityDismissingDockedStack() { + public void onActivityDismissingDockedTask() { mMainHandler.sendEmptyMessage(ON_ACTIVITY_DISMISSING_DOCKED_STACK); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java index c8f89876222e..82468ad999b4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java @@ -105,7 +105,7 @@ class WindowManagerProxy { synchronized (mDockedRect) { mTouchableRegion.set(region); } - WindowManagerGlobal.getWindowManagerService().setDockedStackDividerTouchRegion( + WindowManagerGlobal.getWindowManagerService().setDockedTaskDividerTouchRegion( mTouchableRegion); } catch (RemoteException e) { Log.w(TAG, "Failed to set touchable region: " + e); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java index 8f8ec475a85c..b3b1ba7cd1c1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java @@ -20,7 +20,7 @@ import android.app.RemoteAction; import android.content.ComponentName; import android.content.pm.ParceledListSlice; import android.os.RemoteException; -import android.view.IPinnedStackListener; +import android.view.IPinnedTaskListener; import android.view.WindowManagerGlobal; import androidx.annotation.BinderThread; @@ -32,66 +32,66 @@ import java.util.ArrayList; /** * PinnedStackListener that simply forwards all calls to each listener added via * {@link #addListener}. This is necessary since calling - * {@link com.android.server.wm.WindowManagerService#registerPinnedStackListener} replaces any + * {@link com.android.server.wm.WindowManagerService#registerPinnedTaskListener} replaces any * previously set listener. */ public class PinnedStackListenerForwarder { - private final IPinnedStackListener mListenerImpl = new PinnedStackListenerImpl(); + private final IPinnedTaskListener mListenerImpl = new PinnedTaskListenerImpl(); private final ShellExecutor mMainExecutor; - private final ArrayList<PinnedStackListener> mListeners = new ArrayList<>(); + private final ArrayList<PinnedTaskListener> mListeners = new ArrayList<>(); public PinnedStackListenerForwarder(ShellExecutor mainExecutor) { mMainExecutor = mainExecutor; } /** Adds a listener to receive updates from the WindowManagerService. */ - public void addListener(PinnedStackListener listener) { + public void addListener(PinnedTaskListener listener) { mListeners.add(listener); } /** Removes a listener so it will no longer receive updates from the WindowManagerService. */ - public void removeListener(PinnedStackListener listener) { + public void removeListener(PinnedTaskListener listener) { mListeners.remove(listener); } public void register(int displayId) throws RemoteException { - WindowManagerGlobal.getWindowManagerService().registerPinnedStackListener( + WindowManagerGlobal.getWindowManagerService().registerPinnedTaskListener( displayId, mListenerImpl); } private void onMovementBoundsChanged(boolean fromImeAdjustment) { - for (PinnedStackListener listener : mListeners) { + for (PinnedTaskListener listener : mListeners) { listener.onMovementBoundsChanged(fromImeAdjustment); } } private void onImeVisibilityChanged(boolean imeVisible, int imeHeight) { - for (PinnedStackListener listener : mListeners) { + for (PinnedTaskListener listener : mListeners) { listener.onImeVisibilityChanged(imeVisible, imeHeight); } } private void onActionsChanged(ParceledListSlice<RemoteAction> actions) { - for (PinnedStackListener listener : mListeners) { + for (PinnedTaskListener listener : mListeners) { listener.onActionsChanged(actions); } } private void onActivityHidden(ComponentName componentName) { - for (PinnedStackListener listener : mListeners) { + for (PinnedTaskListener listener : mListeners) { listener.onActivityHidden(componentName); } } private void onAspectRatioChanged(float aspectRatio) { - for (PinnedStackListener listener : mListeners) { + for (PinnedTaskListener listener : mListeners) { listener.onAspectRatioChanged(aspectRatio); } } @BinderThread - private class PinnedStackListenerImpl extends IPinnedStackListener.Stub { + private class PinnedTaskListenerImpl extends IPinnedTaskListener.Stub { @Override public void onMovementBoundsChanged(boolean fromImeAdjustment) { mMainExecutor.execute(() -> { @@ -129,10 +129,10 @@ public class PinnedStackListenerForwarder { } /** - * A counterpart of {@link IPinnedStackListener} with empty implementations. + * A counterpart of {@link IPinnedTaskListener} with empty implementations. * Subclasses can ignore those methods they do not intend to take action upon. */ - public static class PinnedStackListener { + public static class PinnedTaskListener { public void onMovementBoundsChanged(boolean fromImeAdjustment) {} public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java index 66560d339c5a..a52db24aa184 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java @@ -90,15 +90,15 @@ public class PipAnimationController { private final PipSurfaceTransactionHelper mSurfaceTransactionHelper; - private PipTransitionAnimator mCurrentAnimator; - - private ThreadLocal<AnimationHandler> mSfAnimationHandlerThreadLocal = + private final ThreadLocal<AnimationHandler> mSfAnimationHandlerThreadLocal = ThreadLocal.withInitial(() -> { AnimationHandler handler = new AnimationHandler(); handler.setProvider(new SfVsyncFrameCallbackProvider()); return handler; }); + private PipTransitionAnimator mCurrentAnimator; + public PipAnimationController(PipSurfaceTransactionHelper helper) { mSurfaceTransactionHelper = helper; } @@ -268,6 +268,7 @@ public class PipAnimationController { if (mPipAnimationCallback != null) { mPipAnimationCallback.onPipAnimationEnd(mTaskInfo, tx, this); } + mTransitionDirection = TRANSITION_DIRECTION_NONE; } @Override @@ -275,6 +276,7 @@ public class PipAnimationController { if (mPipAnimationCallback != null) { mPipAnimationCallback.onPipAnimationCancel(mTaskInfo, this); } + mTransitionDirection = TRANSITION_DIRECTION_NONE; } @Override public void onAnimationRepeat(Animator animation) {} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index 4a2a032d8d1c..9a584c67f97c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -65,6 +65,7 @@ import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip.PipUtils; import java.io.PrintWriter; +import java.util.Objects; import java.util.function.Consumer; /** @@ -93,17 +94,20 @@ public class PipController implements PipTransitionController.PipTransitionCallb protected PhonePipMenuController mMenuController; protected PipTaskOrganizer mPipTaskOrganizer; - protected PinnedStackListenerForwarder.PinnedStackListener mPinnedStackListener = - new PipControllerPinnedStackListener(); + protected PinnedStackListenerForwarder.PinnedTaskListener mPinnedTaskListener = + new PipControllerPinnedTaskListener(); /** * Handler for display rotation changes. */ private final DisplayChangeController.OnDisplayChangingListener mRotationController = ( int displayId, int fromRotation, int toRotation, WindowContainerTransaction t) -> { - if (!mPipTaskOrganizer.isInPip() || mPipTaskOrganizer.isDeferringEnterPipAnimation()) { - // Skip if we aren't in PIP or haven't actually entered PIP yet. We still need to update - // the display layout in the bounds handler in this case. + if (!mPipTaskOrganizer.isInPip() + || mPipBoundsState.getDisplayLayout().rotation() == toRotation + || mPipTaskOrganizer.isDeferringEnterPipAnimation()) { + // Skip if the same rotation has been set or we aren't in PIP or haven't actually + // entered PIP yet. We still need to update the display layout in the bounds handler + // in this case. onDisplayRotationChangedNotInPip(mContext, toRotation); // do not forget to update the movement bounds as well. updateMovementBounds(mPipBoundsState.getNormalBounds(), true /* fromRotation */, @@ -178,8 +182,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb /** * Handler for messages from the PIP controller. */ - private class PipControllerPinnedStackListener extends - PinnedStackListenerForwarder.PinnedStackListener { + private class PipControllerPinnedTaskListener extends + PinnedStackListenerForwarder.PinnedTaskListener { @Override public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) { mPipBoundsState.setImeVisibility(imeVisible, imeHeight); @@ -310,7 +314,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb mPipBoundsState.setDisplayLayout(new DisplayLayout(context, context.getDisplay())); try { - mWindowManagerShellWrapper.addPinnedStackListener(mPinnedStackListener); + mWindowManagerShellWrapper.addPinnedStackListener(mPinnedTaskListener); } catch (RemoteException e) { Slog.e(TAG, "Failed to register pinned stack listener", e); } @@ -378,6 +382,9 @@ public class PipController implements PipTransitionController.PipTransitionCallb } private void onDisplayChanged(DisplayLayout layout, boolean saveRestoreSnapFraction) { + if (Objects.equals(layout, mPipBoundsState.getDisplayLayout())) { + return; + } Runnable updateDisplayLayout = () -> { mPipBoundsState.setDisplayLayout(layout); updateMovementBounds(null /* toBounds */, @@ -476,8 +483,11 @@ public class PipController implements PipTransitionController.PipTransitionCallb int launcherRotation, int shelfHeight) { setShelfHeightLocked(shelfHeight > 0 /* visible */, shelfHeight); onDisplayRotationChangedNotInPip(mContext, launcherRotation); - return mPipTaskOrganizer.startSwipePipToHome(componentName, activityInfo, + final Rect entryBounds = mPipTaskOrganizer.startSwipePipToHome(componentName, activityInfo, pictureInPictureParams); + // sync mPipBoundsState with the newly calculated bounds. + mPipBoundsState.setNormalBounds(entryBounds); + return entryBounds; } private void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java index 56f183fd7303..70980191f103 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java @@ -377,7 +377,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal private void registerWmShellPinnedStackListener(WindowManagerShellWrapper wmShell) { try { - wmShell.addPinnedStackListener(new PinnedStackListenerForwarder.PinnedStackListener() { + wmShell.addPinnedStackListener(new PinnedStackListenerForwarder.PinnedTaskListener() { @Override public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) { if (DEBUG) { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt index 10aea519f18b..35bab7aaf22c 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt @@ -18,8 +18,6 @@ package com.android.wm.shell.flicker import android.graphics.Region import android.view.Surface -import com.android.server.wm.flicker.APP_PAIR_SPLIT_DIVIDER -import com.android.server.wm.flicker.DOCKED_STACK_DIVIDER import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.helpers.WindowUtils import com.android.server.wm.flicker.traces.layers.getVisibleBounds @@ -32,15 +30,15 @@ fun FlickerTestParameter.appPairsDividerIsVisible() { fun FlickerTestParameter.appPairsDividerIsInvisible() { assertLayersEnd { - this.notExists(APP_PAIR_SPLIT_DIVIDER) + this.notContains(APP_PAIR_SPLIT_DIVIDER) } } fun FlickerTestParameter.appPairsDividerBecomesVisible() { assertLayers { - this.hidesLayer(DOCKED_STACK_DIVIDER) + this.isInvisible(DOCKED_STACK_DIVIDER) .then() - .showsLayer(DOCKED_STACK_DIVIDER) + .isVisible(DOCKED_STACK_DIVIDER) } } @@ -52,30 +50,30 @@ fun FlickerTestParameter.dockedStackDividerIsVisible() { fun FlickerTestParameter.dockedStackDividerBecomesVisible() { assertLayers { - this.hidesLayer(DOCKED_STACK_DIVIDER) + this.isInvisible(DOCKED_STACK_DIVIDER) .then() - .showsLayer(DOCKED_STACK_DIVIDER) + .isVisible(DOCKED_STACK_DIVIDER) } } fun FlickerTestParameter.dockedStackDividerBecomesInvisible() { assertLayers { - this.showsLayer(DOCKED_STACK_DIVIDER) + this.isVisible(DOCKED_STACK_DIVIDER) .then() - .hidesLayer(DOCKED_STACK_DIVIDER) + .isInvisible(DOCKED_STACK_DIVIDER) } } fun FlickerTestParameter.dockedStackDividerIsInvisible() { assertLayersEnd { - this.notExists(DOCKED_STACK_DIVIDER) + this.notContains(DOCKED_STACK_DIVIDER) } } fun FlickerTestParameter.appPairsPrimaryBoundsIsVisible(rotation: Int, primaryLayerName: String) { assertLayersEnd { val dividerRegion = entry.getVisibleBounds(APP_PAIR_SPLIT_DIVIDER) - this.hasVisibleRegion(primaryLayerName, getPrimaryRegion(dividerRegion, rotation)) + this.coversExactly(getPrimaryRegion(dividerRegion, rotation), primaryLayerName) } } @@ -85,7 +83,7 @@ fun FlickerTestParameter.dockedStackPrimaryBoundsIsVisible( ) { assertLayersEnd { val dividerRegion = entry.getVisibleBounds(DOCKED_STACK_DIVIDER) - this.hasVisibleRegion(primaryLayerName, getPrimaryRegion(dividerRegion, rotation)) + this.coversExactly(getPrimaryRegion(dividerRegion, rotation), primaryLayerName) } } @@ -95,7 +93,7 @@ fun FlickerTestParameter.appPairsSecondaryBoundsIsVisible( ) { assertLayersEnd { val dividerRegion = entry.getVisibleBounds(APP_PAIR_SPLIT_DIVIDER) - this.hasVisibleRegion(secondaryLayerName, getSecondaryRegion(dividerRegion, rotation)) + this.coversExactly(getSecondaryRegion(dividerRegion, rotation), secondaryLayerName) } } @@ -105,7 +103,7 @@ fun FlickerTestParameter.dockedStackSecondaryBoundsIsVisible( ) { assertLayersEnd { val dividerRegion = entry.getVisibleBounds(DOCKED_STACK_DIVIDER) - this.hasVisibleRegion(secondaryLayerName, getSecondaryRegion(dividerRegion, rotation)) + this.coversExactly(getSecondaryRegion(dividerRegion, rotation), secondaryLayerName) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt index d2cfb0fbb5f6..03b93c74233c 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt @@ -18,3 +18,5 @@ package com.android.wm.shell.flicker const val IME_WINDOW_NAME = "InputMethod" const val SYSTEM_UI_PACKAGE_NAME = "com.android.systemui" +const val APP_PAIR_SPLIT_DIVIDER = "AppPairSplitDivider" +const val DOCKED_STACK_DIVIDER = "DockedStackDivider"
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt index 5d51b2fd515f..90e71373b1fd 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.apppairs -import android.os.Bundle import android.os.SystemClock import android.platform.test.annotations.Presubmit import androidx.test.filters.RequiresDevice @@ -48,7 +47,7 @@ class AppPairsTestCannotPairNonResizeableApps( testSpec: FlickerTestParameter ) : AppPairsTransition(testSpec) { - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { super.transition(this, it) transitions { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt index 77890ba8ed15..dc51b4fb5a9e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt @@ -16,17 +16,16 @@ package com.android.wm.shell.flicker.apppairs -import android.os.Bundle import android.os.SystemClock import android.platform.test.annotations.Presubmit import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.APP_PAIR_SPLIT_DIVIDER import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.traces.layers.getVisibleBounds +import com.android.wm.shell.flicker.APP_PAIR_SPLIT_DIVIDER import com.android.wm.shell.flicker.appPairsDividerIsVisible import com.android.wm.shell.flicker.helpers.AppPairsHelper import org.junit.FixMethodOrder @@ -46,7 +45,7 @@ import org.junit.runners.Parameterized class AppPairsTestPairPrimaryAndSecondaryApps( testSpec: FlickerTestParameter ) : AppPairsTransition(testSpec) { - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { super.transition(this, it) transitions { @@ -75,10 +74,10 @@ class AppPairsTestPairPrimaryAndSecondaryApps( fun appsEndingBounds() { testSpec.assertLayersEnd { val dividerRegion = entry.getVisibleBounds(APP_PAIR_SPLIT_DIVIDER) - this.hasVisibleRegion(primaryApp.defaultWindowName, - appPairsHelper.getPrimaryBounds(dividerRegion)) - .hasVisibleRegion(secondaryApp.defaultWindowName, - appPairsHelper.getSecondaryBounds(dividerRegion)) + this.coversExactly(appPairsHelper.getPrimaryBounds(dividerRegion), + primaryApp.defaultWindowName) + .coversExactly(appPairsHelper.getSecondaryBounds(dividerRegion), + secondaryApp.defaultWindowName) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt index 3d3ca0cfd450..5bb9b2f8b8ca 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt @@ -16,17 +16,16 @@ package com.android.wm.shell.flicker.apppairs -import android.os.Bundle import android.os.SystemClock import android.platform.test.annotations.Presubmit import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.APP_PAIR_SPLIT_DIVIDER import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.traces.layers.getVisibleBounds +import com.android.wm.shell.flicker.APP_PAIR_SPLIT_DIVIDER import com.android.wm.shell.flicker.appPairsDividerIsInvisible import com.android.wm.shell.flicker.helpers.AppPairsHelper import org.junit.FixMethodOrder @@ -46,7 +45,7 @@ import org.junit.runners.Parameterized class AppPairsTestUnpairPrimaryAndSecondaryApps( testSpec: FlickerTestParameter ) : AppPairsTransition(testSpec) { - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { super.transition(this, it) setup { @@ -80,10 +79,10 @@ class AppPairsTestUnpairPrimaryAndSecondaryApps( fun appsStartingBounds() { testSpec.assertLayersStart { val dividerRegion = entry.getVisibleBounds(APP_PAIR_SPLIT_DIVIDER) - hasVisibleRegion(primaryApp.defaultWindowName, - appPairsHelper.getPrimaryBounds(dividerRegion)) - hasVisibleRegion(secondaryApp.defaultWindowName, - appPairsHelper.getSecondaryBounds(dividerRegion)) + coversExactly(appPairsHelper.getPrimaryBounds(dividerRegion), + primaryApp.defaultWindowName) + coversExactly(appPairsHelper.getSecondaryBounds(dividerRegion), + secondaryApp.defaultWindowName) } } @@ -91,8 +90,8 @@ class AppPairsTestUnpairPrimaryAndSecondaryApps( @Test fun appsEndingBounds() { testSpec.assertLayersEnd { - notExists(primaryApp.defaultWindowName) - notExists(secondaryApp.defaultWindowName) + notContains(primaryApp.defaultWindowName) + notContains(secondaryApp.defaultWindowName) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt index 9e6752db224f..91e080f65550 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt @@ -17,7 +17,6 @@ package com.android.wm.shell.flicker.apppairs import android.app.Instrumentation -import android.os.Bundle import android.platform.test.annotations.Presubmit import android.system.helpers.ActivityHelper import android.util.Log @@ -66,7 +65,7 @@ abstract class AppPairsTransition(protected val testSpec: FlickerTestParameter) } } - internal open val transition: FlickerBuilder.(Bundle) -> Unit + internal open val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { configuration -> setup { test { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt index 35a0020b16a9..5f003ba62b2d 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.apppairs -import android.os.Bundle import android.os.SystemClock import android.platform.test.annotations.Presubmit import android.view.Surface @@ -52,7 +51,7 @@ import org.junit.runners.Parameterized class RotateTwoLaunchedAppsInAppPairsMode( testSpec: FlickerTestParameter ) : RotateTwoLaunchedAppsTransition(testSpec) { - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { super.transition(this, it) transitions { @@ -72,16 +71,16 @@ class RotateTwoLaunchedAppsInAppPairsMode( } } - @FlakyTest + @Presubmit @Test fun appPairsDividerIsVisible() = testSpec.appPairsDividerIsVisible() - @FlakyTest + @Presubmit @Test fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales(Surface.ROTATION_0, testSpec.config.endRotation) - @FlakyTest + @Presubmit @Test fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales(Surface.ROTATION_0, testSpec.config.endRotation) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt index 326a775acc8d..d4792088ac31 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.apppairs -import android.os.Bundle import android.os.SystemClock import android.platform.test.annotations.Presubmit import android.view.Surface @@ -55,7 +54,7 @@ import org.junit.runners.Parameterized class RotateTwoLaunchedAppsRotateAndEnterAppPairsMode( testSpec: FlickerTestParameter ) : RotateTwoLaunchedAppsTransition(testSpec) { - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { super.transition(this, it) transitions { @@ -77,17 +76,8 @@ class RotateTwoLaunchedAppsRotateAndEnterAppPairsMode( @Presubmit @Test - fun navBarLayerRotatesAndScales() { - Assume.assumeFalse(isRotated) - testSpec.navBarLayerRotatesAndScales(Surface.ROTATION_0, testSpec.config.endRotation) - } - - @FlakyTest - @Test - fun navBarLayerRotatesAndScales_Flaky() { - Assume.assumeTrue(isRotated) - testSpec.navBarLayerRotatesAndScales(Surface.ROTATION_0, testSpec.config.endRotation) - } + fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales( + Surface.ROTATION_0, testSpec.config.endRotation) @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsTransition.kt index 271b25fc0ce1..83853e61ab5e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsTransition.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.apppairs -import android.os.Bundle import android.view.Surface import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.dsl.FlickerBuilder @@ -30,7 +29,7 @@ abstract class RotateTwoLaunchedAppsTransition( override val nonResizeableApp: SplitScreenHelper? get() = null - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { setup { test { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt index 9b70fac737e6..17c51fb15b0c 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.legacysplitscreen -import android.os.Bundle import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest @@ -50,11 +49,10 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -// @FlakyTest(bugId = 179116910) class EnterSplitScreenDockActivity( testSpec: FlickerTestParameter ) : LegacySplitScreenTransition(testSpec) { - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { configuration -> super.transition(this, configuration) transitions { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt index bd57a59ea3d9..a94fd463c624 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.legacysplitscreen -import android.os.Bundle import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest @@ -54,7 +53,7 @@ import org.junit.runners.Parameterized class EnterSplitScreenLaunchToSide( testSpec: FlickerTestParameter ) : LegacySplitScreenTransition(testSpec) { - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { configuration -> super.transition(this, configuration) transitions { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNonResizableNotDock.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNonResizableNotDock.kt index 67578b29a36c..238059b484b5 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNonResizableNotDock.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNonResizableNotDock.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.legacysplitscreen -import android.os.Bundle import android.view.Surface import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice @@ -50,7 +49,7 @@ import org.junit.runners.Parameterized class EnterSplitScreenNonResizableNotDock( testSpec: FlickerTestParameter ) : LegacySplitScreenTransition(testSpec) { - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { configuration -> super.transition(this, configuration) teardown { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt index 5d42a4a8fae0..acd570a3773e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt @@ -16,12 +16,10 @@ package com.android.wm.shell.flicker.legacysplitscreen -import android.os.Bundle import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.DOCKED_STACK_DIVIDER import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory @@ -34,6 +32,7 @@ import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry +import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER import com.android.wm.shell.flicker.helpers.SplitScreenHelper import org.junit.FixMethodOrder import org.junit.Test @@ -52,7 +51,7 @@ import org.junit.runners.Parameterized class ExitLegacySplitScreenFromBottom( testSpec: FlickerTestParameter ) : LegacySplitScreenTransition(testSpec) { - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { configuration -> super.transition(this, configuration) setup { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt index ff8f9c6ed865..cef188695ce7 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.legacysplitscreen -import android.os.Bundle import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest @@ -53,7 +52,7 @@ import org.junit.runners.Parameterized class ExitPrimarySplitScreenShowSecondaryFullscreen( testSpec: FlickerTestParameter ) : LegacySplitScreenTransition(testSpec) { - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { configuration -> super.transition(this, configuration) teardown { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenRotateTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenRotateTransition.kt index 893b101d0759..1e89a25c06df 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenRotateTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenRotateTransition.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.legacysplitscreen -import android.os.Bundle import android.view.Surface import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.dsl.FlickerBuilder @@ -27,7 +26,7 @@ import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen abstract class LegacySplitScreenRotateTransition( testSpec: FlickerTestParameter ) : LegacySplitScreenTransition(testSpec) { - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { setup { eachRun { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt index 09a7e31d20e2..7f69a66e6e82 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.legacysplitscreen -import android.os.Bundle import android.platform.test.annotations.Presubmit import android.support.test.launcherhelper.LauncherStrategyFactory import android.view.Surface @@ -66,7 +65,7 @@ class LegacySplitScreenToLauncher( .launcherStrategy.supportedLauncherPackage private val testApp = SimpleAppHelper(instrumentation) - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { configuration -> setup { test { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt index 6ab1f0bfdb89..91ea8716e4f0 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt @@ -17,7 +17,6 @@ package com.android.wm.shell.flicker.legacysplitscreen import android.app.Instrumentation -import android.os.Bundle import android.support.test.launcherhelper.LauncherStrategyFactory import android.view.Surface import androidx.test.platform.app.InstrumentationRegistry @@ -41,7 +40,7 @@ abstract class LegacySplitScreenTransition(protected val testSpec: FlickerTestPa protected val LAUNCHER_PACKAGE_NAME = LauncherStrategyFactory.getInstance(instrumentation) .launcherStrategy.supportedLauncherPackage - protected open val transition: FlickerBuilder.(Bundle) -> Unit + protected open val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { configuration -> setup { eachRun { @@ -70,7 +69,7 @@ abstract class LegacySplitScreenTransition(protected val testSpec: FlickerTestPa } } - internal open val cleanSetup: FlickerBuilder.(Bundle) -> Unit + internal open val cleanSetup: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { configuration -> setup { eachRun { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreen.kt index 1b4b54a74eab..caafa278d297 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreen.kt @@ -16,12 +16,10 @@ package com.android.wm.shell.flicker.legacysplitscreen -import android.os.Bundle import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.DOCKED_STACK_DIVIDER import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory @@ -34,6 +32,7 @@ import com.android.server.wm.flicker.layerBecomesInvisible import com.android.server.wm.flicker.layerBecomesVisible import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry +import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER import com.android.wm.shell.flicker.helpers.SplitScreenHelper import org.junit.FixMethodOrder import org.junit.Test @@ -53,7 +52,7 @@ import org.junit.runners.Parameterized class NonResizableDismissInLegacySplitScreen( testSpec: FlickerTestParameter ) : LegacySplitScreenTransition(testSpec) { - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { configuration -> cleanSetup(this, configuration) setup { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreen.kt index 2365e3bfd8c3..543484ac9759 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreen.kt @@ -16,12 +16,10 @@ package com.android.wm.shell.flicker.legacysplitscreen -import android.os.Bundle import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.DOCKED_STACK_DIVIDER import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory @@ -33,6 +31,7 @@ import com.android.server.wm.flicker.layerBecomesInvisible import com.android.server.wm.flicker.layerBecomesVisible import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry +import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER import com.android.wm.shell.flicker.helpers.SplitScreenHelper import org.junit.FixMethodOrder import org.junit.Test @@ -53,7 +52,7 @@ import org.junit.runners.Parameterized class NonResizableLaunchInLegacySplitScreen( testSpec: FlickerTestParameter ) : LegacySplitScreenTransition(testSpec) { - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { configuration -> cleanSetup(this, configuration) setup { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt index b369a3d32e64..d22833784bcf 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.legacysplitscreen -import android.os.Bundle import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest @@ -53,7 +52,7 @@ import org.junit.runners.Parameterized class OpenAppToLegacySplitScreen( testSpec: FlickerTestParameter ) : LegacySplitScreenTransition(testSpec) { - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { configuration -> super.transition(this, configuration) transitions { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt index ffffa1902976..f5174bc3cef7 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt @@ -17,13 +17,11 @@ package com.android.wm.shell.flicker.legacysplitscreen import android.graphics.Region -import android.os.Bundle import android.util.Rational import android.view.Surface import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import androidx.test.uiautomator.By -import com.android.server.wm.flicker.DOCKED_STACK_DIVIDER import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory @@ -46,6 +44,7 @@ import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEn import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible import com.android.server.wm.flicker.traces.layers.getVisibleBounds +import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER import com.android.wm.shell.flicker.helpers.SimpleAppHelper import org.junit.FixMethodOrder import org.junit.Test @@ -70,7 +69,7 @@ class ResizeLegacySplitScreen( private val testAppTop = SimpleAppHelper(instrumentation) private val testAppBottom = ImeAppHelper(instrumentation) - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { configuration -> setup { eachRun { @@ -151,21 +150,21 @@ class ResizeLegacySplitScreen( @Test fun topAppLayerIsAlwaysVisible() { testSpec.assertLayers { - this.showsLayer(sSimpleActivity) + this.isVisible(sSimpleActivity) } } @Test fun bottomAppLayerIsAlwaysVisible() { testSpec.assertLayers { - this.showsLayer(sImeActivity) + this.isVisible(sImeActivity) } } @Test fun dividerLayerIsAlwaysVisible() { testSpec.assertLayers { - this.showsLayer(DOCKED_STACK_DIVIDER) + this.isVisible(DOCKED_STACK_DIVIDER) } } @@ -183,8 +182,8 @@ class ResizeLegacySplitScreen( dividerBounds.bottom - WindowUtils.dockedStackDividerInset, displayBounds.right, displayBounds.bottom - WindowUtils.navigationBarHeight) - this.hasVisibleRegion("SimpleActivity", topAppBounds) - .hasVisibleRegion("ImeActivity", bottomAppBounds) + this.coversExactly(topAppBounds, "SimpleActivity") + .coversExactly(bottomAppBounds, "ImeActivity") } } @@ -203,8 +202,8 @@ class ResizeLegacySplitScreen( displayBounds.right, displayBounds.bottom - WindowUtils.navigationBarHeight) - this.hasVisibleRegion(sSimpleActivity, topAppBounds) - .hasVisibleRegion(sImeActivity, bottomAppBounds) + this.coversExactly(topAppBounds, sSimpleActivity) + .coversExactly(bottomAppBounds, sImeActivity) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt index c538008aa179..c914adae2b7c 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.legacysplitscreen -import android.os.Bundle import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest @@ -54,7 +53,7 @@ import org.junit.runners.Parameterized class RotateOneLaunchedAppAndEnterSplitScreen( testSpec: FlickerTestParameter ) : LegacySplitScreenRotateTransition(testSpec) { - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { configuration -> super.transition(this, configuration) transitions { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt index c1162560119c..ffb20a4bc99a 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.legacysplitscreen -import android.os.Bundle import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest @@ -54,7 +53,7 @@ import org.junit.runners.Parameterized class RotateOneLaunchedAppInSplitScreenMode( testSpec: FlickerTestParameter ) : LegacySplitScreenRotateTransition(testSpec) { - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { configuration -> super.transition(this, configuration) transitions { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt index 273925c0361c..8cf1990d406f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.legacysplitscreen -import android.os.Bundle import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest @@ -56,7 +55,7 @@ import org.junit.runners.Parameterized class RotateTwoLaunchedAppAndEnterSplitScreen( testSpec: FlickerTestParameter ) : LegacySplitScreenRotateTransition(testSpec) { - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { configuration -> super.transition(this, configuration) transitions { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt index c7188dc227e7..9c798d8ea661 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.legacysplitscreen -import android.os.Bundle import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest @@ -56,7 +55,7 @@ import org.junit.runners.Parameterized class RotateTwoLaunchedAppInSplitScreenMode( testSpec: FlickerTestParameter ) : LegacySplitScreenRotateTransition(testSpec) { - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { configuration -> super.transition(this, configuration) setup { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt index ca48eaa45840..75c33c671008 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.pip -import android.os.Bundle import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.RequiresDevice @@ -51,7 +50,7 @@ class EnterExitPipTest( private val testApp = FixedAppHelper(instrumentation) private val displayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation) - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = buildTransition(eachRun = true) { setup { eachRun { @@ -68,7 +67,7 @@ class EnterExitPipTest( @Test fun pipAppRemainInsideVisibleBounds() { testSpec.assertWm { - coversAtMostRegion(pipApp.defaultWindowName, displayBounds) + coversAtMost(displayBounds, pipApp.defaultWindowName) } } @@ -95,10 +94,10 @@ class EnterExitPipTest( @Test fun showBothAppLayersThenHidePip() { testSpec.assertLayers { - showsLayer(testApp.defaultWindowName) - .showsLayer(pipApp.defaultWindowName) + isVisible(testApp.defaultWindowName) + .isVisible(pipApp.defaultWindowName) .then() - .hidesLayer(testApp.defaultWindowName) + .isInvisible(testApp.defaultWindowName) } } @@ -106,8 +105,8 @@ class EnterExitPipTest( @Test fun testAppCoversFullScreenWithPipOnDisplay() { testSpec.assertLayersStart { - hasVisibleRegion(testApp.defaultWindowName, displayBounds) - coversAtMostRegion(displayBounds, pipApp.defaultWindowName) + coversExactly(displayBounds, testApp.defaultWindowName) + coversAtMost(displayBounds, pipApp.defaultWindowName) } } @@ -115,7 +114,7 @@ class EnterExitPipTest( @Test fun pipAppCoversFullScreen() { testSpec.assertLayersEnd { - hasVisibleRegion(pipApp.defaultWindowName, displayBounds) + coversExactly(displayBounds, pipApp.defaultWindowName) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt index e1fbc16e8ce2..83dca53b1542 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.pip -import android.os.Bundle import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest @@ -48,7 +47,7 @@ import org.junit.runners.Parameterized @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = buildTransition(eachRun = true, stringExtras = emptyMap()) { transitions { pipApp.clickEnterPipButton() @@ -85,7 +84,7 @@ class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { @Test fun pipLayerBecomesVisible() { testSpec.assertLayers { - this.showsLayer(pipApp.launcherName) + this.isVisible(pipApp.launcherName) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt index 215b97bfeb83..9011f1a9fb6a 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt @@ -16,10 +16,8 @@ package com.android.wm.shell.flicker.pip -import android.os.Bundle import android.platform.test.annotations.Presubmit import android.view.Surface -import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter @@ -56,7 +54,7 @@ class EnterPipToOtherOrientationTest( private val startingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_90) private val endingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_0) - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { configuration -> setupAndTeardown(this, configuration) @@ -121,24 +119,24 @@ class EnterPipToOtherOrientationTest( @Test fun pipAppLayerHidesTestApp() { testSpec.assertLayersStart { - hasVisibleRegion(pipApp.defaultWindowName, startingBounds) + coversExactly(startingBounds, pipApp.defaultWindowName) isInvisible(testApp.defaultWindowName) } } - @FlakyTest + @Presubmit @Test fun testAppLayerCoversFullScreen() { testSpec.assertLayersEnd { - hasVisibleRegion(testApp.defaultWindowName, endingBounds) + coversExactly(endingBounds, testApp.defaultWindowName) } } - @FlakyTest(bugId = 140855415) + @Presubmit @Test fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() - @FlakyTest(bugId = 140855415) + @Presubmit @Test fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible() diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/Extensions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/Extensions.kt index 707d28d9c4c0..96eb66c3cc28 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/Extensions.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/Extensions.kt @@ -48,7 +48,7 @@ fun WindowManagerStateSubject.isInPipMode( activity: ComponentName ): WindowManagerStateSubject = apply { val windowName = activity.toWindowName() - hasWindow(windowName) + contains(windowName) val pinnedWindows = wmState.pinnedWindows .map { it.title } Truth.assertWithMessage("Window not in PIP mode") diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseTransition.kt index 968a11de2511..3e331761f767 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToHomeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseTransition.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,41 +16,27 @@ package com.android.wm.shell.flicker.pip -import android.os.Bundle -import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest -import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.focusChanges import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible +import com.android.server.wm.flicker.navBarLayerRotatesAndScales import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible import com.android.server.wm.flicker.noUncoveredRegions import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible -import org.junit.FixMethodOrder import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.MethodSorters import org.junit.runners.Parameterized -/** - * Test Pip launch. - * To run this test: `atest WMShellFlickerTests:PipToHomeTest` - */ -@RequiresDevice -@RunWith(Parameterized::class) -@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -class PipToHomeTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { - override val transition: FlickerBuilder.(Bundle) -> Unit +abstract class PipCloseTransition(testSpec: FlickerTestParameter) : PipTransition(testSpec) { + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = buildTransition(eachRun = true) { configuration -> setup { eachRun { @@ -62,30 +48,27 @@ class PipToHomeTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { this.setRotation(Surface.ROTATION_0) } } - transitions { - pipApp.closePipWindow(wmHelper) - } } - @Postsubmit + @Presubmit @Test - fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() + open fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() @Presubmit @Test - fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible() + open fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible() @Presubmit @Test - fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() + open fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() @Presubmit @Test - fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() + open fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() @Presubmit @Test - fun pipWindowBecomesInvisible() { + open fun pipWindowBecomesInvisible() { testSpec.assertWm { this.showsAppWindow(PIP_WINDOW_TITLE) .then() @@ -95,32 +78,32 @@ class PipToHomeTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { @Presubmit @Test - fun pipLayerBecomesInvisible() { + open fun pipLayerBecomesInvisible() { testSpec.assertLayers { - this.showsLayer(PIP_WINDOW_TITLE) + this.isVisible(PIP_WINDOW_TITLE) .then() - .hidesLayer(PIP_WINDOW_TITLE) + .isInvisible(PIP_WINDOW_TITLE) } } @Presubmit @Test - fun statusBarLayerRotatesScales() = + open fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0) - @Postsubmit + @Presubmit @Test - fun noUncoveredRegions() = + open fun noUncoveredRegions() = testSpec.noUncoveredRegions(testSpec.config.startRotation, Surface.ROTATION_0) - @Postsubmit + @Presubmit @Test - fun navBarLayerRotatesAndScales() = - testSpec.noUncoveredRegions(testSpec.config.startRotation, Surface.ROTATION_0) + open fun navBarLayerRotatesAndScales() = + testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0) @FlakyTest(bugId = 151179149) @Test - fun focusChanges() = testSpec.focusChanges(pipApp.launcherName, "NexusLauncherActivity") + open fun focusChanges() = testSpec.focusChanges(pipApp.launcherName, "NexusLauncherActivity") companion object { @Parameterized.Parameters(name = "{0}") diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseWithDismissButtonTest.kt new file mode 100644 index 000000000000..0408421c72a5 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseWithDismissButtonTest.kt @@ -0,0 +1,44 @@ +/* + * 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.wm.shell.flicker.pip + +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.dsl.FlickerBuilder +import org.junit.FixMethodOrder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test Pip launch. + * To run this test: `atest WMShellFlickerTests:PipCloseWithDismissButton` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class PipCloseWithDismissButtonTest(testSpec: FlickerTestParameter) : PipCloseTransition(testSpec) { + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit + get() = { + super.transition(this, it) + transitions { + pipApp.closePipWindow(wmHelper) + } + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseWithSwipeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseWithSwipeTest.kt new file mode 100644 index 000000000000..afaf33a7c46f --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseWithSwipeTest.kt @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.pip + +import android.platform.test.annotations.Postsubmit +import android.view.Surface +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.startRotation +import com.android.server.wm.flicker.statusBarLayerRotatesScales +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test Pip launch. + * To run this test: `atest WMShellFlickerTests:PipCloseWithSwipe` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class PipCloseWithSwipeTest(testSpec: FlickerTestParameter) : PipCloseTransition(testSpec) { + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit + get() = { + super.transition(this, it) + transitions { + val pipRegion = wmHelper.getWindowRegion(pipApp.component).bounds + val pipCenterX = pipRegion.centerX() + val pipCenterY = pipRegion.centerY() + val displayCenterX = device.displayWidth / 2 + device.swipe(pipCenterX, pipCenterY, displayCenterX, device.displayHeight, 5) + } + } + + @Postsubmit + @Test + override fun navBarLayerIsAlwaysVisible() = super.navBarLayerIsAlwaysVisible() + + @Postsubmit + @Test + override fun statusBarLayerIsAlwaysVisible() = super.statusBarLayerIsAlwaysVisible() + + @Postsubmit + @Test + override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible() + + @Postsubmit + @Test + override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible() + + @Postsubmit + @Test + override fun pipWindowBecomesInvisible() = super.pipWindowBecomesInvisible() + + @Postsubmit + @Test + override fun pipLayerBecomesInvisible() = super.pipLayerBecomesInvisible() + + @Postsubmit + @Test + override fun statusBarLayerRotatesScales() = + testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0) + + @Postsubmit + @Test + override fun noUncoveredRegions() = super.noUncoveredRegions() + + @Postsubmit + @Test + override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt index f3b9ea1455ca..46339603f806 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.pip -import android.os.Bundle import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.RequiresDevice @@ -46,7 +45,7 @@ import org.junit.runners.Parameterized class PipKeyboardTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { private val imeApp = ImeAppHelper(instrumentation) - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = buildTransition(eachRun = false) { configuration -> setup { test { @@ -78,7 +77,7 @@ class PipKeyboardTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) fun pipInVisibleBounds() { testSpec.assertWm { val displayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation) - coversAtMostRegion(pipApp.defaultWindowName, displayBounds) + coversAtMost(displayBounds, pipApp.defaultWindowName) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt index daf381ee9ddb..97afc65b8b61 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.pip -import android.os.Bundle import android.platform.test.annotations.Postsubmit import android.view.Surface import androidx.test.filters.FlakyTest @@ -58,7 +57,7 @@ class PipLegacySplitScreenTest(testSpec: FlickerTestParameter) : PipTransition(t private val testApp = FixedAppHelper(instrumentation) private val displayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation) - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { withTestName { testSpec.name } repeat { testSpec.config.repetitions } @@ -90,7 +89,7 @@ class PipLegacySplitScreenTest(testSpec: FlickerTestParameter) : PipTransition(t @Test fun pipWindowInsideDisplayBounds() { testSpec.assertWm { - coversAtMostRegion(pipApp.defaultWindowName, displayBounds) + coversAtMost(displayBounds, pipApp.defaultWindowName) } } @@ -100,7 +99,7 @@ class PipLegacySplitScreenTest(testSpec: FlickerTestParameter) : PipTransition(t testSpec.assertWmEnd { isVisible(testApp.defaultWindowName) isVisible(imeApp.defaultWindowName) - noWindowsOverlap(setOf(testApp.defaultWindowName, imeApp.defaultWindowName)) + noWindowsOverlap(testApp.defaultWindowName, imeApp.defaultWindowName) } } @@ -116,7 +115,7 @@ class PipLegacySplitScreenTest(testSpec: FlickerTestParameter) : PipTransition(t @Test fun pipLayerInsideDisplayBounds() { testSpec.assertLayers { - coversAtMostRegion(displayBounds, pipApp.defaultWindowName) + coversAtMost(displayBounds, pipApp.defaultWindowName) } } @@ -124,8 +123,8 @@ class PipLegacySplitScreenTest(testSpec: FlickerTestParameter) : PipTransition(t @Test fun bothAppLayersVisible() { testSpec.assertLayersEnd { - coversAtMostRegion(displayBounds, testApp.defaultWindowName) - coversAtMostRegion(displayBounds, imeApp.defaultWindowName) + coversAtMost(displayBounds, testApp.defaultWindowName) + coversAtMost(displayBounds, imeApp.defaultWindowName) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipMovesInAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipMovesInAllApps.kt new file mode 100644 index 000000000000..4c95da284d9e --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipMovesInAllApps.kt @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.pip + +import android.platform.test.annotations.Postsubmit +import android.view.Surface +import androidx.test.filters.RequiresDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.FlickerTestParameterFactory +import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible +import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible +import com.google.common.truth.Truth +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test Pip launch. + * To run this test: `atest WMShellFlickerTests:PipMovesInAllApps` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class PipMovesInAllApps(testSpec: FlickerTestParameter) : PipTransition(testSpec) { + private val taplInstrumentation = LauncherInstrumentation() + + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit + get() = buildTransition(eachRun = false) { + teardown { + eachRun { + taplInstrumentation.pressHome() + } + } + transitions { + taplInstrumentation.pressHome().switchToAllApps() + wmHelper.waitForAppTransitionIdle() + } + } + + @Postsubmit + @Test + fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() + + @Postsubmit + @Test + fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() + + @Postsubmit + @Test + fun pipAlwaysVisible() = testSpec.assertWm { this.showsAppWindow(pipApp.windowName) } + + @Postsubmit + @Test + fun pipWindowMovesUp() = testSpec.assertWmEnd { + val initialState = this.trace?.first()?.wmState + ?: error("Trace should not be empty") + val startPos = initialState.pinnedWindows.first().frame + val currPos = this.wmState.pinnedWindows.first().frame + val subject = Truth.assertWithMessage("Pip should have moved up") + subject.that(currPos.top).isGreaterThan(startPos.top) + subject.that(currPos.bottom).isGreaterThan(startPos.bottom) + subject.that(currPos.left).isEqualTo(startPos.left) + subject.that(currPos.right).isEqualTo(startPos.right) + } + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): List<FlickerTestParameter> { + return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests( + supportedRotations = listOf(Surface.ROTATION_0), repetitions = 5) + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt index 43c12acef9e8..df835d21e73f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.pip -import android.os.Bundle import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest @@ -56,7 +55,7 @@ class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) private val startingBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation) private val endingBounds = WindowUtils.getDisplayBounds(testSpec.config.endRotation) - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = buildTransition(eachRun = false) { configuration -> setup { test { @@ -89,11 +88,11 @@ class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) fun noUncoveredRegions() = testSpec.noUncoveredRegions(testSpec.config.startRotation, testSpec.config.endRotation, allStates = false) - @FlakyTest(bugId = 140855415) + @Presubmit @Test fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() - @FlakyTest(bugId = 140855415) + @Presubmit @Test fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible() @@ -113,8 +112,8 @@ class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) @Test fun appLayerRotates_StartingBounds() { testSpec.assertLayersStart { - hasVisibleRegion(fixedApp.defaultWindowName, startingBounds) - coversAtMostRegion(startingBounds, pipApp.defaultWindowName) + coversExactly(startingBounds, fixedApp.defaultWindowName) + coversAtMost(startingBounds, pipApp.defaultWindowName) } } @@ -122,8 +121,8 @@ class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) @Test fun appLayerRotates_EndingBounds() { testSpec.assertLayersEnd { - hasVisibleRegion(fixedApp.defaultWindowName, endingBounds) - coversAtMostRegion(endingBounds, pipApp.defaultWindowName) + coversExactly(endingBounds, fixedApp.defaultWindowName) + coversAtMost(endingBounds, pipApp.defaultWindowName) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt index 02389a9ccf87..1bb1d2861f3f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.pip -import android.os.Bundle import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest @@ -50,7 +49,7 @@ import org.junit.runners.Parameterized @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) class PipToAppTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = buildTransition(eachRun = true) { configuration -> setup { eachRun { @@ -67,7 +66,7 @@ class PipToAppTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { } } - @FlakyTest(bugId = 140855415) + @Presubmit @Test fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() @@ -102,18 +101,18 @@ class PipToAppTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) { @Test fun appReplacesPipLayer() { testSpec.assertLayers { - this.showsLayer(PIP_WINDOW_TITLE) + this.isVisible(PIP_WINDOW_TITLE) .then() - .showsLayer(pipApp.launcherName) + .isVisible(pipApp.launcherName) } } - @FlakyTest + @Presubmit @Test fun noUncoveredRegions() = testSpec.noUncoveredRegions(testSpec.config.startRotation, Surface.ROTATION_0) - @FlakyTest(bugId = 140855415) + @Presubmit @Test fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt index a94483ec00a0..b0a9afef9215 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt @@ -18,7 +18,6 @@ package com.android.wm.shell.flicker.pip import android.app.Instrumentation import android.content.Intent -import android.os.Bundle import android.view.Surface import androidx.test.platform.app.InstrumentationRegistry import com.android.server.wm.flicker.FlickerBuilderProvider @@ -38,7 +37,7 @@ abstract class PipTransition(protected val testSpec: FlickerTestParameter) { protected val isRotated = testSpec.config.startRotation.isRotated() protected val pipApp = PipAppHelper(instrumentation) protected val broadcastActionTrigger = BroadcastActionTrigger(instrumentation) - protected abstract val transition: FlickerBuilder.(Bundle) -> Unit + protected abstract val transition: FlickerBuilder.(Map<String, Any?>) -> Unit // Helper class to process test actions by broadcast. protected class BroadcastActionTrigger(private val instrumentation: Instrumentation) { @@ -81,7 +80,7 @@ abstract class PipTransition(protected val testSpec: FlickerTestParameter) { /** * Gets a configuration that handles basic setup and teardown of pip tests */ - protected val setupAndTeardown: FlickerBuilder.(Bundle) -> Unit + protected val setupAndTeardown: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { setup { test { @@ -112,8 +111,8 @@ abstract class PipTransition(protected val testSpec: FlickerTestParameter) { protected open fun buildTransition( eachRun: Boolean, stringExtras: Map<String, String> = mapOf(Components.PipActivity.EXTRA_ENTER_PIP to "true"), - extraSpec: FlickerBuilder.(Bundle) -> Unit = {} - ): FlickerBuilder.(Bundle) -> Unit { + extraSpec: FlickerBuilder.(Map<String, Any?>) -> Unit = {} + ): FlickerBuilder.(Map<String, Any?>) -> Unit { return { configuration -> setupAndTeardown(this, configuration) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt index 1f0370dc0505..7916ce59af52 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.pip -import android.os.Bundle import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest @@ -54,7 +53,7 @@ class SetRequestedOrientationWhilePinnedTest( private val startingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_0) private val endingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_90) - override val transition: FlickerBuilder.(Bundle) -> Unit + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit get() = { configuration -> setupAndTeardown(this, configuration) @@ -88,7 +87,7 @@ class SetRequestedOrientationWhilePinnedTest( @Test fun pipWindowInsideDisplay() { testSpec.assertWmStart { - coversAtMostRegion(pipApp.defaultWindowName, startingBounds) + coversAtMost(startingBounds, pipApp.defaultWindowName) } } @@ -112,7 +111,7 @@ class SetRequestedOrientationWhilePinnedTest( @Test fun pipLayerInsideDisplay() { testSpec.assertLayersStart { - coversAtMostRegion(startingBounds, pipApp.defaultWindowName) + coversAtMost(startingBounds, pipApp.defaultWindowName) } } @@ -120,15 +119,15 @@ class SetRequestedOrientationWhilePinnedTest( @Test fun pipAppLayerCoversFullScreen() { testSpec.assertLayersEnd { - hasVisibleRegion(pipApp.defaultWindowName, endingBounds) + coversExactly(endingBounds, pipApp.defaultWindowName) } } - @FlakyTest(bugId = 140855415) + @Presubmit @Test fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() - @FlakyTest(bugId = 140855415) + @Presubmit @Test fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible() diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TaskStackListenerImplTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TaskStackListenerImplTest.java index 21bc32c6563c..d8aebc284bf1 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TaskStackListenerImplTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TaskStackListenerImplTest.java @@ -198,7 +198,7 @@ public class TaskStackListenerImplTest { @Test public void testOnActivityDismissingDockedStack() { - mImpl.onActivityDismissingDockedStack(); + mImpl.onActivityDismissingDockedTask(); verify(mCallback).onActivityDismissingDockedStack(); verify(mOtherCallback).onActivityDismissingDockedStack(); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java index cfe84639d24e..f2b4e9761226 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java @@ -124,7 +124,7 @@ public class PipControllerTest extends ShellTestCase { final ComponentName component1 = new ComponentName(mContext, "component1"); when(mMockPipBoundsState.getLastPipComponentName()).thenReturn(component1); - mPipController.mPinnedStackListener.onActivityHidden(component1); + mPipController.mPinnedTaskListener.onActivityHidden(component1); verify(mMockPipBoundsState).setLastPipComponentName(null); } @@ -135,7 +135,7 @@ public class PipControllerTest extends ShellTestCase { final ComponentName component2 = new ComponentName(mContext, "component2"); when(mMockPipBoundsState.getLastPipComponentName()).thenReturn(component1); - mPipController.mPinnedStackListener.onActivityHidden(component2); + mPipController.mPinnedTaskListener.onActivityHidden(component2); verify(mMockPipBoundsState, never()).setLastPipComponentName(null); } diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl index 5e3966032a11..38b48e97771a 100644 --- a/location/java/android/location/ILocationManager.aidl +++ b/location/java/android/location/ILocationManager.aidl @@ -117,7 +117,8 @@ interface ILocationManager boolean isLocationEnabledForUser(int userId); void setLocationEnabledForUser(boolean enabled, int userId); - void addTestProvider(String name, in ProviderProperties properties, String packageName, @nullable String attributionTag); + void addTestProvider(String name, in ProviderProperties properties, + in List<String> locationTags, String packageName, @nullable String attributionTag); void removeTestProvider(String provider, String packageName, @nullable String attributionTag); void setTestProviderLocation(String provider, in Location location, String packageName, @nullable String attributionTag); void setTestProviderEnabled(String provider, boolean enabled, String packageName, @nullable String attributionTag); diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index dd5b6e6b4222..95bae5ae7aab 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -76,6 +76,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.Set; import java.util.WeakHashMap; import java.util.concurrent.Executor; import java.util.function.Consumer; @@ -1957,12 +1958,32 @@ public class LocationManager { * allowed} for your app. */ public void addTestProvider(@NonNull String provider, @NonNull ProviderProperties properties) { + addTestProvider(provider, properties, Collections.emptySet()); + } + + /** + * Creates a test location provider and adds it to the set of active providers. This provider + * will replace any provider with the same name that exists prior to this call. + * + * @param provider the provider name + * @param properties the provider properties + * @param locationTags the attribution tags for accessing location from the provider + * + * @throws IllegalArgumentException if provider is null + * @throws IllegalArgumentException if properties is null + * @throws SecurityException if {@link android.app.AppOpsManager#OPSTR_MOCK_LOCATION + * mock location app op} is not set to {@link android.app.AppOpsManager#MODE_ALLOWED + * allowed} for your app. + */ + public void addTestProvider(@NonNull String provider, @NonNull ProviderProperties properties, + @NonNull Set<String> locationTags) { Preconditions.checkArgument(provider != null, "invalid null provider"); Preconditions.checkArgument(properties != null, "invalid null properties"); + Preconditions.checkArgument(locationTags != null, "invalid null location tags"); try { - mService.addTestProvider(provider, properties, mContext.getOpPackageName(), - mContext.getFeatureId()); + mService.addTestProvider(provider, properties, new ArrayList<>(locationTags), + mContext.getOpPackageName(), mContext.getFeatureId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/location/java/android/location/LocationManagerInternal.java b/location/java/android/location/LocationManagerInternal.java index a6a0e7aa24ff..763835c9cbe2 100644 --- a/location/java/android/location/LocationManagerInternal.java +++ b/location/java/android/location/LocationManagerInternal.java @@ -21,6 +21,10 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.location.util.identity.CallerIdentity; +import com.android.internal.annotations.Immutable; + +import java.util.Set; + /** * Location manager local system service interface. * @@ -39,6 +43,21 @@ public abstract class LocationManagerInternal { } /** + * Interface for getting callbacks when a location provider's location tags change. + * + * @see LocationTagInfo + */ + public interface OnProviderLocationTagsChangeListener { + + /** + * Called when the location tags for a provider change. + * + * @param providerLocationTagInfo The tag info for a provider. + */ + void onLocationTagsChanged(@NonNull LocationTagInfo providerLocationTagInfo); + } + + /** * Returns true if the given provider is enabled for the given user. * * @param provider A location provider as listed by {@link LocationManager#getAllProviders()} @@ -88,4 +107,60 @@ public abstract class LocationManagerInternal { * provider, and the elapsed nanos since boot the current time was computed at. */ public abstract @Nullable LocationTime getGnssTimeMillis(); + + /** + * Sets a listener for changes in the location providers' tags. Passing + * {@code null} clears the current listener. + * + * @param listener The listener. + */ + public abstract void setOnProviderLocationTagsChangeListener( + @Nullable OnProviderLocationTagsChangeListener listener); + + /** + * This class represents the location permission tags used by the location provider + * packages in a given UID. These tags are strictly used for accessing state guarded + * by the location permission(s) by a location provider which are required for the + * provider to fulfill its function as being a location provider. + */ + @Immutable + public static class LocationTagInfo { + private final int mUid; + + @NonNull + private final String mPackageName; + + @Nullable + private final Set<String> mLocationTags; + + public LocationTagInfo(int uid, @NonNull String packageName, + @Nullable Set<String> locationTags) { + mUid = uid; + mPackageName = packageName; + mLocationTags = locationTags; + } + + /** + * @return The UID for which tags are related. + */ + public int getUid() { + return mUid; + } + + /** + * @return The package for which tags are related. + */ + @NonNull + public String getPackageName() { + return mPackageName; + } + + /** + * @return The tags for the package used for location related accesses. + */ + @Nullable + public Set<String> getTags() { + return mLocationTags; + } + } } diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index f87f90d8e0a2..b2de49deefca 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -568,7 +568,7 @@ public class AudioManager { public static final int FLAG_FROM_KEY = 1 << 12; /** @hide */ - @IntDef(flag = false, prefix = "FLAG", value = { + @IntDef(flag = true, prefix = "FLAG", value = { FLAG_SHOW_UI, FLAG_ALLOW_RINGER_MODES, FLAG_PLAY_SOUND, diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java index 6fcb75616f0d..58174a0cf408 100644 --- a/media/java/android/media/AudioTrack.java +++ b/media/java/android/media/AudioTrack.java @@ -3526,8 +3526,9 @@ public class AudioTrack extends PlayerBase native_enableDeviceCallback(); return true; } catch (IllegalStateException e) { - // Fail silently as track state could have changed in between start - // and enabling routing callback, return false to indicate not enabled + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "testEnableNativeRoutingCallbacks failed", e); + } } } return false; @@ -3577,7 +3578,7 @@ public class AudioTrack extends PlayerBase Handler handler) { synchronized (mRoutingChangeListeners) { if (listener != null && !mRoutingChangeListeners.containsKey(listener)) { - testEnableNativeRoutingCallbacksLocked(); + mEnableSelfRoutingMonitor = testEnableNativeRoutingCallbacksLocked(); mRoutingChangeListeners.put( listener, new NativeRoutingEventHandlerDelegate(this, listener, handler != null ? handler : new Handler(mInitializationLooper))); diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java index 67f1660fff78..f8a642a68dd7 100644 --- a/media/java/android/media/MediaFormat.java +++ b/media/java/android/media/MediaFormat.java @@ -980,6 +980,74 @@ public final class MediaFormat { public static final String KEY_BITRATE_MODE = "bitrate-mode"; /** + * A key describing the maximum Quantization Parameter allowed for encoding video. + * This key applies to all three video frame types (I, P, and B). This value fills + * in for any of the frame-specific #KEY_VIDEO_QP_I_MAX, #KEY_VIDEO_QP_P_MAX, or + * #KEY_VIDEO_QP_B_MAX keys that are not specified + * + * The associated value is an integer. + */ + public static final String KEY_VIDEO_QP_MAX = "video-qp-max"; + + /** + * A key describing the maximum Quantization Parameter allowed for encoding video. + * This key applies to all three video frame types (I, P, and B). This value fills + * in for any of the frame-specific #KEY_VIDEO_QP_I_MIN, #KEY_VIDEO_QP_P_MIN, or + * #KEY_VIDEO_QP_B_MIN keys that are not specified + * + * The associated value is an integer. + */ + public static final String KEY_VIDEO_QP_MIN = "video-qp-min"; + + /** + * A key describing the maximum Quantization Parameter allowed for encoding video. + * This value applies to video I-frames. + * + * The associated value is an integer. + */ + public static final String KEY_VIDEO_QP_I_MAX = "video-qp-i-max"; + + /** + * A key describing the minimum Quantization Parameter allowed for encoding video. + * This value applies to video I-frames. + * + * The associated value is an integer. + */ + public static final String KEY_VIDEO_QP_I_MIN = "video-qp-i-min"; + + /** + * A key describing the maximum Quantization Parameter allowed for encoding video. + * This value applies to video P-frames. + * + * The associated value is an integer. + */ + public static final String KEY_VIDEO_QP_P_MAX = "video-qp-p-max"; + + /** + * A key describing the minimum Quantization Parameter allowed for encoding video. + * This value applies to video P-frames. + * + * The associated value is an integer. + */ + public static final String KEY_VIDEO_QP_P_MIN = "video-qp-p-min"; + + /** + * A key describing the maximum Quantization Parameter allowed for encoding video. + * This value applies to video B-frames. + * + * The associated value is an integer. + */ + public static final String KEY_VIDEO_QP_B_MAX = "video-qp-b-max"; + + /** + * A key describing the minimum Quantization Parameter allowed for encoding video. + * This value applies to video B-frames. + * + * The associated value is an integer. + */ + public static final String KEY_VIDEO_QP_B_MIN = "video-qp-b-min"; + + /** * A key describing the audio session ID of the AudioTrack associated * to a tunneled video codec. * The associated value is an integer. diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java index f3cee17ab238..9176dae8609f 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -1360,6 +1360,7 @@ public class MediaPlayer extends PlayerBase private void startImpl() { baseStart(0); // unknown device at this point stayAwake(true); + tryToEnableNativeRoutingCallback(); _start(); } @@ -1385,6 +1386,7 @@ public class MediaPlayer extends PlayerBase stayAwake(false); _stop(); baseStop(); + tryToDisableNativeRoutingCallback(); } private native void _stop() throws IllegalStateException; @@ -1526,8 +1528,9 @@ public class MediaPlayer extends PlayerBase native_enableDeviceCallback(true); return true; } catch (IllegalStateException e) { - // Fail silently as media player state could have changed in between start - // and enabling routing callback, return false to indicate not enabled + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "testEnableNativeRoutingCallbacks failed", e); + } } } return false; @@ -1590,7 +1593,7 @@ public class MediaPlayer extends PlayerBase Handler handler) { synchronized (mRoutingChangeListeners) { if (listener != null && !mRoutingChangeListeners.containsKey(listener)) { - testEnableNativeRoutingCallbacksLocked(); + mEnableSelfRoutingMonitor = testEnableNativeRoutingCallbacksLocked(); mRoutingChangeListeners.put( listener, new NativeRoutingEventHandlerDelegate(this, listener, handler != null ? handler : mEventHandler)); @@ -3483,9 +3486,6 @@ public class MediaPlayer extends PlayerBase case MEDIA_STOPPED: { - tryToDisableNativeRoutingCallback(); - // FIXME see b/179218630 - //baseStop(); TimeProvider timeProvider = mTimeProvider; if (timeProvider != null) { timeProvider.onStopped(); @@ -3494,18 +3494,9 @@ public class MediaPlayer extends PlayerBase break; case MEDIA_STARTED: - { - // FIXME see b/179218630 - //baseStart(native_getRoutedDeviceId()); - tryToEnableNativeRoutingCallback(); - } // fall through case MEDIA_PAUSED: { - // FIXME see b/179218630 - //if (msg.what == MEDIA_PAUSED) { - // basePause(); - //} TimeProvider timeProvider = mTimeProvider; if (timeProvider != null) { timeProvider.onPaused(msg.what == MEDIA_PAUSED); diff --git a/media/java/android/media/soundtrigger/OWNERS b/media/java/android/media/soundtrigger/OWNERS index 6a351d396836..e5d037003ac4 100644 --- a/media/java/android/media/soundtrigger/OWNERS +++ b/media/java/android/media/soundtrigger/OWNERS @@ -1 +1,2 @@ +ytai@google.com elaurent@google.com diff --git a/media/java/android/mtp/MtpServer.java b/media/java/android/mtp/MtpServer.java index 042d02fa3d48..1d2657a280a2 100644 --- a/media/java/android/mtp/MtpServer.java +++ b/media/java/android/mtp/MtpServer.java @@ -144,11 +144,6 @@ public class MtpServer implements Runnable { native_remove_storage(storage.getStorageId()); } - public static void configure(boolean usePtp) { - native_configure(usePtp); - } - - public static native final void native_configure(boolean usePtp); private native final void native_setup( MtpDatabase database, FileDescriptor controlFd, diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp index 3669bf44c42a..b7cde57beaf5 100644 --- a/media/jni/android_media_tv_Tuner.cpp +++ b/media/jni/android_media_tv_Tuner.cpp @@ -561,7 +561,7 @@ jobjectArray FilterClientCallbackImpl::getTsRecordEvent( const std::vector<DemuxFilterEventExt::Event>& eventsExt) { JNIEnv *env = AndroidRuntime::getJNIEnv(); jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/TsRecordEvent"); - jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(IIIJJ)V"); + jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(IIIJJI)V"); for (int i = 0; i < events.size(); i++) { auto event = events[i]; @@ -614,7 +614,7 @@ jobjectArray FilterClientCallbackImpl::getMmtpRecordEvent( const std::vector<DemuxFilterEventExt::Event>& eventsExt) { JNIEnv *env = AndroidRuntime::getJNIEnv(); jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/MmtpRecordEvent"); - jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(IJIJ)V"); + jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(IJIJII)V"); for (int i = 0; i < events.size(); i++) { auto event = events[i]; @@ -3571,26 +3571,28 @@ static DemuxFilterSettings getFilterConfiguration( .tpid = tpid, }; - DemuxTsFilterType tsType = static_cast<DemuxTsFilterType>(subtype); - switch (tsType) { - case DemuxTsFilterType::SECTION: - tsFilterSettings.filterSettings.section( - getFilterSectionSettings(env, settingsObj)); - break; - case DemuxTsFilterType::AUDIO: - case DemuxTsFilterType::VIDEO: - tsFilterSettings.filterSettings.av(getFilterAvSettings(env, settingsObj)); - break; - case DemuxTsFilterType::PES: - tsFilterSettings.filterSettings.pesData( - getFilterPesDataSettings(env, settingsObj)); - break; - case DemuxTsFilterType::RECORD: - tsFilterSettings.filterSettings.record( - getFilterRecordSettings(env, settingsObj)); - break; - default: - break; + if (settingsObj != NULL) { + DemuxTsFilterType tsType = static_cast<DemuxTsFilterType>(subtype); + switch (tsType) { + case DemuxTsFilterType::SECTION: + tsFilterSettings.filterSettings.section( + getFilterSectionSettings(env, settingsObj)); + break; + case DemuxTsFilterType::AUDIO: + case DemuxTsFilterType::VIDEO: + tsFilterSettings.filterSettings.av(getFilterAvSettings(env, settingsObj)); + break; + case DemuxTsFilterType::PES: + tsFilterSettings.filterSettings.pesData( + getFilterPesDataSettings(env, settingsObj)); + break; + case DemuxTsFilterType::RECORD: + tsFilterSettings.filterSettings.record( + getFilterRecordSettings(env, settingsObj)); + break; + default: + break; + } } filterSettings.ts(tsFilterSettings); break; @@ -3602,60 +3604,55 @@ static DemuxFilterSettings getFilterConfiguration( DemuxMmtpFilterSettings mmtpFilterSettings { .mmtpPid = mmtpPid, }; - DemuxMmtpFilterType mmtpType = static_cast<DemuxMmtpFilterType>(subtype); - switch (mmtpType) { - case DemuxMmtpFilterType::SECTION: - mmtpFilterSettings.filterSettings.section( - getFilterSectionSettings(env, settingsObj)); - break; - case DemuxMmtpFilterType::AUDIO: - case DemuxMmtpFilterType::VIDEO: - mmtpFilterSettings.filterSettings.av(getFilterAvSettings(env, settingsObj)); - break; - case DemuxMmtpFilterType::PES: - mmtpFilterSettings.filterSettings.pesData( - getFilterPesDataSettings(env, settingsObj)); - break; - case DemuxMmtpFilterType::RECORD: - mmtpFilterSettings.filterSettings.record( - getFilterRecordSettings(env, settingsObj)); - break; - case DemuxMmtpFilterType::DOWNLOAD: - mmtpFilterSettings.filterSettings.download( - getFilterDownloadSettings(env, settingsObj)); - break; - default: - break; + + if (settingsObj != NULL) { + DemuxMmtpFilterType mmtpType = static_cast<DemuxMmtpFilterType>(subtype); + switch (mmtpType) { + case DemuxMmtpFilterType::SECTION: + mmtpFilterSettings.filterSettings.section( + getFilterSectionSettings(env, settingsObj)); + break; + case DemuxMmtpFilterType::AUDIO: + case DemuxMmtpFilterType::VIDEO: + mmtpFilterSettings.filterSettings.av(getFilterAvSettings(env, settingsObj)); + break; + case DemuxMmtpFilterType::PES: + mmtpFilterSettings.filterSettings.pesData( + getFilterPesDataSettings(env, settingsObj)); + break; + case DemuxMmtpFilterType::RECORD: + mmtpFilterSettings.filterSettings.record( + getFilterRecordSettings(env, settingsObj)); + break; + case DemuxMmtpFilterType::DOWNLOAD: + mmtpFilterSettings.filterSettings.download( + getFilterDownloadSettings(env, settingsObj)); + break; + default: + break; + } } filterSettings.mmtp(mmtpFilterSettings); break; } case DemuxFilterMainType::IP: { DemuxIpAddress ipAddr = getDemuxIpAddress(env, filterConfigObj); - DemuxIpFilterSettings ipFilterSettings { .ipAddr = ipAddr, }; + DemuxIpFilterType ipType = static_cast<DemuxIpFilterType>(subtype); - switch (ipType) { - case DemuxIpFilterType::SECTION: { - ipFilterSettings.filterSettings.section( - getFilterSectionSettings(env, settingsObj)); - break; - } - case DemuxIpFilterType::IP: { - jclass clazz = env->FindClass( - "android/media/tv/tuner/filter/IpFilterConfiguration"); - bool bPassthrough = static_cast<bool>( - env->GetBooleanField( - filterConfigObj, env->GetFieldID( - clazz, "mPassthrough", "Z"))); - ipFilterSettings.filterSettings.bPassthrough(bPassthrough); - break; - } - default: { - break; - } + if (ipType == DemuxIpFilterType::SECTION && settingsObj != NULL) { + ipFilterSettings.filterSettings.section( + getFilterSectionSettings(env, settingsObj)); + } else if (ipType == DemuxIpFilterType::IP) { + jclass clazz = env->FindClass( + "android/media/tv/tuner/filter/IpFilterConfiguration"); + bool bPassthrough = static_cast<bool>( + env->GetBooleanField( + filterConfigObj, env->GetFieldID( + clazz, "mPassthrough", "Z"))); + ipFilterSettings.filterSettings.bPassthrough(bPassthrough); } filterSettings.ip(ipFilterSettings); break; @@ -3672,24 +3669,17 @@ static DemuxFilterSettings getFilterConfiguration( .packetType = packetType, .isCompressedIpPacket = isCompressedIpPacket, }; + DemuxTlvFilterType tlvType = static_cast<DemuxTlvFilterType>(subtype); - switch (tlvType) { - case DemuxTlvFilterType::SECTION: { - tlvFilterSettings.filterSettings.section( - getFilterSectionSettings(env, settingsObj)); - break; - } - case DemuxTlvFilterType::TLV: { - bool bPassthrough = static_cast<bool>( - env->GetBooleanField( - filterConfigObj, env->GetFieldID( - clazz, "mPassthrough", "Z"))); - tlvFilterSettings.filterSettings.bPassthrough(bPassthrough); - break; - } - default: { - break; - } + if (tlvType == DemuxTlvFilterType::SECTION && settingsObj != NULL) { + tlvFilterSettings.filterSettings.section( + getFilterSectionSettings(env, settingsObj)); + } else if (tlvType == DemuxTlvFilterType::TLV) { + bool bPassthrough = static_cast<bool>( + env->GetBooleanField( + filterConfigObj, env->GetFieldID( + clazz, "mPassthrough", "Z"))); + tlvFilterSettings.filterSettings.bPassthrough(bPassthrough); } filterSettings.tlv(tlvFilterSettings); break; @@ -3704,14 +3694,17 @@ static DemuxFilterSettings getFilterConfiguration( .packetType = packetType, .lengthType = lengthType, }; - DemuxAlpFilterType alpType = static_cast<DemuxAlpFilterType>(subtype); - switch (alpType) { - case DemuxAlpFilterType::SECTION: - alpFilterSettings.filterSettings.section( - getFilterSectionSettings(env, settingsObj)); - break; - default: - break; + + if (settingsObj != NULL) { + DemuxAlpFilterType alpType = static_cast<DemuxAlpFilterType>(subtype); + switch (alpType) { + case DemuxAlpFilterType::SECTION: + alpFilterSettings.filterSettings.section( + getFilterSectionSettings(env, settingsObj)); + break; + default: + break; + } } filterSettings.alp(alpFilterSettings); break; diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java index 28072ca4f113..f1a98adc0ab2 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java @@ -143,7 +143,7 @@ public class CompanionDeviceActivity extends Activity { } static void notifyDevicesChanged() { - if (sInstance != null && !sInstance.isFinishing()) { + if (sInstance != null && sInstance.mDevicesAdapter != null && !sInstance.isFinishing()) { sInstance.mDevicesAdapter.notifyDataSetChanged(); } } diff --git a/packages/Connectivity/framework/Android.bp b/packages/Connectivity/framework/Android.bp index c7e261c93689..86b85e8398ce 100644 --- a/packages/Connectivity/framework/Android.bp +++ b/packages/Connectivity/framework/Android.bp @@ -58,3 +58,28 @@ filegroup { "//packages/modules/Connectivity:__subpackages__", ], } + +java_sdk_library { + name: "framework-connectivity", + api_only: true, + defaults: ["framework-module-defaults"], + // TODO: build against module API + platform_apis: true, + srcs: [ + ":framework-connectivity-sources", + ], + aidl: { + include_dirs: [ + // Include directories for parcelables that are part of the stable API, and need a + // one-line "parcelable X" .aidl declaration to be used in AIDL interfaces. + // TODO(b/180293679): remove these dependencies as they should not be necessary once + // the module builds against API (the parcelable declarations exist in framework.aidl) + "frameworks/base/core/java", // For framework parcelables + "frameworks/native/aidl/binder", // For PersistableBundle.aidl + ], + }, + libs: [ + "unsupportedappusage", + ], + permitted_packages: ["android.net", "com.android.connectivity.aidl"], +} diff --git a/packages/Connectivity/framework/api/current.txt b/packages/Connectivity/framework/api/current.txt new file mode 100644 index 000000000000..31b8fc8ae53a --- /dev/null +++ b/packages/Connectivity/framework/api/current.txt @@ -0,0 +1,470 @@ +// Signature format: 2.0 +package android.net { + + public class CaptivePortal implements android.os.Parcelable { + method public int describeContents(); + method public void ignoreNetwork(); + method public void reportCaptivePortalDismissed(); + method public void writeToParcel(android.os.Parcel, int); + 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); + } + + public abstract static class ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback { + ctor public ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback(); + method public void onConnectivityReportAvailable(@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 final class ConnectivityDiagnosticsManager.ConnectivityReport implements android.os.Parcelable { + ctor public ConnectivityDiagnosticsManager.ConnectivityReport(@NonNull android.net.Network, long, @NonNull android.net.LinkProperties, @NonNull android.net.NetworkCapabilities, @NonNull android.os.PersistableBundle); + method public int describeContents(); + method @NonNull public android.os.PersistableBundle getAdditionalInfo(); + method @NonNull public android.net.LinkProperties getLinkProperties(); + method @NonNull public android.net.Network getNetwork(); + method @NonNull public android.net.NetworkCapabilities getNetworkCapabilities(); + method public long getReportTimestamp(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.ConnectivityDiagnosticsManager.ConnectivityReport> CREATOR; + field public static final String KEY_NETWORK_PROBES_ATTEMPTED_BITMASK = "networkProbesAttempted"; + field public static final String KEY_NETWORK_PROBES_SUCCEEDED_BITMASK = "networkProbesSucceeded"; + field public static final String KEY_NETWORK_VALIDATION_RESULT = "networkValidationResult"; + field public static final int NETWORK_PROBE_DNS = 4; // 0x4 + field public static final int NETWORK_PROBE_FALLBACK = 32; // 0x20 + field public static final int NETWORK_PROBE_HTTP = 8; // 0x8 + field public static final int NETWORK_PROBE_HTTPS = 16; // 0x10 + field public static final int NETWORK_PROBE_PRIVATE_DNS = 64; // 0x40 + field public static final int NETWORK_VALIDATION_RESULT_INVALID = 0; // 0x0 + field public static final int NETWORK_VALIDATION_RESULT_PARTIALLY_VALID = 2; // 0x2 + field public static final int NETWORK_VALIDATION_RESULT_SKIPPED = 3; // 0x3 + field public static final int NETWORK_VALIDATION_RESULT_VALID = 1; // 0x1 + } + + public static final class ConnectivityDiagnosticsManager.DataStallReport implements android.os.Parcelable { + ctor public ConnectivityDiagnosticsManager.DataStallReport(@NonNull android.net.Network, long, int, @NonNull android.net.LinkProperties, @NonNull android.net.NetworkCapabilities, @NonNull android.os.PersistableBundle); + method public int describeContents(); + method public int getDetectionMethod(); + method @NonNull public android.net.LinkProperties getLinkProperties(); + method @NonNull public android.net.Network getNetwork(); + method @NonNull public android.net.NetworkCapabilities getNetworkCapabilities(); + method public long getReportTimestamp(); + method @NonNull public android.os.PersistableBundle getStallDetails(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.ConnectivityDiagnosticsManager.DataStallReport> CREATOR; + field public static final int DETECTION_METHOD_DNS_EVENTS = 1; // 0x1 + field public static final int DETECTION_METHOD_TCP_METRICS = 2; // 0x2 + field public static final String KEY_DNS_CONSECUTIVE_TIMEOUTS = "dnsConsecutiveTimeouts"; + field public static final String KEY_TCP_METRICS_COLLECTION_PERIOD_MILLIS = "tcpMetricsCollectionPeriodMillis"; + field public static final String KEY_TCP_PACKET_FAIL_RATE = "tcpPacketFailRate"; + } + + public class ConnectivityManager { + method public void addDefaultNetworkActiveListener(android.net.ConnectivityManager.OnNetworkActiveListener); + method public boolean bindProcessToNetwork(@Nullable android.net.Network); + method @NonNull public android.net.SocketKeepalive createSocketKeepalive(@NonNull android.net.Network, @NonNull android.net.IpSecManager.UdpEncapsulationSocket, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback); + method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.Network getActiveNetwork(); + method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo getActiveNetworkInfo(); + method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo[] getAllNetworkInfo(); + method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.Network[] getAllNetworks(); + method @Deprecated public boolean getBackgroundDataSetting(); + method @Nullable public android.net.Network getBoundNetworkForProcess(); + method public int getConnectionOwnerUid(int, @NonNull java.net.InetSocketAddress, @NonNull java.net.InetSocketAddress); + method @Nullable public android.net.ProxyInfo getDefaultProxy(); + method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.LinkProperties getLinkProperties(@Nullable android.net.Network); + method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public int getMultipathPreference(@Nullable android.net.Network); + method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkCapabilities getNetworkCapabilities(@Nullable android.net.Network); + method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo getNetworkInfo(int); + method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo getNetworkInfo(@Nullable android.net.Network); + method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public int getNetworkPreference(); + method @Nullable public byte[] getNetworkWatchlistConfigHash(); + method @Deprecated @Nullable public static android.net.Network getProcessDefaultNetwork(); + method public int getRestrictBackgroundStatus(); + method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public boolean isActiveNetworkMetered(); + method public boolean isDefaultNetworkActive(); + method @Deprecated public static boolean isNetworkTypeValid(int); + method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback); + method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); + method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerNetworkCallback(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback); + method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerNetworkCallback(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); + method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerNetworkCallback(@NonNull android.net.NetworkRequest, @NonNull android.app.PendingIntent); + method public void releaseNetworkRequest(@NonNull android.app.PendingIntent); + method public void removeDefaultNetworkActiveListener(@NonNull android.net.ConnectivityManager.OnNetworkActiveListener); + method @Deprecated public void reportBadNetwork(@Nullable android.net.Network); + method public void reportNetworkConnectivity(@Nullable android.net.Network, boolean); + method public boolean requestBandwidthUpdate(@NonNull android.net.Network); + method public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback); + method public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); + method public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, int); + method public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler, int); + method public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.app.PendingIntent); + method @Deprecated public void setNetworkPreference(int); + method @Deprecated public static boolean setProcessDefaultNetwork(@Nullable android.net.Network); + method public void unregisterNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback); + method public void unregisterNetworkCallback(@NonNull android.app.PendingIntent); + field @Deprecated public static final String ACTION_BACKGROUND_DATA_SETTING_CHANGED = "android.net.conn.BACKGROUND_DATA_SETTING_CHANGED"; + field public static final String ACTION_CAPTIVE_PORTAL_SIGN_IN = "android.net.conn.CAPTIVE_PORTAL"; + field public static final String ACTION_RESTRICT_BACKGROUND_CHANGED = "android.net.conn.RESTRICT_BACKGROUND_CHANGED"; + field @Deprecated public static final String CONNECTIVITY_ACTION = "android.net.conn.CONNECTIVITY_CHANGE"; + field @Deprecated public static final int DEFAULT_NETWORK_PREFERENCE = 1; // 0x1 + field public static final String EXTRA_CAPTIVE_PORTAL = "android.net.extra.CAPTIVE_PORTAL"; + field public static final String EXTRA_CAPTIVE_PORTAL_URL = "android.net.extra.CAPTIVE_PORTAL_URL"; + field @Deprecated public static final String EXTRA_EXTRA_INFO = "extraInfo"; + field @Deprecated public static final String EXTRA_IS_FAILOVER = "isFailover"; + field public static final String EXTRA_NETWORK = "android.net.extra.NETWORK"; + field @Deprecated public static final String EXTRA_NETWORK_INFO = "networkInfo"; + field public static final String EXTRA_NETWORK_REQUEST = "android.net.extra.NETWORK_REQUEST"; + field @Deprecated public static final String EXTRA_NETWORK_TYPE = "networkType"; + field public static final String EXTRA_NO_CONNECTIVITY = "noConnectivity"; + field @Deprecated public static final String EXTRA_OTHER_NETWORK_INFO = "otherNetwork"; + field public static final String EXTRA_REASON = "reason"; + field public static final int MULTIPATH_PREFERENCE_HANDOVER = 1; // 0x1 + field public static final int MULTIPATH_PREFERENCE_PERFORMANCE = 4; // 0x4 + field public static final int MULTIPATH_PREFERENCE_RELIABILITY = 2; // 0x2 + field public static final int RESTRICT_BACKGROUND_STATUS_DISABLED = 1; // 0x1 + field public static final int RESTRICT_BACKGROUND_STATUS_ENABLED = 3; // 0x3 + field public static final int RESTRICT_BACKGROUND_STATUS_WHITELISTED = 2; // 0x2 + field @Deprecated public static final int TYPE_BLUETOOTH = 7; // 0x7 + field @Deprecated public static final int TYPE_DUMMY = 8; // 0x8 + field @Deprecated public static final int TYPE_ETHERNET = 9; // 0x9 + field @Deprecated public static final int TYPE_MOBILE = 0; // 0x0 + field @Deprecated public static final int TYPE_MOBILE_DUN = 4; // 0x4 + field @Deprecated public static final int TYPE_MOBILE_HIPRI = 5; // 0x5 + field @Deprecated public static final int TYPE_MOBILE_MMS = 2; // 0x2 + field @Deprecated public static final int TYPE_MOBILE_SUPL = 3; // 0x3 + field @Deprecated public static final int TYPE_VPN = 17; // 0x11 + field @Deprecated public static final int TYPE_WIFI = 1; // 0x1 + field @Deprecated public static final int TYPE_WIMAX = 6; // 0x6 + } + + public static class ConnectivityManager.NetworkCallback { + ctor public ConnectivityManager.NetworkCallback(); + method public void onAvailable(@NonNull android.net.Network); + method public void onBlockedStatusChanged(@NonNull android.net.Network, boolean); + method public void onCapabilitiesChanged(@NonNull android.net.Network, @NonNull android.net.NetworkCapabilities); + method public void onLinkPropertiesChanged(@NonNull android.net.Network, @NonNull android.net.LinkProperties); + method public void onLosing(@NonNull android.net.Network, int); + method public void onLost(@NonNull android.net.Network); + method public void onUnavailable(); + } + + public static interface ConnectivityManager.OnNetworkActiveListener { + method public void onNetworkActive(); + } + + public class DhcpInfo implements android.os.Parcelable { + 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; + field public int ipAddress; + field public int leaseDuration; + field public int netmask; + field public int serverAddress; + } + + public final class DnsResolver { + method @NonNull public static android.net.DnsResolver getInstance(); + method public void query(@Nullable android.net.Network, @NonNull String, int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.Callback<? super java.util.List<java.net.InetAddress>>); + method public void query(@Nullable android.net.Network, @NonNull String, int, int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.Callback<? super java.util.List<java.net.InetAddress>>); + method public void rawQuery(@Nullable android.net.Network, @NonNull byte[], int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.Callback<? super byte[]>); + method public void rawQuery(@Nullable android.net.Network, @NonNull String, int, int, int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.Callback<? super byte[]>); + field public static final int CLASS_IN = 1; // 0x1 + field public static final int ERROR_PARSE = 0; // 0x0 + field public static final int ERROR_SYSTEM = 1; // 0x1 + field public static final int FLAG_EMPTY = 0; // 0x0 + field public static final int FLAG_NO_CACHE_LOOKUP = 4; // 0x4 + field public static final int FLAG_NO_CACHE_STORE = 2; // 0x2 + field public static final int FLAG_NO_RETRY = 1; // 0x1 + field public static final int TYPE_A = 1; // 0x1 + field public static final int TYPE_AAAA = 28; // 0x1c + } + + public static interface DnsResolver.Callback<T> { + method public void onAnswer(@NonNull T, int); + method public void onError(@NonNull android.net.DnsResolver.DnsException); + } + + public static class DnsResolver.DnsException extends java.lang.Exception { + field public final int code; + } + + public class InetAddresses { + method public static boolean isNumericAddress(@NonNull String); + method @NonNull public static java.net.InetAddress parseNumericAddress(@NonNull String); + } + + public final class IpPrefix implements android.os.Parcelable { + method public boolean contains(@NonNull java.net.InetAddress); + method public int describeContents(); + method @NonNull public java.net.InetAddress getAddress(); + method @IntRange(from=0, to=128) public int getPrefixLength(); + method @NonNull public byte[] getRawAddress(); + method public void writeToParcel(android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.IpPrefix> CREATOR; + } + + public class LinkAddress implements android.os.Parcelable { + method public int describeContents(); + method public java.net.InetAddress getAddress(); + method public int getFlags(); + method @IntRange(from=0, to=128) public int getPrefixLength(); + method public int getScope(); + method public void writeToParcel(android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.LinkAddress> CREATOR; + } + + public final class LinkProperties implements android.os.Parcelable { + ctor public LinkProperties(); + method public boolean addRoute(@NonNull android.net.RouteInfo); + method public void clear(); + method public int describeContents(); + method @Nullable public java.net.Inet4Address getDhcpServerAddress(); + method @NonNull public java.util.List<java.net.InetAddress> getDnsServers(); + method @Nullable public String getDomains(); + method @Nullable public android.net.ProxyInfo getHttpProxy(); + method @Nullable public String getInterfaceName(); + method @NonNull public java.util.List<android.net.LinkAddress> getLinkAddresses(); + method public int getMtu(); + method @Nullable public android.net.IpPrefix getNat64Prefix(); + method @Nullable public String getPrivateDnsServerName(); + method @NonNull public java.util.List<android.net.RouteInfo> getRoutes(); + method public boolean isPrivateDnsActive(); + method public boolean isWakeOnLanSupported(); + method public void setDhcpServerAddress(@Nullable java.net.Inet4Address); + method public void setDnsServers(@NonNull java.util.Collection<java.net.InetAddress>); + method public void setDomains(@Nullable String); + method public void setHttpProxy(@Nullable android.net.ProxyInfo); + method public void setInterfaceName(@Nullable String); + method public void setLinkAddresses(@NonNull java.util.Collection<android.net.LinkAddress>); + method public void setMtu(int); + method public void setNat64Prefix(@Nullable android.net.IpPrefix); + method public void writeToParcel(android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.LinkProperties> CREATOR; + } + + public final class MacAddress implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public static android.net.MacAddress fromBytes(@NonNull byte[]); + method @NonNull public static android.net.MacAddress fromString(@NonNull String); + method public int getAddressType(); + method @Nullable public java.net.Inet6Address getLinkLocalIpv6FromEui48Mac(); + method public boolean isLocallyAssigned(); + method public boolean matches(@NonNull android.net.MacAddress, @NonNull android.net.MacAddress); + method @NonNull public byte[] toByteArray(); + method @NonNull public String toOuiString(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.net.MacAddress BROADCAST_ADDRESS; + field @NonNull public static final android.os.Parcelable.Creator<android.net.MacAddress> CREATOR; + field public static final int TYPE_BROADCAST = 3; // 0x3 + field public static final int TYPE_MULTICAST = 2; // 0x2 + field public static final int TYPE_UNICAST = 1; // 0x1 + } + + public class Network implements android.os.Parcelable { + method public void bindSocket(java.net.DatagramSocket) throws java.io.IOException; + method public void bindSocket(java.net.Socket) throws java.io.IOException; + method public void bindSocket(java.io.FileDescriptor) throws java.io.IOException; + method public int describeContents(); + method public static android.net.Network fromNetworkHandle(long); + method public java.net.InetAddress[] getAllByName(String) throws java.net.UnknownHostException; + method public java.net.InetAddress getByName(String) throws java.net.UnknownHostException; + method public long getNetworkHandle(); + method public javax.net.SocketFactory getSocketFactory(); + method public java.net.URLConnection openConnection(java.net.URL) throws java.io.IOException; + method public java.net.URLConnection openConnection(java.net.URL, java.net.Proxy) throws java.io.IOException; + method public void writeToParcel(android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.Network> CREATOR; + } + + public final class NetworkCapabilities implements android.os.Parcelable { + ctor public NetworkCapabilities(); + ctor public NetworkCapabilities(android.net.NetworkCapabilities); + method public int describeContents(); + method public int getLinkDownstreamBandwidthKbps(); + method public int getLinkUpstreamBandwidthKbps(); + method @Nullable public android.net.NetworkSpecifier getNetworkSpecifier(); + method public int getOwnerUid(); + method public int getSignalStrength(); + method @Nullable public android.net.TransportInfo getTransportInfo(); + method public boolean hasCapability(int); + method public boolean hasTransport(int); + method public void writeToParcel(android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkCapabilities> CREATOR; + field public static final int NET_CAPABILITY_CAPTIVE_PORTAL = 17; // 0x11 + field public static final int NET_CAPABILITY_CBS = 5; // 0x5 + field public static final int NET_CAPABILITY_DUN = 2; // 0x2 + field public static final int NET_CAPABILITY_EIMS = 10; // 0xa + field public static final int NET_CAPABILITY_ENTERPRISE = 29; // 0x1d + field public static final int NET_CAPABILITY_FOREGROUND = 19; // 0x13 + field public static final int NET_CAPABILITY_FOTA = 3; // 0x3 + field public static final int NET_CAPABILITY_IA = 7; // 0x7 + field public static final int NET_CAPABILITY_IMS = 4; // 0x4 + field public static final int NET_CAPABILITY_INTERNET = 12; // 0xc + field public static final int NET_CAPABILITY_MCX = 23; // 0x17 + field public static final int NET_CAPABILITY_MMS = 0; // 0x0 + field public static final int NET_CAPABILITY_NOT_CONGESTED = 20; // 0x14 + field public static final int NET_CAPABILITY_NOT_METERED = 11; // 0xb + field public static final int NET_CAPABILITY_NOT_RESTRICTED = 13; // 0xd + field public static final int NET_CAPABILITY_NOT_ROAMING = 18; // 0x12 + field public static final int NET_CAPABILITY_NOT_SUSPENDED = 21; // 0x15 + field public static final int NET_CAPABILITY_NOT_VPN = 15; // 0xf + field public static final int NET_CAPABILITY_RCS = 8; // 0x8 + field public static final int NET_CAPABILITY_SUPL = 1; // 0x1 + field public static final int NET_CAPABILITY_TEMPORARILY_NOT_METERED = 25; // 0x19 + field public static final int NET_CAPABILITY_TRUSTED = 14; // 0xe + field public static final int NET_CAPABILITY_VALIDATED = 16; // 0x10 + field public static final int NET_CAPABILITY_WIFI_P2P = 6; // 0x6 + field public static final int NET_CAPABILITY_XCAP = 9; // 0x9 + field public static final int SIGNAL_STRENGTH_UNSPECIFIED = -2147483648; // 0x80000000 + field public static final int TRANSPORT_BLUETOOTH = 2; // 0x2 + field public static final int TRANSPORT_CELLULAR = 0; // 0x0 + field public static final int TRANSPORT_ETHERNET = 3; // 0x3 + field public static final int TRANSPORT_LOWPAN = 6; // 0x6 + field public static final int TRANSPORT_VPN = 4; // 0x4 + field public static final int TRANSPORT_WIFI = 1; // 0x1 + field public static final int TRANSPORT_WIFI_AWARE = 5; // 0x5 + } + + @Deprecated public class NetworkInfo implements android.os.Parcelable { + ctor @Deprecated public NetworkInfo(int, int, @Nullable String, @Nullable String); + method @Deprecated public int describeContents(); + method @Deprecated @NonNull public android.net.NetworkInfo.DetailedState getDetailedState(); + method @Deprecated public String getExtraInfo(); + method @Deprecated public String getReason(); + method @Deprecated public android.net.NetworkInfo.State getState(); + method @Deprecated public int getSubtype(); + method @Deprecated public String getSubtypeName(); + method @Deprecated public int getType(); + method @Deprecated public String getTypeName(); + method @Deprecated public boolean isAvailable(); + method @Deprecated public boolean isConnected(); + method @Deprecated public boolean isConnectedOrConnecting(); + method @Deprecated public boolean isFailover(); + method @Deprecated public boolean isRoaming(); + method @Deprecated public void setDetailedState(@NonNull android.net.NetworkInfo.DetailedState, @Nullable String, @Nullable String); + method @Deprecated public void writeToParcel(android.os.Parcel, int); + field @Deprecated @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkInfo> CREATOR; + } + + @Deprecated public enum NetworkInfo.DetailedState { + enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState AUTHENTICATING; + enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState BLOCKED; + enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState CAPTIVE_PORTAL_CHECK; + enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState CONNECTED; + enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState CONNECTING; + enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState DISCONNECTED; + enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState DISCONNECTING; + enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState FAILED; + enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState IDLE; + enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState OBTAINING_IPADDR; + enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState SCANNING; + enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState SUSPENDED; + enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState VERIFYING_POOR_LINK; + } + + @Deprecated public enum NetworkInfo.State { + enum_constant @Deprecated public static final android.net.NetworkInfo.State CONNECTED; + enum_constant @Deprecated public static final android.net.NetworkInfo.State CONNECTING; + enum_constant @Deprecated public static final android.net.NetworkInfo.State DISCONNECTED; + enum_constant @Deprecated public static final android.net.NetworkInfo.State DISCONNECTING; + enum_constant @Deprecated public static final android.net.NetworkInfo.State SUSPENDED; + enum_constant @Deprecated public static final android.net.NetworkInfo.State UNKNOWN; + } + + public class NetworkRequest implements android.os.Parcelable { + method public boolean canBeSatisfiedBy(@Nullable android.net.NetworkCapabilities); + method public int describeContents(); + method @Nullable public android.net.NetworkSpecifier getNetworkSpecifier(); + method public boolean hasCapability(int); + method public boolean hasTransport(int); + method public void writeToParcel(android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkRequest> CREATOR; + } + + public static class NetworkRequest.Builder { + ctor public NetworkRequest.Builder(); + 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 @Deprecated public android.net.NetworkRequest.Builder setNetworkSpecifier(String); + method public android.net.NetworkRequest.Builder setNetworkSpecifier(android.net.NetworkSpecifier); + } + + public final class Proxy { + ctor public Proxy(); + method @Deprecated public static String getDefaultHost(); + method @Deprecated public static int getDefaultPort(); + method @Deprecated public static String getHost(android.content.Context); + method @Deprecated public static int getPort(android.content.Context); + field @Deprecated public static final String EXTRA_PROXY_INFO = "android.intent.extra.PROXY_INFO"; + field public static final String PROXY_CHANGE_ACTION = "android.intent.action.PROXY_CHANGE"; + } + + public class ProxyInfo implements android.os.Parcelable { + ctor public ProxyInfo(@Nullable android.net.ProxyInfo); + method public static android.net.ProxyInfo buildDirectProxy(String, int); + method public static android.net.ProxyInfo buildDirectProxy(String, int, java.util.List<java.lang.String>); + method public static android.net.ProxyInfo buildPacProxy(android.net.Uri); + method @NonNull public static android.net.ProxyInfo buildPacProxy(@NonNull android.net.Uri, int); + method public int describeContents(); + method public String[] getExclusionList(); + method public String getHost(); + method public android.net.Uri getPacFileUrl(); + method public int getPort(); + method public boolean isValid(); + method public void writeToParcel(android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.ProxyInfo> CREATOR; + } + + public final class RouteInfo implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public android.net.IpPrefix getDestination(); + method @Nullable public java.net.InetAddress getGateway(); + method @Nullable public String getInterface(); + method public boolean hasGateway(); + method public boolean isDefaultRoute(); + method public boolean matches(java.net.InetAddress); + method public void writeToParcel(android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.RouteInfo> CREATOR; + } + + public abstract class SocketKeepalive implements java.lang.AutoCloseable { + method public final void close(); + method public final void start(@IntRange(from=0xa, to=0xe10) int); + method public final void stop(); + field public static final int ERROR_HARDWARE_ERROR = -31; // 0xffffffe1 + field public static final int ERROR_INSUFFICIENT_RESOURCES = -32; // 0xffffffe0 + field public static final int ERROR_INVALID_INTERVAL = -24; // 0xffffffe8 + field public static final int ERROR_INVALID_IP_ADDRESS = -21; // 0xffffffeb + field public static final int ERROR_INVALID_LENGTH = -23; // 0xffffffe9 + field public static final int ERROR_INVALID_NETWORK = -20; // 0xffffffec + field public static final int ERROR_INVALID_PORT = -22; // 0xffffffea + field public static final int ERROR_INVALID_SOCKET = -25; // 0xffffffe7 + field public static final int ERROR_SOCKET_NOT_IDLE = -26; // 0xffffffe6 + field public static final int ERROR_UNSUPPORTED = -30; // 0xffffffe2 + } + + public static class SocketKeepalive.Callback { + ctor public SocketKeepalive.Callback(); + method public void onDataReceived(); + method public void onError(int); + method public void onStarted(); + method public void onStopped(); + } + + public interface TransportInfo { + } + +} + diff --git a/packages/Connectivity/framework/api/lint-baseline.txt b/packages/Connectivity/framework/api/lint-baseline.txt new file mode 100644 index 000000000000..2f4004ab723d --- /dev/null +++ b/packages/Connectivity/framework/api/lint-baseline.txt @@ -0,0 +1,4 @@ +// Baseline format: 1.0 +VisiblySynchronized: android.net.NetworkInfo#toString(): + Internal locks must not be exposed (synchronizing on this or class is still + externally observable): method android.net.NetworkInfo.toString() diff --git a/packages/Connectivity/framework/api/module-lib-current.txt b/packages/Connectivity/framework/api/module-lib-current.txt new file mode 100644 index 000000000000..3af855ec1e11 --- /dev/null +++ b/packages/Connectivity/framework/api/module-lib-current.txt @@ -0,0 +1,66 @@ +// Signature format: 2.0 +package android.net { + + public final class ConnectivityFrameworkInitializer { + method public static void registerServiceWrappers(); + } + + public class ConnectivityManager { + method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); + method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @Nullable android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback); + method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle); + } + + public final class NetworkAgentConfig implements android.os.Parcelable { + method @Nullable public String getSubscriberId(); + } + + public static final class NetworkAgentConfig.Builder { + method @NonNull public android.net.NetworkAgentConfig.Builder setSubscriberId(@Nullable String); + } + + public final class NetworkCapabilities implements android.os.Parcelable { + field public static final int TRANSPORT_TEST = 7; // 0x7 + } + + public final class Proxy { + method public static void setHttpProxyConfiguration(@Nullable android.net.ProxyInfo); + } + + public final class TcpRepairWindow { + ctor public TcpRepairWindow(int, int, int, int, int, int); + field public final int maxWindow; + field public final int rcvWnd; + field public final int rcvWndScale; + field public final int rcvWup; + field public final int sndWl1; + field public final int sndWnd; + } + + public final class TestNetworkInterface implements android.os.Parcelable { + ctor public TestNetworkInterface(@NonNull android.os.ParcelFileDescriptor, @NonNull String); + method public int describeContents(); + method @NonNull public android.os.ParcelFileDescriptor getFileDescriptor(); + method @NonNull public String getInterfaceName(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.TestNetworkInterface> CREATOR; + } + + public class TestNetworkManager { + method @NonNull public android.net.TestNetworkInterface createTapInterface(); + method @NonNull public android.net.TestNetworkInterface createTunInterface(@NonNull java.util.Collection<android.net.LinkAddress>); + method public void setupTestNetwork(@NonNull String, @NonNull android.os.IBinder); + method public void teardownTestNetwork(@NonNull android.net.Network); + field public static final String TEST_TAP_PREFIX = "testtap"; + } + + public final class VpnTransportInfo implements android.os.Parcelable android.net.TransportInfo { + ctor public VpnTransportInfo(int); + method public int describeContents(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.VpnTransportInfo> CREATOR; + field public final int type; + } + +} + diff --git a/packages/Connectivity/framework/api/module-lib-removed.txt b/packages/Connectivity/framework/api/module-lib-removed.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/packages/Connectivity/framework/api/module-lib-removed.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/packages/Connectivity/framework/api/removed.txt b/packages/Connectivity/framework/api/removed.txt new file mode 100644 index 000000000000..303a1e6173ba --- /dev/null +++ b/packages/Connectivity/framework/api/removed.txt @@ -0,0 +1,11 @@ +// Signature format: 2.0 +package android.net { + + public class ConnectivityManager { + method @Deprecated public boolean requestRouteToHost(int, int); + method @Deprecated public int startUsingNetworkFeature(int, String); + method @Deprecated public int stopUsingNetworkFeature(int, String); + } + +} + diff --git a/packages/Connectivity/framework/api/system-current.txt b/packages/Connectivity/framework/api/system-current.txt new file mode 100644 index 000000000000..41ebc5774f3d --- /dev/null +++ b/packages/Connectivity/framework/api/system-current.txt @@ -0,0 +1,407 @@ +// Signature format: 2.0 +package android.net { + + public class CaptivePortal implements android.os.Parcelable { + method public void logEvent(int, @NonNull String); + method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void reevaluateNetwork(); + method public void useNetwork(); + field public static final int APP_REQUEST_REEVALUATION_REQUIRED = 100; // 0x64 + field public static final int APP_RETURN_DISMISSED = 0; // 0x0 + field public static final int APP_RETURN_UNWANTED = 1; // 0x1 + field public static final int APP_RETURN_WANTED_AS_IS = 2; // 0x2 + } + + public final class CaptivePortalData implements android.os.Parcelable { + method public int describeContents(); + method public long getByteLimit(); + method public long getExpiryTimeMillis(); + method public long getRefreshTimeMillis(); + method @Nullable public android.net.Uri getUserPortalUrl(); + method public int getUserPortalUrlSource(); + method @Nullable public String getVenueFriendlyName(); + method @Nullable public android.net.Uri getVenueInfoUrl(); + method public int getVenueInfoUrlSource(); + method public boolean isCaptive(); + method public boolean isSessionExtendable(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field public static final int CAPTIVE_PORTAL_DATA_SOURCE_OTHER = 0; // 0x0 + field public static final int CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT = 1; // 0x1 + field @NonNull public static final android.os.Parcelable.Creator<android.net.CaptivePortalData> CREATOR; + } + + public static class CaptivePortalData.Builder { + ctor public CaptivePortalData.Builder(); + ctor public CaptivePortalData.Builder(@Nullable android.net.CaptivePortalData); + method @NonNull public android.net.CaptivePortalData build(); + method @NonNull public android.net.CaptivePortalData.Builder setBytesRemaining(long); + method @NonNull public android.net.CaptivePortalData.Builder setCaptive(boolean); + method @NonNull public android.net.CaptivePortalData.Builder setExpiryTime(long); + method @NonNull public android.net.CaptivePortalData.Builder setRefreshTime(long); + method @NonNull public android.net.CaptivePortalData.Builder setSessionExtendable(boolean); + method @NonNull public android.net.CaptivePortalData.Builder setUserPortalUrl(@Nullable android.net.Uri); + method @NonNull public android.net.CaptivePortalData.Builder setUserPortalUrl(@Nullable android.net.Uri, int); + method @NonNull public android.net.CaptivePortalData.Builder setVenueFriendlyName(@Nullable String); + method @NonNull public android.net.CaptivePortalData.Builder setVenueInfoUrl(@Nullable android.net.Uri); + method @NonNull public android.net.CaptivePortalData.Builder setVenueInfoUrl(@Nullable android.net.Uri, int); + } + + public class ConnectivityManager { + method @NonNull @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD) public android.net.SocketKeepalive createNattKeepalive(@NonNull android.net.Network, @NonNull android.os.ParcelFileDescriptor, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback); + method @NonNull @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD) public android.net.SocketKeepalive createSocketKeepalive(@NonNull android.net.Network, @NonNull java.net.Socket, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback); + method @Deprecated @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public String getCaptivePortalServerUrl(); + method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void getLatestTetheringEntitlementResult(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEntitlementResultListener); + method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public boolean isTetheringSupported(); + method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_FACTORY}) public int registerNetworkProvider(@NonNull android.net.NetworkProvider); + method public void registerQosCallback(@NonNull android.net.QosSocketInfo, @NonNull android.net.QosCallback, @NonNull java.util.concurrent.Executor); + method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEventCallback); + method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void requestNetwork(@NonNull android.net.NetworkRequest, int, int, @NonNull android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback); + 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(android.Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE) public void setOemNetworkPreference(@NonNull android.net.OemNetworkPreferences, @Nullable java.util.concurrent.Executor, @Nullable android.net.ConnectivityManager.OnSetOemNetworkPreferenceListener); + method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public boolean shouldAvoidBadWifi(); + method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void startCaptivePortalApp(@NonNull android.net.Network, @NonNull android.os.Bundle); + method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback); + method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback, android.os.Handler); + method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void stopTethering(int); + method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_FACTORY}) public void unregisterNetworkProvider(@NonNull android.net.NetworkProvider); + method public void unregisterQosCallback(@NonNull android.net.QosCallback); + method @Deprecated @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"; + field public static final int TETHERING_BLUETOOTH = 2; // 0x2 + field public static final int TETHERING_USB = 1; // 0x1 + field public static final int TETHERING_WIFI = 0; // 0x0 + field @Deprecated public static final int TETHER_ERROR_ENTITLEMENT_UNKONWN = 13; // 0xd + field @Deprecated public static final int TETHER_ERROR_NO_ERROR = 0; // 0x0 + field @Deprecated public static final int TETHER_ERROR_PROVISION_FAILED = 11; // 0xb + field public static final int TYPE_NONE = -1; // 0xffffffff + field @Deprecated public static final int TYPE_PROXY = 16; // 0x10 + field @Deprecated public static final int TYPE_WIFI_P2P = 13; // 0xd + } + + public static interface ConnectivityManager.OnSetOemNetworkPreferenceListener { + method public void onComplete(); + } + + @Deprecated public abstract static class ConnectivityManager.OnStartTetheringCallback { + ctor @Deprecated public ConnectivityManager.OnStartTetheringCallback(); + method @Deprecated public void onTetheringFailed(); + method @Deprecated public void onTetheringStarted(); + } + + @Deprecated public static interface ConnectivityManager.OnTetheringEntitlementResultListener { + method @Deprecated public void onTetheringEntitlementResult(int); + } + + @Deprecated public abstract static class ConnectivityManager.OnTetheringEventCallback { + ctor @Deprecated public ConnectivityManager.OnTetheringEventCallback(); + method @Deprecated public void onUpstreamChanged(@Nullable android.net.Network); + } + + public final class InvalidPacketException extends java.lang.Exception { + ctor public InvalidPacketException(int); + method public int getError(); + field public static final int ERROR_INVALID_IP_ADDRESS = -21; // 0xffffffeb + field public static final int ERROR_INVALID_LENGTH = -23; // 0xffffffe9 + field public static final int ERROR_INVALID_PORT = -22; // 0xffffffea + } + + public final class IpConfiguration implements android.os.Parcelable { + ctor public IpConfiguration(); + ctor public IpConfiguration(@NonNull android.net.IpConfiguration); + method public int describeContents(); + method @Nullable public android.net.ProxyInfo getHttpProxy(); + method @NonNull public android.net.IpConfiguration.IpAssignment getIpAssignment(); + method @NonNull public android.net.IpConfiguration.ProxySettings getProxySettings(); + method @Nullable public android.net.StaticIpConfiguration getStaticIpConfiguration(); + method public void setHttpProxy(@Nullable android.net.ProxyInfo); + method public void setIpAssignment(@NonNull android.net.IpConfiguration.IpAssignment); + method public void setProxySettings(@NonNull android.net.IpConfiguration.ProxySettings); + method public void setStaticIpConfiguration(@Nullable android.net.StaticIpConfiguration); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.IpConfiguration> CREATOR; + } + + public enum IpConfiguration.IpAssignment { + enum_constant public static final android.net.IpConfiguration.IpAssignment DHCP; + enum_constant public static final android.net.IpConfiguration.IpAssignment STATIC; + enum_constant public static final android.net.IpConfiguration.IpAssignment UNASSIGNED; + } + + public enum IpConfiguration.ProxySettings { + enum_constant public static final android.net.IpConfiguration.ProxySettings NONE; + enum_constant public static final android.net.IpConfiguration.ProxySettings PAC; + enum_constant public static final android.net.IpConfiguration.ProxySettings STATIC; + enum_constant public static final android.net.IpConfiguration.ProxySettings UNASSIGNED; + } + + public final class IpPrefix implements android.os.Parcelable { + ctor public IpPrefix(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int); + ctor public IpPrefix(@NonNull String); + } + + public class KeepalivePacketData { + ctor protected KeepalivePacketData(@NonNull java.net.InetAddress, @IntRange(from=0, to=65535) int, @NonNull java.net.InetAddress, @IntRange(from=0, to=65535) int, @NonNull byte[]) throws android.net.InvalidPacketException; + method @NonNull public java.net.InetAddress getDstAddress(); + method public int getDstPort(); + method @NonNull public byte[] getPacket(); + method @NonNull public java.net.InetAddress getSrcAddress(); + method public int getSrcPort(); + } + + public class LinkAddress implements android.os.Parcelable { + ctor public LinkAddress(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int, int, int); + ctor public LinkAddress(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int, int, int, long, long); + ctor public LinkAddress(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int); + ctor public LinkAddress(@NonNull String); + ctor public LinkAddress(@NonNull String, int, int); + method public long getDeprecationTime(); + method public long getExpirationTime(); + method public boolean isGlobalPreferred(); + method public boolean isIpv4(); + method public boolean isIpv6(); + method public boolean isSameAddressAs(@Nullable android.net.LinkAddress); + field public static final long LIFETIME_PERMANENT = 9223372036854775807L; // 0x7fffffffffffffffL + field public static final long LIFETIME_UNKNOWN = -1L; // 0xffffffffffffffffL + } + + public final class LinkProperties implements android.os.Parcelable { + ctor public LinkProperties(@Nullable android.net.LinkProperties); + ctor public LinkProperties(@Nullable android.net.LinkProperties, boolean); + method public boolean addDnsServer(@NonNull java.net.InetAddress); + method public boolean addLinkAddress(@NonNull android.net.LinkAddress); + method public boolean addPcscfServer(@NonNull java.net.InetAddress); + method @NonNull public java.util.List<java.net.InetAddress> getAddresses(); + method @NonNull public java.util.List<java.lang.String> getAllInterfaceNames(); + method @NonNull public java.util.List<android.net.LinkAddress> getAllLinkAddresses(); + method @NonNull public java.util.List<android.net.RouteInfo> getAllRoutes(); + method @Nullable public android.net.Uri getCaptivePortalApiUrl(); + method @Nullable public android.net.CaptivePortalData getCaptivePortalData(); + method @NonNull public java.util.List<java.net.InetAddress> getPcscfServers(); + method @Nullable public String getTcpBufferSizes(); + method @NonNull public java.util.List<java.net.InetAddress> getValidatedPrivateDnsServers(); + method public boolean hasGlobalIpv6Address(); + method public boolean hasIpv4Address(); + method public boolean hasIpv4DefaultRoute(); + method public boolean hasIpv4DnsServer(); + method public boolean hasIpv6DefaultRoute(); + method public boolean hasIpv6DnsServer(); + method public boolean isIpv4Provisioned(); + method public boolean isIpv6Provisioned(); + method public boolean isProvisioned(); + method public boolean isReachable(@NonNull java.net.InetAddress); + method public boolean removeDnsServer(@NonNull java.net.InetAddress); + method public boolean removeLinkAddress(@NonNull android.net.LinkAddress); + method public boolean removeRoute(@NonNull android.net.RouteInfo); + method public void setCaptivePortalApiUrl(@Nullable android.net.Uri); + method public void setCaptivePortalData(@Nullable android.net.CaptivePortalData); + method public void setPcscfServers(@NonNull java.util.Collection<java.net.InetAddress>); + method public void setPrivateDnsServerName(@Nullable String); + method public void setTcpBufferSizes(@Nullable String); + method public void setUsePrivateDns(boolean); + method public void setValidatedPrivateDnsServers(@NonNull java.util.Collection<java.net.InetAddress>); + } + + public final class NattKeepalivePacketData extends android.net.KeepalivePacketData implements android.os.Parcelable { + ctor public NattKeepalivePacketData(@NonNull java.net.InetAddress, int, @NonNull java.net.InetAddress, int, @NonNull byte[]) throws android.net.InvalidPacketException; + method public int describeContents(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.NattKeepalivePacketData> CREATOR; + } + + public class Network implements android.os.Parcelable { + ctor public Network(@NonNull android.net.Network); + method public int getNetId(); + method @NonNull public android.net.Network getPrivateDnsBypassingCopy(); + } + + public abstract class NetworkAgent { + ctor public NetworkAgent(@NonNull android.content.Context, @NonNull android.os.Looper, @NonNull String, @NonNull android.net.NetworkCapabilities, @NonNull android.net.LinkProperties, int, @NonNull android.net.NetworkAgentConfig, @Nullable android.net.NetworkProvider); + method @Nullable public android.net.Network getNetwork(); + method public void markConnected(); + method public void onAddKeepalivePacketFilter(int, @NonNull android.net.KeepalivePacketData); + method public void onAutomaticReconnectDisabled(); + method public void onNetworkUnwanted(); + method public void onQosCallbackRegistered(int, @NonNull android.net.QosFilter); + method public void onQosCallbackUnregistered(int); + method public void onRemoveKeepalivePacketFilter(int); + method public void onSaveAcceptUnvalidated(boolean); + method public void onSignalStrengthThresholdsUpdated(@NonNull int[]); + method public void onStartSocketKeepalive(int, @NonNull java.time.Duration, @NonNull android.net.KeepalivePacketData); + method public void onStopSocketKeepalive(int); + method public void onValidationStatus(int, @Nullable android.net.Uri); + method @NonNull public android.net.Network register(); + method public final void sendLinkProperties(@NonNull android.net.LinkProperties); + method public final void sendNetworkCapabilities(@NonNull android.net.NetworkCapabilities); + method public final void sendNetworkScore(@IntRange(from=0, to=99) int); + method public final void sendQosCallbackError(int, int); + method public final void sendQosSessionAvailable(int, int, @NonNull android.telephony.data.EpsBearerQosSessionAttributes); + method public final void sendQosSessionLost(int, int); + method public final void sendSocketKeepaliveEvent(int, int); + method public final void setUnderlyingNetworks(@Nullable java.util.List<android.net.Network>); + method public void unregister(); + field public static final int VALIDATION_STATUS_NOT_VALID = 2; // 0x2 + field public static final int VALIDATION_STATUS_VALID = 1; // 0x1 + } + + public final class NetworkAgentConfig implements android.os.Parcelable { + method public int describeContents(); + method public int getLegacyType(); + method @NonNull public String getLegacyTypeName(); + method public boolean isExplicitlySelected(); + method public boolean isPartialConnectivityAcceptable(); + method public boolean isUnvalidatedConnectivityAcceptable(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkAgentConfig> CREATOR; + } + + public static final class NetworkAgentConfig.Builder { + ctor public NetworkAgentConfig.Builder(); + method @NonNull public android.net.NetworkAgentConfig build(); + method @NonNull public android.net.NetworkAgentConfig.Builder setExplicitlySelected(boolean); + method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyType(int); + method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyTypeName(@NonNull String); + method @NonNull public android.net.NetworkAgentConfig.Builder setPartialConnectivityAcceptable(boolean); + method @NonNull public android.net.NetworkAgentConfig.Builder setUnvalidatedConnectivityAcceptable(boolean); + } + + public final class NetworkCapabilities implements android.os.Parcelable { + ctor public NetworkCapabilities(@Nullable android.net.NetworkCapabilities, boolean); + method @NonNull public int[] getAdministratorUids(); + method @Nullable public String getSsid(); + method @NonNull public int[] getTransportTypes(); + method public boolean isPrivateDnsBroken(); + method public boolean satisfiedByNetworkCapabilities(@Nullable android.net.NetworkCapabilities); + field public static final int NET_CAPABILITY_NOT_VCN_MANAGED = 28; // 0x1c + field public static final int NET_CAPABILITY_OEM_PAID = 22; // 0x16 + field public static final int NET_CAPABILITY_OEM_PRIVATE = 26; // 0x1a + field public static final int NET_CAPABILITY_PARTIAL_CONNECTIVITY = 24; // 0x18 + field public static final int NET_CAPABILITY_VEHICLE_INTERNAL = 27; // 0x1b + } + + public static final class NetworkCapabilities.Builder { + ctor public NetworkCapabilities.Builder(); + ctor public NetworkCapabilities.Builder(@NonNull android.net.NetworkCapabilities); + method @NonNull public android.net.NetworkCapabilities.Builder addCapability(int); + method @NonNull public android.net.NetworkCapabilities.Builder addTransportType(int); + method @NonNull public android.net.NetworkCapabilities build(); + method @NonNull public android.net.NetworkCapabilities.Builder clearAll(); + method @NonNull public android.net.NetworkCapabilities.Builder removeCapability(int); + method @NonNull public android.net.NetworkCapabilities.Builder removeTransportType(int); + method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setAdministratorUids(@NonNull int[]); + method @NonNull public android.net.NetworkCapabilities.Builder setLinkDownstreamBandwidthKbps(int); + method @NonNull public android.net.NetworkCapabilities.Builder setLinkUpstreamBandwidthKbps(int); + method @NonNull public android.net.NetworkCapabilities.Builder setNetworkSpecifier(@Nullable android.net.NetworkSpecifier); + method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setOwnerUid(int); + method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setRequestorPackageName(@Nullable String); + method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setRequestorUid(int); + method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) public android.net.NetworkCapabilities.Builder setSignalStrength(int); + method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setSsid(@Nullable String); + method @NonNull public android.net.NetworkCapabilities.Builder setTransportInfo(@Nullable android.net.TransportInfo); + } + + 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 public int getProviderId(); + method public void onNetworkRequestWithdrawn(@NonNull android.net.NetworkRequest); + method public void onNetworkRequested(@NonNull android.net.NetworkRequest, @IntRange(from=0, to=99) int, int); + field public static final int ID_NONE = -1; // 0xffffffff + } + + public class NetworkRequest implements android.os.Parcelable { + method @Nullable public String getRequestorPackageName(); + method public int getRequestorUid(); + } + + public static class NetworkRequest.Builder { + method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) public android.net.NetworkRequest.Builder setSignalStrength(int); + } + + public final class RouteInfo implements android.os.Parcelable { + ctor public RouteInfo(@Nullable android.net.IpPrefix, @Nullable java.net.InetAddress, @Nullable String, int); + ctor public RouteInfo(@Nullable android.net.IpPrefix, @Nullable java.net.InetAddress, @Nullable String, int, int); + method public int getMtu(); + method public int getType(); + field public static final int RTN_THROW = 9; // 0x9 + field public static final int RTN_UNICAST = 1; // 0x1 + field public static final int RTN_UNREACHABLE = 7; // 0x7 + } + + public abstract class SocketKeepalive implements java.lang.AutoCloseable { + field public static final int SUCCESS = 0; // 0x0 + } + + public final class StaticIpConfiguration implements android.os.Parcelable { + ctor public StaticIpConfiguration(); + ctor public StaticIpConfiguration(@Nullable android.net.StaticIpConfiguration); + method public void addDnsServer(@NonNull java.net.InetAddress); + method public void clear(); + method public int describeContents(); + method @NonNull public java.util.List<java.net.InetAddress> getDnsServers(); + method @Nullable public String getDomains(); + method @Nullable public java.net.InetAddress getGateway(); + method @Nullable public android.net.LinkAddress getIpAddress(); + method @NonNull public java.util.List<android.net.RouteInfo> getRoutes(@Nullable String); + method public void writeToParcel(android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.StaticIpConfiguration> CREATOR; + } + + public static final class StaticIpConfiguration.Builder { + ctor public StaticIpConfiguration.Builder(); + method @NonNull public android.net.StaticIpConfiguration build(); + method @NonNull public android.net.StaticIpConfiguration.Builder setDnsServers(@NonNull Iterable<java.net.InetAddress>); + method @NonNull public android.net.StaticIpConfiguration.Builder setDomains(@Nullable String); + method @NonNull public android.net.StaticIpConfiguration.Builder setGateway(@Nullable java.net.InetAddress); + method @NonNull public android.net.StaticIpConfiguration.Builder setIpAddress(@Nullable android.net.LinkAddress); + } + + public final class TcpKeepalivePacketData extends android.net.KeepalivePacketData implements android.os.Parcelable { + ctor public TcpKeepalivePacketData(@NonNull java.net.InetAddress, int, @NonNull java.net.InetAddress, int, @NonNull byte[], int, int, int, int, int, int) throws android.net.InvalidPacketException; + method public int describeContents(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.TcpKeepalivePacketData> CREATOR; + field public final int ipTos; + field public final int ipTtl; + field public final int tcpAck; + field public final int tcpSeq; + field public final int tcpWindow; + field public final int tcpWindowScale; + } + + public interface TransportInfo { + method public default boolean hasLocationSensitiveFields(); + method @NonNull public default android.net.TransportInfo makeCopy(boolean); + } + +} + +package android.net.apf { + + public final class ApfCapabilities implements android.os.Parcelable { + ctor public ApfCapabilities(int, int, int); + method public int describeContents(); + method public static boolean getApfDrop8023Frames(); + method @NonNull public static int[] getApfEtherTypeBlackList(); + method public boolean hasDataAccess(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.net.apf.ApfCapabilities> CREATOR; + field public final int apfPacketFormat; + field public final int apfVersionSupported; + field public final int maximumApfProgramSize; + } + +} + +package android.net.util { + + public final class SocketUtils { + method public static void bindSocketToInterface(@NonNull java.io.FileDescriptor, @NonNull String) throws android.system.ErrnoException; + method public static void closeSocket(@Nullable java.io.FileDescriptor) throws java.io.IOException; + method @NonNull public static java.net.SocketAddress makeNetlinkSocketAddress(int, int); + method @NonNull public static java.net.SocketAddress makePacketSocketAddress(int, int); + method @Deprecated @NonNull public static java.net.SocketAddress makePacketSocketAddress(int, @NonNull byte[]); + method @NonNull public static java.net.SocketAddress makePacketSocketAddress(int, int, @NonNull byte[]); + } + +} + diff --git a/packages/Connectivity/framework/api/system-lint-baseline.txt b/packages/Connectivity/framework/api/system-lint-baseline.txt new file mode 100644 index 000000000000..9a97707763f1 --- /dev/null +++ b/packages/Connectivity/framework/api/system-lint-baseline.txt @@ -0,0 +1 @@ +// Baseline format: 1.0 diff --git a/packages/Connectivity/framework/api/system-removed.txt b/packages/Connectivity/framework/api/system-removed.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/packages/Connectivity/framework/api/system-removed.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/packages/Connectivity/framework/src/android/net/NetworkRequest.java b/packages/Connectivity/framework/src/android/net/NetworkRequest.java index 4e3085f4704d..b4a651c0607e 100644 --- a/packages/Connectivity/framework/src/android/net/NetworkRequest.java +++ b/packages/Connectivity/framework/src/android/net/NetworkRequest.java @@ -16,6 +16,22 @@ package android.net; +import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL; +import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN; +import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND; +import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; +import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY; +import static android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; + import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -30,6 +46,8 @@ import android.os.Process; import android.text.TextUtils; import android.util.proto.ProtoOutputStream; +import java.util.Arrays; +import java.util.List; import java.util.Objects; import java.util.Set; @@ -154,8 +172,30 @@ public class NetworkRequest implements Parcelable { * needed in terms of {@link NetworkCapabilities} features */ public static class Builder { + /** + * Capabilities that are currently compatible with VCN networks. + */ + private static final List<Integer> VCN_SUPPORTED_CAPABILITIES = Arrays.asList( + NET_CAPABILITY_CAPTIVE_PORTAL, + NET_CAPABILITY_DUN, + NET_CAPABILITY_FOREGROUND, + NET_CAPABILITY_INTERNET, + NET_CAPABILITY_NOT_CONGESTED, + NET_CAPABILITY_NOT_METERED, + NET_CAPABILITY_NOT_RESTRICTED, + NET_CAPABILITY_NOT_ROAMING, + NET_CAPABILITY_NOT_SUSPENDED, + NET_CAPABILITY_NOT_VPN, + NET_CAPABILITY_PARTIAL_CONNECTIVITY, + NET_CAPABILITY_TEMPORARILY_NOT_METERED, + NET_CAPABILITY_TRUSTED, + NET_CAPABILITY_VALIDATED); + private final NetworkCapabilities mNetworkCapabilities; + // A boolean that represents the user modified NOT_VCN_MANAGED capability. + private boolean mModifiedNotVcnManaged = false; + /** * Default constructor for Builder. */ @@ -177,6 +217,7 @@ public class NetworkRequest implements Parcelable { // maybeMarkCapabilitiesRestricted() doesn't add back. final NetworkCapabilities nc = new NetworkCapabilities(mNetworkCapabilities); nc.maybeMarkCapabilitiesRestricted(); + deduceNotVcnManagedCapability(nc); return new NetworkRequest(nc, ConnectivityManager.TYPE_NONE, ConnectivityManager.REQUEST_ID_UNSET, Type.NONE); } @@ -193,6 +234,9 @@ public class NetworkRequest implements Parcelable { */ public Builder addCapability(@NetworkCapabilities.NetCapability int capability) { mNetworkCapabilities.addCapability(capability); + if (capability == NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED) { + mModifiedNotVcnManaged = true; + } return this; } @@ -204,6 +248,9 @@ public class NetworkRequest implements Parcelable { */ public Builder removeCapability(@NetworkCapabilities.NetCapability int capability) { mNetworkCapabilities.removeCapability(capability); + if (capability == NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED) { + mModifiedNotVcnManaged = true; + } return this; } @@ -261,6 +308,9 @@ public class NetworkRequest implements Parcelable { @NonNull public Builder clearCapabilities() { mNetworkCapabilities.clearAll(); + // If the caller explicitly clear all capabilities, the NOT_VCN_MANAGED capabilities + // should not be add back later. + mModifiedNotVcnManaged = true; return this; } @@ -380,6 +430,25 @@ public class NetworkRequest implements Parcelable { mNetworkCapabilities.setSignalStrength(signalStrength); return this; } + + /** + * Deduce the NET_CAPABILITY_NOT_VCN_MANAGED capability from other capabilities + * and user intention, which includes: + * 1. For the requests that don't have anything besides + * {@link #VCN_SUPPORTED_CAPABILITIES}, add the NET_CAPABILITY_NOT_VCN_MANAGED to + * allow the callers automatically utilize VCN networks if available. + * 2. For the requests that explicitly add or remove NET_CAPABILITY_NOT_VCN_MANAGED, + * do not alter them to allow user fire request that suits their need. + * + * @hide + */ + private void deduceNotVcnManagedCapability(final NetworkCapabilities nc) { + if (mModifiedNotVcnManaged) return; + for (final int cap : nc.getCapabilities()) { + if (!VCN_SUPPORTED_CAPABILITIES.contains(cap)) return; + } + nc.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED); + } } // implement the Parcelable interface diff --git a/packages/Connectivity/framework/src/android/net/util/MultinetworkPolicyTracker.java b/packages/Connectivity/framework/src/android/net/util/MultinetworkPolicyTracker.java index 85e3fa3048ed..43fffd733e91 100644 --- a/packages/Connectivity/framework/src/android/net/util/MultinetworkPolicyTracker.java +++ b/packages/Connectivity/framework/src/android/net/util/MultinetworkPolicyTracker.java @@ -40,6 +40,8 @@ import com.android.internal.annotations.VisibleForTesting; import java.util.Arrays; import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.RejectedExecutionException; /** * A class to encapsulate management of the "Smart Networking" capability of @@ -73,6 +75,32 @@ public class MultinetworkPolicyTracker { private volatile int mMeteredMultipathPreference; private int mActiveSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; + // Mainline module can't use internal HandlerExecutor, so add an identical executor here. + private static class HandlerExecutor implements Executor { + @NonNull + private final Handler mHandler; + + HandlerExecutor(@NonNull Handler handler) { + mHandler = handler; + } + @Override + public void execute(Runnable command) { + if (!mHandler.post(command)) { + throw new RejectedExecutionException(mHandler + " is shutting down"); + } + } + } + + @VisibleForTesting + protected class ActiveDataSubscriptionIdChangedListener extends PhoneStateListener + implements PhoneStateListener.ActiveDataSubscriptionIdChangedListener { + @Override + public void onActiveDataSubscriptionIdChanged(int subId) { + mActiveSubId = subId; + reevaluateInternal(); + } + } + public MultinetworkPolicyTracker(Context ctx, Handler handler) { this(ctx, handler, null); } @@ -93,14 +121,8 @@ public class MultinetworkPolicyTracker { } }; - ctx.getSystemService(TelephonyManager.class).listen( - new PhoneStateListener(handler.getLooper()) { - @Override - public void onActiveDataSubscriptionIdChanged(int subId) { - mActiveSubId = subId; - reevaluateInternal(); - } - }, PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE); + ctx.getSystemService(TelephonyManager.class).registerPhoneStateListener( + new HandlerExecutor(handler), new ActiveDataSubscriptionIdChangedListener()); updateAvoidBadWifi(); updateMeteredMultipathPreference(); diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java index 66165b6d1ff2..ad6a5312f156 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java @@ -147,5 +147,10 @@ public class GlobalSettingsValidators { VALIDATORS.put(Global.DEVELOPMENT_SETTINGS_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Global.NOTIFICATION_FEEDBACK_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Global.RESTRICTED_NETWORKING_MODE, BOOLEAN_VALIDATOR); + VALIDATORS.put( + Global.ONE_HANDED_KEYGUARD_SIDE, + new InclusiveIntegerRangeValidator( + /* first= */Global.ONE_HANDED_KEYGUARD_SIDE_LEFT, + /* last= */Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT)); } } diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index 664fd4ac2fcb..303e5bb7050d 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -283,6 +283,7 @@ public class SettingsBackupTest { Settings.Global.EUICC_REMOVING_INVISIBLE_PROFILES_TIMEOUT_MILLIS, Settings.Global.EUICC_SWITCH_SLOT_TIMEOUT_MILLIS, Settings.Global.FANCY_IME_ANIMATIONS, + Settings.Global.ONE_HANDED_KEYGUARD_SIDE, Settings.Global.FORCE_ALLOW_ON_EXTERNAL, Settings.Global.FORCED_APP_STANDBY_ENABLED, Settings.Global.FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED, diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 259484073162..71e09106368b 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -84,8 +84,6 @@ <uses-permission android:name="android.permission.READ_INPUT_STATE" /> <uses-permission android:name="android.permission.SET_ORIENTATION" /> <uses-permission android:name="android.permission.INSTALL_PACKAGES" /> - <!-- TODO(b/152310230): remove once APIs are confirmed to be sufficient --> - <uses-permission android:name="com.android.permission.USE_INSTALLER_V2" /> <uses-permission android:name="com.android.permission.USE_SYSTEM_DATA_LOADERS" /> <uses-permission android:name="android.permission.MOVE_PACKAGE" /> <uses-permission android:name="android.permission.KEEP_UNINSTALLED_PACKAGES" /> @@ -125,6 +123,8 @@ <uses-permission android:name="android.permission.MANAGE_CREDENTIAL_MANAGEMENT_APP" /> <uses-permission android:name="android.permission.MANAGE_DEVICE_ADMINS" /> <uses-permission android:name="android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS" /> + <uses-permission android:name="android.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS" /> + <uses-permission android:name="android.permission.CLEAR_FREEZE_PERIOD" /> <uses-permission android:name="android.permission.QUERY_USERS" /> <uses-permission android:name="android.permission.MODIFY_QUIET_MODE" /> <uses-permission android:name="android.permission.ACCESS_LOWPAN_STATE"/> @@ -398,6 +398,7 @@ <!-- Permission required for CTS to test sensor privacy behavior --> <uses-permission android:name="android.permission.MANAGE_SENSOR_PRIVACY" /> + <uses-permission android:name="android.permission.OBSERVE_SENSOR_PRIVACY" /> <!-- Permission needed for CTS test - CallLogTest --> <uses-permission android:name="com.android.voicemail.permission.READ_VOICEMAIL" /> diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index fbe58c505662..044216e5423d 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -87,6 +87,7 @@ <uses-permission android:name="android.permission.MASTER_CLEAR" /> <uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.MANAGE_SENSOR_PRIVACY" /> + <uses-permission android:name="android.permission.OBSERVE_SENSOR_PRIVACY" /> <!-- ActivityManager --> <uses-permission android:name="android.permission.REAL_GET_TASKS" /> diff --git a/packages/SystemUI/res/drawable/control_no_favorites_background.xml b/packages/SystemUI/res/drawable/control_no_favorites_background.xml index d895dd0c85c7..2165b12e5697 100644 --- a/packages/SystemUI/res/drawable/control_no_favorites_background.xml +++ b/packages/SystemUI/res/drawable/control_no_favorites_background.xml @@ -26,12 +26,4 @@ <corners android:radius="@dimen/control_corner_radius" /> </shape> </item> - <item> - <shape> - <stroke - android:width="1dp" - android:color="#4DFFFFFF" /> - <corners android:radius="@dimen/control_corner_radius"/> - </shape> - </item> </ripple> diff --git a/packages/SystemUI/res/drawable/controls_dialog_bg.xml b/packages/SystemUI/res/drawable/controls_dialog_bg.xml index cb4686dd04a7..1ccb176b8689 100644 --- a/packages/SystemUI/res/drawable/controls_dialog_bg.xml +++ b/packages/SystemUI/res/drawable/controls_dialog_bg.xml @@ -16,6 +16,6 @@ --> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> - <solid android:color="?android:attr/colorBackgroundFloating" /> + <solid android:color="?android:attr/colorBackground" /> <corners android:radius="@dimen/notification_corner_radius" /> </shape> diff --git a/packages/SystemUI/res/drawable/horizontal_ellipsis.xml b/packages/SystemUI/res/drawable/horizontal_ellipsis.xml new file mode 100644 index 000000000000..1800857a826c --- /dev/null +++ b/packages/SystemUI/res/drawable/horizontal_ellipsis.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0" + android:tint="?android:attr/colorBackgroundFloating" > + + <path + android:pathData="M6 10c-1.1 0-2 0.9-2 2s0.9 2 2 2 2-0.9 2-2-0.9-2-2-2zm12 0c-1.1 0-2 0.9-2 2s0.9 2 2 2 2-0.9 2-2-0.9-2-2-2zm-6 0c-1.1 0-2 0.9-2 2s0.9 2 2 2 2-0.9 2-2-0.9-2-2-2z" + android:fillColor="@android:color/white" /> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/qs_tile_label_divider.xml b/packages/SystemUI/res/drawable/volume_drawer_bg.xml index 150a5b8bfef1..f0e22926d07a 100644 --- a/packages/SystemUI/res/layout/qs_tile_label_divider.xml +++ b/packages/SystemUI/res/drawable/volume_drawer_bg.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - ~ Copyright (C) 2020 The Android Open Source Project + ~ Copyright (C) 2021 The Android Open Source Project ~ ~ Licensed under the Apache License, Version 2.0 (the "License"); ~ you may not use this file except in compliance with the License. @@ -14,5 +14,9 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - -<View />
\ No newline at end of file +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:paddingMode="stack" > + <size android:width="@dimen/volume_ringer_drawer_item_size" /> + <solid android:color="?android:attr/colorBackgroundFloating" /> + <corners android:radius="@dimen/volume_ringer_drawer_item_size_half" /> +</shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/volume_drawer_selection_bg.xml b/packages/SystemUI/res/drawable/volume_drawer_selection_bg.xml new file mode 100644 index 000000000000..5e7cb12d1c5f --- /dev/null +++ b/packages/SystemUI/res/drawable/volume_drawer_selection_bg.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:paddingMode="stack" > + <size + android:height="@dimen/volume_ringer_drawer_item_size" + android:width="@dimen/volume_ringer_drawer_item_size" /> + <solid android:color="?android:attr/colorAccent" /> + <corners android:radius="@dimen/volume_ringer_drawer_item_size_half" /> +</shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/volume_row_seekbar.xml b/packages/SystemUI/res/drawable/volume_row_seekbar.xml new file mode 100644 index 000000000000..b0e0ed5079e6 --- /dev/null +++ b/packages/SystemUI/res/drawable/volume_row_seekbar.xml @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<!-- SeekBar drawable for volume rows. This contains a background layer (with a solid round rect, + and a bottom-aligned icon) and a progress layer (with an accent-colored round rect and icon) + that moves up and down with the progress value. --> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android" + android:paddingMode="stack" > + <item android:id="@android:id/background" + android:gravity="center_vertical|fill_horizontal"> + <layer-list> + <item android:id="@+id/volume_seekbar_background_solid"> + <shape> + <size android:height="@dimen/volume_dialog_panel_width" /> + <solid android:color="?android:attr/colorBackgroundFloating" /> + <corners android:radius="@dimen/volume_dialog_panel_width_half" /> + </shape> + </item> + <item + android:id="@+id/volume_seekbar_background_icon" + android:gravity="center_vertical|left" + android:height="@dimen/rounded_slider_icon_size" + android:width="@dimen/rounded_slider_icon_size" + android:left="@dimen/rounded_slider_icon_inset"> + <rotate + android:fromDegrees="-270" + android:toDegrees="-270"> + <!-- A placeholder drawable is required here - it'll be replaced in code. --> + <com.android.systemui.util.AlphaTintDrawableWrapper + android:drawable="@drawable/ic_volume_media" + android:tint="?android:attr/colorAccent" /> + </rotate> + </item> + </layer-list> + </item> + <item android:id="@android:id/progress" + android:gravity="center_vertical|fill_horizontal"> + <com.android.systemui.util.RoundedCornerProgressDrawable + android:drawable="@drawable/volume_row_seekbar_progress" + /> + </item> +</layer-list>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/volume_row_seekbar_progress.xml b/packages/SystemUI/res/drawable/volume_row_seekbar_progress.xml new file mode 100644 index 000000000000..ef202360b1ce --- /dev/null +++ b/packages/SystemUI/res/drawable/volume_row_seekbar_progress.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<!-- Progress drawable for volume row SeekBars. This is the accent-colored round rect that moves up + and down as the progress value changes. --> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android" + android:autoMirrored="true"> + <item android:id="@+id/volume_seekbar_progress_solid"> + <shape> + <size android:height="@dimen/volume_dialog_panel_width" /> + <solid android:color="?android:attr/colorAccent" /> + <corners android:radius="@dimen/volume_dialog_panel_width_half"/> + </shape> + </item> + <item + android:id="@+id/volume_seekbar_progress_icon" + android:gravity="center_vertical|right" + android:height="@dimen/rounded_slider_icon_size" + android:width="@dimen/rounded_slider_icon_size" + android:right="@dimen/rounded_slider_icon_inset"> + <rotate + android:fromDegrees="-270" + android:toDegrees="-270"> + <!-- A placeholder drawable is required here - it'll be replaced in code. --> + <com.android.systemui.util.AlphaTintDrawableWrapper + android:drawable="@drawable/ic_volume_media" + android:tint="?android:attr/colorBackgroundFloating" /> + </rotate> + </item> +</layer-list>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout-land/volume_dialog.xml b/packages/SystemUI/res/layout-land/volume_dialog.xml index c420117073c5..237dc02e5d8c 100644 --- a/packages/SystemUI/res/layout-land/volume_dialog.xml +++ b/packages/SystemUI/res/layout-land/volume_dialog.xml @@ -32,105 +32,124 @@ android:gravity="right" android:layout_gravity="right" android:background="@android:color/transparent" - android:paddingRight="@dimen/volume_dialog_panel_transparent_padding_right" - android:paddingTop="@dimen/volume_dialog_panel_transparent_padding" - android:paddingBottom="@dimen/volume_dialog_panel_transparent_padding" + android:paddingRight="@dimen/volume_dialog_stream_padding" android:paddingLeft="@dimen/volume_dialog_panel_transparent_padding" android:clipToPadding="false"> - <FrameLayout - android:id="@+id/ringer" - android:layout_width="@dimen/volume_dialog_ringer_size" - android:layout_height="@dimen/volume_dialog_ringer_size" - android:layout_marginBottom="@dimen/volume_dialog_spacer" - android:gravity="right" - android:layout_gravity="right" - android:translationZ="@dimen/volume_dialog_elevation" - android:clipToPadding="false" - android:background="@drawable/rounded_bg_full"> - <com.android.keyguard.AlphaOptimizedImageButton - android:id="@+id/ringer_icon" - style="@style/VolumeButtons" - android:background="@drawable/rounded_ripple" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:scaleType="fitCenter" - android:padding="@dimen/volume_dialog_ringer_icon_padding" - android:tint="@color/accent_tint_color_selector" - android:layout_gravity="center" - android:soundEffectsEnabled="false" /> - - <include layout="@layout/volume_dnd_icon" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginRight="@dimen/volume_dialog_stream_padding" - android:layout_marginTop="6dp"/> - </FrameLayout> - + <!-- + Container for a) the ringer drawer and the caption button next to b) the volume rows. + --> <LinearLayout - android:id="@+id/main" android:layout_width="wrap_content" - android:minWidth="@dimen/volume_dialog_panel_width" android:layout_height="wrap_content" - android:layout_marginTop="68dp" - android:gravity="right" - android:layout_gravity="right" - android:orientation="vertical" - android:translationZ="@dimen/volume_dialog_elevation" + android:orientation="horizontal" android:clipChildren="false" - android:clipToPadding="false" - android:background="@drawable/rounded_bg_full" > - <LinearLayout - android:id="@+id/volume_dialog_rows" + android:clipToPadding="false"> + + <!-- The ringer drawer and the caption button. --> + <FrameLayout android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:minWidth="@dimen/volume_dialog_panel_width" - android:gravity="center" - android:orientation="horizontal" + android:layout_height="match_parent" android:paddingRight="@dimen/volume_dialog_stream_padding" - android:paddingLeft="@dimen/volume_dialog_stream_padding"> - <!-- volume rows added and removed here! :-) --> - </LinearLayout> + android:clipChildren="false" + android:clipToPadding="false" + android:orientation="vertical"> + + <include layout="@layout/volume_ringer_drawer" + android:layout_gravity="top|right"/> + + <FrameLayout + android:id="@+id/odi_captions" + android:layout_width="@dimen/volume_dialog_caption_size" + android:layout_height="@dimen/volume_dialog_caption_size" + android:gravity="center" + android:layout_gravity="bottom|right" + android:layout_marginBottom="@dimen/volume_dialog_tap_target_size" + android:clipToPadding="false"> + + <com.android.systemui.volume.CaptionsToggleImageButton + android:id="@+id/odi_captions_icon" + android:src="@drawable/ic_volume_odi_captions_disabled" + style="@style/VolumeButtons" + android:background="@drawable/rounded_ripple" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:tint="@color/caption_tint_color_selector" + android:layout_gravity="center" + android:soundEffectsEnabled="false" + sysui:optedOut="false"/> + + </FrameLayout> + + </FrameLayout> + <FrameLayout - android:id="@+id/settings_container" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:background="@drawable/rounded_bg_bottom_background"> + android:visibility="gone" + android:id="@+id/ringer" + android:layout_width="@dimen/volume_dialog_ringer_size" + android:layout_height="@dimen/volume_dialog_ringer_size" + android:layout_marginBottom="@dimen/volume_dialog_spacer" + android:gravity="right" + android:layout_gravity="right" + android:translationZ="@dimen/volume_dialog_elevation" + android:clipToPadding="false" + android:background="@drawable/rounded_bg_full"> <com.android.keyguard.AlphaOptimizedImageButton - android:id="@+id/settings" - android:src="@drawable/ic_tune_black_16dp" - android:layout_width="@dimen/volume_dialog_tap_target_size" - android:layout_height="@dimen/volume_dialog_tap_target_size" + android:id="@+id/ringer_icon" + style="@style/VolumeButtons" + android:background="@drawable/rounded_ripple" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:scaleType="fitCenter" + android:padding="@dimen/volume_dialog_ringer_icon_padding" + android:tint="@color/accent_tint_color_selector" android:layout_gravity="center" - android:contentDescription="@string/accessibility_volume_settings" - android:background="@drawable/ripple_drawable_20dp" - android:tint="?android:attr/textColorSecondary" android:soundEffectsEnabled="false" /> + + <include layout="@layout/volume_dnd_icon" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginRight="@dimen/volume_dialog_stream_padding" + android:layout_marginTop="6dp"/> </FrameLayout> - </LinearLayout> - <FrameLayout - android:id="@+id/odi_captions" - android:layout_width="@dimen/volume_dialog_caption_size" - android:layout_height="@dimen/volume_dialog_caption_size" - android:layout_marginRight="68dp" - android:gravity="right" - android:layout_gravity="right" - android:clipToPadding="false" - android:translationZ="@dimen/volume_dialog_elevation" - android:background="@drawable/rounded_bg_full"> - <com.android.systemui.volume.CaptionsToggleImageButton - android:id="@+id/odi_captions_icon" - android:src="@drawable/ic_volume_odi_captions_disabled" - style="@style/VolumeButtons" - android:background="@drawable/rounded_ripple" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:tint="@color/caption_tint_color_selector" - android:layout_gravity="center" - android:soundEffectsEnabled="false" - sysui:optedOut="false"/> - </FrameLayout> + <LinearLayout + android:id="@+id/main" + android:layout_width="wrap_content" + android:minWidth="@dimen/volume_dialog_panel_width" + android:layout_height="wrap_content" + android:gravity="right" + android:layout_gravity="right" + android:orientation="vertical" + android:clipChildren="false" + android:clipToPadding="false"> + <LinearLayout + android:id="@+id/volume_dialog_rows" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:minWidth="@dimen/volume_dialog_panel_width" + android:gravity="center" + android:orientation="horizontal"> + <!-- volume rows added and removed here! :-) --> + </LinearLayout> + <FrameLayout + android:id="@+id/settings_container" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + <com.android.keyguard.AlphaOptimizedImageButton + android:id="@+id/settings" + android:src="@drawable/horizontal_ellipsis" + android:layout_width="@dimen/volume_dialog_tap_target_size" + android:layout_height="@dimen/volume_dialog_tap_target_size" + android:layout_gravity="center" + android:contentDescription="@string/accessibility_volume_settings" + android:background="@drawable/ripple_drawable_20dp" + android:tint="?android:attr/colorBackgroundFloating" + android:soundEffectsEnabled="false" /> + </FrameLayout> + </LinearLayout> + + </LinearLayout> <ViewStub android:id="@+id/odi_captions_tooltip_stub" diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml index 0822947e8b16..93dd1a12f147 100644 --- a/packages/SystemUI/res/layout/qs_footer_impl.xml +++ b/packages/SystemUI/res/layout/qs_footer_impl.xml @@ -81,6 +81,22 @@ android:layout_height="match_parent" android:layout_weight="@integer/qs_footer_actions_weight" android:gravity="center_vertical|end" > + + <com.android.systemui.statusbar.AlphaOptimizedImageView + android:id="@+id/pm_lite" + android:layout_width="@dimen/qs_footer_action_button_size" + android:layout_height="@dimen/qs_footer_action_button_size" + android:background="?android:attr/selectableItemBackgroundBorderless" + android:clickable="true" + android:clipToPadding="false" + android:contentDescription="@string/accessibility_quick_settings_edit" + android:focusable="true" + android:padding="@dimen/qs_footer_icon_padding" + android:src="@*android:drawable/ic_lock_power_off" + android:tint="?android:attr/colorForeground" + android:visibility="gone" + /> + <com.android.systemui.statusbar.phone.MultiUserSwitch android:id="@+id/multi_user_switch" android:layout_width="@dimen/qs_footer_action_button_size" diff --git a/packages/SystemUI/res/layout/qs_paged_page_side_labels.xml b/packages/SystemUI/res/layout/qs_paged_page_side_labels.xml index ee54f1da8897..c83077371bb0 100644 --- a/packages/SystemUI/res/layout/qs_paged_page_side_labels.xml +++ b/packages/SystemUI/res/layout/qs_paged_page_side_labels.xml @@ -14,4 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. --> -<include layout="@layout/qs_paged_page" /> +<com.android.systemui.qs.SideLabelTileLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/tile_page" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:clipChildren="false" + android:clipToPadding="false" /> diff --git a/packages/SystemUI/res/layout/qs_tile_label.xml b/packages/SystemUI/res/layout/qs_tile_label.xml index 571cbbcc77db..1ce87c1c0236 100644 --- a/packages/SystemUI/res/layout/qs_tile_label.xml +++ b/packages/SystemUI/res/layout/qs_tile_label.xml @@ -20,7 +20,6 @@ android:layout_height="wrap_content" android:clipChildren="false" android:clipToPadding="false" - android:minHeight="48dp" android:paddingTop="12dp"> <LinearLayout android:id="@+id/label_group" diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml index 4ca59f5082ad..bbb6107d149e 100644 --- a/packages/SystemUI/res/layout/status_bar_expanded.xml +++ b/packages/SystemUI/res/layout/status_bar_expanded.xml @@ -55,12 +55,14 @@ android:id="@+id/qs_frame" android:layout="@layout/qs_panel" android:layout_width="@dimen/qs_panel_width" - android:layout_height="match_parent" + android:layout_height="0dp" android:clipToPadding="false" android:clipChildren="false" systemui:viewType="com.android.systemui.plugins.qs.QS" systemui:layout_constraintStart_toStartOf="parent" systemui:layout_constraintEnd_toEndOf="parent" + systemui:layout_constraintTop_toTopOf="parent" + systemui:layout_constraintBottom_toBottomOf="parent" /> <androidx.constraintlayout.widget.Guideline diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml index 1810c196c83d..6aac5a34821b 100644 --- a/packages/SystemUI/res/layout/volume_dialog.xml +++ b/packages/SystemUI/res/layout/volume_dialog.xml @@ -21,6 +21,8 @@ android:layout_height="wrap_content" android:gravity="right" android:layout_gravity="right" + android:paddingRight="@dimen/volume_dialog_stream_padding" + android:clipToPadding="false" android:background="@android:color/transparent" android:theme="@style/volume_dialog_theme"> @@ -34,13 +36,15 @@ android:layout_gravity="right" android:background="@android:color/transparent" android:paddingRight="@dimen/volume_dialog_panel_transparent_padding_right" - android:paddingTop="@dimen/volume_dialog_panel_transparent_padding" - android:paddingBottom="@dimen/volume_dialog_panel_transparent_padding" android:paddingLeft="@dimen/volume_dialog_panel_transparent_padding" android:orientation="vertical" - android:clipToPadding="false"> + android:clipToPadding="false" + android:clipChildren="false"> + + <include layout="@layout/volume_ringer_drawer" /> <FrameLayout + android:visibility="gone" android:id="@+id/ringer" android:layout_width="@dimen/volume_dialog_ringer_size" android:layout_height="@dimen/volume_dialog_ringer_size" @@ -77,10 +81,8 @@ android:gravity="right" android:layout_gravity="right" android:orientation="vertical" - android:translationZ="@dimen/volume_dialog_elevation" android:clipChildren="false" - android:clipToPadding="false" - android:background="@drawable/rounded_bg_full" > + android:clipToPadding="false" > <LinearLayout android:id="@+id/volume_dialog_rows" android:layout_width="wrap_content" @@ -88,24 +90,22 @@ android:minWidth="@dimen/volume_dialog_panel_width" android:gravity="center" android:orientation="horizontal" - android:paddingRight="@dimen/volume_dialog_stream_padding" - android:paddingLeft="@dimen/volume_dialog_stream_padding"> + android:layout_marginTop="@dimen/volume_row_slider_padding_start"> <!-- volume rows added and removed here! :-) --> </LinearLayout> <FrameLayout android:id="@+id/settings_container" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:background="@drawable/rounded_bg_bottom_background"> + android:layout_height="wrap_content"> <com.android.keyguard.AlphaOptimizedImageButton android:id="@+id/settings" - android:src="@drawable/ic_tune_black_16dp" + android:src="@drawable/horizontal_ellipsis" android:layout_width="@dimen/volume_dialog_tap_target_size" android:layout_height="@dimen/volume_dialog_tap_target_size" android:layout_gravity="center" android:contentDescription="@string/accessibility_volume_settings" android:background="@drawable/ripple_drawable_20dp" - android:tint="?android:attr/textColorPrimary" + android:tint="?android:attr/colorBackgroundFloating" android:soundEffectsEnabled="false" /> </FrameLayout> </LinearLayout> @@ -118,7 +118,6 @@ android:gravity="right" android:layout_gravity="right" android:clipToPadding="false" - android:translationZ="@dimen/volume_dialog_elevation" android:background="@drawable/rounded_bg_full"> <com.android.systemui.volume.CaptionsToggleImageButton android:id="@+id/odi_captions_icon" @@ -127,7 +126,7 @@ android:background="@drawable/rounded_ripple" android:layout_width="match_parent" android:layout_height="match_parent" - android:tint="?android:attr/textColorPrimary" + android:tint="?android:attr/colorAccent" android:layout_gravity="center" android:soundEffectsEnabled="false" sysui:optedOut="false"/> diff --git a/packages/SystemUI/res/layout/volume_dialog_row.xml b/packages/SystemUI/res/layout/volume_dialog_row.xml index b9efc5be70c1..fda59b50104a 100644 --- a/packages/SystemUI/res/layout/volume_dialog_row.xml +++ b/packages/SystemUI/res/layout/volume_dialog_row.xml @@ -20,11 +20,12 @@ android:layout_width="@dimen/volume_dialog_panel_width" android:clipChildren="false" android:clipToPadding="false" + android:translationZ="@dimen/volume_dialog_elevation" android:theme="@style/volume_dialog_theme"> <LinearLayout android:layout_height="wrap_content" - android:layout_width="match_parent" + android:layout_width="@dimen/volume_dialog_panel_width" android:gravity="center" android:layout_gravity="center" android:orientation="vertical" > @@ -41,21 +42,23 @@ <FrameLayout android:id="@+id/volume_row_slider_frame" android:layout_width="match_parent" - android:layout_marginTop="@dimen/volume_dialog_slider_margin_top" - android:layout_marginBottom="@dimen/volume_dialog_slider_margin_bottom" - android:layoutDirection="rtl" - android:layout_height="@dimen/volume_dialog_slider_height"> + android:layout_height="@dimen/volume_row_slider_height"> + <include layout="@layout/volume_dnd_icon"/> <SeekBar android:id="@+id/volume_row_slider" + android:paddingLeft="0dp" + android:paddingRight="0dp" + android:paddingStart="0dp" + android:paddingEnd="0dp" android:clickable="true" - android:layout_width="@dimen/volume_dialog_slider_height" + android:layout_width="@dimen/volume_row_slider_height" android:layout_height="match_parent" - android:layoutDirection="rtl" android:layout_gravity="center" - android:rotation="90" /> + android:rotation="270" /> </FrameLayout> <com.android.keyguard.AlphaOptimizedImageButton + android:visibility="gone" android:id="@+id/volume_row_icon" style="@style/VolumeButtons" android:layout_width="@dimen/volume_dialog_tap_target_size" @@ -66,6 +69,4 @@ android:soundEffectsEnabled="false" /> </LinearLayout> - <include layout="@layout/volume_dnd_icon"/> - </FrameLayout> diff --git a/packages/SystemUI/res/layout/volume_ringer_drawer.xml b/packages/SystemUI/res/layout/volume_ringer_drawer.xml new file mode 100644 index 000000000000..d6e1782382fa --- /dev/null +++ b/packages/SystemUI/res/layout/volume_ringer_drawer.xml @@ -0,0 +1,126 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<!-- Contains the active ringer icon and a hidden drawer containing all three ringer options. --> +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center" + android:clipToPadding="false" + android:clipChildren="false"> + + <!-- Drawer view, invisible by default. --> + <FrameLayout + android:id="@+id/volume_drawer_container" + android:alpha="0.0" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="@drawable/volume_drawer_bg" + android:orientation="vertical"> + + <!-- View that is animated to a tapped ringer selection, so it appears selected. --> + <FrameLayout + android:id="@+id/volume_drawer_selection_background" + android:alpha="0.0" + android:layout_width="@dimen/volume_ringer_drawer_item_size" + android:layout_height="@dimen/volume_ringer_drawer_item_size" + android:layout_gravity="bottom|right" + android:background="@drawable/volume_drawer_selection_bg" /> + + <LinearLayout + android:id="@+id/volume_drawer_options" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <FrameLayout + android:id="@+id/volume_drawer_vibrate" + android:layout_width="@dimen/volume_ringer_drawer_item_size" + android:layout_height="@dimen/volume_ringer_drawer_item_size" + android:description="@string/volume_ringer_hint_vibrate" + android:gravity="center"> + + <ImageView + android:id="@+id/volume_drawer_vibrate_icon" + android:layout_width="@dimen/volume_ringer_drawer_icon_size" + android:layout_height="@dimen/volume_ringer_drawer_icon_size" + android:layout_gravity="center" + android:tint="?android:attr/colorAccent" + android:src="@drawable/ic_volume_ringer_vibrate" /> + + </FrameLayout> + + <FrameLayout + android:id="@+id/volume_drawer_mute" + android:layout_width="@dimen/volume_ringer_drawer_item_size" + android:layout_height="@dimen/volume_ringer_drawer_item_size" + android:description="@string/volume_ringer_hint_mute" + android:gravity="center"> + + <ImageView + android:id="@+id/volume_drawer_mute_icon" + android:layout_width="@dimen/volume_ringer_drawer_icon_size" + android:layout_height="@dimen/volume_ringer_drawer_icon_size" + android:layout_gravity="center" + android:tint="?android:attr/colorAccent" + android:src="@drawable/ic_volume_ringer_mute" /> + + </FrameLayout> + + <FrameLayout + android:id="@+id/volume_drawer_normal" + android:layout_width="@dimen/volume_ringer_drawer_item_size" + android:layout_height="@dimen/volume_ringer_drawer_item_size" + android:description="@string/volume_ringer_hint_unmute" + android:gravity="center"> + + <ImageView + android:id="@+id/volume_drawer_normal_icon" + android:layout_width="@dimen/volume_ringer_drawer_icon_size" + android:layout_height="@dimen/volume_ringer_drawer_icon_size" + android:layout_gravity="center" + android:tint="?android:attr/colorAccent" + android:src="@drawable/ic_volume_ringer" /> + + </FrameLayout> + + </LinearLayout> + + </FrameLayout> + + <!-- The current ringer selection. When the drawer is opened, this animates to the corresponding + position in the drawer. When the drawer is closed, it animates back. --> + <FrameLayout + android:id="@+id/volume_new_ringer_active_icon_container" + android:layout_width="@dimen/volume_ringer_drawer_item_size" + android:layout_height="@dimen/volume_ringer_drawer_item_size" + android:layout_gravity="bottom|right" + android:description="@string/volume_ringer_change" + android:background="@drawable/volume_drawer_selection_bg"> + + <ImageView + android:id="@+id/volume_new_ringer_active_icon" + android:layout_width="@dimen/volume_ringer_drawer_icon_size" + android:layout_height="@dimen/volume_ringer_drawer_icon_size" + android:layout_gravity="center" + android:tint="?android:attr/colorBackgroundFloating" + android:src="@drawable/ic_volume_media" /> + + </FrameLayout> + +</FrameLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index acd671cb6297..3bc1c8053db3 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -265,7 +265,6 @@ <color name="control_enabled_cool_foreground">@color/GM2_blue_300</color> <color name="control_thumbnail_tint">#33000000</color> <color name="control_thumbnail_shadow_color">@*android:color/black</color> - <color name="controls_lockscreen_scrim">#AA000000</color> <!-- Docked misalignment message --> <color name="misalignment_text_color">#F28B82</color> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index f13fdd34e4f3..0893c1488005 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -91,6 +91,9 @@ <!-- The number of columns in the QuickSettings --> <integer name="quick_settings_num_columns">3</integer> + <!-- The number of columns in the Quick Settings customizer --> + <integer name="quick_settings_edit_num_columns">@integer/quick_settings_num_columns</integer> + <!-- The number of rows in the QuickSettings --> <integer name="quick_settings_max_rows">3</integer> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 89c849a0ef4a..ea0ea5e9472a 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -456,10 +456,12 @@ <dimen name="volume_dialog_panel_transparent_padding">20dp</dimen> - <dimen name="volume_dialog_stream_padding">8dp</dimen> + <dimen name="volume_dialog_stream_padding">12dp</dimen> <dimen name="volume_dialog_panel_width">64dp</dimen> + <dimen name="volume_dialog_panel_width_half">32dp</dimen> + <dimen name="volume_dialog_slider_height">116dp</dimen> <dimen name="volume_dialog_ringer_size">64dp</dimen> @@ -486,6 +488,13 @@ <dimen name="volume_tool_tip_arrow_corner_radius">2dp</dimen> + <!-- Size of each item in the ringer selector drawer. --> + <dimen name="volume_ringer_drawer_item_size">64dp</dimen> + <dimen name="volume_ringer_drawer_item_size_half">32dp</dimen> + + <!-- Size of the icon inside each item in the ringer selector drawer. --> + <dimen name="volume_ringer_drawer_icon_size">24dp</dimen> + <!-- Gravity for the notification panel --> <integer name="notification_panel_layout_gravity">0x31</integer><!-- center_horizontal|top --> @@ -966,7 +975,7 @@ <dimen name="volume_row_padding_start">4dp</dimen> <dimen name="volume_row_header_padding_start">16dp</dimen> <dimen name="volume_row_height">64dp</dimen> - <dimen name="volume_row_slider_height">48dp</dimen> + <dimen name="volume_row_slider_height">192dp</dimen> <dimen name="volume_row_slider_padding_start">12dp</dimen> <dimen name="volume_expander_margin_end">2dp</dimen> diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml index 3e16cd47336c..e5518928c98c 100644 --- a/packages/SystemUI/res/values/flags.xml +++ b/packages/SystemUI/res/values/flags.xml @@ -44,4 +44,6 @@ <bool name="flag_toast_style">false</bool> <bool name="flag_navigation_bar_overlay">false</bool> + + <bool name="flag_pm_lite">false</bool> </resources> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 4b95c16a3602..0414de6eb7ca 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -959,7 +959,7 @@ <!-- QuickSettings: Secondary text for when the Dark theme or some other tile will be on until some user-selected time. [CHAR LIMIT=20] --> <string name="quick_settings_dark_mode_secondary_label_until">Until <xliff:g id="time" example="7 am">%s</xliff:g></string> <!-- QuickSettings: Label for the toggle that controls whether Reduce Brightness is enabled. [CHAR LIMIT=NONE] --> - <string name="quick_settings_reduce_bright_colors_label">Reduce Brightness</string> + <string name="quick_settings_reduce_bright_colors_label">Reduce brightness</string> <!-- QuickSettings: NFC tile [CHAR LIMIT=NONE] --> <string name="quick_settings_nfc_label">NFC</string> @@ -1558,6 +1558,8 @@ <string name="volume_stream_content_description_vibrate_a11y">%1$s. Tap to set to vibrate.</string> <string name="volume_stream_content_description_mute_a11y">%1$s. Tap to mute.</string> + <string name="volume_ringer_change">Tap to change ringer mode</string> + <!-- Hint for accessibility. For example: double tap to mute [CHAR_LIMIT=NONE] --> <string name="volume_ringer_hint_mute">mute</string> <!-- Hint for accessibility. For example: double tap to unmute [CHAR_LIMIT=NONE] --> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 14b376a8bf6c..2d202fb45bbc 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -663,16 +663,16 @@ </style> <style name="Theme.SystemUI.Dialog.Control.LockScreen" parent="@android:style/Theme.DeviceDefault.Dialog.NoActionBar"> - <item name="android:windowAnimationStyle">@style/Animation.Fade</item> + <item name="android:windowAnimationStyle">@style/Animation.ControlDialog</item> <item name="android:windowFullscreen">true</item> <item name="android:windowIsFloating">false</item> - <item name="android:windowBackground">@color/controls_lockscreen_scrim</item> + <item name="android:windowBackground">@null</item> <item name="android:backgroundDimEnabled">true</item> </style> - <style name="Animation.Fade"> - <item name="android:windowEnterAnimation">@android:anim/fade_in</item> - <item name="android:windowExitAnimation">@android:anim/fade_out</item> + <style name="Animation.ControlDialog"> + <item name="android:windowEnterAnimation">@*android:anim/dialog_enter</item> + <item name="android:windowExitAnimation">@*android:anim/dialog_exit</item> </style> <style name="Control" /> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java index d2698ee9fe3b..b3a29a3fec78 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java @@ -190,7 +190,7 @@ public class TaskStackChangeListeners { } @Override - public void onActivityDismissingDockedStack() { + public void onActivityDismissingDockedTask() { mHandler.sendEmptyMessage(ON_ACTIVITY_DISMISSING_DOCKED_STACK); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index d9a1eb6c0b28..a4054bea1167 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -305,6 +305,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private boolean mLogoutEnabled; // cached value to avoid IPCs private boolean mIsUdfpsEnrolled; + private boolean mKeyguardQsUserSwitchEnabled; // If the user long pressed the lock icon, disabling face auth for the current session. private boolean mLockIconPressed; private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID; @@ -1916,7 +1917,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab return isFaceAuthEnabledForUser(KeyguardUpdateMonitor.getCurrentUser()) && !isUdfpsEnrolled(); } - return true; + return !isKeyguardQsUserSwitchEnabled(); } /** @@ -1926,6 +1927,17 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab return mIsUdfpsEnrolled; } + /** + * @return true if the keyguard qs user switcher shortcut is enabled + */ + public boolean isKeyguardQsUserSwitchEnabled() { + return mKeyguardQsUserSwitchEnabled; + } + + public void setKeyguardQsUserSwitchEnabled(boolean enabled) { + mKeyguardQsUserSwitchEnabled = enabled; + } + private final UserSwitchObserver mUserSwitchObserver = new UserSwitchObserver() { @Override public void onUserSwitching(int newUserId, IRemoteCallback reply) { diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java index 8cb1bc4878a5..5db3349b646a 100644 --- a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java +++ b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java @@ -69,7 +69,9 @@ public class NumPadButton extends AlphaOptimizedImageButton { @Override public boolean onTouchEvent(MotionEvent event) { - if (mAnimator != null) mAnimator.start(); + if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { + if (mAnimator != null) mAnimator.start(); + } return super.onTouchEvent(event); } diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java index a4a781dc6ff5..e6a9d4fdd547 100644 --- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java +++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java @@ -160,10 +160,9 @@ public class NumPadKey extends ViewGroup { public boolean onTouchEvent(MotionEvent event) { if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { doHapticKeyClick(); + if (mAnimator != null) mAnimator.start(); } - if (mAnimator != null) mAnimator.start(); - return super.onTouchEvent(event); } diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java index 9686c91f2c31..79f0688db869 100644 --- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java @@ -16,8 +16,8 @@ package com.android.systemui.appops; -import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_CAMERA; -import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE; +import static android.hardware.SensorPrivacyManager.Sensors.CAMERA; +import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE; import static android.media.AudioManager.ACTION_MICROPHONE_MUTE_CHANGED; import android.Manifest; @@ -137,8 +137,8 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon mAudioManager = audioManager; mSensorPrivacyController = sensorPrivacyController; mMicMuted = audioManager.isMicrophoneMute() - || mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_MICROPHONE); - mCameraDisabled = mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_CAMERA); + || mSensorPrivacyController.isSensorBlocked(MICROPHONE); + mCameraDisabled = mSensorPrivacyController.isSensorBlocked(CAMERA); mLocationManager = context.getSystemService(LocationManager.class); mPackageManager = context.getPackageManager(); dumpManager.registerDumpable(TAG, this); @@ -159,8 +159,8 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon mSensorPrivacyController.addCallback(this); mMicMuted = mAudioManager.isMicrophoneMute() - || mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_MICROPHONE); - mCameraDisabled = mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_CAMERA); + || mSensorPrivacyController.isSensorBlocked(MICROPHONE); + mCameraDisabled = mSensorPrivacyController.isSensorBlocked(CAMERA); mBGHandler.post(() -> mAudioRecordingCallback.onRecordingConfigChanged( mAudioManager.getActiveRecordingConfigurations())); @@ -583,16 +583,16 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon @Override public void onReceive(Context context, Intent intent) { mMicMuted = mAudioManager.isMicrophoneMute() - || mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_MICROPHONE); + || mSensorPrivacyController.isSensorBlocked(MICROPHONE); updateSensorDisabledStatus(); } @Override public void onSensorBlockedChanged(int sensor, boolean blocked) { mBGHandler.post(() -> { - if (sensor == INDIVIDUAL_SENSOR_CAMERA) { + if (sensor == CAMERA) { mCameraDisabled = blocked; - } else if (sensor == INDIVIDUAL_SENSOR_MICROPHONE) { + } else if (sensor == MICROPHONE) { mMicMuted = mAudioManager.isMicrophoneMute() || blocked; } updateSensorDisabledStatus(); diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt index cad166d7cd9e..1ea1d97cace5 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt @@ -41,7 +41,7 @@ import com.android.systemui.controls.ui.ControlsUiController object ControlsAnimations { - private const val ALPHA_EXIT_DURATION = 167L + private const val ALPHA_EXIT_DURATION = 183L private const val ALPHA_ENTER_DELAY = ALPHA_EXIT_DURATION private const val ALPHA_ENTER_DURATION = 350L - ALPHA_ENTER_DELAY diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt index 1c2f17c55671..2d647a907b17 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt @@ -313,6 +313,10 @@ class ControlsFavoritingActivity @Inject constructor( setOnClickListener { val i = Intent().apply { component = ComponentName(context, ControlsProviderSelectorActivity::class.java) + putExtra( + ControlsUiController.BACK_TO_GLOBAL_ACTIONS, + backToGlobalActions + ) } if (doneButton.isEnabled) { // The user has made changes diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt index 08147746a4c8..d5e41d031eac 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt @@ -32,6 +32,8 @@ import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver import com.android.systemui.R import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.controls.controller.ControlsController +import com.android.systemui.controls.ui.ControlsDialog +import com.android.systemui.controls.ui.ControlsUiController import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.globalactions.GlobalActionsComponent @@ -49,13 +51,15 @@ class ControlsProviderSelectorActivity @Inject constructor( private val listingController: ControlsListingController, private val controlsController: ControlsController, private val globalActionsComponent: GlobalActionsComponent, - broadcastDispatcher: BroadcastDispatcher + private val broadcastDispatcher: BroadcastDispatcher, + private val uiController: ControlsUiController ) : LifecycleActivity() { companion object { private const val TAG = "ControlsProviderSelectorActivity" } + private var backToGlobalActions = true private lateinit var recyclerView: RecyclerView private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) { private val startingUser = listingController.currentUserId @@ -101,10 +105,19 @@ class ControlsProviderSelectorActivity @Inject constructor( } } requireViewById<View>(R.id.done).visibility = View.GONE + + backToGlobalActions = intent.getBooleanExtra( + ControlsUiController.BACK_TO_GLOBAL_ACTIONS, + true + ) } override fun onBackPressed() { - globalActionsComponent.handleShowGlobalActionsMenu() + if (backToGlobalActions) { + globalActionsComponent.handleShowGlobalActionsMenu() + } else { + ControlsDialog(applicationContext, broadcastDispatcher).show(uiController) + } animateExitAndFinish() } @@ -152,8 +165,13 @@ class ControlsProviderSelectorActivity @Inject constructor( listingController.getAppLabel(it)) putExtra(Intent.EXTRA_COMPONENT_NAME, it) putExtra(ControlsFavoritingActivity.EXTRA_FROM_PROVIDER_SELECTOR, true) + putExtra( + ControlsUiController.BACK_TO_GLOBAL_ACTIONS, + backToGlobalActions + ) } startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(this).toBundle()) + animateExitAndFinish() } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java index d4678f39e404..127128dda112 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java @@ -103,6 +103,11 @@ public class KeyguardIndicationRotateTextViewController extends */ public void updateIndication(@IndicationType int type, KeyguardIndication newIndication, boolean showImmediately) { + if (type == INDICATION_TYPE_NOW_PLAYING + || type == INDICATION_TYPE_REVERSE_CHARGING) { + // temporarily don't show here, instead use AmbientContainer b/181049781 + return; + } final boolean hasPreviousIndication = mIndicationMessages.get(type) != null; final boolean hasNewIndication = newIndication != null; if (!hasNewIndication) { diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java index 2f9b17aece8e..2ea8657f88e5 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java @@ -19,8 +19,6 @@ package com.android.systemui.people; import static android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID; import static android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID; -import static com.android.systemui.people.PeopleSpaceUtils.getUserHandle; - import android.app.Activity; import android.app.INotificationManager; import android.app.people.IPeopleManager; @@ -155,7 +153,7 @@ public class PeopleSpaceActivity extends Activity { if (DEBUG) Log.d(TAG, "Caching shortcut for PeopleTile: " + tile.getId()); mLauncherApps.cacheShortcuts(tile.getPackageName(), Collections.singletonList(tile.getId()), - getUserHandle(tile), LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS); + tile.getUserHandle(), LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS); } catch (Exception e) { Log.w(TAG, "Exception caching shortcut:" + e); } diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTileView.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTileView.java index 9ae7847031aa..6f89332e66a9 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTileView.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTileView.java @@ -21,7 +21,6 @@ import android.content.Context; import android.content.pm.LauncherApps; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; -import android.os.UserHandle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -82,7 +81,7 @@ public class PeopleSpaceTileView extends LinearLayout { public void setOnClickListener(LauncherApps launcherApps, PeopleSpaceTile tile) { mTileView.setOnClickListener(v -> launcherApps.startShortcut(tile.getPackageName(), tile.getId(), null, null, - UserHandle.getUserHandleForUid(tile.getUid()))); + tile.getUserHandle())); } /** Sets the click listener of the tile directly. */ diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java index 5dda23e4a47e..41080afb3604 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java @@ -55,7 +55,6 @@ import android.net.Uri; import android.os.Bundle; import android.os.Parcelable; import android.os.ServiceManager; -import android.os.UserHandle; import android.provider.ContactsContract; import android.provider.Settings; import android.service.notification.ConversationChannelWrapper; @@ -160,7 +159,6 @@ public class PeopleSpaceUtils { List<ConversationChannelWrapper> conversations = notificationManager.getConversations( false).getList(); - // Add priority conversations to tiles list. Stream<ShortcutInfo> priorityConversations = conversations.stream() .filter(c -> c.getNotificationChannel() != null @@ -252,7 +250,11 @@ public class PeopleSpaceUtils { } // If tile is null, we need to retrieve from persisted storage. - if (DEBUG) Log.d(TAG, "Retrieving from storage after reboots"); + if (DEBUG) { + Log.d(TAG, + "Retrieving from storage after reboots: " + shortcutId + " user: " + userId + + " pkg: " + pkg); + } LauncherApps launcherApps = context.getSystemService(LauncherApps.class); ConversationChannel channel = peopleManager.getConversation(pkg, userId, shortcutId); if (channel == null) { @@ -384,7 +386,7 @@ public class PeopleSpaceUtils { PeopleSpaceTile tile, Map<String, NotificationEntry> visibleNotifications) { String shortcutId = tile.getId(); String packageName = tile.getPackageName(); - int userId = UserHandle.getUserHandleForUid(tile.getUid()).getIdentifier(); + int userId = getUserId(tile); String key = getKey(shortcutId, packageName, userId); if (!visibleNotifications.containsKey(key)) { if (DEBUG) Log.d(TAG, "No existing notifications for key:" + key); @@ -641,7 +643,8 @@ public class PeopleSpaceUtils { activityIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_TILE_ID, tile.getId()); activityIntent.putExtra( PeopleSpaceWidgetProvider.EXTRA_PACKAGE_NAME, tile.getPackageName()); - activityIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_UID, tile.getUid()); + activityIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_USER_HANDLE, + tile.getUserHandle()); views.setOnClickPendingIntent(R.id.item, PendingIntent.getActivity( context, appWidgetId, @@ -788,7 +791,6 @@ public class PeopleSpaceUtils { Log.i(TAG, "ConversationChannel is null"); return null; } - PeopleSpaceTile tile = new PeopleSpaceTile.Builder(channel, launcherApps).build(); if (!PeopleSpaceUtils.shouldKeepConversation(tile)) { Log.i(TAG, "PeopleSpaceTile is not valid"); @@ -1069,11 +1071,6 @@ public class PeopleSpaceUtils { /** Returns the userId associated with a {@link PeopleSpaceTile} */ public static int getUserId(PeopleSpaceTile tile) { - return getUserHandle(tile).getIdentifier(); - } - - /** Returns the {@link UserHandle} associated with a {@link PeopleSpaceTile} */ - public static UserHandle getUserHandle(PeopleSpaceTile tile) { - return UserHandle.getUserHandleForUid(tile.getUid()); + return tile.getUserHandle().getIdentifier(); } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java b/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java index 358f3113ef88..13e30f920f42 100644 --- a/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java +++ b/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java @@ -41,7 +41,8 @@ public class LaunchConversationActivity extends Activity { Intent intent = getIntent(); String tileId = intent.getStringExtra(PeopleSpaceWidgetProvider.EXTRA_TILE_ID); String packageName = intent.getStringExtra(PeopleSpaceWidgetProvider.EXTRA_PACKAGE_NAME); - int uid = intent.getIntExtra(PeopleSpaceWidgetProvider.EXTRA_UID, 0); + UserHandle userHandle = intent.getParcelableExtra( + PeopleSpaceWidgetProvider.EXTRA_USER_HANDLE); if (tileId != null && !tileId.isEmpty()) { if (DEBUG) { @@ -52,7 +53,7 @@ public class LaunchConversationActivity extends Activity { LauncherApps launcherApps = getApplicationContext().getSystemService(LauncherApps.class); launcherApps.startShortcut( - packageName, tileId, null, null, UserHandle.getUserHandleForUid(uid)); + packageName, tileId, null, null, userHandle); } catch (Exception e) { Log.e(TAG, "Exception starting shortcut:" + e); } diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java index 3d1055fdece2..90baf56e0137 100644 --- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java +++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java @@ -48,7 +48,7 @@ public class PeopleSpaceWidgetProvider extends AppWidgetProvider { public static final String EXTRA_TILE_ID = "extra_tile_id"; public static final String EXTRA_PACKAGE_NAME = "extra_package_name"; - public static final String EXTRA_UID = "extra_uid"; + public static final String EXTRA_USER_HANDLE = "extra_user_handle"; public UiEventLogger mUiEventLogger = new UiEventLoggerImpl(); diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java index 80794cb64883..050352292b38 100644 --- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java +++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java @@ -123,7 +123,8 @@ public class PeopleSpaceWidgetRemoteViewsFactory implements RemoteViewsService.R fillInIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_TILE_ID, tile.getId()); fillInIntent.putExtra( PeopleSpaceWidgetProvider.EXTRA_PACKAGE_NAME, tile.getPackageName()); - fillInIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_UID, tile.getUid()); + fillInIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_USER_HANDLE, + tile.getUserHandle()); personView.setOnClickFillInIntent(R.id.item, fillInIntent); } catch (Exception e) { Log.e(TAG, "Couldn't retrieve shortcut information", e); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java index c8edaec98ee4..fe76668ab68b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java @@ -29,7 +29,6 @@ import com.android.systemui.qs.QSPanel.QSTileLayout; import com.android.systemui.qs.TouchAnimator.Builder; import com.android.systemui.qs.TouchAnimator.Listener; import com.android.systemui.qs.dagger.QSScope; -import com.android.systemui.qs.tileimpl.QSTileBaseView; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.tuner.TunerService; import com.android.systemui.tuner.TunerService.Tunable; @@ -56,7 +55,8 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha private final ArrayList<View> mAllViews = new ArrayList<>(); /** * List of {@link View}s representing Quick Settings that are being animated from the quick QS - * position to the normal QS panel. + * position to the normal QS panel. These views will only show once the animation is complete, + * to prevent overlapping of semi transparent views */ private final ArrayList<View> mQuickQsViews = new ArrayList<>(); private final QuickQSPanel mQuickQsPanel; @@ -233,7 +233,6 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha // Quick tiles. QSTileView quickTileView = mQuickQSPanelController.getTileView(tile); if (quickTileView == null) continue; - View qqsBgCircle = ((QSTileBaseView) quickTileView).getBgCircle(); getRelativePosition(loc1, quickTileView.getIcon().getIconView(), view); getRelativePosition(loc2, tileIcon, view); @@ -255,11 +254,6 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha translationXBuilder.addFloat(tileView, "translationX", -xDiff, 0); translationYBuilder.addFloat(tileView, "translationY", -yDiff, 0); - if (mFeatureFlags.isQSLabelsEnabled()) { - firstPageBuilder.addFloat(qqsBgCircle, "alpha", 1, 1, 0); - mAllViews.add(qqsBgCircle); - } - } else { // These tiles disappear when expanding firstPageBuilder.addFloat(quickTileView, "alpha", 1, 0); translationYBuilder.addFloat(quickTileView, "translationY", 0, yDiff); @@ -271,7 +265,11 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha translationX); } - mQuickQsViews.add(tileView.getIconWithBackground()); + if (mFeatureFlags.isQSLabelsEnabled()) { + mQuickQsViews.add(tileView); + } else { + mQuickQsViews.add(tileView.getIconWithBackground()); + } mAllViews.add(tileView.getIcon()); mAllViews.add(quickTileView); } else if (mFullRows && isIconInAnimatedRow(count)) { @@ -362,7 +360,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha if(view == parent || view == null) return; // Ignore tile pages as they can have some offset we don't want to take into account in // RTL. - if (!(view instanceof PagedTileLayout.TilePage || view instanceof SideLabelTileLayout)) { + if (!isAPage(view)) { loc1[0] += view.getLeft(); loc1[1] += view.getTop(); } @@ -374,6 +372,16 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha getRelativePositionInt(loc1, (View) view.getParent(), parent); } + // Returns true if the view is a possible page in PagedTileLayout + private boolean isAPage(View view) { + if (view instanceof PagedTileLayout.TilePage) { + return true; + } else if (view instanceof SideLabelTileLayout) { + return !(view instanceof QuickQSPanel.QQSSideLabelTileLayout); + } + return false; + } + public void setPosition(float position) { if (mNeedsAnimatorUpdate) { updateAnimators(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java index 16e519657a41..2bea72cc0c7e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java @@ -16,6 +16,8 @@ package com.android.systemui.qs; +import static com.android.systemui.qs.dagger.QSFlagsModule.PM_LITE_ENABLED; + import android.content.ClipData; import android.content.ClipboardManager; import android.content.Intent; @@ -41,6 +43,7 @@ import com.android.systemui.tuner.TunerService; import com.android.systemui.util.ViewController; import javax.inject.Inject; +import javax.inject.Named; /** * Controller for {@link QSFooterView}. @@ -63,6 +66,8 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme private final View mEdit; private final MultiUserSwitch mMultiUserSwitch; private final PageIndicator mPageIndicator; + private final View mPowerMenuLite; + private final boolean mShowPMLiteButton; private final UserInfoController.OnUserInfoChangedListener mOnUserInfoChangedListener = new UserInfoController.OnUserInfoChangedListener() { @@ -123,7 +128,8 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme DeviceProvisionedController deviceProvisionedController, UserTracker userTracker, QSPanelController qsPanelController, QSDetailDisplayer qsDetailDisplayer, QuickQSPanelController quickQSPanelController, - TunerService tunerService, MetricsLogger metricsLogger) { + TunerService tunerService, MetricsLogger metricsLogger, + @Named(PM_LITE_ENABLED) boolean showPMLiteButton) { super(view); mUserManager = userManager; mUserInfoController = userInfoController; @@ -141,10 +147,15 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme mEdit = mView.findViewById(android.R.id.edit); mMultiUserSwitch = mView.findViewById(R.id.multi_user_switch); mPageIndicator = mView.findViewById(R.id.footer_page_indicator); + mPowerMenuLite = mView.findViewById(R.id.pm_lite); + mShowPMLiteButton = showPMLiteButton; } @Override protected void onViewAttached() { + if (mShowPMLiteButton) { + mPowerMenuLite.setVisibility(View.VISIBLE); + } mView.addOnLayoutChangeListener( (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> mView.updateAnimator( diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 91ae571d1cfb..05633071be86 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -112,7 +112,7 @@ public class QSPanel extends LinearLayout implements Tunable { private int mMediaTotalBottomMargin; private int mFooterMarginStartHorizontal; private Consumer<Boolean> mMediaVisibilityChangedListener; - private boolean mSideLabels; + protected boolean mSideLabels; public QSPanel(Context context, AttributeSet attrs) { super(context, attrs); @@ -201,16 +201,20 @@ public class QSPanel extends LinearLayout implements Tunable { mFooterPageIndicator.setNumPages(((PagedTileLayout) mTileLayout).getNumPages()); } - // Allow the UI to be as big as it want's to, we're in a scroll view - int newHeight = 10000; - int availableHeight = MeasureSpec.getSize(heightMeasureSpec); - int excessHeight = newHeight - availableHeight; - // Measure with EXACTLY. That way, The content will only use excess height and will - // be measured last, after other views and padding is accounted for. This only - // works because our Layouts in here remeasure themselves with the exact content - // height. - heightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY); - ((PagedTileLayout) mTileLayout).setExcessHeight(excessHeight); + // In landscape, mTileLayout's parent is not the panel but a view that contains the + // tile layout and the media controls. + if (((View) mTileLayout).getParent() == this) { + // Allow the UI to be as big as it want's to, we're in a scroll view + int newHeight = 10000; + int availableHeight = MeasureSpec.getSize(heightMeasureSpec); + int excessHeight = newHeight - availableHeight; + // Measure with EXACTLY. That way, The content will only use excess height and will + // be measured last, after other views and padding is accounted for. This only + // works because our Layouts in here remeasure themselves with the exact content + // height. + heightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY); + ((PagedTileLayout) mTileLayout).setExcessHeight(excessHeight); + } } super.onMeasure(widthMeasureSpec, heightMeasureSpec); @@ -758,7 +762,7 @@ public class QSPanel extends LinearLayout implements Tunable { // Let's use 3 columns to match the current layout int columns; if (mSideLabels) { - columns = horizontal ? 1 : 2; + columns = horizontal ? 2 : 4; } else { columns = horizontal ? 3 : TileLayout.NO_MAX_COLUMNS; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java index fcb35e2040ea..d60801ef2d03 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java @@ -18,7 +18,6 @@ package com.android.systemui.qs; import static com.android.systemui.media.dagger.MediaModule.QS_PANEL; import static com.android.systemui.qs.QSPanel.QS_SHOW_BRIGHTNESS; -import static com.android.systemui.qs.dagger.QSFlagsModule.QS_LABELS_FLAG; import static com.android.systemui.qs.dagger.QSFragmentModule.QS_USING_MEDIA_PLAYER; import android.annotation.NonNull; @@ -66,7 +65,6 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { private BrightnessMirrorController mBrightnessMirrorController; private boolean mGridContentVisible = true; - private boolean mQsLabelsFlag; private final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener = new QSPanel.OnConfigurationChangedListener() { @@ -93,7 +91,6 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { DumpManager dumpManager, MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger, BrightnessController.Factory brightnessControllerFactory, BrightnessSlider.Factory brightnessSliderFactory, - @Named(QS_LABELS_FLAG) boolean qsLabelsFlag, FeatureFlags featureFlags) { super(view, qstileHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger, uiEventLogger, qsLogger, dumpManager, featureFlags); @@ -108,9 +105,6 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { mView.setBrightnessView(mBrightnessSlider.getRootView()); mBrightnessController = brightnessControllerFactory.create(mBrightnessSlider); - - mQsLabelsFlag = qsLabelsFlag; - mSideLabels = qsLabelsFlag; } @Override @@ -329,7 +323,7 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { @Override public void onTuningChanged(String key, String newValue) { if (QS_REMOVE_LABELS.equals(key)) { - if (!mQsLabelsFlag) return; + if (!mQSLabelFlag) return; boolean newShowLabels = newValue == null || "0".equals(newValue); if (mShowLabels == newShowLabels) return; mShowLabels = newShowLabels; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java index 9426e7122c1c..f1174fbe7aef 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java @@ -75,7 +75,7 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr private final QSHost.Callback mQSHostCallback = this::setTiles; protected boolean mShowLabels = true; - protected boolean mSideLabels; + protected boolean mQSLabelFlag; private final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener = new QSPanel.OnConfigurationChangedListener() { @@ -118,11 +118,12 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr mQSLogger = qsLogger; mDumpManager = dumpManager; mFeatureFlags = featureFlags; + mQSLabelFlag = featureFlags.isQSLabelsEnabled(); } @Override protected void onInit() { - mView.initialize(mSideLabels); + mView.initialize(mQSLabelFlag); mQSLogger.logAllTilesChangeListening(mView.isListening(), mView.getDumpableTag(), ""); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java index a29ac3bb77e9..9b66b59c06df 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java @@ -69,12 +69,22 @@ public class QuickQSPanel extends QSPanel { @Override public TileLayout createRegularTileLayout() { - return new QuickQSPanel.HeaderTileLayout(mContext); + if (mSideLabels) { + return new QQSSideLabelTileLayout(mContext); + } else { + return new QuickQSPanel.HeaderTileLayout(mContext); + } } @Override protected QSTileLayout createHorizontalTileLayout() { - return new DoubleLineTileLayout(mContext); + if (mSideLabels) { + TileLayout t = createRegularTileLayout(); + t.setMaxColumns(2); + return t; + } else { + return new DoubleLineTileLayout(mContext); + } } @Override @@ -331,4 +341,38 @@ public class QuickQSPanel extends QSPanel { } } } + + static class QQSSideLabelTileLayout extends SideLabelTileLayout { + QQSSideLabelTileLayout(Context context) { + super(context, null); + setClipChildren(false); + setClipToPadding(false); + LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, + LayoutParams.WRAP_CONTENT); + lp.gravity = Gravity.CENTER_HORIZONTAL; + setLayoutParams(lp); + setMaxColumns(4); + } + + @Override + public boolean updateResources() { + boolean b = super.updateResources(); + mMaxAllowedRows = 2; + return b; + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + updateResources(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + // Make sure to always use the correct number of rows. As it's determined by the + // columns, just use as many as needed. + updateMaxRows(10000, mRecords.size()); + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java index 383e932a6955..671f8f7dd2d1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java @@ -17,7 +17,6 @@ package com.android.systemui.qs; import static com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL; -import static com.android.systemui.qs.dagger.QSFlagsModule.QS_LABELS_FLAG; import static com.android.systemui.qs.dagger.QSFragmentModule.QS_USING_MEDIA_PLAYER; import com.android.internal.logging.MetricsLogger; @@ -42,8 +41,6 @@ import javax.inject.Named; @QSScope public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel> { - private boolean mUseSideLabels; - private final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener = newConfig -> { int newMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns); @@ -58,12 +55,10 @@ public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel> @Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer, @Named(QUICK_QS_PANEL) MediaHost mediaHost, MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger, - DumpManager dumpManager, @Named(QS_LABELS_FLAG) boolean qsLabelsFlag, - FeatureFlags featureFlags + DumpManager dumpManager, FeatureFlags featureFlags ) { super(view, qsTileHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger, uiEventLogger, qsLogger, dumpManager, featureFlags); - mUseSideLabels = qsLabelsFlag; } @Override @@ -104,19 +99,7 @@ public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel> break; } } - if (mUseSideLabels) { - List<QSTile> newTiles = new ArrayList<>(); - for (int i = 0; i < tiles.size(); i += 2) { - newTiles.add(tiles.get(i)); - } - for (int i = 1; i < tiles.size(); i += 2) { - newTiles.add(tiles.get(i)); - } - super.setTiles(newTiles, true); - - } else { - super.setTiles(tiles, true); - } + super.setTiles(tiles, !mQSLabelFlag); } /** */ diff --git a/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt index 74a7ac1cb6dd..4de4a78e9e8a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt @@ -20,11 +20,13 @@ import android.content.Context import android.util.AttributeSet import com.android.systemui.R -open class SideLabelTileLayout(context: Context, attrs: AttributeSet) : TileLayout(context, attrs) { +open class SideLabelTileLayout( + context: Context, + attrs: AttributeSet? +) : TileLayout(context, attrs) { override fun updateResources(): Boolean { return super.updateResources().also { - mResourceColumns = 2 mMaxAllowedRows = 4 mCellMarginHorizontal = (mCellMarginHorizontal * 1.2).toInt() mCellMarginVertical = mCellMarginHorizontal diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java index d559e07f3ff6..14cbf980df23 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java @@ -124,6 +124,7 @@ public class TileLayout extends ViewGroup implements QSTileLayout { public boolean updateResources() { final Resources res = mContext.getResources(); mResourceColumns = Math.max(1, res.getInteger(R.integer.quick_settings_num_columns)); + updateColumns(); mMaxCellHeight = mContext.getResources().getDimensionPixelSize(R.dimen.qs_tile_height); mCellMarginHorizontal = res.getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal); mCellMarginVertical= res.getDimensionPixelSize(R.dimen.qs_tile_margin_vertical); diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java index 048fdc3a0e5a..7a91421b00a1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java @@ -75,6 +75,8 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta private static final int ACTION_ADD = 1; private static final int ACTION_MOVE = 2; + private static final int NUM_COLUMNS_ID = R.integer.quick_settings_edit_num_columns; + private final Context mContext; private final Handler mHandler = new Handler(); @@ -87,6 +89,7 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta private int mEditIndex; private int mTileDividerIndex; private int mFocusIndex; + private boolean mNeedsFocus; private List<String> mCurrentSpecs; private List<TileInfo> mOtherTiles; @@ -109,7 +112,7 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta mDecoration = new TileItemDecoration(context); mMarginDecoration = new MarginTileDecoration(); mMinNumTiles = context.getResources().getInteger(R.integer.quick_settings_min_num_tiles); - mNumColumns = context.getResources().getInteger(R.integer.quick_settings_num_columns); + mNumColumns = context.getResources().getInteger(NUM_COLUMNS_ID); mAccessibilityDelegate = new TileAdapterDelegate(); } @@ -129,7 +132,7 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta * @return {@code true} if the number of columns changed, {@code false} otherwise */ public boolean updateNumColumns() { - int numColumns = mContext.getResources().getInteger(R.integer.quick_settings_num_columns); + int numColumns = mContext.getResources().getInteger(NUM_COLUMNS_ID); if (numColumns != mNumColumns) { mNumColumns = numColumns; return true; diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlagsModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlagsModule.java index 35a8257bd5a7..10192bc20df9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlagsModule.java +++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlagsModule.java @@ -21,6 +21,7 @@ import android.hardware.display.ColorDisplayManager; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.statusbar.FeatureFlags; +import com.android.systemui.util.settings.GlobalSettings; import javax.inject.Named; @@ -31,6 +32,8 @@ import dagger.Provides; public interface QSFlagsModule { String QS_LABELS_FLAG = "qs_labels_flag"; String RBC_AVAILABLE = "rbc_available"; + String PM_LITE_ENABLED = "pm_lite"; + String PM_LITE_SETTING = "sysui_pm_lite"; @Provides @SysUISingleton @@ -46,4 +49,11 @@ public interface QSFlagsModule { static boolean isReduceBrightColorsAvailable(Context context) { return ColorDisplayManager.isReduceBrightColorsAvailable(context); } + + @Provides + @SysUISingleton + @Named(PM_LITE_ENABLED) + static boolean isPMLiteEnabled(FeatureFlags featureFlags, GlobalSettings globalSettings) { + return featureFlags.isPMLiteEnabled() && globalSettings.getInt(PM_LITE_SETTING, 0) != 0; + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java index a699e2ec7cfc..424aafac1f7a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java @@ -105,22 +105,25 @@ public class QSTileView extends QSTileBaseView { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + mLabel.setSingleLine(false); super.onMeasure(widthMeasureSpec, heightMeasureSpec); - // Remeasure view if the primary label requires more then 2 lines or the secondary label - // text will be cut off. - if (mLabel.getLineCount() > mMaxLabelLines || !TextUtils.isEmpty(mSecondLine.getText()) - && mSecondLine.getLineHeight() > mSecondLine.getHeight()) { - if (!mLabel.isSingleLine()) { - mLabel.setSingleLine(); - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - } else { - if (mLabel.isSingleLine()) { - mLabel.setSingleLine(false); - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } + // Remeasure view if the primary label requires more than mMaxLabelLines lines or the + // secondary label text will be cut off. + if (shouldLabelBeSingleLine()) { + mLabel.setSingleLine(); + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + } + + private boolean shouldLabelBeSingleLine() { + if (mLabel.getLineCount() > mMaxLabelLines) { + return true; + } else if (!TextUtils.isEmpty(mSecondLine.getText()) + && mLabel.getLineCount() > mMaxLabelLines - 1) { + return true; } + return false; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt index dc81b702021f..07d48f32ff20 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewHorizontal.kt @@ -16,6 +16,7 @@ package com.android.systemui.qs.tileimpl +import android.animation.ValueAnimator import android.content.Context import android.content.res.ColorStateList import android.graphics.Color @@ -24,20 +25,24 @@ import android.graphics.drawable.PaintDrawable import android.graphics.drawable.RippleDrawable import android.service.quicksettings.Tile.STATE_ACTIVE import android.view.Gravity -import android.view.View import android.widget.LinearLayout +import android.widget.RelativeLayout import com.android.systemui.R import com.android.systemui.plugins.qs.QSIconView import com.android.systemui.plugins.qs.QSTile import com.android.systemui.qs.tileimpl.QSTileImpl.getColorForState +// Placeholder +private const val CORNER_RADIUS = 40f + class QSTileViewHorizontal( context: Context, icon: QSIconView ) : QSTileView(context, icon, false) { private var paintDrawable: PaintDrawable? = null - private var divider: View? = null + private var paintColor = Color.TRANSPARENT + private var paintAnimator: ValueAnimator? = null init { orientation = HORIZONTAL @@ -49,7 +54,12 @@ class QSTileViewHorizontal( override fun createLabel() { super.createLabel() - findViewById<LinearLayout>(R.id.label_group)?.gravity = Gravity.START + findViewById<LinearLayout>(R.id.label_group)?.apply { + gravity = Gravity.START + (layoutParams as? RelativeLayout.LayoutParams)?.apply { + removeRule(RelativeLayout.ALIGN_PARENT_TOP) + } + } mLabel.gravity = Gravity.START mLabel.textDirection = TEXT_DIRECTION_LOCALE mSecondLine.gravity = Gravity.START @@ -57,7 +67,7 @@ class QSTileViewHorizontal( val padding = context.resources.getDimensionPixelSize(R.dimen.qs_tile_side_label_padding) mLabelContainer.setPaddingRelative(0, padding, padding, padding) (mLabelContainer.layoutParams as LayoutParams).gravity = - Gravity.CENTER_VERTICAL or Gravity.START + Gravity.CENTER_VERTICAL or Gravity.START } override fun updateRippleSize() { @@ -66,8 +76,8 @@ class QSTileViewHorizontal( override fun newTileBackground(): Drawable? { val d = super.newTileBackground() if (paintDrawable == null) { - paintDrawable = PaintDrawable(Color.WHITE).apply { - setCornerRadius(50f) + paintDrawable = PaintDrawable(paintColor).apply { + setCornerRadius(CORNER_RADIUS) } } if (d is RippleDrawable) { @@ -90,10 +100,39 @@ class QSTileViewHorizontal( override fun handleStateChanged(state: QSTile.State) { super.handleStateChanged(state) - paintDrawable?.setTint(getCircleColor(state.state)) mSecondLine.setTextColor(mLabel.textColors) mLabelContainer.background = null - divider?.backgroundTintList = mLabel.textColors + + val allowAnimations = animationsEnabled() && paintColor != Color.TRANSPARENT + val newColor = getCircleColor(state.state) + if (allowAnimations) { + animateToNewState(newColor) + } else { + if (newColor != paintColor) { + clearAnimator() + paintDrawable?.paint?.color = newColor + paintDrawable?.invalidateSelf() + } + } + paintColor = newColor + } + + private fun animateToNewState(newColor: Int) { + if (newColor != paintColor) { + clearAnimator() + paintAnimator = ValueAnimator.ofArgb(paintColor, newColor) + .setDuration(QSIconViewImpl.QS_ANIM_LENGTH).apply { + addUpdateListener { animation: ValueAnimator -> + paintDrawable?.paint?.color = animation.animatedValue as Int + paintDrawable?.invalidateSelf() + } + start() + } + } + } + + private fun clearAnimator() { + paintAnimator?.cancel()?.also { paintAnimator = null } } override fun handleExpand(dualTarget: Boolean) {} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java index 3841daca7ebe..70287cd37d01 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java @@ -16,10 +16,13 @@ package com.android.systemui.qs.tiles; -import static android.service.SensorPrivacyIndividualEnabledSensorProto.CAMERA; +import static android.content.pm.PackageManager.FEATURE_CAMERA_TOGGLE; +import static android.hardware.SensorPrivacyManager.Sensors.CAMERA; import static com.android.systemui.DejankUtils.whitelistIpcs; +import android.hardware.SensorPrivacyManager; +import android.hardware.SensorPrivacyManager.Sensors.Sensor; import android.os.Handler; import android.os.Looper; import android.provider.DeviceConfig; @@ -58,8 +61,8 @@ public class CameraToggleTile extends SensorPrivacyToggleTile { @Override public boolean isAvailable() { - return /*getHost().getContext().getPackageManager().hasSystemFeature(FEATURE_CAMERA_TOGGLE) - && */whitelistIpcs(() -> DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, + return getHost().getContext().getPackageManager().hasSystemFeature(FEATURE_CAMERA_TOGGLE) + && whitelistIpcs(() -> DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, "camera_toggle_enabled", false)); } @@ -75,7 +78,7 @@ public class CameraToggleTile extends SensorPrivacyToggleTile { } @Override - public int getSensorId() { + public @Sensor int getSensorId() { return CAMERA; } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java index 2f0071a1f198..e9b712df2154 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java @@ -16,10 +16,13 @@ package com.android.systemui.qs.tiles; -import static android.service.SensorPrivacyIndividualEnabledSensorProto.MICROPHONE; +import static android.content.pm.PackageManager.FEATURE_MICROPHONE_TOGGLE; +import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE; import static com.android.systemui.DejankUtils.whitelistIpcs; +import android.hardware.SensorPrivacyManager; +import android.hardware.SensorPrivacyManager.Sensors.Sensor; import android.os.Handler; import android.os.Looper; import android.provider.DeviceConfig; @@ -58,7 +61,9 @@ public class MicrophoneToggleTile extends SensorPrivacyToggleTile { @Override public boolean isAvailable() { - return whitelistIpcs(() -> DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, + return getHost().getContext().getPackageManager() + .hasSystemFeature(FEATURE_MICROPHONE_TOGGLE) + && whitelistIpcs(() -> DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, "mic_toggle_enabled", false)); } @@ -74,7 +79,7 @@ public class MicrophoneToggleTile extends SensorPrivacyToggleTile { } @Override - public int getSensorId() { + public @Sensor int getSensorId() { return MICROPHONE; } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java index 00703e7f8403..0c582bdbe12f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java @@ -17,7 +17,7 @@ package com.android.systemui.qs.tiles; import android.content.Intent; -import android.hardware.SensorPrivacyManager.IndividualSensor; +import android.hardware.SensorPrivacyManager.Sensors.Sensor; import android.os.Handler; import android.os.Looper; import android.service.quicksettings.Tile; @@ -49,7 +49,7 @@ public abstract class SensorPrivacyToggleTile extends QSTileImpl<QSTile.BooleanS /** * @return Id of the sensor that will be toggled */ - public abstract @IndividualSensor int getSensorId(); + public abstract @Sensor int getSensorId(); /** * @return icon for the QS tile diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS b/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS new file mode 100644 index 000000000000..9b3e386bc0d8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS @@ -0,0 +1,12 @@ +# Scroll Capture (Long Screenshots) +# Bug component: 801322 +# +# Referenced by: +# +# core/java/src/android/view/OWNERS +# core/java/src/com/android/internal/view/OWNERS +# core/tests/coretests/src/android/view/OWNERS +# core/tests/coretests/src/com/android/internal/view/OWNERS + +mrcasey@google.com +mrenouf@google.com diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 131fde6b4b56..805ac7cf1ec9 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -325,6 +325,7 @@ public class ScreenshotController { attachWindow(); mWindow.setContentView(mScreenshotView); + mScreenshotView.requestApplyInsets(); mScreenshotView.takePartialScreenshot( rect -> takeScreenshotInternal(finisher, rect)); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java index dc639dce4951..54b99bbe74cc 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java @@ -21,24 +21,25 @@ import static com.android.systemui.screenshot.LogConfig.DEBUG_SCROLL; import static java.lang.Math.min; import static java.util.Objects.requireNonNull; +import android.annotation.BinderThread; import android.annotation.UiContext; import android.app.ActivityTaskManager; import android.content.Context; import android.graphics.PixelFormat; -import android.graphics.Point; import android.graphics.Rect; import android.hardware.HardwareBuffer; import android.media.Image; import android.media.ImageReader; import android.os.IBinder; +import android.os.ICancellationSignal; import android.os.RemoteException; import android.util.Log; import android.view.IScrollCaptureCallbacks; import android.view.IScrollCaptureConnection; import android.view.IWindowManager; +import android.view.ScrollCaptureResponse; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.view.ScrollCaptureViewSupport; import java.util.function.Consumer; @@ -62,16 +63,19 @@ public class ScrollCaptureClient { */ public interface Connection { /** - * Session start should be deferred until UI is active because of resource allocation and - * potential visible side effects in the target window. - * + * Start a session. + * @param sessionConsumer listener to receive the session once active * @param maxPages the capture buffer size expressed as a multiple of the content height */ + // TODO ListenableFuture void start(Consumer<Session> sessionConsumer, float maxPages); /** - * Close the connection. + * Close the connection. Must end capture if started to avoid potential unwanted visual + * artifacts. + * + * @see Session#end(Runnable) */ void close(); } @@ -119,6 +123,7 @@ public class ScrollCaptureClient { * @param top the top (y) position of the tile to capture, in content rect space * @param consumer listener to be informed of the result */ + // TODO ListenableFuture void requestTile(int top, Consumer<CaptureResult> consumer); /** @@ -129,16 +134,31 @@ public class ScrollCaptureClient { */ int getMaxTiles(); + /** + * @return the height of each image tile + */ int getTileHeight(); + /** + * @return the height of scrollable content being captured + */ int getPageHeight(); + /** + * @return the width of the scrollable page + */ int getPageWidth(); /** + * @return the bounds on screen of the window being captured. + */ + Rect getWindowBounds(); + + /** * End the capture session, return the target app to original state. The listener * will be called when the target app is ready to before visible and interactive. */ + // TODO ListenableFuture void end(Runnable listener); } @@ -185,13 +205,13 @@ public class ScrollCaptureClient { + ", taskId=" + taskId + ", consumer=" + consumer + ")"); } mWindowManagerService.requestScrollCapture(displayId, mHostWindowToken, taskId, - new ControllerCallbacks(consumer)); + new ClientCallbacks(consumer)); } catch (RemoteException e) { Log.e(TAG, "Ignored remote exception", e); } } - private static class ControllerCallbacks extends IScrollCaptureCallbacks.Stub implements + private static class ClientCallbacks extends IScrollCaptureCallbacks.Stub implements Connection, Session, IBinder.DeathRecipient { private IScrollCaptureConnection mConnection; @@ -206,46 +226,63 @@ public class ScrollCaptureClient { private int mTileWidth; private Rect mRequestRect; private boolean mStarted; + + private ICancellationSignal mCancellationSignal; + private Rect mWindowBounds; + private Rect mBoundsInWindow; private int mMaxTiles; - private ControllerCallbacks(Consumer<Connection> connectionConsumer) { + private ClientCallbacks(Consumer<Connection> connectionConsumer) { mConnectionConsumer = connectionConsumer; } - // IScrollCaptureCallbacks - + @BinderThread @Override - public void onConnected(IScrollCaptureConnection connection, Rect scrollBounds, - Point positionInWindow) throws RemoteException { + public void onScrollCaptureResponse(ScrollCaptureResponse response) throws RemoteException { if (DEBUG_SCROLL) { - Log.d(TAG, "onConnected(connection=" + connection + ", scrollBounds=" + scrollBounds - + ", positionInWindow=" + positionInWindow + ")"); + Log.d(TAG, "onScrollCaptureResponse(response=" + response + ")"); } - mConnection = connection; - mConnection.asBinder().linkToDeath(this, 0); - mScrollBounds = scrollBounds; - mConnectionConsumer.accept(this); - mConnectionConsumer = null; - - int pxPerPage = mScrollBounds.width() * mScrollBounds.height(); - int pxPerTile = min(TILE_SIZE_PX_MAX, (pxPerPage / TILES_PER_PAGE)); - mTileWidth = mScrollBounds.width(); - mTileHeight = pxPerTile / mScrollBounds.width(); - if (DEBUG_SCROLL) { - Log.d(TAG, "scrollBounds: " + mScrollBounds); - Log.d(TAG, "tile dimen: " + mTileWidth + "x" + mTileHeight); + if (response.isConnected()) { + mConnection = response.getConnection(); + mConnection.asBinder().linkToDeath(this, 0); + mWindowBounds = response.getWindowBounds(); + mBoundsInWindow = response.getBoundsInWindow(); + + int pxPerPage = mBoundsInWindow.width() * mBoundsInWindow.height(); + int pxPerTile = min(TILE_SIZE_PX_MAX, (pxPerPage / TILES_PER_PAGE)); + mTileWidth = mBoundsInWindow.width(); + mTileHeight = pxPerTile / mBoundsInWindow.width(); + if (DEBUG_SCROLL) { + Log.d(TAG, "boundsInWindow: " + mBoundsInWindow); + Log.d(TAG, "tile size: " + mTileWidth + "x" + mTileHeight); + Log.d(TAG, "maxHeight: " + (mMaxTiles * mTileHeight) + "px"); + } + mConnectionConsumer.accept(this); } + mConnectionConsumer = null; } @Override - public void onUnavailable() throws RemoteException { + public void start(Consumer<Session> sessionConsumer, float maxPages) { if (DEBUG_SCROLL) { - Log.d(TAG, "onUnavailable"); + Log.d(TAG, "start(sessionConsumer=" + sessionConsumer + "," + + " maxPages=" + maxPages + ")"); + } + mMaxTiles = (int) Math.ceil(maxPages * TILES_PER_PAGE); + mReader = ImageReader.newInstance(mTileWidth, mTileHeight, PixelFormat.RGBA_8888, + mMaxTiles, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE); + mSessionConsumer = sessionConsumer; + + try { + mCancellationSignal = mConnection.startCapture(mReader.getSurface()); + mStarted = true; + } catch (RemoteException e) { + Log.w(TAG, "Failed to start", e); + mReader.close(); } - // The targeted app does not support scroll capture - // or the window could not be found... etc etc. } + @BinderThread @Override public void onCaptureStarted() { if (DEBUG_SCROLL) { @@ -256,13 +293,25 @@ public class ScrollCaptureClient { } @Override - public void onCaptureBufferSent(long frameNumber, Rect contentArea) { - Image image = null; - if (frameNumber != ScrollCaptureViewSupport.NO_FRAME_PRODUCED) { - image = mReader.acquireNextImage(); + public void requestTile(int top, Consumer<CaptureResult> consumer) { + if (DEBUG_SCROLL) { + Log.d(TAG, "requestTile(top=" + top + ", consumer=" + consumer + ")"); + } + cancelPendingRequest(); + mRequestRect = new Rect(0, top, mTileWidth, top + mTileHeight); + mResultConsumer = consumer; + try { + mCancellationSignal = mConnection.requestImage(mRequestRect); + } catch (RemoteException e) { + Log.e(TAG, "Caught remote exception from requestImage", e); } + } + + @Override + public void onImageRequestCompleted(int flags, Rect contentArea) { + Image image = mReader.acquireLatestImage(); if (DEBUG_SCROLL) { - Log.d(TAG, "onCaptureBufferSent(frameNumber=" + frameNumber + Log.d(TAG, "onCaptureBufferSent(flags=" + flags + ", contentArea=" + contentArea + ") image=" + image); } // Save and clear first, since the consumer will likely request the next @@ -273,17 +322,49 @@ public class ScrollCaptureClient { } @Override - public void onConnectionClosed() { + public void end(Runnable listener) { if (DEBUG_SCROLL) { - Log.d(TAG, "onConnectionClosed()"); + Log.d(TAG, "end(listener=" + listener + ")"); } - disconnect(); + if (mStarted) { + mShutdownListener = listener; + mReader.close(); + try { + // listener called from onConnectionClosed callback + mConnection.endCapture(); + } catch (RemoteException e) { + Log.d(TAG, "Ignored exception from endCapture()", e); + disconnect(); + listener.run(); + } + } else { + disconnect(); + listener.run(); + } + } + + @BinderThread + @Override + public void onCaptureEnded() { + close(); if (mShutdownListener != null) { mShutdownListener.run(); mShutdownListener = null; } } + @Override + public void close() { + if (mConnection != null) { + try { + mConnection.close(); + } catch (RemoteException e) { + /* ignore */ + } + disconnect(); + } + } + // Misc private void disconnect() { @@ -293,63 +374,25 @@ public class ScrollCaptureClient { mConnection = null; } - // ScrollCaptureController.Connection - - @Override - public void start(Consumer<Session> sessionConsumer, float maxPages) { - if (DEBUG_SCROLL) { - Log.d(TAG, "start(sessionConsumer=" + sessionConsumer + "," - + " maxPages=" + maxPages + ")" - + " [maxHeight: " + (mMaxTiles * mTileHeight) + "px]"); - } - mMaxTiles = (int) Math.ceil(maxPages * TILES_PER_PAGE); - mReader = ImageReader.newInstance(mTileWidth, mTileHeight, PixelFormat.RGBA_8888, - mMaxTiles, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE); - mSessionConsumer = sessionConsumer; - try { - mConnection.startCapture(mReader.getSurface()); - mStarted = true; - } catch (RemoteException e) { - Log.w(TAG, "Failed to start", e); - } - } - - @Override - public void close() { - end(null); - } - - // ScrollCaptureController.Session - + /** + * The process hosting the window went away abruptly! + */ @Override - public void end(Runnable listener) { + public void binderDied() { if (DEBUG_SCROLL) { - Log.d(TAG, "end(listener=" + listener + ")"); - } - if (mStarted) { - mShutdownListener = listener; - try { - // listener called from onConnectionClosed callback - mConnection.endCapture(); - } catch (RemoteException e) { - Log.d(TAG, "Ignored exception from endCapture()", e); - disconnect(); - listener.run(); - } - } else { - disconnect(); - listener.run(); + Log.d(TAG, "binderDied()"); } + disconnect(); } @Override public int getPageHeight() { - return mScrollBounds.height(); + return mBoundsInWindow.height(); } @Override public int getPageWidth() { - return mScrollBounds.width(); + return mBoundsInWindow.width(); } @Override @@ -357,34 +400,24 @@ public class ScrollCaptureClient { return mTileHeight; } - @Override - public int getMaxTiles() { - return mMaxTiles; + public Rect getWindowBounds() { + return new Rect(mWindowBounds); } @Override - public void requestTile(int top, Consumer<CaptureResult> consumer) { - if (DEBUG_SCROLL) { - Log.d(TAG, "requestTile(top=" + top + ", consumer=" + consumer + ")"); - } - mRequestRect = new Rect(0, top, mTileWidth, top + mTileHeight); - mResultConsumer = consumer; - try { - mConnection.requestImage(mRequestRect); - } catch (RemoteException e) { - Log.e(TAG, "Caught remote exception from requestImage", e); - } + public int getMaxTiles() { + return mMaxTiles; } - /** - * The process hosting the window went away abruptly! - */ - @Override - public void binderDied() { - if (DEBUG_SCROLL) { - Log.d(TAG, "binderDied()"); + private void cancelPendingRequest() { + if (mCancellationSignal != null) { + try { + mCancellationSignal.cancel(); + } catch (RemoteException e) { + /* ignore */ + } + mCancellationSignal = null; } - disconnect(); } } } diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt index 9f182e19efaf..658613796498 100644 --- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt @@ -25,8 +25,8 @@ import android.content.pm.PackageManager import android.content.res.Resources import android.hardware.SensorPrivacyManager import android.hardware.SensorPrivacyManager.EXTRA_SENSOR -import android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_CAMERA -import android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE +import android.hardware.SensorPrivacyManager.Sensors.CAMERA +import android.hardware.SensorPrivacyManager.Sensors.MICROPHONE import android.os.Bundle import android.os.Handler import android.text.Html @@ -81,7 +81,7 @@ class SensorUseStartedActivity : AlertActivity(), DialogInterface.OnClickListene dismiss() } } - if (!sensorPrivacyManager.isIndividualSensorPrivacyEnabled(sensor)) { + if (!sensorPrivacyManager.isSensorPrivacyEnabled(sensor)) { finish() return } @@ -89,9 +89,9 @@ class SensorUseStartedActivity : AlertActivity(), DialogInterface.OnClickListene mAlertParams.apply { try { mMessage = Html.fromHtml(getString(when (sensor) { - INDIVIDUAL_SENSOR_MICROPHONE -> + MICROPHONE -> R.string.sensor_privacy_start_use_mic_dialog_content - INDIVIDUAL_SENSOR_CAMERA -> + CAMERA -> R.string.sensor_privacy_start_use_camera_dialog_content else -> Resources.ID_NULL }, packageManager.getApplicationInfo(sensorUsePackageName, 0) @@ -102,9 +102,9 @@ class SensorUseStartedActivity : AlertActivity(), DialogInterface.OnClickListene } mIconId = when (sensor) { - INDIVIDUAL_SENSOR_MICROPHONE -> + MICROPHONE -> com.android.internal.R.drawable.perm_group_microphone - INDIVIDUAL_SENSOR_CAMERA -> com.android.internal.R.drawable.perm_group_camera + CAMERA -> com.android.internal.R.drawable.perm_group_camera else -> Resources.ID_NULL } @@ -121,7 +121,7 @@ class SensorUseStartedActivity : AlertActivity(), DialogInterface.OnClickListene override fun onStart() { super.onStart() - sensorPrivacyManager.suppressIndividualSensorPrivacyReminders(sensorUsePackageName, true) + sensorPrivacyManager.suppressSensorPrivacyReminders(sensorUsePackageName, true) unsuppressImmediately = false } @@ -156,11 +156,11 @@ class SensorUseStartedActivity : AlertActivity(), DialogInterface.OnClickListene if (unsuppressImmediately) { sensorPrivacyManager - .suppressIndividualSensorPrivacyReminders(sensorUsePackageName, false) + .suppressSensorPrivacyReminders(sensorUsePackageName, false) } else { Handler(mainLooper).postDelayed({ sensorPrivacyManager - .suppressIndividualSensorPrivacyReminders(sensorUsePackageName, false) + .suppressSensorPrivacyReminders(sensorUsePackageName, false) }, SUPPRESS_REMINDERS_REMOVAL_DELAY_MILLIS) } } @@ -170,7 +170,7 @@ class SensorUseStartedActivity : AlertActivity(), DialogInterface.OnClickListene } private fun disableSensorPrivacy() { - sensorPrivacyManager.setIndividualSensorPrivacyForProfileGroup(sensor, false) + sensorPrivacyManager.setSensorPrivacyForProfileGroup(sensor, false) unsuppressImmediately = true setResult(RESULT_OK) } diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java index 0bfc8e5d554b..fea521f15b84 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java @@ -72,8 +72,6 @@ public class BrightnessController implements ToggleSlider.Listener { private static final Uri BRIGHTNESS_FOR_VR_FLOAT_URI = Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_FOR_VR_FLOAT); - private final float mMinimumBacklight; - private final float mMaximumBacklight; private final float mDefaultBacklight; private final float mMinimumBacklightForVr; private final float mMaximumBacklightForVr; @@ -314,10 +312,6 @@ public class BrightnessController implements ToggleSlider.Listener { mDisplayId = mContext.getDisplayId(); PowerManager pm = context.getSystemService(PowerManager.class); - mMinimumBacklight = pm.getBrightnessConstraint( - PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM); - mMaximumBacklight = pm.getBrightnessConstraint( - PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM); mDefaultBacklight = mContext.getDisplay().getBrightnessDefault(); mMinimumBacklightForVr = pm.getBrightnessConstraint( PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM_VR); @@ -375,8 +369,8 @@ public class BrightnessController implements ToggleSlider.Listener { metric = mAutomatic ? MetricsEvent.ACTION_BRIGHTNESS_AUTO : MetricsEvent.ACTION_BRIGHTNESS; - minBacklight = mMinimumBacklight; - maxBacklight = mMaximumBacklight; + minBacklight = PowerManager.BRIGHTNESS_MIN; + maxBacklight = PowerManager.BRIGHTNESS_MAX; settingToChange = Settings.System.SCREEN_BRIGHTNESS_FLOAT; } final float valFloat = MathUtils.min(convertGammaToLinearFloat(value, @@ -439,8 +433,8 @@ public class BrightnessController implements ToggleSlider.Listener { min = mMinimumBacklightForVr; max = mMaximumBacklightForVr; } else { - min = mMinimumBacklight; - max = mMaximumBacklight; + min = PowerManager.BRIGHTNESS_MIN; + max = PowerManager.BRIGHTNESS_MAX; } // convertGammaToLinearFloat returns 0-1 if (BrightnessSynchronizer.floatEquals(brightnessValue, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java index cf77e290ebe3..1d59257c9c4f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java @@ -86,4 +86,8 @@ public class FeatureFlags { public boolean isNavigationBarOverlayEnabled() { return mFlagReader.isEnabled(R.bool.flag_navigation_bar_overlay); } + + public boolean isPMLiteEnabled() { + return mFlagReader.isEnabled(R.bool.flag_pm_lite); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapper.java index 414d62092ab2..222735aeb35a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapper.java @@ -49,7 +49,7 @@ public class NotificationCustomViewWrapper extends NotificationViewWrapper { // Custom views will most likely use just white or black as their text color. // We need to scan through and replace these colors by Material NEXT colors. - ensureThemeOnChildren(); + ensureThemeOnChildren(mView); // Let's invert the notification colors when we're in night mode and // the notification background isn't colorized. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationDecoratedCustomViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationDecoratedCustomViewWrapper.java index 301c3726793a..d21ae13a1e01 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationDecoratedCustomViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationDecoratedCustomViewWrapper.java @@ -64,7 +64,7 @@ public class NotificationDecoratedCustomViewWrapper extends NotificationTemplate // Custom views will most likely use just white or black as their text color. // We need to scan through and replace these colors by Material NEXT colors. - ensureThemeOnChildren(); + ensureThemeOnChildren(mWrappedView); if (needsInversion(resolveBackgroundColor(), mWrappedView)) { invertViewLuminosity(mWrappedView); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java index 0ef4c4d3b788..89babf0835c6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java @@ -58,9 +58,10 @@ public abstract class NotificationViewWrapper implements TransformableView { private final Rect mTmpRect = new Rect(); protected int mBackgroundColor = 0; - private int mLightTextColor; - private int mDarkTextColor; - private int mDefaultTextColor; + private int mMaterialTextColorPrimary; + private int mMaterialTextColorSecondary; + private int mThemedTextColorPrimary; + private int mThemedTextColorSecondary; private boolean mAdjustTheme; public static NotificationViewWrapper wrap(Context ctx, View v, ExpandableNotificationRow row) { @@ -124,15 +125,22 @@ public abstract class NotificationViewWrapper implements TransformableView { mBackgroundColor = backgroundColor; mView.setBackground(new ColorDrawable(Color.TRANSPARENT)); } - mLightTextColor = mView.getContext().getColor( - com.android.internal.R.color.notification_primary_text_color_light); - mDarkTextColor = mView.getContext().getColor( - com.android.internal.R.color.notification_primary_text_color_dark); + + Context materialTitleContext = new ContextThemeWrapper(mView.getContext(), + com.android.internal.R.style.TextAppearance_Material_Notification_Title); + mMaterialTextColorPrimary = Utils.getColorAttr(materialTitleContext, + com.android.internal.R.attr.textColor).getDefaultColor(); + Context materialContext = new ContextThemeWrapper(mView.getContext(), + com.android.internal.R.style.TextAppearance_Material_Notification); + mMaterialTextColorSecondary = Utils.getColorAttr(materialContext, + com.android.internal.R.attr.textColor).getDefaultColor(); Context themedContext = new ContextThemeWrapper(mView.getContext(), com.android.internal.R.style.Theme_DeviceDefault_DayNight); - mDefaultTextColor = Utils.getColorAttr(themedContext, + mThemedTextColorPrimary = Utils.getColorAttr(themedContext, com.android.internal.R.attr.textColorPrimary).getDefaultColor(); + mThemedTextColorSecondary = Utils.getColorAttr(themedContext, + com.android.internal.R.attr.textColorSecondary).getDefaultColor(); } protected boolean needsInversion(int defaultBackgroundColor, View view) { @@ -210,27 +218,30 @@ public abstract class NotificationViewWrapper implements TransformableView { return false; } - protected void ensureThemeOnChildren() { - if (!mAdjustTheme || mView == null) { + protected void ensureThemeOnChildren(View rootView) { + if (!mAdjustTheme || mView == null || rootView == null) { return; } // Notifications with custom backgrounds should not be adjusted if (mBackgroundColor != Color.TRANSPARENT - || getBackgroundColor(mView) != Color.TRANSPARENT) { + || getBackgroundColor(mView) != Color.TRANSPARENT + || getBackgroundColor(rootView) != Color.TRANSPARENT) { return; } // Now let's check if there's unprotected text somewhere, and apply the theme if we find it. - processTextColorRecursive(mView); + processTextColorRecursive(rootView); } private void processTextColorRecursive(View view) { if (view instanceof TextView) { TextView textView = (TextView) view; int foreground = textView.getCurrentTextColor(); - if (foreground == mLightTextColor || foreground == mDarkTextColor) { - textView.setTextColor(mDefaultTextColor); + if (foreground == mMaterialTextColorPrimary) { + textView.setTextColor(mThemedTextColorPrimary); + } else if (foreground == mMaterialTextColorSecondary) { + textView.setTextColor(mThemedTextColorSecondary); } } else if (view instanceof ViewGroup) { ViewGroup viewGroup = (ViewGroup) view; 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 093f57adb21a..83c347b05012 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -605,6 +605,7 @@ public class NotificationPanelViewController extends PanelViewController { mKeyguardQsUserSwitchEnabled = mKeyguardUserSwitcherEnabled && mResources.getBoolean( R.bool.config_keyguard_user_switch_opens_qs_details); + keyguardUpdateMonitor.setKeyguardQsUserSwitchEnabled(mKeyguardQsUserSwitchEnabled); mView.setWillNotDraw(!DEBUG); mLayoutInflater = layoutInflater; mFalsingManager = falsingManager; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java index a76d08a438f2..7f935d28285f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java @@ -16,17 +16,17 @@ package com.android.systemui.statusbar.policy; -import android.hardware.SensorPrivacyManager.IndividualSensor; +import android.hardware.SensorPrivacyManager.Sensors.Sensor; public interface IndividualSensorPrivacyController extends CallbackController<IndividualSensorPrivacyController.Callback> { void init(); - boolean isSensorBlocked(@IndividualSensor int sensor); + boolean isSensorBlocked(@Sensor int sensor); - void setSensorBlocked(@IndividualSensor int sensor, boolean blocked); + void setSensorBlocked(@Sensor int sensor, boolean blocked); interface Callback { - void onSensorBlockedChanged(@IndividualSensor int sensor, boolean blocked); + void onSensorBlockedChanged(@Sensor int sensor, boolean blocked); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java index 32d15ed41648..295df05797ea 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java @@ -16,11 +16,11 @@ package com.android.systemui.statusbar.policy; -import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_CAMERA; -import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE; +import static android.hardware.SensorPrivacyManager.Sensors.CAMERA; +import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE; import android.hardware.SensorPrivacyManager; -import android.hardware.SensorPrivacyManager.IndividualSensor; +import android.hardware.SensorPrivacyManager.Sensors.Sensor; import android.util.ArraySet; import android.util.SparseBooleanArray; @@ -30,8 +30,7 @@ import java.util.Set; public class IndividualSensorPrivacyControllerImpl implements IndividualSensorPrivacyController { - private static final int[] SENSORS = new int[] {INDIVIDUAL_SENSOR_CAMERA, - INDIVIDUAL_SENSOR_MICROPHONE}; + private static final int[] SENSORS = new int[] {CAMERA, MICROPHONE}; private final @NonNull SensorPrivacyManager mSensorPrivacyManager; private final SparseBooleanArray mState = new SparseBooleanArray(); @@ -48,18 +47,18 @@ public class IndividualSensorPrivacyControllerImpl implements IndividualSensorPr mSensorPrivacyManager.addSensorPrivacyListener(sensor, (enabled) -> onSensorPrivacyChanged(sensor, enabled)); - mState.put(sensor, mSensorPrivacyManager.isIndividualSensorPrivacyEnabled(sensor)); + mState.put(sensor, mSensorPrivacyManager.isSensorPrivacyEnabled(sensor)); } } @Override - public boolean isSensorBlocked(@IndividualSensor int sensor) { + public boolean isSensorBlocked(@Sensor int sensor) { return mState.get(sensor, false); } @Override - public void setSensorBlocked(@IndividualSensor int sensor, boolean blocked) { - mSensorPrivacyManager.setIndividualSensorPrivacyForProfileGroup(sensor, blocked); + public void setSensorBlocked(@Sensor int sensor, boolean blocked) { + mSensorPrivacyManager.setSensorPrivacyForProfileGroup(sensor, blocked); } @Override @@ -72,7 +71,7 @@ public class IndividualSensorPrivacyControllerImpl implements IndividualSensorPr mCallbacks.remove(listener); } - private void onSensorPrivacyChanged(@IndividualSensor int sensor, boolean blocked) { + private void onSensorPrivacyChanged(@Sensor int sensor, boolean blocked) { mState.put(sensor, blocked); for (Callback callback : mCallbacks) { diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java index 9effc6728bab..bbb2f1a5259a 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java @@ -161,10 +161,10 @@ public class ThemeOverlayApplier implements Dumpable { void applyCurrentUserOverlays( Map<String, OverlayIdentifier> categoryToPackage, FabricatedOverlay[] pendingCreation, - Set<UserHandle> userHandles) { + int currentUser, + Set<UserHandle> managedProfiles) { // Disable all overlays that have not been specified in the user setting. final Set<String> overlayCategoriesToDisable = new HashSet<>(THEME_CATEGORIES); - overlayCategoriesToDisable.removeAll(categoryToPackage.keySet()); final Set<String> targetPackagesToQuery = overlayCategoriesToDisable.stream() .map(category -> mCategoryToTargetPackage.get(category)) .collect(Collectors.toSet()); @@ -175,6 +175,7 @@ public class ThemeOverlayApplier implements Dumpable { .filter(o -> mTargetPackageToCategories.get(o.targetPackageName).contains(o.category)) .filter(o -> overlayCategoriesToDisable.contains(o.category)) + .filter(o -> !categoryToPackage.containsValue(new OverlayIdentifier(o.packageName))) .filter(o -> o.isEnabled()) .map(o -> new Pair<>(o.category, o.packageName)) .collect(Collectors.toList()); @@ -186,17 +187,18 @@ public class ThemeOverlayApplier implements Dumpable { } } - // Toggle overlays in the order of THEME_CATEGORIES. + for (Pair<String, String> packageToDisable : overlaysToDisable) { + OverlayIdentifier overlayInfo = new OverlayIdentifier(packageToDisable.second); + setEnabled(transaction, overlayInfo, packageToDisable.first, currentUser, + managedProfiles, false); + } + for (String category : THEME_CATEGORIES) { if (categoryToPackage.containsKey(category)) { OverlayIdentifier overlayInfo = categoryToPackage.get(category); - setEnabled(transaction, overlayInfo, category, userHandles, true); + setEnabled(transaction, overlayInfo, category, currentUser, managedProfiles, true); } } - for (Pair<String, String> packageToDisable : overlaysToDisable) { - OverlayIdentifier overlayInfo = new OverlayIdentifier(packageToDisable.second); - setEnabled(transaction, overlayInfo, packageToDisable.first, userHandles, false); - } mExecutor.execute(() -> { try { @@ -213,18 +215,30 @@ public class ThemeOverlayApplier implements Dumpable { } private void setEnabled(OverlayManagerTransaction.Builder transaction, - OverlayIdentifier identifier, String category, Set<UserHandle> handles, - boolean enabled) { + OverlayIdentifier identifier, String category, int currentUser, + Set<UserHandle> managedProfiles, boolean enabled) { if (DEBUG) { Log.d(TAG, "setEnabled: " + identifier.getPackageName() + " category: " + category + ": " + enabled); } - for (UserHandle userHandle : handles) { - transaction.setEnabled(identifier, enabled, userHandle.getIdentifier()); - } - if (!handles.contains(UserHandle.SYSTEM) && SYSTEM_USER_CATEGORIES.contains(category)) { + + transaction.setEnabled(identifier, enabled, currentUser); + if (currentUser != UserHandle.SYSTEM.getIdentifier() + && SYSTEM_USER_CATEGORIES.contains(category)) { transaction.setEnabled(identifier, enabled, UserHandle.SYSTEM.getIdentifier()); } + + // Do not apply Launcher or Theme picker overlays to managed users. Apps are not + // installed in there. + OverlayInfo overlayInfo = mOverlayManager.getOverlayInfo(identifier, UserHandle.SYSTEM); + if (overlayInfo == null || overlayInfo.targetPackageName.equals(mLauncherPackage) + || overlayInfo.targetPackageName.equals(mThemePickerPackage)) { + return; + } + + for (UserHandle userHandle : managedProfiles) { + transaction.setEnabled(identifier, enabled, userHandle.getIdentifier()); + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java index 028cbd084677..5d028454a417 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java @@ -55,14 +55,13 @@ import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.settings.SecureSettings; -import com.google.android.collect.Sets; - import org.json.JSONException; import org.json.JSONObject; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Collection; +import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.Executor; @@ -151,7 +150,7 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { @Override public void onReceive(Context context, Intent intent) { if (DEBUG) Log.d(TAG, "Updating overlays for user switch / profile added."); - updateThemeOverlays(); + reevaluateSystemTheme(true /* forceReload */); } }, filter, mBgExecutor, UserHandle.ALL); mSecureSettings.registerContentObserverForUser( @@ -163,7 +162,7 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { int userId) { if (DEBUG) Log.d(TAG, "Overlay changed for user: " + userId); if (ActivityManager.getCurrentUser() == userId) { - updateThemeOverlays(); + reevaluateSystemTheme(true /* forceReload */); } } }, @@ -180,7 +179,7 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { mLockColors = lockColors; } mSystemColors = systemColor; - reevaluateSystemTheme(); + reevaluateSystemTheme(false /* forceReload */); }); }); if (USE_LOCK_SCREEN_WALLPAPER) { @@ -192,7 +191,7 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { } // It's possible that the user has a lock screen wallpaper. On this case we'll // end up with different colors after unlocking. - reevaluateSystemTheme(); + reevaluateSystemTheme(false /* forceReload */); } }); } @@ -209,11 +208,11 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { Log.d(TAG, "got new lock colors: " + wallpaperColors + " where: " + which); } } - reevaluateSystemTheme(); + reevaluateSystemTheme(false /* forceReload */); }, null, UserHandle.USER_ALL); } - private void reevaluateSystemTheme() { + private void reevaluateSystemTheme(boolean forceReload) { WallpaperColors currentColors = mKeyguardStateController.isShowing() && mLockColors != null ? mLockColors : mSystemColors; @@ -228,7 +227,8 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { accentCandidate = getAccentColor(currentColors); } - if (mMainWallpaperColor == mainColor && mWallpaperAccentColor == accentCandidate) { + if (mMainWallpaperColor == mainColor && mWallpaperAccentColor == accentCandidate + && !forceReload) { return; } @@ -309,6 +309,16 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { } catch (NumberFormatException e) { Log.w(TAG, "Invalid color definition: " + systemPalette.getPackageName()); } + } else if (!mIsMonetEnabled && systemPalette != null) { + try { + // It's possible that we flipped the flag off and still have a @ColorInt in the + // setting. We need to sanitize the input, otherwise the overlay transaction will + // fail. + Integer.parseInt(systemPalette.getPackageName().toLowerCase(), 16); + categoryToPackage.remove(OVERLAY_CATEGORY_SYSTEM_PALETTE); + } catch (NumberFormatException e) { + // This is a package name. All good, let's continue + } } // Same for accent color. @@ -322,6 +332,13 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { } catch (NumberFormatException e) { Log.w(TAG, "Invalid color definition: " + accentPalette.getPackageName()); } + } else if (!mIsMonetEnabled && accentPalette != null) { + try { + Integer.parseInt(accentPalette.getPackageName().toLowerCase(), 16); + categoryToPackage.remove(OVERLAY_CATEGORY_ACCENT_COLOR); + } catch (NumberFormatException e) { + // This is a package name. All good, let's continue + } } // Compatibility with legacy themes, where full packages were defined, instead of just @@ -337,10 +354,10 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { categoryToPackage.put(OVERLAY_CATEGORY_ACCENT_COLOR, mSecondaryOverlay.getIdentifier()); } - Set<UserHandle> userHandles = Sets.newHashSet(UserHandle.of(currentUser)); + Set<UserHandle> managedProfiles = new HashSet<>(); for (UserInfo userInfo : mUserManager.getEnabledProfiles(currentUser)) { if (userInfo.isManagedProfile()) { - userHandles.add(userInfo.getUserHandle()); + managedProfiles.add(userInfo.getUserHandle()); } } if (DEBUG) { @@ -352,9 +369,10 @@ public class ThemeOverlayController extends SystemUI implements Dumpable { mNeedsOverlayCreation = false; mThemeManager.applyCurrentUserOverlays(categoryToPackage, new FabricatedOverlay[] { mPrimaryOverlay, mSecondaryOverlay, mNeutralOverlay - }, userHandles); + }, currentUser, managedProfiles); } else { - mThemeManager.applyCurrentUserOverlays(categoryToPackage, null, userHandles); + mThemeManager.applyCurrentUserOverlays(categoryToPackage, null, currentUser, + managedProfiles); } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index df54eabca8e7..25345d5c4b4c 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -33,7 +33,11 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ArgbEvaluator; import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; import android.annotation.SuppressLint; import android.app.ActivityManager; import android.app.Dialog; @@ -51,6 +55,9 @@ import android.graphics.Color; import android.graphics.PixelFormat; import android.graphics.Region; import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; +import android.graphics.drawable.RotateDrawable; import android.media.AudioManager; import android.media.AudioSystem; import android.os.Debug; @@ -81,6 +88,8 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.DecelerateInterpolator; import android.widget.FrameLayout; import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; import android.widget.TextView; @@ -88,6 +97,7 @@ import android.widget.Toast; import com.android.settingslib.Utils; import com.android.systemui.Dependency; +import com.android.systemui.Interpolators; import com.android.systemui.Prefs; import com.android.systemui.R; import com.android.systemui.media.dialog.MediaOutputDialogFactory; @@ -99,6 +109,8 @@ import com.android.systemui.plugins.VolumeDialogController.StreamState; import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; +import com.android.systemui.util.AlphaTintDrawableWrapper; +import com.android.systemui.util.RoundedCornerProgressDrawable; import java.io.PrintWriter; import java.util.ArrayList; @@ -124,8 +136,14 @@ public class VolumeDialogImpl implements VolumeDialog, static final int DIALOG_ODI_CAPTIONS_TOOLTIP_TIMEOUT_MILLIS = 5000; static final int DIALOG_HOVERING_TIMEOUT_MILLIS = 16000; + private static final int DRAWER_ANIMATION_DURATION_SHORT = 175; + private static final int DRAWER_ANIMATION_DURATION = 250; + private final int mDialogShowAnimationDurationMs; private final int mDialogHideAnimationDurationMs; + private final int mRingerDrawerItemSize; + private final boolean mShowVibrate; + private final int mRingerCount; private final boolean mShowLowMediaVolumeIcon; private final boolean mChangeVolumeRowTintWhenInactive; @@ -140,6 +158,30 @@ public class VolumeDialogImpl implements VolumeDialog, private ViewGroup mDialogView; private ViewGroup mDialogRowsView; private ViewGroup mRinger; + + private ViewGroup mSelectedRingerContainer; + private ImageView mSelectedRingerIcon; + + private ViewGroup mRingerDrawerContainer; + private ViewGroup mRingerDrawerMute; + private ViewGroup mRingerDrawerVibrate; + private ViewGroup mRingerDrawerNormal; + private ImageView mRingerDrawerMuteIcon; + private ImageView mRingerDrawerVibrateIcon; + private ImageView mRingerDrawerNormalIcon; + + /** + * View that draws the 'selected' background behind one of the three ringer choices in the + * drawer. + */ + private ViewGroup mRingerDrawerNewSelectionBg; + + private final ValueAnimator mRingerDrawerIconColorAnimator = ValueAnimator.ofFloat(0f, 1f); + private ImageView mRingerDrawerIconAnimatingSelected; + private ImageView mRingerDrawerIconAnimatingDeselected; + + private boolean mIsRingerDrawerOpen = false; + private ImageButton mRingerIcon; private ViewGroup mODICaptionsView; private CaptionsToggleImageButton mODICaptionsIcon; @@ -191,6 +233,12 @@ public class VolumeDialogImpl implements VolumeDialog, mContext.getResources().getInteger(R.integer.config_dialogShowAnimationDurationMs); mDialogHideAnimationDurationMs = mContext.getResources().getInteger(R.integer.config_dialogHideAnimationDurationMs); + mRingerDrawerItemSize = mContext.getResources().getDimensionPixelSize( + R.dimen.volume_ringer_drawer_item_size); + mShowVibrate = mController.hasVibrator(); + + // Normal, mute, and possibly vibrate. + mRingerCount = mShowVibrate ? 3 : 2; } @Override @@ -314,6 +362,20 @@ public class VolumeDialogImpl implements VolumeDialog, mZenIcon = mRinger.findViewById(R.id.dnd_icon); } + mSelectedRingerIcon = mDialog.findViewById(R.id.volume_new_ringer_active_icon); + mSelectedRingerContainer = mDialog.findViewById( + R.id.volume_new_ringer_active_icon_container); + + mRingerDrawerMute = mDialog.findViewById(R.id.volume_drawer_mute); + mRingerDrawerNormal = mDialog.findViewById(R.id.volume_drawer_normal); + mRingerDrawerVibrate = mDialog.findViewById(R.id.volume_drawer_vibrate); + mRingerDrawerMuteIcon = mDialog.findViewById(R.id.volume_drawer_mute_icon); + mRingerDrawerVibrateIcon = mDialog.findViewById(R.id.volume_drawer_vibrate_icon); + mRingerDrawerNormalIcon = mDialog.findViewById(R.id.volume_drawer_normal_icon); + mRingerDrawerNewSelectionBg = mDialog.findViewById(R.id.volume_drawer_selection_background); + + setupRingerDrawer(); + mODICaptionsView = mDialog.findViewById(R.id.odi_captions); if (mODICaptionsView != null) { mODICaptionsIcon = mODICaptionsView.findViewById(R.id.odi_captions_icon); @@ -475,38 +537,273 @@ public class VolumeDialogImpl implements VolumeDialog, row.anim = null; + final LayerDrawable seekbarDrawable = + (LayerDrawable) mContext.getDrawable(R.drawable.volume_row_seekbar); + + final LayerDrawable seekbarBgDrawable = + (LayerDrawable) seekbarDrawable.findDrawableByLayerId(android.R.id.background); + + row.sliderBgSolid = seekbarBgDrawable.findDrawableByLayerId( + R.id.volume_seekbar_background_solid); + + row.sliderBgIcon = (AlphaTintDrawableWrapper) + ((RotateDrawable) seekbarBgDrawable.findDrawableByLayerId( + R.id.volume_seekbar_background_icon)).getDrawable(); + + final LayerDrawable seekbarProgressDrawable = (LayerDrawable) + ((RoundedCornerProgressDrawable) seekbarDrawable.findDrawableByLayerId( + android.R.id.progress)).getDrawable(); + + row.sliderProgressSolid = seekbarProgressDrawable.findDrawableByLayerId( + R.id.volume_seekbar_progress_solid); + + row.sliderProgressIcon = (AlphaTintDrawableWrapper) + ((RotateDrawable) seekbarProgressDrawable.findDrawableByLayerId( + R.id.volume_seekbar_progress_icon)).getDrawable(); + + row.slider.setProgressDrawable(seekbarDrawable); + row.slider.setThumb(null); + row.icon = row.view.findViewById(R.id.volume_row_icon); - row.icon.setImageResource(iconRes); - if (row.stream != AudioSystem.STREAM_ACCESSIBILITY) { - row.icon.setOnClickListener(v -> { - Events.writeEvent(Events.EVENT_ICON_CLICK, row.stream, row.iconState); - mController.setActiveStream(row.stream); - if (row.stream == AudioManager.STREAM_RING) { - final boolean hasVibrator = mController.hasVibrator(); - if (mState.ringerModeInternal == AudioManager.RINGER_MODE_NORMAL) { - if (hasVibrator) { - mController.setRingerMode(AudioManager.RINGER_MODE_VIBRATE, false); + + row.setIcon(iconRes, mContext.getTheme()); + + if (row.icon != null) { + if (row.stream != AudioSystem.STREAM_ACCESSIBILITY) { + row.icon.setOnClickListener(v -> { + Events.writeEvent(Events.EVENT_ICON_CLICK, row.stream, row.iconState); + mController.setActiveStream(row.stream); + if (row.stream == AudioManager.STREAM_RING) { + final boolean hasVibrator = mController.hasVibrator(); + if (mState.ringerModeInternal == AudioManager.RINGER_MODE_NORMAL) { + if (hasVibrator) { + mController.setRingerMode(AudioManager.RINGER_MODE_VIBRATE, false); + } else { + final boolean wasZero = row.ss.level == 0; + mController.setStreamVolume(stream, + wasZero ? row.lastAudibleLevel : 0); + } } else { - final boolean wasZero = row.ss.level == 0; - mController.setStreamVolume(stream, - wasZero ? row.lastAudibleLevel : 0); + mController.setRingerMode( + AudioManager.RINGER_MODE_NORMAL, false); + if (row.ss.level == 0) { + mController.setStreamVolume(stream, 1); + } } } else { - mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, false); - if (row.ss.level == 0) { - mController.setStreamVolume(stream, 1); - } + final boolean vmute = row.ss.level == row.ss.levelMin; + mController.setStreamVolume(stream, + vmute ? row.lastAudibleLevel : row.ss.levelMin); } - } else { - final boolean vmute = row.ss.level == row.ss.levelMin; - mController.setStreamVolume(stream, - vmute ? row.lastAudibleLevel : row.ss.levelMin); - } - row.userAttempt = 0; // reset the grace period, slider updates immediately - }); + row.userAttempt = 0; // reset the grace period, slider updates immediately + }); + } else { + row.icon.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); + } + } + } + + private void setRingerMode(int newRingerMode) { + Events.writeEvent(Events.EVENT_RINGER_TOGGLE, newRingerMode); + incrementManualToggleCount(); + updateRingerH(); + provideTouchFeedbackH(newRingerMode); + mController.setRingerMode(newRingerMode, false); + maybeShowToastH(newRingerMode); + } + + private void setupRingerDrawer() { + mRingerDrawerContainer = mDialog.findViewById(R.id.volume_drawer_container); + + if (mRingerDrawerContainer == null) { + return; + } + + if (!mShowVibrate) { + mRingerDrawerVibrate.setVisibility(GONE); + } + + // In portrait, add padding to the bottom to account for the height of the open ringer + // drawer. + if (!isLandscape()) { + mDialogView.setPadding( + mDialogView.getPaddingLeft(), + mDialogView.getPaddingTop(), + mDialogView.getPaddingRight(), + mDialogView.getPaddingBottom() + (mRingerCount - 1) * mRingerDrawerItemSize); + } else { + mDialogView.setPadding( + mDialogView.getPaddingLeft() + (mRingerCount - 1) * mRingerDrawerItemSize, + mDialogView.getPaddingTop(), + mDialogView.getPaddingRight(), + mDialogView.getPaddingBottom()); + } + + ((LinearLayout) mRingerDrawerContainer.findViewById(R.id.volume_drawer_options)) + .setOrientation(isLandscape() ? LinearLayout.HORIZONTAL : LinearLayout.VERTICAL); + + mSelectedRingerContainer.setOnClickListener(view -> { + if (mIsRingerDrawerOpen) { + hideRingerDrawer(); + } else { + showRingerDrawer(); + } + }); + + mRingerDrawerVibrate.setOnClickListener( + new RingerDrawerItemClickListener(RINGER_MODE_VIBRATE)); + mRingerDrawerMute.setOnClickListener( + new RingerDrawerItemClickListener(RINGER_MODE_SILENT)); + mRingerDrawerNormal.setOnClickListener( + new RingerDrawerItemClickListener(RINGER_MODE_NORMAL)); + + final int unselectedColor = Utils.getColorAccentDefaultColor(mContext); + final int selectedColor = Utils.getColorAttrDefaultColor( + mContext, android.R.attr.colorBackgroundFloating); + + // Add an update listener that animates the deselected icon to the unselected color, and the + // selected icon to the selected color. + mRingerDrawerIconColorAnimator.addUpdateListener( + anim -> { + final float currentValue = (float) anim.getAnimatedValue(); + final int curUnselectedColor = (int) ArgbEvaluator.getInstance().evaluate( + currentValue, selectedColor, unselectedColor); + final int curSelectedColor = (int) ArgbEvaluator.getInstance().evaluate( + currentValue, unselectedColor, selectedColor); + + mRingerDrawerIconAnimatingDeselected.setColorFilter(curUnselectedColor); + mRingerDrawerIconAnimatingSelected.setColorFilter(curSelectedColor); + }); + mRingerDrawerIconColorAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mRingerDrawerIconAnimatingDeselected.clearColorFilter(); + mRingerDrawerIconAnimatingSelected.clearColorFilter(); + } + }); + mRingerDrawerIconColorAnimator.setDuration(DRAWER_ANIMATION_DURATION_SHORT); + } + + private ImageView getDrawerIconViewForMode(int mode) { + if (mode == RINGER_MODE_VIBRATE) { + return mRingerDrawerVibrateIcon; + } else if (mode == RINGER_MODE_SILENT) { + return mRingerDrawerMuteIcon; + } else { + return mRingerDrawerNormalIcon; + } + } + + /** + * Translation to apply form the origin (either top or left) to overlap the selection background + * with the given mode in the drawer. + */ + private float getTranslationInDrawerForRingerMode(int mode) { + return mode == RINGER_MODE_VIBRATE + ? -mRingerDrawerItemSize * 2 + : mode == RINGER_MODE_SILENT + ? -mRingerDrawerItemSize + : 0; + } + + /** Animates in the ringer drawer. */ + private void showRingerDrawer() { + // Show all ringer icons except the currently selected one, since we're going to animate the + // ringer button to that position. + mRingerDrawerVibrateIcon.setVisibility( + mState.ringerModeInternal == RINGER_MODE_VIBRATE ? INVISIBLE : VISIBLE); + mRingerDrawerMuteIcon.setVisibility( + mState.ringerModeInternal == RINGER_MODE_SILENT ? INVISIBLE : VISIBLE); + mRingerDrawerNormalIcon.setVisibility( + mState.ringerModeInternal == RINGER_MODE_NORMAL ? INVISIBLE : VISIBLE); + + // Hide the selection background - we use this to show a selection when one is + // tapped, so it should be invisible until that happens. However, position it below + // the currently selected ringer so that it's ready to animate. + mRingerDrawerNewSelectionBg.setAlpha(0f); + + if (!isLandscape()) { + mRingerDrawerNewSelectionBg.setTranslationY( + getTranslationInDrawerForRingerMode(mState.ringerModeInternal)); + } else { + mRingerDrawerNewSelectionBg.setTranslationX( + getTranslationInDrawerForRingerMode(mState.ringerModeInternal)); + } + + // Move the drawer so that the top/rightmost ringer choice overlaps with the selected ringer + // icon. + if (!isLandscape()) { + mRingerDrawerContainer.setTranslationY(mRingerDrawerItemSize * (mRingerCount - 1)); + } else { + mRingerDrawerContainer.setTranslationX(mRingerDrawerItemSize * (mRingerCount - 1)); + } + mRingerDrawerContainer.setAlpha(0f); + mRingerDrawerContainer.setVisibility(VISIBLE); + + // Animate the drawer up and visible. + mRingerDrawerContainer.animate() + .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) + // Vibrate is way farther up, so give the selected ringer icon a head start if + // vibrate is selected. + .setDuration(mState.ringerModeInternal == RINGER_MODE_VIBRATE + ? DRAWER_ANIMATION_DURATION_SHORT + : DRAWER_ANIMATION_DURATION) + .setStartDelay(mState.ringerModeInternal == RINGER_MODE_VIBRATE + ? DRAWER_ANIMATION_DURATION - DRAWER_ANIMATION_DURATION_SHORT + : 0) + .alpha(1f) + .translationX(0f) + .translationY(0f) + .start(); + + // Animate the selected ringer view up to that ringer's position in the drawer. + mSelectedRingerContainer.animate() + .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) + .setDuration(DRAWER_ANIMATION_DURATION) + .withEndAction(() -> + getDrawerIconViewForMode(mState.ringerModeInternal).setVisibility(VISIBLE)); + + if (!isLandscape()) { + mSelectedRingerContainer.animate() + .translationY(getTranslationInDrawerForRingerMode(mState.ringerModeInternal)) + .start(); } else { - row.icon.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); + mSelectedRingerContainer.animate() + .translationX(getTranslationInDrawerForRingerMode(mState.ringerModeInternal)) + .start(); } + + mIsRingerDrawerOpen = true; + } + + /** Animates away the ringer drawer. */ + private void hideRingerDrawer() { + // Hide the drawer icon for the selected ringer - it's visible in the ringer button and we + // don't want to be able to see it while it animates away. + getDrawerIconViewForMode(mState.ringerModeInternal).setVisibility(INVISIBLE); + + mRingerDrawerContainer.animate() + .alpha(0f) + .setDuration(DRAWER_ANIMATION_DURATION) + .setStartDelay(0) + .withEndAction(() -> mRingerDrawerContainer.setVisibility(INVISIBLE)); + + if (!isLandscape()) { + mRingerDrawerContainer.animate() + .translationY(mRingerDrawerItemSize * 2) + .start(); + } else { + mRingerDrawerContainer.animate() + .translationX(mRingerDrawerItemSize * 2) + .start(); + } + + mSelectedRingerContainer.animate() + .translationX(0f) + .translationY(0f) + .start(); + + mIsRingerDrawerOpen = false; } public void initSettingsH() { @@ -555,12 +852,8 @@ public class VolumeDialogImpl implements VolumeDialog, mController.setStreamVolume(AudioManager.STREAM_RING, 1); } } - Events.writeEvent(Events.EVENT_RINGER_TOGGLE, newRingerMode); - incrementManualToggleCount(); - updateRingerH(); - provideTouchFeedbackH(newRingerMode); - mController.setRingerMode(newRingerMode, false); - maybeShowToastH(newRingerMode); + + setRingerMode(newRingerMode); }); } updateRingerH(); @@ -809,6 +1102,8 @@ public class VolumeDialogImpl implements VolumeDialog, mDialog.dismiss(); tryToRemoveCaptionsTooltip(); mIsAnimatingDismiss = false; + + hideRingerDrawer(); }, 50)); if (!isLandscape()) animator.translationX(mDialogView.getWidth() / 2.0f); animator.start(); @@ -889,12 +1184,14 @@ public class VolumeDialogImpl implements VolumeDialog, switch (mState.ringerModeInternal) { case AudioManager.RINGER_MODE_VIBRATE: mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_vibrate); + mSelectedRingerIcon.setImageResource(R.drawable.ic_volume_ringer_vibrate); addAccessibilityDescription(mRingerIcon, RINGER_MODE_VIBRATE, mContext.getString(R.string.volume_ringer_hint_mute)); mRingerIcon.setTag(Events.ICON_STATE_VIBRATE); break; case AudioManager.RINGER_MODE_SILENT: mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute); + mSelectedRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute); mRingerIcon.setTag(Events.ICON_STATE_MUTE); addAccessibilityDescription(mRingerIcon, RINGER_MODE_SILENT, mContext.getString(R.string.volume_ringer_hint_unmute)); @@ -904,11 +1201,13 @@ public class VolumeDialogImpl implements VolumeDialog, boolean muted = (mAutomute && ss.level == 0) || ss.muted; if (!isZenMuted && muted) { mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute); + mSelectedRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute); addAccessibilityDescription(mRingerIcon, RINGER_MODE_NORMAL, mContext.getString(R.string.volume_ringer_hint_unmute)); mRingerIcon.setTag(Events.ICON_STATE_MUTE); } else { mRingerIcon.setImageResource(R.drawable.ic_volume_ringer); + mSelectedRingerIcon.setImageResource(R.drawable.ic_volume_ringer); if (mController.hasVibrator()) { addAccessibilityDescription(mRingerIcon, RINGER_MODE_NORMAL, mContext.getString(R.string.volume_ringer_hint_vibrate)); @@ -1075,8 +1374,6 @@ public class VolumeDialogImpl implements VolumeDialog, // update icon final boolean iconEnabled = (mAutomute || ss.muteSupported) && !zenMuted; - row.icon.setEnabled(iconEnabled); - row.icon.setAlpha(iconEnabled ? 1 : 0.5f); final int iconRes; if (isRingVibrate) { iconRes = R.drawable.ic_volume_ringer_vibrate; @@ -1092,7 +1389,7 @@ public class VolumeDialogImpl implements VolumeDialog, ? R.drawable.ic_volume_media_low : row.iconRes; } - row.icon.setImageResource(iconRes); + row.setIcon(iconRes, mContext.getTheme()); row.iconState = iconRes == R.drawable.ic_volume_ringer_vibrate ? Events.ICON_STATE_VIBRATE : (iconRes == R.drawable.ic_volume_media_bt_mute || iconRes == row.iconMuteRes) @@ -1101,18 +1398,35 @@ public class VolumeDialogImpl implements VolumeDialog, || iconRes == R.drawable.ic_volume_media_low) ? Events.ICON_STATE_UNMUTE : Events.ICON_STATE_UNKNOWN; - if (iconEnabled) { - if (isRingStream) { - if (isRingVibrate) { - row.icon.setContentDescription(mContext.getString( - R.string.volume_stream_content_description_unmute, - getStreamLabelH(ss))); + + if (row.icon != null) { + if (iconEnabled) { + if (isRingStream) { + if (isRingVibrate) { + row.icon.setContentDescription(mContext.getString( + R.string.volume_stream_content_description_unmute, + getStreamLabelH(ss))); + } else { + if (mController.hasVibrator()) { + row.icon.setContentDescription(mContext.getString( + mShowA11yStream + ? R.string.volume_stream_content_description_vibrate_a11y + : R.string.volume_stream_content_description_vibrate, + getStreamLabelH(ss))); + } else { + row.icon.setContentDescription(mContext.getString( + mShowA11yStream + ? R.string.volume_stream_content_description_mute_a11y + : R.string.volume_stream_content_description_mute, + getStreamLabelH(ss))); + } + } + } else if (isA11yStream) { + row.icon.setContentDescription(getStreamLabelH(ss)); } else { - if (mController.hasVibrator()) { + if (ss.muted || mAutomute && ss.level == 0) { row.icon.setContentDescription(mContext.getString( - mShowA11yStream - ? R.string.volume_stream_content_description_vibrate_a11y - : R.string.volume_stream_content_description_vibrate, + R.string.volume_stream_content_description_unmute, getStreamLabelH(ss))); } else { row.icon.setContentDescription(mContext.getString( @@ -1122,23 +1436,9 @@ public class VolumeDialogImpl implements VolumeDialog, getStreamLabelH(ss))); } } - } else if (isA11yStream) { - row.icon.setContentDescription(getStreamLabelH(ss)); } else { - if (ss.muted || mAutomute && ss.level == 0) { - row.icon.setContentDescription(mContext.getString( - R.string.volume_stream_content_description_unmute, - getStreamLabelH(ss))); - } else { - row.icon.setContentDescription(mContext.getString( - mShowA11yStream - ? R.string.volume_stream_content_description_mute_a11y - : R.string.volume_stream_content_description_mute, - getStreamLabelH(ss))); - } + row.icon.setContentDescription(getStreamLabelH(ss)); } - } else { - row.icon.setContentDescription(getStreamLabelH(ss)); } // ensure tracking is disabled if zenMuted @@ -1167,22 +1467,29 @@ public class VolumeDialogImpl implements VolumeDialog, if (!useActiveColoring && !mChangeVolumeRowTintWhenInactive) { return; } - final ColorStateList tint = useActiveColoring + final ColorStateList colorTint = useActiveColoring ? Utils.getColorAccent(mContext) : Utils.getColorAttr(mContext, android.R.attr.colorForeground); final int alpha = useActiveColoring - ? Color.alpha(tint.getDefaultColor()) + ? Color.alpha(colorTint.getDefaultColor()) : getAlphaAttr(android.R.attr.secondaryContentAlpha); - if (tint == row.cachedTint) return; - row.slider.setProgressTintList(tint); - row.slider.setThumbTintList(tint); - row.slider.setProgressBackgroundTintList(tint); - row.slider.setAlpha(((float) alpha) / 255); - row.icon.setImageTintList(tint); - row.icon.setImageAlpha(alpha); - row.cachedTint = tint; + + final ColorStateList bgTint = Utils.getColorAttr( + mContext, android.R.attr.colorBackgroundFloating); + + row.sliderProgressSolid.setTintList(colorTint); + row.sliderBgIcon.setTintList(colorTint); + + row.sliderBgSolid.setTintList(bgTint); + row.sliderProgressIcon.setTintList(bgTint); + + if (row.icon != null) { + row.icon.setImageTintList(colorTint); + row.icon.setImageAlpha(alpha); + } + if (row.number != null) { - row.number.setTextColor(tint); + row.number.setTextColor(colorTint); row.number.setAlpha(alpha); } } @@ -1538,6 +1845,10 @@ public class VolumeDialogImpl implements VolumeDialog, private View view; private TextView header; private ImageButton icon; + private Drawable sliderBgSolid; + private AlphaTintDrawableWrapper sliderBgIcon; + private Drawable sliderProgressSolid; + private AlphaTintDrawableWrapper sliderProgressIcon; private SeekBar slider; private TextView number; private int stream; @@ -1555,5 +1866,69 @@ public class VolumeDialogImpl implements VolumeDialog, private int animTargetProgress; private int lastAudibleLevel = 1; private FrameLayout dndIcon; + + void setIcon(int iconRes, Resources.Theme theme) { + if (icon != null) { + icon.setImageResource(iconRes); + } + + sliderProgressIcon.setDrawable(view.getResources().getDrawable(iconRes, theme)); + sliderBgIcon.setDrawable(view.getResources().getDrawable(iconRes, theme)); + } + } + + /** + * Click listener added to each ringer option in the drawer. This will initiate the animation to + * select and then close the ringer drawer, and actually change the ringer mode. + */ + private class RingerDrawerItemClickListener implements View.OnClickListener { + private final int mClickedRingerMode; + + RingerDrawerItemClickListener(int clickedRingerMode) { + mClickedRingerMode = clickedRingerMode; + } + + @Override + public void onClick(View view) { + setRingerMode(mClickedRingerMode); + + mRingerDrawerIconAnimatingSelected = getDrawerIconViewForMode(mClickedRingerMode); + mRingerDrawerIconAnimatingDeselected = getDrawerIconViewForMode( + mState.ringerModeInternal); + + // Begin switching the selected icon and deselected icon colors since the background is + // going to animate behind the new selection. + mRingerDrawerIconColorAnimator.start(); + + mSelectedRingerContainer.setVisibility(View.INVISIBLE); + mRingerDrawerNewSelectionBg.setAlpha(1f); + mRingerDrawerNewSelectionBg.animate() + .setInterpolator(Interpolators.ACCELERATE_DECELERATE) + .setDuration(DRAWER_ANIMATION_DURATION_SHORT) + .withEndAction(() -> { + mRingerDrawerNewSelectionBg.setAlpha(0f); + + if (!isLandscape()) { + mSelectedRingerContainer.setTranslationY( + getTranslationInDrawerForRingerMode(mClickedRingerMode)); + } else { + mSelectedRingerContainer.setTranslationX( + getTranslationInDrawerForRingerMode(mClickedRingerMode)); + } + + mSelectedRingerContainer.setVisibility(VISIBLE); + hideRingerDrawer(); + }); + + if (!isLandscape()) { + mRingerDrawerNewSelectionBg.animate() + .translationY(getTranslationInDrawerForRingerMode(mClickedRingerMode)) + .start(); + } else { + mRingerDrawerNewSelectionBg.animate() + .translationX(getTranslationInDrawerForRingerMode(mClickedRingerMode)) + .start(); + } + } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java index 97cb8736f01c..2526990dfd40 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java @@ -16,8 +16,8 @@ package com.android.systemui.appops; -import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_CAMERA; -import static android.hardware.SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE; +import static android.hardware.SensorPrivacyManager.Sensors.CAMERA; +import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE; import static junit.framework.TestCase.assertFalse; @@ -125,9 +125,9 @@ public class AppOpsControllerTest extends SysuiTestCase { when(mAudioManager.getActiveRecordingConfigurations()) .thenReturn(List.of(mPausedMockRecording)); - when(mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_CAMERA)) + when(mSensorPrivacyController.isSensorBlocked(CAMERA)) .thenReturn(false); - when(mSensorPrivacyController.isSensorBlocked(INDIVIDUAL_SENSOR_CAMERA)) + when(mSensorPrivacyController.isSensorBlocked(CAMERA)) .thenReturn(false); mController = new AppOpsControllerImpl( @@ -505,7 +505,7 @@ public class AppOpsControllerTest extends SysuiTestCase { assertFalse(list.get(0).isDisabled()); // Add a camera op, and disable the microphone. The camera op should be the only op returned - mController.onSensorBlockedChanged(INDIVIDUAL_SENSOR_MICROPHONE, true); + mController.onSensorBlockedChanged(MICROPHONE, true); mController.onOpActiveChanged( AppOpsManager.OP_CAMERA, TEST_UID_OTHER, TEST_PACKAGE_NAME, true); mTestableLooper.processAllMessages(); @@ -515,7 +515,7 @@ public class AppOpsControllerTest extends SysuiTestCase { // Re enable the microphone, and verify the op returns - mController.onSensorBlockedChanged(INDIVIDUAL_SENSOR_MICROPHONE, false); + mController.onSensorBlockedChanged(MICROPHONE, false); mTestableLooper.processAllMessages(); list = mController.getActiveAppOps(); @@ -538,7 +538,7 @@ public class AppOpsControllerTest extends SysuiTestCase { assertFalse(list.get(0).isDisabled()); // Add an audio op, and disable the camera. The audio op should be the only op returned - mController.onSensorBlockedChanged(INDIVIDUAL_SENSOR_CAMERA, true); + mController.onSensorBlockedChanged(CAMERA, true); mController.onOpActiveChanged( AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, true); mTestableLooper.processAllMessages(); @@ -547,7 +547,7 @@ public class AppOpsControllerTest extends SysuiTestCase { assertEquals(AppOpsManager.OP_RECORD_AUDIO, list.get(0).getCode()); // Re enable the camera, and verify the op returns - mController.onSensorBlockedChanged(INDIVIDUAL_SENSOR_CAMERA, false); + mController.onSensorBlockedChanged(CAMERA, false); mTestableLooper.processAllMessages(); list = mController.getActiveAppOps(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java index c8e939609e87..2a4b41cbfe32 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java @@ -464,7 +464,7 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase { new PeopleSpaceTile .Builder(SHORTCUT_ID_1, "userName", ICON, new Intent()) .setPackageName(PACKAGE_NAME) - .setUid(0) + .setUserHandle(new UserHandle(0)) .build(); PeopleSpaceTile actual = PeopleSpaceUtils .augmentTileFromNotification(mContext, tile, sbn); @@ -482,7 +482,7 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase { new PeopleSpaceTile .Builder(SHORTCUT_ID_3, "userName", ICON, new Intent()) .setPackageName(PACKAGE_NAME) - .setUid(0) + .setUserHandle(new UserHandle(0)) .build(); PeopleSpaceTile actual = PeopleSpaceUtils .augmentTileFromNotification(mContext, tile, sbn); @@ -496,7 +496,7 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase { new PeopleSpaceTile .Builder(SHORTCUT_ID_1, "userName", ICON, new Intent()) .setPackageName(PACKAGE_NAME) - .setUid(0) + .setUserHandle(new UserHandle(0)) .build(); PeopleSpaceTile actual = PeopleSpaceUtils .augmentTileFromVisibleNotifications(mContext, tile, @@ -511,7 +511,7 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase { new PeopleSpaceTile .Builder(SHORTCUT_ID_4, "userName", ICON, new Intent()) .setPackageName(PACKAGE_NAME) - .setUid(0) + .setUserHandle(new UserHandle(0)) .build(); PeopleSpaceTile actual = PeopleSpaceUtils .augmentTileFromVisibleNotifications(mContext, tile, @@ -526,7 +526,7 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase { new PeopleSpaceTile .Builder(SHORTCUT_ID_1, "userName", ICON, new Intent()) .setPackageName(PACKAGE_NAME) - .setUid(0) + .setUserHandle(new UserHandle(0)) .build(); List<PeopleSpaceTile> actualList = PeopleSpaceUtils .augmentTilesFromVisibleNotifications( @@ -545,13 +545,13 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase { new PeopleSpaceTile .Builder(SHORTCUT_ID_1, "userName", ICON, new Intent()) .setPackageName(PACKAGE_NAME) - .setUid(1) + .setUserHandle(new UserHandle(0)) .build(); PeopleSpaceTile tile2 = new PeopleSpaceTile .Builder(SHORTCUT_ID_2, "userName2", ICON, new Intent()) .setPackageName(PACKAGE_NAME) - .setUid(0) + .setUserHandle(new UserHandle(0)) .build(); List<PeopleSpaceTile> actualList = PeopleSpaceUtils .augmentTilesFromVisibleNotifications(mContext, List.of(tile1, tile2), diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java index 1c8324c524f4..800d8593035d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java @@ -109,7 +109,7 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { new PeopleSpaceTile .Builder(SHORTCUT_ID, "username", ICON, new Intent()) .setPackageName(TEST_PACKAGE_A) - .setUid(0) + .setUserHandle(new UserHandle(1)) .setNotificationKey(NOTIFICATION_KEY + "1") .setNotificationContent(NOTIFICATION_CONTENT) .setNotificationDataUri(URI) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java index 2dfd38832997..d40e6a4586b8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java @@ -89,6 +89,8 @@ public class QSFooterViewControllerTest extends LeakCheckedTest { private View mEdit; @Mock private MultiUserSwitch mMultiUserSwitch; + @Mock + private View mPowerMenuLiteView; private QSFooterViewController mController; @@ -111,11 +113,12 @@ public class QSFooterViewControllerTest extends LeakCheckedTest { when(mView.findViewById(R.id.build)).thenReturn(mBuildText); when(mView.findViewById(android.R.id.edit)).thenReturn(mEdit); when(mView.findViewById(R.id.multi_user_switch)).thenReturn(mMultiUserSwitch); + when(mView.findViewById(R.id.pm_lite)).thenReturn(mPowerMenuLiteView); mController = new QSFooterViewController(mView, mUserManager, mUserInfoController, mActivityStarter, mDeviceProvisionedController, mUserTracker, mQSPanelController, new QSDetailDisplayer(), mQuickQSPanelController, mFakeTunerService, - mMetricsLogger); + mMetricsLogger, false); mController.init(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java index 0dfebab59feb..a2a179ea29df 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java @@ -121,7 +121,6 @@ public class QSPanelControllerTest extends SysuiTestCase { mQSTileHost, mQSCustomizerController, true, mMediaHost, mQSTileRevealControllerFactory, mDumpManager, mMetricsLogger, mUiEventLogger, mQSLogger, mBrightnessControllerFactory, mToggleSliderViewControllerFactory, - /* labelsFlag */ false, mFeatureFlags ); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt index 587020090433..cb380d51bd79 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt @@ -87,7 +87,6 @@ class QuickQSPanelControllerTest : SysuiTestCase() { uiEventLogger, qsLogger, dumpManager, - false, featureFlags ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeScrollCaptureConnection.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeScrollCaptureConnection.java index a75c39c33f14..9e62a6263a43 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeScrollCaptureConnection.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeScrollCaptureConnection.java @@ -24,6 +24,7 @@ import android.graphics.Paint; import android.graphics.RecordingCanvas; import android.graphics.Rect; import android.graphics.RenderNode; +import android.os.ICancellationSignal; import android.os.RemoteException; import android.view.IScrollCaptureCallbacks; import android.view.IScrollCaptureConnection; @@ -46,7 +47,7 @@ class FakeScrollCaptureConnection extends IScrollCaptureConnection.Stub { } @Override - public void startCapture(Surface surface) { + public ICancellationSignal startCapture(Surface surface) { mSurface = surface; mHwuiContext = new HwuiContext(false, surface); mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); @@ -56,27 +57,28 @@ class FakeScrollCaptureConnection extends IScrollCaptureConnection.Stub { } catch (RemoteException e) { e.rethrowAsRuntimeException(); } + return null; } @Override - public void requestImage(Rect rect) { + public ICancellationSignal requestImage(Rect rect) { Canvas canvas = mHwuiContext.lockCanvas(rect.width(), rect.height()); mPaint.setColor(mColors[mNextColor]); canvas.drawRect(rect, mPaint); mNextColor = (mNextColor++) % mColors.length; - long frameNumber = mSurface.getNextFrameNumber(); mHwuiContext.unlockAndPost(canvas); try { - mCallbacks.onCaptureBufferSent(frameNumber, rect); + mCallbacks.onImageRequestCompleted(0, rect); } catch (RemoteException e) { e.rethrowAsRuntimeException(); } + return null; } @Override - public void endCapture() { + public ICancellationSignal endCapture() { try { - mCallbacks.onConnectionClosed(); + mCallbacks.onCaptureEnded(); } catch (RemoteException e) { e.rethrowAsRuntimeException(); } finally { @@ -84,6 +86,12 @@ class FakeScrollCaptureConnection extends IScrollCaptureConnection.Stub { mSurface = null; mCallbacks = null; } + return null; + } + + @Override + public void close() throws RemoteException { + } // From android.view.Surface, but issues render requests synchronously with waitForPresent(true) diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java index 580f800fbc44..802b462ec10e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java @@ -29,7 +29,6 @@ import static org.mockito.Mockito.verify; import static java.util.Objects.requireNonNull; import android.content.Context; -import android.graphics.Point; import android.graphics.Rect; import android.hardware.display.DisplayManager; import android.os.RemoteException; @@ -37,6 +36,7 @@ import android.testing.AndroidTestingRunner; import android.view.Display; import android.view.IScrollCaptureCallbacks; import android.view.IWindowManager; +import android.view.ScrollCaptureResponse; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; @@ -82,10 +82,11 @@ public class ScrollCaptureClientTest extends SysuiTestCase { public void testBasicClientFlow() throws RemoteException { doAnswer((Answer<Void>) invocation -> { IScrollCaptureCallbacks cb = invocation.getArgument(3); - cb.onConnected( - new FakeScrollCaptureConnection(cb), - /* scrollBounds */ new Rect(0, 0, 100, 100), - /* positionInWindow */ new Point(0, 0)); + cb.onScrollCaptureResponse(new ScrollCaptureResponse.Builder() + .setBoundsInWindow(new Rect(0, 0, 100, 100)) + .setWindowBounds(new Rect(0, 0, 100, 100)) + .setConnection(new FakeScrollCaptureConnection(cb)) + .build()); return null; }).when(mWm).requestScrollCapture(/* displayId */ anyInt(), /* token */ isNull(), /* taskId */ anyInt(), any(IScrollCaptureCallbacks.class)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureTest.java index 2b3ca7c00a49..6564d588f4ea 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureTest.java @@ -19,15 +19,14 @@ package com.android.systemui.screenshot; import static org.junit.Assert.fail; import android.content.Intent; -import android.graphics.Point; import android.graphics.Rect; import android.os.RemoteException; import android.testing.AndroidTestingRunner; import android.util.Log; import android.view.Display; import android.view.IScrollCaptureCallbacks; -import android.view.IScrollCaptureConnection; import android.view.IWindowManager; +import android.view.ScrollCaptureResponse; import android.view.WindowManagerGlobal; import androidx.test.filters.SmallTest; @@ -67,31 +66,27 @@ public class ScrollCaptureTest extends SysuiTestCase { wms.requestScrollCapture(Display.DEFAULT_DISPLAY, null, -1, new IScrollCaptureCallbacks.Stub() { @Override - public void onConnected( - IScrollCaptureConnection connection, Rect scrollBounds, - Point positionInWindow) { - Log.d(TAG, - "client connected: " + connection + "[scrollBounds= " - + scrollBounds + ", " - + "positionInWindow=" + positionInWindow + "]"); + public void onScrollCaptureResponse(ScrollCaptureResponse response) + throws RemoteException { + Log.d(TAG, "onScrollCaptureResponse: " + response); latch.countDown(); } @Override - public void onUnavailable() { - } - - @Override public void onCaptureStarted() { } @Override - public void onCaptureBufferSent(long frameNumber, Rect capturedArea) { + public void onImageRequestCompleted(int i, Rect rect) + throws RemoteException { + } @Override - public void onConnectionClosed() { + public void onCaptureEnded() throws RemoteException { + } + }); } catch (RemoteException e) { Log.e(TAG, "request failed", e); diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java index e798207c6947..6067b42e0ef8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java @@ -32,7 +32,6 @@ import static com.android.systemui.theme.ThemeOverlayApplier.SYSUI_PACKAGE; import static com.android.systemui.theme.ThemeOverlayApplier.THEME_CATEGORIES; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; @@ -88,7 +87,9 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { private static final String THEMEPICKER_PACKAGE = "com.android.wallpaper"; private static final String LAUNCHER_PACKAGE = "com.android.launcher3"; private static final UserHandle TEST_USER = UserHandle.of(5); - private static final Set<UserHandle> TEST_USER_HANDLES = Sets.newHashSet(TEST_USER); + private static final UserHandle TEST_USER_MANAGED_PROFILE = UserHandle.of(6); + private static final Set<UserHandle> TEST_USER_HANDLES = + Sets.newHashSet(TEST_USER_MANAGED_PROFILE); @Mock OverlayManager mOverlayManager; @@ -159,13 +160,19 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { THEMEPICKER_PACKAGE, OVERLAY_CATEGORY_ICON_THEME_PICKER, false), createOverlayInfo(TEST_ENABLED_PREFIX + OVERLAY_CATEGORY_ICON_THEME_PICKER, THEMEPICKER_PACKAGE, OVERLAY_CATEGORY_ICON_THEME_PICKER, true))); + + OverlayInfo launcherTargetInfo = new OverlayInfo("packageName", LAUNCHER_PACKAGE, + null, null, "/", 0, 0, 0, false); + when(mOverlayManager.getOverlayInfo(any(OverlayIdentifier.class), any())) + .thenReturn(launcherTargetInfo); clearInvocations(mOverlayManager); verify(mDumpManager).registerDumpable(any(), any()); } @Test public void allCategoriesSpecified_allEnabledExclusively() { - mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER_HANDLES); + mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER.getIdentifier(), + TEST_USER_HANDLES); verify(mOverlayManager).commit(any()); for (OverlayIdentifier overlayPackage : ALL_CATEGORIES_MAP.values()) { @@ -176,7 +183,8 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { @Test public void allCategoriesSpecified_sysuiCategoriesAlsoAppliedToSysuiUser() { - mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER_HANDLES); + mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER.getIdentifier(), + TEST_USER_HANDLES); for (Map.Entry<String, OverlayIdentifier> entry : ALL_CATEGORIES_MAP.entrySet()) { if (SYSTEM_USER_CATEGORIES.contains(entry.getKey())) { @@ -192,27 +200,25 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { @Test public void allCategoriesSpecified_enabledForAllUserHandles() { Set<UserHandle> userHandles = Sets.newHashSet(TEST_USER_HANDLES); - UserHandle newUserHandle = UserHandle.of(10); - userHandles.add(newUserHandle); - mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, userHandles); + mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER.getIdentifier(), + userHandles); for (OverlayIdentifier overlayPackage : ALL_CATEGORIES_MAP.values()) { verify(mTransactionBuilder).setEnabled(eq(overlayPackage), eq(true), eq(TEST_USER.getIdentifier())); - verify(mTransactionBuilder).setEnabled(eq(overlayPackage), eq(true), - eq(newUserHandle.getIdentifier())); + // Not enabled for work profile because the target package is LAUNCHER_PACKAGE + verify(mTransactionBuilder, never()).setEnabled(eq(overlayPackage), eq(true), + eq(TEST_USER_MANAGED_PROFILE.getIdentifier())); } } @Test public void applyCurrentUserOverlays_createsPendingOverlays() { - Set<UserHandle> userHandles = Sets.newHashSet(TEST_USER_HANDLES); - UserHandle newUserHandle = UserHandle.of(10); - userHandles.add(newUserHandle); - FabricatedOverlay[] pendingCreation = new FabricatedOverlay[] { + FabricatedOverlay[] pendingCreation = new FabricatedOverlay[]{ mock(FabricatedOverlay.class) }; - mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, pendingCreation, userHandles); + mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, pendingCreation, + TEST_USER.getIdentifier(), TEST_USER_HANDLES); for (FabricatedOverlay overlay : pendingCreation) { verify(mTransactionBuilder).registerFabricatedOverlay(eq(overlay)); @@ -220,20 +226,13 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { } @Test - public void allCategoriesSpecified_overlayManagerNotQueried() { - mManager.applyCurrentUserOverlays(ALL_CATEGORIES_MAP, null, TEST_USER_HANDLES); - - verify(mOverlayManager, never()) - .getOverlayInfosForTarget(anyString(), any(UserHandle.class)); - } - - @Test public void someCategoriesSpecified_specifiedEnabled_unspecifiedDisabled() { Map<String, OverlayIdentifier> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP); categoryToPackage.remove(OVERLAY_CATEGORY_ICON_SETTINGS); categoryToPackage.remove(OVERLAY_CATEGORY_ICON_ANDROID); - mManager.applyCurrentUserOverlays(categoryToPackage, null, TEST_USER_HANDLES); + mManager.applyCurrentUserOverlays(categoryToPackage, null, TEST_USER.getIdentifier(), + TEST_USER_HANDLES); for (OverlayIdentifier overlayPackage : categoryToPackage.values()) { verify(mTransactionBuilder).setEnabled(eq(overlayPackage), eq(true), @@ -249,7 +248,8 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { @Test public void zeroCategoriesSpecified_allDisabled() { - mManager.applyCurrentUserOverlays(Maps.newArrayMap(), null, TEST_USER_HANDLES); + mManager.applyCurrentUserOverlays(Maps.newArrayMap(), null, TEST_USER.getIdentifier(), + TEST_USER_HANDLES); for (String category : THEME_CATEGORIES) { verify(mTransactionBuilder).setEnabled( @@ -263,7 +263,8 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { Map<String, OverlayIdentifier> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP); categoryToPackage.put("blah.category", new OverlayIdentifier("com.example.blah.category")); - mManager.applyCurrentUserOverlays(categoryToPackage, null, TEST_USER_HANDLES); + mManager.applyCurrentUserOverlays(categoryToPackage, null, TEST_USER.getIdentifier(), + TEST_USER_HANDLES); verify(mTransactionBuilder, never()).setEnabled( eq(new OverlayIdentifier("com.example.blah.category")), eq(false), @@ -273,23 +274,6 @@ public class ThemeOverlayApplierTest extends SysuiTestCase { eq(TEST_USER.getIdentifier())); } - @Test - public void overlayManagerOnlyQueriedForUnspecifiedPackages() { - Map<String, OverlayIdentifier> categoryToPackage = new HashMap<>(ALL_CATEGORIES_MAP); - categoryToPackage.remove(OVERLAY_CATEGORY_ICON_SETTINGS); - - mManager.applyCurrentUserOverlays(categoryToPackage, null, TEST_USER_HANDLES); - - verify(mOverlayManager).getOverlayInfosForTarget(SETTINGS_PACKAGE, UserHandle.SYSTEM); - verify(mOverlayManager, never()).getOverlayInfosForTarget(ANDROID_PACKAGE, - UserHandle.SYSTEM); - verify(mOverlayManager, never()).getOverlayInfosForTarget(SYSUI_PACKAGE, UserHandle.SYSTEM); - verify(mOverlayManager, never()).getOverlayInfosForTarget(LAUNCHER_PACKAGE, - UserHandle.SYSTEM); - verify(mOverlayManager, never()).getOverlayInfosForTarget(THEMEPICKER_PACKAGE, - UserHandle.SYSTEM); - } - private static OverlayInfo createOverlayInfo(String packageName, String targetPackageName, String category, boolean enabled) { return new OverlayInfo(packageName, null, targetPackageName, null, category, "", diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java index aa385effa931..d80c40fcd07b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java @@ -143,7 +143,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { ArgumentCaptor.forClass(Map.class); verify(mThemeOverlayApplier) - .applyCurrentUserOverlays(themeOverlays.capture(), any(), any()); + .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any()); // Assert that we received the colors that we were expecting assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE)) @@ -175,7 +175,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { ArgumentCaptor.forClass(Map.class); verify(mThemeOverlayApplier) - .applyCurrentUserOverlays(themeOverlays.capture(), any(), any()); + .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any()); // Assert that we received the colors that we were expecting assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE)) @@ -198,7 +198,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { ArgumentCaptor.forClass(Map.class); verify(mThemeOverlayApplier) - .applyCurrentUserOverlays(themeOverlays.capture(), any(), any()); + .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any()); // Assert that we received the colors that we were expecting assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE)) diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java index c0856892dc44..3cea17567173 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java @@ -22,6 +22,7 @@ import static junit.framework.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.app.KeyguardManager; @@ -37,6 +38,8 @@ import android.view.accessibility.AccessibilityManager; import androidx.test.filters.SmallTest; +import com.android.systemui.Prefs; +import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.VolumeDialogController; import com.android.systemui.plugins.VolumeDialogController.State; @@ -58,6 +61,11 @@ import java.util.function.Predicate; public class VolumeDialogImplTest extends SysuiTestCase { VolumeDialogImpl mDialog; + View mActiveRinger; + View mDrawerContainer; + View mDrawerVibrate; + View mDrawerMute; + View mDrawerNormal; @Mock VolumeDialogController mController; @@ -80,6 +88,17 @@ public class VolumeDialogImplTest extends SysuiTestCase { mDialog.init(0, null); State state = createShellState(); mDialog.onStateChangedH(state); + + mActiveRinger = mDialog.getDialogView().findViewById( + R.id.volume_new_ringer_active_icon_container); + mDrawerContainer = mDialog.getDialogView().findViewById(R.id.volume_drawer_container); + mDrawerVibrate = mDrawerContainer.findViewById(R.id.volume_drawer_vibrate); + mDrawerMute = mDrawerContainer.findViewById(R.id.volume_drawer_mute); + mDrawerNormal = mDrawerContainer.findViewById(R.id.volume_drawer_normal); + + Prefs.putInt(mContext, + Prefs.Key.SEEN_RINGER_GUIDANCE_COUNT, + VolumePrefs.SHOW_RINGER_TOAST_COUNT + 1); } private State createShellState() { @@ -207,6 +226,48 @@ public class VolumeDialogImplTest extends SysuiTestCase { verify(mController, never()).vibrate(any()); } + @Test + public void testSelectVibrateFromDrawer() { + final State initialUnsetState = new State(); + initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL; + mDialog.onStateChangedH(initialUnsetState); + + mActiveRinger.performClick(); + mDrawerVibrate.performClick(); + + // Make sure we've actually changed the ringer mode. + verify(mController, times(1)).setRingerMode( + AudioManager.RINGER_MODE_VIBRATE, false); + } + + @Test + public void testSelectMuteFromDrawer() { + final State initialUnsetState = new State(); + initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL; + mDialog.onStateChangedH(initialUnsetState); + + mActiveRinger.performClick(); + mDrawerMute.performClick(); + + // Make sure we've actually changed the ringer mode. + verify(mController, times(1)).setRingerMode( + AudioManager.RINGER_MODE_SILENT, false); + } + + @Test + public void testSelectNormalFromDrawer() { + final State initialUnsetState = new State(); + initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE; + mDialog.onStateChangedH(initialUnsetState); + + mActiveRinger.performClick(); + mDrawerNormal.performClick(); + + // Make sure we've actually changed the ringer mode. + verify(mController, times(1)).setRingerMode( + AudioManager.RINGER_MODE_NORMAL, false); + } + /* @Test public void testContentDescriptions() { diff --git a/services/api/Android.bp b/services/api/Android.bp new file mode 100644 index 000000000000..b8ca5488c5cd --- /dev/null +++ b/services/api/Android.bp @@ -0,0 +1,29 @@ +// Copyright (C) 2021 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package { + default_visibility: ["//visibility:private"], +} + +filegroup { + name: "non-updatable-system-server-current.txt", + srcs: ["non-updatable-current.txt"], + visibility: ["//frameworks/base/api"], +} + +filegroup { + name: "non-updatable-system-server-removed.txt", + srcs: ["non-updatable-removed.txt"], + visibility: ["//frameworks/base/api"], +}
\ No newline at end of file diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index 7518c7a8bdc9..50ad6617b1fe 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -137,7 +137,6 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -181,9 +180,6 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } switch (action) { - case Intent.ACTION_CONFIGURATION_CHANGED: - onConfigurationChanged(); - break; case Intent.ACTION_MANAGED_PROFILE_AVAILABLE: case Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE: synchronized (mLock) { @@ -243,8 +239,6 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku private Handler mSaveStateHandler; private Handler mCallbackHandler; - private Locale mLocale; - private final SparseIntArray mNextAppWidgetIds = new SparseIntArray(); private boolean mSafeMode; @@ -290,13 +284,6 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } private void registerBroadcastReceiver() { - // Register for configuration changes so we can update the names - // of the widgets when the locale changes. - IntentFilter configFilter = new IntentFilter(); - configFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); - mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, - configFilter, null, null); - // Register for broadcasts about package install, etc., so we can // update the provider list. IntentFilter packageFilter = new IntentFilter(); @@ -338,62 +325,6 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku mSafeMode = safeMode; } - private void onConfigurationChanged() { - if (DEBUG) { - Slog.i(TAG, "onConfigurationChanged()"); - } - - Locale revised = Locale.getDefault(); - if (revised == null || mLocale == null || !revised.equals(mLocale)) { - mLocale = revised; - - synchronized (mLock) { - SparseIntArray changedGroups = null; - - // Note: updateProvidersForPackageLocked() may remove providers, so we must copy the - // list of installed providers and skip providers that we don't need to update. - // Also note that remove the provider does not clear the Provider component data. - ArrayList<Provider> installedProviders = new ArrayList<>(mProviders); - HashSet<ProviderId> removedProviders = new HashSet<>(); - - int N = installedProviders.size(); - for (int i = N - 1; i >= 0; i--) { - Provider provider = installedProviders.get(i); - - final int userId = provider.getUserId(); - if (!mUserManager.isUserUnlockingOrUnlocked(userId) || - isProfileWithLockedParent(userId)) { - continue; - } - ensureGroupStateLoadedLocked(userId); - - if (!removedProviders.contains(provider.id)) { - final boolean changed = updateProvidersForPackageLocked( - provider.id.componentName.getPackageName(), - provider.getUserId(), removedProviders); - - if (changed) { - if (changedGroups == null) { - changedGroups = new SparseIntArray(); - } - final int groupId = mSecurityPolicy.getGroupParent( - provider.getUserId()); - changedGroups.put(groupId, groupId); - } - } - } - - if (changedGroups != null) { - final int groupCount = changedGroups.size(); - for (int i = 0; i < groupCount; i++) { - final int groupId = changedGroups.get(i); - saveGroupStateAsync(groupId); - } - } - } - } - } - private void onPackageBroadcastReceived(Intent intent, int userId) { final String action = intent.getAction(); boolean added = false; diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index ebfa9171b2b9..21cae453d702 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -742,6 +742,15 @@ public class CompanionDeviceManagerService extends SystemService implements Bind } } } + + if (association.isNotifyOnDeviceNearby()) { + ServiceConnector<ICompanionDeviceService> serviceConnector = + mDeviceListenerServiceConnectors.forUser(association.getUserId()) + .get(association.getPackageName()); + if (serviceConnector != null) { + serviceConnector.unbind(); + } + } } private void updateSpecialAccessPermissionForAssociatedPackage(Association association) { diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index f4138d10a84d..542d527177a1 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -44,6 +44,7 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID; import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE; @@ -91,7 +92,6 @@ import android.net.IConnectivityManager; import android.net.IDnsResolver; import android.net.INetd; import android.net.INetworkActivityListener; -import android.net.INetworkManagementEventObserver; import android.net.INetworkMonitor; import android.net.INetworkMonitorCallbacks; import android.net.INetworkPolicyListener; @@ -194,6 +194,7 @@ import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.LocationPermissionChecker; import com.android.internal.util.MessageUtils; import com.android.modules.utils.BasicShellCommandHandler; +import com.android.net.module.util.BaseNetdUnsolicitedEventListener; import com.android.net.module.util.CollectionUtils; import com.android.net.module.util.LinkPropertiesUtils.CompareOrUpdateResult; import com.android.net.module.util.LinkPropertiesUtils.CompareResult; @@ -214,7 +215,6 @@ import com.android.server.connectivity.NetworkRanker; import com.android.server.connectivity.PermissionMonitor; import com.android.server.connectivity.ProxyTracker; import com.android.server.connectivity.QosCallbackTracker; -import com.android.server.net.BaseNetworkObserver; import com.android.server.net.NetworkPolicyManagerInternal; import com.android.server.utils.PriorityDump; @@ -332,6 +332,7 @@ public class ConnectivityService extends IConnectivityManager.Stub private INetworkStatsService mStatsService; private NetworkPolicyManager mPolicyManager; private NetworkPolicyManagerInternal mPolicyManagerInternal; + private final NetdCallback mNetdCallback; /** * TestNetworkService (lazily) created upon first usage. Locked to prevent creation of multiple @@ -1204,6 +1205,13 @@ public class ConnectivityService extends IConnectivityManager.Stub mNetworkActivityTracker = new LegacyNetworkActivityTracker(mContext, mHandler, mNMS, mNetd); + mNetdCallback = new NetdCallback(); + try { + mNetd.registerUnsolicitedEventListener(mNetdCallback); + } catch (RemoteException | ServiceSpecificException e) { + loge("Error registering event listener :" + e); + } + mSettingsObserver = new SettingsObserver(mContext, mHandler); registerSettingsCallbacks(); @@ -1241,6 +1249,7 @@ public class ConnectivityService extends IConnectivityManager.Stub private static NetworkCapabilities createDefaultNetworkCapabilitiesForUid(int uid) { final NetworkCapabilities netCap = new NetworkCapabilities(); netCap.addCapability(NET_CAPABILITY_INTERNET); + netCap.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED); netCap.removeCapability(NET_CAPABILITY_NOT_VPN); netCap.setSingleUid(uid); return netCap; @@ -1255,6 +1264,7 @@ public class ConnectivityService extends IConnectivityManager.Stub int transportType, NetworkRequest.Type type) { final NetworkCapabilities netCap = new NetworkCapabilities(); netCap.addCapability(NET_CAPABILITY_INTERNET); + netCap.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED); netCap.setRequestorUidAndPackageName(Process.myUid(), mContext.getPackageName()); if (transportType > TYPE_NONE) { netCap.addTransportType(transportType); @@ -8649,6 +8659,14 @@ public class ConnectivityService extends IConnectivityManager.Stub notifyDataStallSuspected(p, network.getNetId()); } + private class NetdCallback extends BaseNetdUnsolicitedEventListener { + @Override + public void onInterfaceClassActivityChanged(boolean isActive, int timerLabel, + long timestampNs, int uid) { + mNetworkActivityTracker.setAndReportNetworkActive(isActive, timerLabel, timestampNs); + } + } + private final LegacyNetworkActivityTracker mNetworkActivityTracker; /** @@ -8659,7 +8677,6 @@ public class ConnectivityService extends IConnectivityManager.Stub private static final int NO_UID = -1; private final Context mContext; private final INetd mNetd; - private final INetworkManagementService mNMS; private final RemoteCallbackList<INetworkActivityListener> mNetworkActivityListeners = new RemoteCallbackList<>(); // Indicate the current system default network activity is active or not. @@ -8682,41 +8699,27 @@ public class ConnectivityService extends IConnectivityManager.Stub LegacyNetworkActivityTracker(@NonNull Context context, @NonNull Handler handler, @NonNull INetworkManagementService nms, @NonNull INetd netd) { mContext = context; - mNMS = nms; mNetd = netd; mHandler = handler; - try { - mNMS.registerObserver(mDataActivityObserver); - } catch (RemoteException e) { - loge("Error registering observer :" + e); - } - } - - // TODO: Migrate away the dependency with INetworkManagementEventObserver. - private final INetworkManagementEventObserver mDataActivityObserver = - new BaseNetworkObserver() { - @Override - public void interfaceClassDataActivityChanged(int transportType, boolean active, - long tsNanos, int uid) { - sendDataActivityBroadcast(transportTypeToLegacyType(transportType), active, - tsNanos); - synchronized (mActiveIdleTimers) { - mNetworkActive = active; - // If there are no idle timers, it means that system is not monitoring - // activity, so the system default network for those default network - // unspecified apps is always considered active. - // - // TODO: If the mActiveIdleTimers is empty, netd will actually not send - // any network activity change event. Whenever this event is received, - // the mActiveIdleTimers should be always not empty. The legacy behavior - // is no-op. Remove to refer to mNetworkActive only. - if (mNetworkActive || mActiveIdleTimers.isEmpty()) { - mHandler.sendMessage( - mHandler.obtainMessage(EVENT_REPORT_NETWORK_ACTIVITY)); - } - } - } - }; + } + + public void setAndReportNetworkActive(boolean active, int transportType, long tsNanos) { + sendDataActivityBroadcast(transportTypeToLegacyType(transportType), active, tsNanos); + synchronized (mActiveIdleTimers) { + mNetworkActive = active; + // If there are no idle timers, it means that system is not monitoring + // activity, so the system default network for those default network + // unspecified apps is always considered active. + // + // TODO: If the mActiveIdleTimers is empty, netd will actually not send + // any network activity change event. Whenever this event is received, + // the mActiveIdleTimers should be always not empty. The legacy behavior + // is no-op. Remove to refer to mNetworkActive only. + if (mNetworkActive || mActiveIdleTimers.isEmpty()) { + mHandler.sendMessage(mHandler.obtainMessage(EVENT_REPORT_NETWORK_ACTIVITY)); + } + } + } // The network activity should only be updated from ConnectivityService handler thread // when mActiveIdleTimers lock is held. diff --git a/services/core/java/com/android/server/SensorPrivacyService.java b/services/core/java/com/android/server/SensorPrivacyService.java index 84e429dc83e4..f2782f64995a 100644 --- a/services/core/java/com/android/server/SensorPrivacyService.java +++ b/services/core/java/com/android/server/SensorPrivacyService.java @@ -25,9 +25,9 @@ import static android.app.AppOpsManager.OP_RECORD_AUDIO; import static android.content.Intent.EXTRA_PACKAGE_NAME; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.hardware.SensorPrivacyManager.EXTRA_SENSOR; +import static android.hardware.SensorPrivacyManager.Sensors.CAMERA; +import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE; import static android.os.UserHandle.USER_SYSTEM; -import static android.service.SensorPrivacyIndividualEnabledSensorProto.CAMERA; -import static android.service.SensorPrivacyIndividualEnabledSensorProto.MICROPHONE; import static android.service.SensorPrivacyIndividualEnabledSensorProto.UNKNOWN; import android.annotation.NonNull; @@ -406,12 +406,12 @@ public final class SensorPrivacyService extends SystemService { */ @Override public void setSensorPrivacy(boolean enable) { + enforceManageSensorPrivacyPermission(); // Keep the state consistent between all users to make it a single global state forAllUsers(userId -> setSensorPrivacy(userId, enable)); } private void setSensorPrivacy(@UserIdInt int userId, boolean enable) { - enforceSensorPrivacyPermission(); synchronized (mLock) { mEnabled.put(userId, enable); persistSensorPrivacyStateLocked(); @@ -421,7 +421,7 @@ public final class SensorPrivacyService extends SystemService { @Override public void setIndividualSensorPrivacy(@UserIdInt int userId, int sensor, boolean enable) { - enforceSensorPrivacyPermission(); + enforceManageSensorPrivacyPermission(); synchronized (mLock) { SparseBooleanArray userIndividualEnabled = mIndividualEnabled.get(userId, new SparseBooleanArray()); @@ -448,6 +448,7 @@ public final class SensorPrivacyService extends SystemService { @Override public void setIndividualSensorPrivacyForProfileGroup(@UserIdInt int userId, int sensor, boolean enable) { + enforceManageSensorPrivacyPermission(); int parentId = mUserManagerInternal.getProfileParentId(userId); forAllUsers(userId2 -> { if (parentId == mUserManagerInternal.getProfileParentId(userId2)) { @@ -460,21 +461,35 @@ public final class SensorPrivacyService extends SystemService { * Enforces the caller contains the necessary permission to change the state of sensor * privacy. */ - private void enforceSensorPrivacyPermission() { - if (mContext.checkCallingOrSelfPermission( - MANAGE_SENSOR_PRIVACY) == PERMISSION_GRANTED) { - return; - } - throw new SecurityException( + private void enforceManageSensorPrivacyPermission() { + enforcePermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY, "Changing sensor privacy requires the following permission: " + MANAGE_SENSOR_PRIVACY); } /** + * Enforces the caller contains the necessary permission to observe changes to the sate of + * sensor privacy. + */ + private void enforceObserveSensorPrivacyPermission() { + enforcePermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY, + "Observing sensor privacy changes requires the following permission: " + + android.Manifest.permission.OBSERVE_SENSOR_PRIVACY); + } + + private void enforcePermission(String permission, String message) { + if (mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) { + return; + } + throw new SecurityException(message); + } + + /** * Returns whether sensor privacy is enabled. */ @Override public boolean isSensorPrivacyEnabled() { + enforceObserveSensorPrivacyPermission(); return isSensorPrivacyEnabled(USER_SYSTEM); } @@ -486,6 +501,7 @@ public final class SensorPrivacyService extends SystemService { @Override public boolean isIndividualSensorPrivacyEnabled(@UserIdInt int userId, int sensor) { + enforceObserveSensorPrivacyPermission(); synchronized (mLock) { SparseBooleanArray states = mIndividualEnabled.get(userId); if (states == null) { @@ -703,6 +719,7 @@ public final class SensorPrivacyService extends SystemService { */ @Override public void addSensorPrivacyListener(ISensorPrivacyListener listener) { + enforceObserveSensorPrivacyPermission(); if (listener == null) { throw new NullPointerException("listener cannot be null"); } @@ -715,6 +732,7 @@ public final class SensorPrivacyService extends SystemService { @Override public void addIndividualSensorPrivacyListener(int userId, int sensor, ISensorPrivacyListener listener) { + enforceObserveSensorPrivacyPermission(); if (listener == null) { throw new NullPointerException("listener cannot be null"); } @@ -726,6 +744,7 @@ public final class SensorPrivacyService extends SystemService { */ @Override public void removeSensorPrivacyListener(ISensorPrivacyListener listener) { + enforceObserveSensorPrivacyPermission(); if (listener == null) { throw new NullPointerException("listener cannot be null"); } @@ -735,6 +754,7 @@ public final class SensorPrivacyService extends SystemService { @Override public void suppressIndividualSensorPrivacyReminders(int userId, String packageName, IBinder token, boolean suppress) { + enforceManageSensorPrivacyPermission(); Objects.requireNonNull(packageName); Objects.requireNonNull(token); @@ -886,13 +906,13 @@ public final class SensorPrivacyService extends SystemService { } /** - * Convert a string into a {@link SensorPrivacyManager.IndividualSensor id}. + * Convert a string into a {@link SensorPrivacyManager.Sensors.Sensor id}. * * @param sensor The name to convert * * @return The id corresponding to the name */ - private @SensorPrivacyManager.IndividualSensor int sensorStrToId(@Nullable String sensor) { + private @SensorPrivacyManager.Sensors.Sensor int sensorStrToId(@Nullable String sensor) { if (sensor == null) { return UNKNOWN; } @@ -950,7 +970,7 @@ public final class SensorPrivacyService extends SystemService { return -1; } - enforceSensorPrivacyPermission(); + enforceManageSensorPrivacyPermission(); synchronized (mLock) { SparseBooleanArray individualEnabled = diff --git a/services/core/java/com/android/server/ServiceWatcher.java b/services/core/java/com/android/server/ServiceWatcher.java index c2d8fa24157a..8a2894c84cc4 100644 --- a/services/core/java/com/android/server/ServiceWatcher.java +++ b/services/core/java/com/android/server/ServiceWatcher.java @@ -45,14 +45,18 @@ import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; import android.os.UserHandle; +import android.util.ArrayMap; import android.util.Log; +import com.android.internal.annotations.Immutable; import com.android.internal.content.PackageMonitor; import com.android.internal.util.Preconditions; +import com.android.internal.util.function.pooled.PooledLambda; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.function.Predicate; @@ -87,26 +91,30 @@ public class ServiceWatcher implements ServiceConnection { /** Function to run on binder interface when first bound. */ public interface OnBindRunner { /** Called to run client code with the binder. */ - void run(IBinder binder, ComponentName service) throws RemoteException; + void run(IBinder binder, BoundService service) throws RemoteException; } /** * Information on the service ServiceWatcher has selected as the best option for binding. */ - private static final class ServiceInfo implements Comparable<ServiceInfo> { + @Immutable + public static final class BoundService implements Comparable<BoundService> { - public static final ServiceInfo NONE = new ServiceInfo(Integer.MIN_VALUE, null, - UserHandle.USER_NULL, false); + public static final BoundService NONE = new BoundService(Integer.MIN_VALUE, null, + false, null, -1); public final int version; - @Nullable public final ComponentName component; - @UserIdInt public final int userId; + @Nullable + public final ComponentName component; public final boolean serviceIsMultiuser; + public final int uid; + @Nullable + public final Bundle metadata; - ServiceInfo(ResolveInfo resolveInfo, int currentUserId) { + BoundService(ResolveInfo resolveInfo) { Preconditions.checkArgument(resolveInfo.serviceInfo.getComponentName() != null); - Bundle metadata = resolveInfo.serviceInfo.metaData; + metadata = resolveInfo.serviceInfo.metaData; if (metadata != null) { version = metadata.getInt(EXTRA_SERVICE_VERSION, Integer.MIN_VALUE); serviceIsMultiuser = metadata.getBoolean(EXTRA_SERVICE_IS_MULTIUSER, false); @@ -116,16 +124,17 @@ public class ServiceWatcher implements ServiceConnection { } component = resolveInfo.serviceInfo.getComponentName(); - userId = serviceIsMultiuser ? UserHandle.USER_SYSTEM : currentUserId; + uid = resolveInfo.serviceInfo.applicationInfo.uid; } - private ServiceInfo(int version, @Nullable ComponentName component, int userId, - boolean serviceIsMultiuser) { + private BoundService(int version, @Nullable ComponentName component, + boolean serviceIsMultiuser, @Nullable Bundle metadata, int uid) { Preconditions.checkArgument(component != null || version == Integer.MIN_VALUE); this.version = version; this.component = component; - this.userId = userId; this.serviceIsMultiuser = serviceIsMultiuser; + this.metadata = metadata; + this.uid = uid; } public @Nullable String getPackageName() { @@ -137,21 +146,21 @@ public class ServiceWatcher implements ServiceConnection { if (this == o) { return true; } - if (!(o instanceof ServiceInfo)) { + if (!(o instanceof BoundService)) { return false; } - ServiceInfo that = (ServiceInfo) o; - return version == that.version && userId == that.userId + BoundService that = (BoundService) o; + return version == that.version && uid == that.uid && Objects.equals(component, that.component); } @Override public int hashCode() { - return Objects.hash(version, component, userId); + return Objects.hash(version, component, uid); } @Override - public int compareTo(ServiceInfo that) { + public int compareTo(BoundService that) { // ServiceInfos with higher version numbers always win (having a version number > // MIN_VALUE implies having a non-null component). if version numbers are equal, a // non-null component wins over a null component. if the version numbers are equal and @@ -164,10 +173,11 @@ public class ServiceWatcher implements ServiceConnection { } else if (component != null && that.component == null) { ret = 1; } else { - if (userId != UserHandle.USER_SYSTEM && that.userId == UserHandle.USER_SYSTEM) { + if (UserHandle.getUserId(uid) != UserHandle.USER_SYSTEM + && UserHandle.getUserId(that.uid) == UserHandle.USER_SYSTEM) { ret = -1; - } else if (userId == UserHandle.USER_SYSTEM - && that.userId != UserHandle.USER_SYSTEM) { + } else if (UserHandle.getUserId(uid) == UserHandle.USER_SYSTEM + && UserHandle.getUserId(that.uid) != UserHandle.USER_SYSTEM) { ret = 1; } } @@ -180,7 +190,8 @@ public class ServiceWatcher implements ServiceConnection { if (component == null) { return "none"; } else { - return component.toShortString() + "@" + version + "[u" + userId + "]"; + return component.toShortString() + "@" + version + "[u" + + UserHandle.getUserId(uid) + "]"; } } } @@ -227,17 +238,23 @@ public class ServiceWatcher implements ServiceConnection { } }; - @Nullable private final OnBindRunner mOnBind; - @Nullable private final Runnable mOnUnbind; + // read/write from handler thread only + private final Map<ComponentName, BoundService> mPendingBinds = new ArrayMap<>(); + + @Nullable + private final OnBindRunner mOnBind; + + @Nullable + private final Runnable mOnUnbind; - // write from caller thread only, read anywhere - private volatile boolean mRegistered; + // read/write from handler thread only + private boolean mRegistered; // read/write from handler thread only private int mCurrentUserId; // write from handler thread only, read anywhere - private volatile ServiceInfo mTargetService; + private volatile BoundService mTargetService; private volatile IBinder mBinder; public ServiceWatcher(Context context, String action, @@ -274,7 +291,7 @@ public class ServiceWatcher implements ServiceConnection { mCurrentUserId = UserHandle.USER_NULL; - mTargetService = ServiceInfo.NONE; + mTargetService = BoundService.NONE; mBinder = null; } @@ -299,6 +316,11 @@ public class ServiceWatcher implements ServiceConnection { * Starts the process of determining the best matching service and maintaining a binding to it. */ public void register() { + mHandler.sendMessage(PooledLambda.obtainMessage(ServiceWatcher::registerInternal, + ServiceWatcher.this)); + } + + private void registerInternal() { Preconditions.checkState(!mRegistered); mPackageMonitor.register(mContext, UserHandle.ALL, true, mHandler); @@ -309,6 +331,8 @@ public class ServiceWatcher implements ServiceConnection { mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, intentFilter, null, mHandler); + // TODO: This makes the behavior of the class unpredictable as the caller needs + // to know the internal impl detail that calling register would pick the current user. mCurrentUserId = ActivityManager.getCurrentUser(); mRegistered = true; @@ -320,6 +344,11 @@ public class ServiceWatcher implements ServiceConnection { * Stops the process of determining the best matching service and releases any binding. */ public void unregister() { + mHandler.sendMessage(PooledLambda.obtainMessage(ServiceWatcher::unregisterInternal, + ServiceWatcher.this)); + } + + private void unregisterInternal() { Preconditions.checkState(mRegistered); mRegistered = false; @@ -333,7 +362,7 @@ public class ServiceWatcher implements ServiceConnection { private void onBestServiceChanged(boolean forceRebind) { Preconditions.checkState(Looper.myLooper() == mHandler.getLooper()); - ServiceInfo bestServiceInfo = ServiceInfo.NONE; + BoundService bestServiceInfo = BoundService.NONE; if (mRegistered) { List<ResolveInfo> resolveInfos = mContext.getPackageManager().queryIntentServicesAsUser( @@ -344,7 +373,7 @@ public class ServiceWatcher implements ServiceConnection { if (!mServiceCheckPredicate.test(resolveInfo)) { continue; } - ServiceInfo serviceInfo = new ServiceInfo(resolveInfo, mCurrentUserId); + BoundService serviceInfo = new BoundService(resolveInfo); if (serviceInfo.compareTo(bestServiceInfo) > 0) { bestServiceInfo = serviceInfo; } @@ -356,21 +385,22 @@ public class ServiceWatcher implements ServiceConnection { } } - private void rebind(ServiceInfo newServiceInfo) { + private void rebind(BoundService newServiceInfo) { Preconditions.checkState(Looper.myLooper() == mHandler.getLooper()); - if (!mTargetService.equals(ServiceInfo.NONE)) { + if (!mTargetService.equals(BoundService.NONE)) { if (D) { Log.d(TAG, "[" + mIntent.getAction() + "] unbinding from " + mTargetService); } mContext.unbindService(this); onServiceDisconnected(mTargetService.component); - mTargetService = ServiceInfo.NONE; + mPendingBinds.remove(mTargetService.component); + mTargetService = BoundService.NONE; } mTargetService = newServiceInfo; - if (mTargetService.equals(ServiceInfo.NONE)) { + if (mTargetService.equals(BoundService.NONE)) { return; } @@ -381,10 +411,12 @@ public class ServiceWatcher implements ServiceConnection { Intent bindIntent = new Intent(mIntent).setComponent(mTargetService.component); if (!mContext.bindServiceAsUser(bindIntent, this, BIND_AUTO_CREATE | BIND_NOT_FOREGROUND | BIND_NOT_VISIBLE, - mHandler, UserHandle.of(mTargetService.userId))) { - mTargetService = ServiceInfo.NONE; + mHandler, UserHandle.of(UserHandle.getUserId(mTargetService.uid)))) { + mTargetService = BoundService.NONE; Log.e(TAG, getLogPrefix() + " unexpected bind failure - retrying later"); mHandler.postDelayed(() -> onBestServiceChanged(false), RETRY_DELAY_MS); + } else { + mPendingBinds.put(mTargetService.component, mTargetService); } } @@ -397,10 +429,15 @@ public class ServiceWatcher implements ServiceConnection { Log.d(TAG, getLogPrefix() + " connected to " + component.toShortString()); } + final BoundService boundService = mPendingBinds.remove(component); + if (boundService == null) { + return; + } + mBinder = binder; if (mOnBind != null) { try { - mOnBind.run(binder, component); + mOnBind.run(binder, boundService); } catch (RuntimeException | RemoteException e) { // binders may propagate some specific non-RemoteExceptions from the other side // through the binder as well - we cannot allow those to crash the system server diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 2f9819997257..6be7f05f6cc6 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -51,6 +51,7 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManagerInternal; +import android.app.AnrController; import android.app.AppOpsManager; import android.app.IActivityManager; import android.app.KeyguardManager; @@ -938,14 +939,29 @@ class StorageManagerService extends IStorageManager.Stub if (transcodeEnabled) { LocalServices.getService(ActivityManagerInternal.class) - .registerAnrController((packageName, uid) -> { - try { - return mStorageSessionController.getAnrDelayMillis(packageName, uid); - } catch (ExternalStorageServiceException e) { - Log.e(TAG, "Failed to get ANR delay for " + packageName, e); - return 0; - } - }); + .registerAnrController(new ExternalStorageServiceAnrController()); + } + } + + // TODO(b/170486601): Check transcoding status based on events pushed from the MediaProvider + private class ExternalStorageServiceAnrController implements AnrController { + @Override + public long getAnrDelayMillis(String packageName, int uid) { + int delay = SystemProperties.getInt("sys.fuse.transcode_anr_delay", 0); + Log.d(TAG, "getAnrDelayMillis: " + packageName + ". Delaying for " + delay + "ms"); + return delay; + } + + @Override + public void onAnrDelayStarted(String packageName, int uid) { + Log.d(TAG, "onAnrDelayStarted: " + packageName); + } + + @Override + public boolean onAnrDelayCompleted(String packageName, int uid) { + boolean show = SystemProperties.getBoolean("sys.fuse.transcode_anr_dialog_show", true); + Log.d(TAG, "onAnrDelayCompleted: " + packageName + ". Show: " + show); + return show; } } diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING index 95af84293377..a09dbc7e599d 100644 --- a/services/core/java/com/android/server/TEST_MAPPING +++ b/services/core/java/com/android/server/TEST_MAPPING @@ -21,7 +21,9 @@ } ], "file_patterns": ["NotificationManagerService\\.java"] - }, + } + ], + "presubmit-large": [ { "name": "CtsScopedStorageCoreHostTest", "file_patterns": ["StorageManagerService\\.java"] diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index b09b6ca61377..5a5f1a3f3723 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -313,9 +313,9 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { private List<PhysicalChannelConfig> mPhysicalChannelConfigs; - private boolean mIsDataEnabled = false; + private boolean[] mIsDataEnabled; - private int mDataEnabledReason; + private int[] mDataEnabledReason; private Map<Integer, Long> mAllowedNetworkTypesList; @@ -524,6 +524,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mOutgoingCallEmergencyNumber = copyOf(mOutgoingCallEmergencyNumber, mNumPhones); mOutgoingSmsEmergencyNumber = copyOf(mOutgoingSmsEmergencyNumber, mNumPhones); mTelephonyDisplayInfos = copyOf(mTelephonyDisplayInfos, mNumPhones); + mIsDataEnabled= copyOf(mIsDataEnabled, mNumPhones); + mDataEnabledReason = copyOf(mDataEnabledReason, mNumPhones); // ds -> ss switch. if (mNumPhones < oldNumPhones) { @@ -565,6 +567,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mPreciseDataConnectionStates.add(new ArrayMap<>()); mBarringInfo.add(i, new BarringInfo()); mTelephonyDisplayInfos[i] = null; + mIsDataEnabled[i] = false; + mDataEnabledReason[i] = TelephonyManager.DATA_ENABLED_REASON_USER; mPhysicalChannelConfigs.add(i, new PhysicalChannelConfig.Builder().build()); } } @@ -626,6 +630,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mTelephonyDisplayInfos = new TelephonyDisplayInfo[numPhones]; mPhysicalChannelConfigs = new ArrayList<>(); mAllowedNetworkTypesList = new HashMap<>(); + mIsDataEnabled = new boolean[numPhones]; + mDataEnabledReason = new int[numPhones]; for (int i = 0; i < numPhones; i++) { mCallState[i] = TelephonyManager.CALL_STATE_IDLE; mDataActivity[i] = TelephonyManager.DATA_ACTIVITY_NONE; @@ -655,6 +661,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mPreciseDataConnectionStates.add(new ArrayMap<>()); mBarringInfo.add(i, new BarringInfo()); mTelephonyDisplayInfos[i] = null; + mIsDataEnabled[i] = false; + mDataEnabledReason[i] = TelephonyManager.DATA_ENABLED_REASON_USER; mPhysicalChannelConfigs.add(i, new PhysicalChannelConfig.Builder().build()); } @@ -1150,7 +1158,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { if (events.contains( PhoneStateListener.EVENT_DATA_ENABLED_CHANGED)) { try { - r.callback.onDataEnabledChanged(mIsDataEnabled, mDataEnabledReason); + r.callback.onDataEnabledChanged( + mIsDataEnabled[phoneId], mDataEnabledReason[phoneId]); } catch (RemoteException ex) { remove(r.binder); } @@ -2370,30 +2379,36 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { /** * Notify that the data enabled has changed. * + * @param phoneId the phone id. + * @param subId the subId. * @param enabled True if data is enabled, otherwise disabled. * @param reason Reason for data enabled/disabled. See {@code DATA_*} in * {@link TelephonyManager}. */ - public void notifyDataEnabled(boolean enabled, + public void notifyDataEnabled(int phoneId, int subId, boolean enabled, @TelephonyManager.DataEnabledReason int reason) { if (!checkNotifyPermission("notifyDataEnabled()")) { return; } if (VDBG) { - log("notifyDataEnabled: enabled=" + enabled + " reason=" + reason); + log("notifyDataEnabled: PhoneId=" + phoneId + " subId=" + subId + + " enabled=" + enabled + " reason=" + reason); } - mIsDataEnabled = enabled; - mDataEnabledReason = reason; synchronized (mRecords) { - for (Record r : mRecords) { - if (r.matchPhoneStateListenerEvent( - PhoneStateListener.EVENT_DATA_ENABLED_CHANGED)) { - try { - r.callback.onDataEnabledChanged(enabled, reason); - } catch (RemoteException ex) { - mRemoveList.add(r.binder); + if (validatePhoneId(phoneId)) { + mIsDataEnabled[phoneId] = enabled; + mDataEnabledReason[phoneId] = reason; + for (Record r : mRecords) { + if (r.matchPhoneStateListenerEvent( + PhoneStateListener.EVENT_DATA_ENABLED_CHANGED) + && idMatch(r.subId, subId, phoneId)) { + try { + r.callback.onDataEnabledChanged(enabled, reason); + } catch (RemoteException ex) { + mRemoveList.add(r.binder); + } } } } @@ -2481,6 +2496,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { pw.println("mOutgoingSmsEmergencyNumber=" + mOutgoingSmsEmergencyNumber[i]); pw.println("mBarringInfo=" + mBarringInfo.get(i)); pw.println("mTelephonyDisplayInfo=" + mTelephonyDisplayInfos[i]); + pw.println("mIsDataEnabled=" + mIsDataEnabled); + pw.println("mDataEnabledReason=" + mDataEnabledReason); pw.decreaseIndent(); } pw.println("mCarrierNetworkChangeState=" + mCarrierNetworkChangeState); @@ -2491,8 +2508,6 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { pw.println("mDefaultPhoneId=" + mDefaultPhoneId); pw.println("mDefaultSubId=" + mDefaultSubId); pw.println("mPhysicalChannelConfigs=" + mPhysicalChannelConfigs); - pw.println("mIsDataEnabled=" + mIsDataEnabled); - pw.println("mDataEnabledReason=" + mDataEnabledReason); pw.decreaseIndent(); diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 2efc83c4af06..e5ef9353135d 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -20,10 +20,35 @@ import static android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND; import static android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND; import static android.Manifest.permission.SYSTEM_ALERT_WINDOW; import static android.app.ActivityManager.PROCESS_STATE_HEAVY_WEIGHT; +import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT; import static android.app.ActivityManager.PROCESS_STATE_RECEIVER; import static android.app.ActivityManager.PROCESS_STATE_TOP; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST; +import static android.os.PowerWhitelistManager.REASON_ACTIVITY_STARTER; +import static android.os.PowerWhitelistManager.REASON_ALLOWLISTED_PACKAGE; +import static android.os.PowerWhitelistManager.REASON_BACKGROUND_ACTIVITY_PERMISSION; +import static android.os.PowerWhitelistManager.REASON_BACKGROUND_FGS_PERMISSION; +import static android.os.PowerWhitelistManager.REASON_COMPANION_DEVICE_MANAGER; +import static android.os.PowerWhitelistManager.REASON_DENIED; +import static android.os.PowerWhitelistManager.REASON_DEVICE_DEMO_MODE; +import static android.os.PowerWhitelistManager.REASON_DEVICE_OWNER; +import static android.os.PowerWhitelistManager.REASON_EXEMPTED_PACKAGE; +import static android.os.PowerWhitelistManager.REASON_FGS_BINDING; +import static android.os.PowerWhitelistManager.REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION; +import static android.os.PowerWhitelistManager.REASON_INSTR_BACKGROUND_FGS_PERMISSION; +import static android.os.PowerWhitelistManager.REASON_PROC_STATE_PERSISTENT; +import static android.os.PowerWhitelistManager.REASON_PROC_STATE_PERSISTENT_UI; +import static android.os.PowerWhitelistManager.REASON_PROC_STATE_TOP; +import static android.os.PowerWhitelistManager.REASON_PROFILE_OWNER; +import static android.os.PowerWhitelistManager.REASON_START_ACTIVITY_FLAG; +import static android.os.PowerWhitelistManager.REASON_SYSTEM_ALERT_WINDOW_PERMISSION; +import static android.os.PowerWhitelistManager.REASON_SYSTEM_ALLOW_LISTED; +import static android.os.PowerWhitelistManager.REASON_SYSTEM_UID; +import static android.os.PowerWhitelistManager.REASON_UID_VISIBLE; +import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED; +import static android.os.PowerWhitelistManager.getReasonCodeFromProcState; +import static android.os.PowerWhitelistManager.reasonCodeToString; import static android.os.Process.NFC_UID; import static android.os.Process.ROOT_UID; import static android.os.Process.SHELL_UID; @@ -43,7 +68,6 @@ import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_SERVICE_E import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; -import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; @@ -86,6 +110,8 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.PowerWhitelistManager; +import android.os.PowerWhitelistManager.ReasonCode; import android.os.Process; import android.os.RemoteCallback; import android.os.RemoteException; @@ -130,8 +156,6 @@ import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Comparator; @@ -152,58 +176,6 @@ public final class ActiveServices { private static final boolean SHOW_DUNGEON_NOTIFICATION = false; - public static final int FGS_FEATURE_DENIED = 0; - public static final int FGS_FEATURE_ALLOWED_BY_UID_STATE = 1; - public static final int FGS_FEATURE_ALLOWED_BY_PROC_STATE = 2; - public static final int FGS_FEATURE_ALLOWED_BY_UID_VISIBLE = 3; - public static final int FGS_FEATURE_ALLOWED_BY_FLAG = 4; - public static final int FGS_FEATURE_ALLOWED_BY_SYSTEM_UID = 5; - public static final int FGS_FEATURE_ALLOWED_BY_INSTR_BACKGROUND_ACTIVITY_PERMISSION = 6; - public static final int FGS_FEATURE_ALLOWED_BY_INSTR_BACKGROUND_FGS_PERMISSION = 7; - public static final int FGS_FEATURE_ALLOWED_BY_ACTIVITY_TOKEN = 8; - public static final int FGS_FEATURE_ALLOWED_BY_FGS_TOKEN = 9; - public static final int FGS_FEATURE_ALLOWED_BY_BACKGROUND_ACTIVITY_PERMISSION = 10; - public static final int FGS_FEATURE_ALLOWED_BY_BACKGROUND_FGS_PERMISSION = 12; - public static final int FGS_FEATURE_ALLOWED_BY_ALLOWLIST = 13; - public static final int FGS_FEATURE_ALLOWED_BY_DEVICE_OWNER = 14; - public static final int FGS_FEATURE_ALLOWED_BY_DEVICE_IDLE_ALLOW_LIST = 15; - public static final int FGS_FEATURE_ALLOWED_BY_SYSTEM_ALERT_WINDOW_PERMISSION = 16; - public static final int FGS_FEATURE_ALLOWED_BY_FGS_BINDING = 17; - public static final int FGS_FEATURE_ALLOWED_BY_DEVICE_DEMO_MODE = 18; - public static final int FGS_FEATURE_ALLOWED_BY_PROCESS_RECORD = 19; - public static final int FGS_FEATURE_ALLOWED_BY_EXEMPTED_PACKAGES = 20; - public static final int FGS_FEATURE_ALLOWED_BY_ACTIVITY_STARTER = 21; - public static final int FGS_FEATURE_ALLOWED_BY_COMPANION_APP = 22; - public static final int FGS_FEATURE_ALLOWED_BY_PROFILE_OWNER = 23; - - @IntDef(flag = true, prefix = { "FGS_FEATURE_" }, value = { - FGS_FEATURE_DENIED, - FGS_FEATURE_ALLOWED_BY_UID_STATE, - FGS_FEATURE_ALLOWED_BY_PROC_STATE, - FGS_FEATURE_ALLOWED_BY_UID_VISIBLE, - FGS_FEATURE_ALLOWED_BY_FLAG, - FGS_FEATURE_ALLOWED_BY_SYSTEM_UID, - FGS_FEATURE_ALLOWED_BY_INSTR_BACKGROUND_ACTIVITY_PERMISSION, - FGS_FEATURE_ALLOWED_BY_INSTR_BACKGROUND_FGS_PERMISSION, - FGS_FEATURE_ALLOWED_BY_ACTIVITY_TOKEN, - FGS_FEATURE_ALLOWED_BY_FGS_TOKEN, - FGS_FEATURE_ALLOWED_BY_BACKGROUND_ACTIVITY_PERMISSION, - FGS_FEATURE_ALLOWED_BY_BACKGROUND_FGS_PERMISSION, - FGS_FEATURE_ALLOWED_BY_ALLOWLIST, - FGS_FEATURE_ALLOWED_BY_DEVICE_OWNER, - FGS_FEATURE_ALLOWED_BY_DEVICE_IDLE_ALLOW_LIST, - FGS_FEATURE_ALLOWED_BY_SYSTEM_ALERT_WINDOW_PERMISSION, - FGS_FEATURE_ALLOWED_BY_FGS_BINDING, - FGS_FEATURE_ALLOWED_BY_DEVICE_DEMO_MODE, - FGS_FEATURE_ALLOWED_BY_PROCESS_RECORD, - FGS_FEATURE_ALLOWED_BY_EXEMPTED_PACKAGES, - FGS_FEATURE_ALLOWED_BY_ACTIVITY_STARTER, - FGS_FEATURE_ALLOWED_BY_COMPANION_APP, - FGS_FEATURE_ALLOWED_BY_PROFILE_OWNER - }) - @Retention(RetentionPolicy.SOURCE) - public @interface FgsFeatureRetCode {} - // How long we wait for a service to finish executing. static final int SERVICE_TIMEOUT = 20*1000; @@ -275,7 +247,7 @@ public final class ActiveServices { AppWidgetManagerInternal mAppWidgetManagerInternal; - // white listed packageName. + // allowlisted packageName. ArraySet<String> mAllowListWhileInUsePermissionInFgs = new ArraySet<>(); // TODO: remove this after feature development is done @@ -675,7 +647,7 @@ public final class ActiveServices { if (fgRequired) { logFgsBackgroundStart(r); - if (r.mAllowStartForeground == FGS_FEATURE_DENIED && isBgFgsRestrictionEnabled(r)) { + if (r.mAllowStartForeground == REASON_DENIED && isBgFgsRestrictionEnabled(r)) { String msg = "startForegroundService() not allowed due to " + "mAllowStartForeground false: service " + r.shortInstanceName; @@ -1768,8 +1740,7 @@ public final class ActiveServices { if (!ignoreForeground) { logFgsBackgroundStart(r); - if (r.mAllowStartForeground == FGS_FEATURE_DENIED - && isBgFgsRestrictionEnabled(r)) { + if (r.mAllowStartForeground == REASON_DENIED && isBgFgsRestrictionEnabled(r)) { final String msg = "Service.startForeground() not allowed due to " + "mAllowStartForeground false: service " + r.shortInstanceName; @@ -2250,7 +2221,7 @@ public final class ActiveServices { psr.mAllowlistManager = false; for (int i = psr.numberOfRunningServices() - 1; i >= 0; i--) { ServiceRecord sr = psr.getRunningServiceAt(i); - if (sr.whitelistManager) { + if (sr.allowlistManager) { psr.mAllowlistManager = true; break; } @@ -2261,7 +2232,7 @@ public final class ActiveServices { final ProcessServiceRecord psr = service.app.mServices; psr.stopService(service); psr.updateBoundClientUids(); - if (service.whitelistManager) { + if (service.allowlistManager) { updateAllowlistManagerLocked(psr); } } @@ -2483,7 +2454,7 @@ public final class ActiveServices { clientPsr.setHasAboveClient(true); } if ((c.flags&Context.BIND_ALLOW_WHITELIST_MANAGEMENT) != 0) { - s.whitelistManager = true; + s.allowlistManager = true; } if ((flags & Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS) != 0) { s.setAllowedBgActivityStartsByBinding(true); @@ -2520,7 +2491,7 @@ public final class ActiveServices { if ((flags&Context.BIND_TREAT_LIKE_ACTIVITY) != 0) { servicePsr.setTreatLikeActivity(true); } - if (s.whitelistManager) { + if (s.allowlistManager) { servicePsr.mAllowlistManager = true; } // This could have made the service more important. @@ -2949,7 +2920,6 @@ public final class ActiveServices { final ServiceRestarter res = new ServiceRestarter(); r = new ServiceRecord(mAm, className, name, definingPackageName, definingUid, filter, sInfo, callingFromFg, res); - r.mRecentCallingPackage = callingPackage; res.setService(r); smap.mServicesByInstanceName.put(name, r); smap.mServicesByIntent.put(filter, r); @@ -2978,6 +2948,8 @@ public final class ActiveServices { } } if (r != null) { + r.mRecentCallingPackage = callingPackage; + r.mRecentCallingUid = callingUid; if (!mAm.validateAssociationAllowedLocked(callingPackage, callingUid, r.packageName, r.appInfo.uid)) { String msg = "association not allowed between packages " @@ -3440,12 +3412,13 @@ public final class ActiveServices { if (r.fgRequired) { if (DEBUG_FOREGROUND_SERVICE) { - Slog.v(TAG, "Whitelisting " + UserHandle.formatUid(r.appInfo.uid) + Slog.v(TAG, "Allowlisting " + UserHandle.formatUid(r.appInfo.uid) + " for fg-service launch"); } mAm.tempAllowlistUidLocked(r.appInfo.uid, - SERVICE_START_FOREGROUND_TIMEOUT, "fg-service-launch", - BroadcastOptions.TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_ALLOWED); + SERVICE_START_FOREGROUND_TIMEOUT, PowerWhitelistManager.REASON_SERVICE_LAUNCH, + "fg-service-launch", TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, + r.mRecentCallingUid); } if (!mPendingServices.contains(r)) { @@ -3551,7 +3524,7 @@ public final class ActiveServices { } } - if (r.whitelistManager) { + if (r.allowlistManager) { psr.mAllowlistManager = true; } @@ -3941,11 +3914,11 @@ public final class ActiveServices { if ((c.flags&Context.BIND_ABOVE_CLIENT) != 0) { psr.updateHasAboveClientLocked(); } - // If this connection requested whitelist management, see if we should + // If this connection requested allowlist management, see if we should // now clear that state. if ((c.flags&Context.BIND_ALLOW_WHITELIST_MANAGEMENT) != 0) { - s.updateWhitelistManager(); - if (!s.whitelistManager && s.app != null) { + s.updateAllowlistManager(); + if (!s.allowlistManager && s.app != null) { updateAllowlistManagerLocked(s.app.mServices); } } @@ -5400,13 +5373,13 @@ public final class ActiveServices { } if (!r.mAllowWhileInUsePermissionInFgs - || (r.mAllowStartForeground == FGS_FEATURE_DENIED)) { - final @FgsFeatureRetCode int allowWhileInUse = shouldAllowFgsWhileInUsePermissionLocked( + || (r.mAllowStartForeground == REASON_DENIED)) { + final @ReasonCode int allowWhileInUse = shouldAllowFgsWhileInUsePermissionLocked( callingPackage, callingPid, callingUid, r, allowBackgroundActivityStarts); if (!r.mAllowWhileInUsePermissionInFgs) { - r.mAllowWhileInUsePermissionInFgs = (allowWhileInUse != FGS_FEATURE_DENIED); + r.mAllowWhileInUsePermissionInFgs = (allowWhileInUse != REASON_DENIED); } - if (r.mAllowStartForeground == FGS_FEATURE_DENIED) { + if (r.mAllowStartForeground == REASON_DENIED) { r.mAllowStartForeground = shouldAllowFgsStartForegroundLocked(allowWhileInUse, callingPackage, callingPid, callingUid, intent, r, allowBackgroundActivityStarts); @@ -5420,37 +5393,37 @@ public final class ActiveServices { * @param callingPackage caller app's package name. * @param callingUid caller app's uid. * @param r the service to start. - * @return {@link FgsFeatureRetCode} + * @return {@link ReasonCode} */ - private @FgsFeatureRetCode int shouldAllowFgsWhileInUsePermissionLocked(String callingPackage, + private @ReasonCode int shouldAllowFgsWhileInUsePermissionLocked(String callingPackage, int callingPid, int callingUid, ServiceRecord r, boolean allowBackgroundActivityStarts) { - int ret = FGS_FEATURE_DENIED; + int ret = REASON_DENIED; final int uidState = mAm.getUidStateLocked(callingUid); - if (ret == FGS_FEATURE_DENIED) { + if (ret == REASON_DENIED) { // Is the calling UID at PROCESS_STATE_TOP or above? if (uidState <= PROCESS_STATE_TOP) { - ret = FGS_FEATURE_ALLOWED_BY_UID_STATE; + ret = getReasonCodeFromProcState(uidState); } } - if (ret == FGS_FEATURE_DENIED) { + if (ret == REASON_DENIED) { // Does the calling UID have any visible activity? final boolean isCallingUidVisible = mAm.mAtmInternal.isUidForeground(callingUid); if (isCallingUidVisible) { - ret = FGS_FEATURE_ALLOWED_BY_UID_VISIBLE; + ret = REASON_UID_VISIBLE; } } - if (ret == FGS_FEATURE_DENIED) { + if (ret == REASON_DENIED) { // Is the allow activity background start flag on? if (allowBackgroundActivityStarts) { - ret = FGS_FEATURE_ALLOWED_BY_FLAG; + ret = REASON_START_ACTIVITY_FLAG; } } - if (ret == FGS_FEATURE_DENIED) { + if (ret == REASON_DENIED) { boolean isCallerSystem = false; final int callingAppId = UserHandle.getAppId(callingUid); switch (callingAppId) { @@ -5466,15 +5439,15 @@ public final class ActiveServices { } if (isCallerSystem) { - ret = FGS_FEATURE_ALLOWED_BY_SYSTEM_UID; + ret = REASON_SYSTEM_UID; } } - if (ret == FGS_FEATURE_DENIED) { + if (ret == REASON_DENIED) { final Integer allowedType = mAm.mProcessList.searchEachLruProcessesLOSP(false, pr -> { if (pr.uid == callingUid) { if (pr.getWindowProcessController().areBackgroundFgsStartsAllowed()) { - return FGS_FEATURE_ALLOWED_BY_ACTIVITY_STARTER; + return REASON_ACTIVITY_STARTER; } } return null; @@ -5484,35 +5457,35 @@ public final class ActiveServices { } } - if (ret == FGS_FEATURE_DENIED) { + if (ret == REASON_DENIED) { if (r.app != null) { ActiveInstrumentation instr = r.app.getActiveInstrumentation(); if (instr != null && instr.mHasBackgroundActivityStartsPermission) { - ret = FGS_FEATURE_ALLOWED_BY_INSTR_BACKGROUND_ACTIVITY_PERMISSION; + ret = REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION; } } } - if (ret == FGS_FEATURE_DENIED) { + if (ret == REASON_DENIED) { if (mAm.checkPermission(START_ACTIVITIES_FROM_BACKGROUND, callingPid, callingUid) == PERMISSION_GRANTED) { - ret = FGS_FEATURE_ALLOWED_BY_BACKGROUND_ACTIVITY_PERMISSION; + ret = REASON_BACKGROUND_ACTIVITY_PERMISSION; } } - if (ret == FGS_FEATURE_DENIED) { + if (ret == REASON_DENIED) { final boolean isAllowedPackage = mAllowListWhileInUsePermissionInFgs.contains(callingPackage); if (isAllowedPackage) { - ret = FGS_FEATURE_ALLOWED_BY_ALLOWLIST; + ret = REASON_ALLOWLISTED_PACKAGE; } } - if (ret == FGS_FEATURE_DENIED) { + if (ret == REASON_DENIED) { // Is the calling UID a device owner app? final boolean isDeviceOwner = mAm.mInternal.isDeviceOwner(callingUid); if (isDeviceOwner) { - ret = FGS_FEATURE_ALLOWED_BY_DEVICE_OWNER; + ret = REASON_DEVICE_OWNER; } } return ret; @@ -5527,38 +5500,40 @@ public final class ActiveServices { * @param callingUid caller app's uid. * @param intent intent to start/bind service. * @param r the service to start. - * @return {@link FgsFeatureRetCode} + * @return {@link ReasonCode} */ - private @FgsFeatureRetCode int shouldAllowFgsStartForegroundLocked( - @FgsFeatureRetCode int allowWhileInUse, String callingPackage, int callingPid, + private @ReasonCode int shouldAllowFgsStartForegroundLocked( + @ReasonCode int allowWhileInUse, String callingPackage, int callingPid, int callingUid, Intent intent, ServiceRecord r, boolean allowBackgroundActivityStarts) { int ret = allowWhileInUse; + FgsStartTempAllowList.TempFgsAllowListEntry tempAllowListReason = + r.mInfoTempFgsAllowListReason = mAm.isAllowlistedForFgsStartLOSP(callingUid); final StringBuilder sb = new StringBuilder(64); final int uidState = mAm.getUidStateLocked(callingUid); - if (ret == FGS_FEATURE_DENIED) { + if (ret == REASON_DENIED) { // Is the calling UID at PROCESS_STATE_TOP or above? if (uidState <= PROCESS_STATE_TOP) { sb.append("uidState=").append(uidState); - ret = FGS_FEATURE_ALLOWED_BY_UID_STATE; + ret = getReasonCodeFromProcState(uidState); } } - if (ret == FGS_FEATURE_DENIED) { + if (ret == REASON_DENIED) { final Integer allowedType = mAm.mProcessList.searchEachLruProcessesLOSP(false, app -> { if (app.uid == callingUid) { final ProcessStateRecord state = app.mState; - if (state.getAllowedStartFgs() != FGS_FEATURE_DENIED) { + if (state.getAllowedStartFgs() != REASON_DENIED) { return state.getAllowedStartFgs(); } else if (state.isAllowedStartFgsState()) { - return FGS_FEATURE_ALLOWED_BY_PROC_STATE; + return getReasonCodeFromProcState(state.getAllowStartFgsState()); } else if (state.areBackgroundFgsStartsAllowedByToken()) { - return FGS_FEATURE_ALLOWED_BY_FGS_BINDING; + return REASON_FGS_BINDING; } else { final ActiveInstrumentation instr = app.getActiveInstrumentation(); if (instr != null && instr.mHasBackgroundForegroundServiceStartsPermission) { - return FGS_FEATURE_ALLOWED_BY_INSTR_BACKGROUND_FGS_PERMISSION; + return REASON_INSTR_BACKGROUND_FGS_PERMISSION; } } } @@ -5569,55 +5544,59 @@ public final class ActiveServices { } } - if (ret == FGS_FEATURE_DENIED) { + if (ret == REASON_DENIED) { if (mAm.checkPermission(START_FOREGROUND_SERVICES_FROM_BACKGROUND, callingPid, callingUid) == PERMISSION_GRANTED) { - ret = FGS_FEATURE_ALLOWED_BY_BACKGROUND_FGS_PERMISSION; + ret = REASON_BACKGROUND_FGS_PERMISSION; } } - if (ret == FGS_FEATURE_DENIED) { + if (ret == REASON_DENIED) { if (mAm.checkPermission(SYSTEM_ALERT_WINDOW, callingPid, callingUid) == PERMISSION_GRANTED) { - ret = FGS_FEATURE_ALLOWED_BY_SYSTEM_ALERT_WINDOW_PERMISSION; + ret = REASON_SYSTEM_ALERT_WINDOW_PERMISSION; } } - if (ret == FGS_FEATURE_DENIED) { - if (mAm.isAllowlistedForFgsStartLOSP(callingUid)) { - // uid is on DeviceIdleController's user/system allowlist - // or AMS's FgsStartTempAllowList. - ret = FGS_FEATURE_ALLOWED_BY_DEVICE_IDLE_ALLOW_LIST; + if (ret == REASON_DENIED) { + FgsStartTempAllowList.TempFgsAllowListEntry entry = + mAm.isAllowlistedForFgsStartLOSP(callingUid); + if (entry != null) { + if (entry == ActivityManagerService.FAKE_TEMP_ALLOWLIST_ENTRY) { + ret = REASON_SYSTEM_ALLOW_LISTED; + } else { + ret = entry.mReasonCode; + } } } - if (ret == FGS_FEATURE_DENIED) { + if (ret == REASON_DENIED) { if (UserManager.isDeviceInDemoMode(mAm.mContext)) { - ret = FGS_FEATURE_ALLOWED_BY_DEVICE_DEMO_MODE; + ret = REASON_DEVICE_DEMO_MODE; } } - if (ret == FGS_FEATURE_DENIED) { + if (ret == REASON_DENIED) { // Is the calling UID a profile owner app? final boolean isProfileOwner = mAm.mInternal.isProfileOwner(callingUid); if (isProfileOwner) { - ret = FGS_FEATURE_ALLOWED_BY_PROFILE_OWNER; + ret = REASON_PROFILE_OWNER; } } // NOTE this should always be the last check. - if (ret == FGS_FEATURE_DENIED) { + if (ret == REASON_DENIED) { if (isPackageExemptedFromFgsRestriction(r.appInfo.packageName, r.appInfo.uid) || isPackageExemptedFromFgsRestriction(callingPackage, callingUid)) { - ret = FGS_FEATURE_ALLOWED_BY_EXEMPTED_PACKAGES; + ret = REASON_EXEMPTED_PACKAGE; } } - if (ret == FGS_FEATURE_DENIED) { + if (ret == REASON_DENIED) { final boolean isCompanionApp = mAm.mInternal.isAssociatedCompanionApp( UserHandle.getUserId(callingUid), callingUid); if (isCompanionApp) { - ret = FGS_FEATURE_ALLOWED_BY_COMPANION_APP; + ret = REASON_COMPANION_DEVICE_MANAGER; } } @@ -5626,7 +5605,15 @@ public final class ActiveServices { + "; callingUid: " + callingUid + "; uidState: " + ProcessList.makeProcStateString(uidState) + "; intent: " + intent - + "; code:" + fgsCodeToString(ret) + + "; code:" + reasonCodeToString(ret) + + "; tempAllowListReason:<" + + (tempAllowListReason == null ? null : + (tempAllowListReason.mReason + + ",reasonCode:" + + reasonCodeToString(tempAllowListReason.mReasonCode) + + ",duration:" + tempAllowListReason.mDuration + + ",callingUid:" + tempAllowListReason.mCallingUid)) + + ">" + "; extra:" + sb.toString() + "; targetSdkVersion:" + r.appInfo.targetSdkVersion + "]"; @@ -5660,62 +5647,11 @@ public final class ActiveServices { return CompatChanges.isChangeEnabled(FGS_BG_START_USE_EXEMPTION_LIST_CHANGE_ID, uid); } - static String fgsCodeToString(@FgsFeatureRetCode int code) { - switch (code) { - case FGS_FEATURE_DENIED: - return "DENIED"; - case FGS_FEATURE_ALLOWED_BY_UID_STATE: - return "ALLOWED_BY_UID_STATE"; - case FGS_FEATURE_ALLOWED_BY_PROC_STATE: - return "ALLOWED_BY_PROC_STATE"; - case FGS_FEATURE_ALLOWED_BY_UID_VISIBLE: - return "ALLOWED_BY_UID_VISIBLE"; - case FGS_FEATURE_ALLOWED_BY_FLAG: - return "ALLOWED_BY_FLAG"; - case FGS_FEATURE_ALLOWED_BY_SYSTEM_UID: - return "ALLOWED_BY_SYSTEM_UID"; - case FGS_FEATURE_ALLOWED_BY_INSTR_BACKGROUND_ACTIVITY_PERMISSION: - return "ALLOWED_BY_INSTR_BACKGROUND_ACTIVITY_PERMISSION"; - case FGS_FEATURE_ALLOWED_BY_INSTR_BACKGROUND_FGS_PERMISSION: - return "ALLOWED_BY_INSTR_BACKGROUND_FGS_PERMISSION"; - case FGS_FEATURE_ALLOWED_BY_ACTIVITY_TOKEN: - return "ALLOWED_BY_ACTIVITY_TOKEN"; - case FGS_FEATURE_ALLOWED_BY_FGS_TOKEN: - return "ALLOWED_BY_FGS_TOKEN"; - case FGS_FEATURE_ALLOWED_BY_BACKGROUND_ACTIVITY_PERMISSION: - return "ALLOWED_BY_BACKGROUND_ACTIVITY_PERMISSION"; - case FGS_FEATURE_ALLOWED_BY_BACKGROUND_FGS_PERMISSION: - return "ALLOWED_BY_BACKGROUND_FGS_PERMISSION"; - case FGS_FEATURE_ALLOWED_BY_ALLOWLIST: - return "ALLOWED_BY_ALLOWLIST"; - case FGS_FEATURE_ALLOWED_BY_DEVICE_OWNER: - return "ALLOWED_BY_DEVICE_OWNER"; - case FGS_FEATURE_ALLOWED_BY_DEVICE_IDLE_ALLOW_LIST: - return "ALLOWED_BY_DEVICE_IDLE_ALLOW_LIST"; - case FGS_FEATURE_ALLOWED_BY_SYSTEM_ALERT_WINDOW_PERMISSION: - return "ALLOWED_BY_SYSTEM_ALERT_WINDOW_PERMISSION"; - case FGS_FEATURE_ALLOWED_BY_FGS_BINDING: - return "ALLOWED_BY_FGS_BINDING"; - case FGS_FEATURE_ALLOWED_BY_DEVICE_DEMO_MODE: - return "ALLOWED_BY_DEVICE_DEMO_MODE"; - case FGS_FEATURE_ALLOWED_BY_PROCESS_RECORD: - return "ALLOWED_BY_PROCESS_RECORD"; - case FGS_FEATURE_ALLOWED_BY_EXEMPTED_PACKAGES: - return "FGS_FEATURE_ALLOWED_BY_EXEMPTED_PACKAGES"; - case FGS_FEATURE_ALLOWED_BY_ACTIVITY_STARTER: - return "ALLOWED_BY_ACTIVITY_STARTER"; - case FGS_FEATURE_ALLOWED_BY_COMPANION_APP: - return "ALLOWED_BY_COMPANION_APP"; - case FGS_FEATURE_ALLOWED_BY_PROFILE_OWNER: - return "ALLOWED_BY_PROFILE_OWNER"; - default: - return ""; - } - } - - private static boolean isFgsBgStart(@FgsFeatureRetCode int code) { - return code != FGS_FEATURE_ALLOWED_BY_UID_STATE - && code != FGS_FEATURE_ALLOWED_BY_UID_VISIBLE; + private static boolean isFgsBgStart(@ReasonCode int code) { + return code != REASON_PROC_STATE_PERSISTENT + && code != REASON_PROC_STATE_PERSISTENT_UI + && code != REASON_PROC_STATE_TOP + && code != REASON_UID_VISIBLE; } // TODO: remove this notification after feature development is done @@ -5754,10 +5690,10 @@ public final class ActiveServices { } if (!r.mLoggedInfoAllowStartForeground) { final String msg = "Background started FGS: " - + ((r.mAllowStartForeground != FGS_FEATURE_DENIED) ? "Allowed " : "Disallowed ") + + ((r.mAllowStartForeground != REASON_DENIED) ? "Allowed " : "Disallowed ") + r.mInfoAllowStartForeground; Slog.wtfQuiet(TAG, msg); - if (r.mAllowStartForeground != FGS_FEATURE_DENIED) { + if (r.mAllowStartForeground != REASON_DENIED) { Slog.i(TAG, msg); } else { Slog.w(TAG, msg); diff --git a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java index 171b20c03689..9d1c83894d46 100644 --- a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java +++ b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java @@ -68,7 +68,7 @@ class ActivityManagerDebugConfig { static final boolean DEBUG_UID_OBSERVERS = DEBUG_ALL || false; static final boolean DEBUG_USAGE_STATS = DEBUG_ALL || false; static final boolean DEBUG_PERMISSIONS_REVIEW = DEBUG_ALL || false; - static final boolean DEBUG_WHITELISTS = DEBUG_ALL || false; + static final boolean DEBUG_ALLOWLISTS = DEBUG_ALL || false; static final String POSTFIX_BACKUP = (APPEND_CATEGORY_NAME) ? "_Backup" : ""; static final String POSTFIX_BROADCAST = (APPEND_CATEGORY_NAME) ? "_Broadcast" : ""; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 083a963bc8fd..7cd494976c94 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -33,7 +33,6 @@ import static android.app.ActivityManager.PROCESS_STATE_TOP; import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY; import static android.app.ActivityManagerInternal.ALLOW_NON_FULL; import static android.app.AppOpsManager.OP_NONE; -import static android.app.BroadcastOptions.TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_ALLOWED; import static android.content.pm.ApplicationInfo.HIDDEN_API_ENFORCEMENT_DEFAULT; import static android.content.pm.PackageManager.GET_SHARED_LIBRARY_FILES; import static android.content.pm.PackageManager.MATCH_ALL; @@ -50,8 +49,12 @@ import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL; import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_HIGH; import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL; import static android.os.IServiceManager.DUMP_FLAG_PROTO; +import static android.os.PowerWhitelistManager.REASON_SYSTEM_ALLOW_LISTED; +import static android.os.PowerWhitelistManager.REASON_UNKNOWN; +import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED; import static android.os.Process.BLUETOOTH_UID; import static android.os.Process.FIRST_APPLICATION_UID; +import static android.os.Process.INVALID_UID; import static android.os.Process.NETWORK_STACK_UID; import static android.os.Process.NFC_UID; import static android.os.Process.PHONE_UID; @@ -101,7 +104,7 @@ import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_POWER; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESSES; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SERVICE; -import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_WHITELISTS; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALLOWLISTS; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BACKUP; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CLEANUP; @@ -143,6 +146,7 @@ import android.app.Activity; import android.app.ActivityClient; import android.app.ActivityManager; import android.app.ActivityManager.PendingIntentInfo; +import android.app.ActivityManager.ProcessCapability; import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityManagerInternal; import android.app.ActivityTaskManager.RootTaskInfo; @@ -252,6 +256,7 @@ import android.os.ParcelFileDescriptor; import android.os.PowerManager; import android.os.PowerManager.ServiceType; import android.os.PowerManagerInternal; +import android.os.PowerWhitelistManager.ReasonCode; import android.os.PowerWhitelistManager.TempAllowListType; import android.os.Process; import android.os.RemoteCallback; @@ -306,6 +311,7 @@ import com.android.internal.app.IAppOpsCallback; import com.android.internal.app.IAppOpsService; import com.android.internal.app.ProcessMap; import com.android.internal.app.SystemUserHomeActivity; +import com.android.internal.app.procstats.ProcessState; import com.android.internal.app.procstats.ProcessStats; import com.android.internal.content.PackageHelper; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; @@ -623,7 +629,7 @@ public class ActivityManagerService extends IActivityManager.Stub */ private volatile String mDeviceOwnerName; - private volatile int mDeviceOwnerUid = Process.INVALID_UID; + private volatile int mDeviceOwnerUid = INVALID_UID; /** * Map userId to its companion app uids. @@ -1149,13 +1155,13 @@ public class ActivityManagerService extends IActivityManager.Stub DeviceIdleInternal mLocalDeviceIdleController; /** - * Power-save whitelisted app-ids (not including except-idle-whitelisted ones). + * Power-save allowlisted app-ids (not including except-idle-allowlisted ones). */ @CompositeRWLock({"this", "mProcLock"}) int[] mDeviceIdleAllowlist = new int[0]; /** - * Power-save whitelisted app-ids (including except-idle-whitelisted ones). + * Power-save allowlisted app-ids (including except-idle-allowlisted ones). */ @CompositeRWLock({"this", "mProcLock"}) int[] mDeviceIdleExceptIdleAllowlist = new int[0]; @@ -1171,20 +1177,27 @@ public class ActivityManagerService extends IActivityManager.Stub final long duration; final String tag; final int type; + final @ReasonCode int reasonCode; - PendingTempAllowlist(int targetUid, long duration, String tag, int type) { + PendingTempAllowlist(int targetUid, long duration, @ReasonCode int reasonCode, String tag, + int type) { this.targetUid = targetUid; this.duration = duration; this.tag = tag; this.type = type; + this.reasonCode = reasonCode; } void dumpDebug(ProtoOutputStream proto, long fieldId) { final long token = proto.start(fieldId); - proto.write(ActivityManagerServiceDumpProcessesProto.PendingTempWhitelist.TARGET_UID, targetUid); - proto.write(ActivityManagerServiceDumpProcessesProto.PendingTempWhitelist.DURATION_MS, duration); + proto.write(ActivityManagerServiceDumpProcessesProto.PendingTempWhitelist.TARGET_UID, + targetUid); + proto.write(ActivityManagerServiceDumpProcessesProto.PendingTempWhitelist.DURATION_MS, + duration); proto.write(ActivityManagerServiceDumpProcessesProto.PendingTempWhitelist.TAG, tag); proto.write(ActivityManagerServiceDumpProcessesProto.PendingTempWhitelist.TYPE, type); + proto.write(ActivityManagerServiceDumpProcessesProto.PendingTempWhitelist.REASON_CODE, + reasonCode); proto.end(token); } } @@ -1198,6 +1211,9 @@ public class ActivityManagerService extends IActivityManager.Stub @CompositeRWLock({"this", "mProcLock"}) final FgsStartTempAllowList mFgsStartTempAllowList = new FgsStartTempAllowList(); + static final FgsStartTempAllowList.TempFgsAllowListEntry FAKE_TEMP_ALLOWLIST_ENTRY = new + FgsStartTempAllowList.TempFgsAllowListEntry(Long.MAX_VALUE, Long.MAX_VALUE, + REASON_SYSTEM_ALLOW_LISTED, "", INVALID_UID); /** * Information about and control over application operations */ @@ -1819,6 +1835,15 @@ public class ActivityManagerService extends IActivityManager.Stub ncl.start(); } + /** + * Sets a policy for handling app ops. + * + * @param appOpsPolicy The policy. + */ + public void setAppOpsPolicy(@Nullable CheckOpsDelegate appOpsPolicy) { + mAppOpsService.setAppOpsPolicy(appOpsPolicy); + } + public IAppOpsService getAppOpsService() { return mAppOpsService; } @@ -3778,10 +3803,11 @@ public class ActivityManagerService extends IActivityManager.Stub mi.getTotalUss(), mi.getTotalRss(), false, ProcessStats.ADD_PSS_EXTERNAL_SLOW, duration); proc.getPkgList().forEachPackageProcessStats(holder -> { + final ProcessState state = holder.state; FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_MEMORY_STAT_REPORTED, proc.info.uid, - holder.state.getName(), - holder.state.getPackage(), + state != null ? state.getName() : proc.processName, + state != null ? state.getPackage() : proc.info.packageName, mi.getTotalPss(), mi.getTotalUss(), mi.getTotalRss(), @@ -4817,12 +4843,12 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override - public int sendIntentSender(IIntentSender target, IBinder whitelistToken, int code, + public int sendIntentSender(IIntentSender target, IBinder allowlistToken, int code, Intent intent, String resolvedType, IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) { if (target instanceof PendingIntentRecord) { return ((PendingIntentRecord)target).sendWithResult(code, intent, resolvedType, - whitelistToken, finishedReceiver, requiredPermission, options); + allowlistToken, finishedReceiver, requiredPermission, options); } else { if (intent == null) { // Weird case: someone has given us their own custom IIntentSender, and now @@ -4834,7 +4860,7 @@ public class ActivityManagerService extends IActivityManager.Stub intent = new Intent(Intent.ACTION_MAIN); } try { - target.send(code, intent, resolvedType, whitelistToken, null, + target.send(code, intent, resolvedType, allowlistToken, null, requiredPermission, options); } catch (RemoteException e) { } @@ -5402,7 +5428,7 @@ public class ActivityManagerService extends IActivityManager.Stub } switch (appop) { case AppOpsManager.MODE_ALLOWED: - // If force-background-check is enabled, restrict all apps that aren't whitelisted. + // If force-background-check is enabled, restrict all apps that aren't allowlisted. if (mForceBackgroundCheck && !UserHandle.isCore(uid) && !isOnDeviceIdleAllowlistLOSP(uid, /*allowExceptIdleToo=*/ true)) { @@ -5438,7 +5464,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (uidOnBackgroundAllowlistLOSP(uid)) { if (DEBUG_BACKGROUND_CHECK) { Slog.i(TAG, "App " + uid + "/" + packageName - + " on background whitelist; not restricted in background"); + + " on background allowlist; not restricted in background"); } return ActivityManager.APP_START_MODE_NORMAL; } @@ -5447,7 +5473,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (isOnDeviceIdleAllowlistLOSP(uid, /*allowExceptIdleToo=*/ false)) { if (DEBUG_BACKGROUND_CHECK) { Slog.i(TAG, "App " + uid + "/" + packageName - + " on idle whitelist; not restricted in background"); + + " on idle allowlist; not restricted in background"); } return ActivityManager.APP_START_MODE_NORMAL; } @@ -5535,10 +5561,19 @@ public class ActivityManagerService extends IActivityManager.Stub || mPendingTempAllowlist.indexOfKey(uid) >= 0; } + /** + * Is the uid allowlisted to start FGS? + * @param uid + * @return a TempAllowListEntry if the uid is allowed. + * null if the uid is not allowed. + */ + @Nullable @GuardedBy(anyOf = {"this", "mProcLock"}) - boolean isAllowlistedForFgsStartLOSP(int uid) { - return Arrays.binarySearch(mDeviceIdleExceptIdleAllowlist, UserHandle.getAppId(uid)) >= 0 - || mFgsStartTempAllowList.isAllowed(uid); + FgsStartTempAllowList.TempFgsAllowListEntry isAllowlistedForFgsStartLOSP(int uid) { + if (Arrays.binarySearch(mDeviceIdleExceptIdleAllowlist, UserHandle.getAppId(uid)) >= 0) { + return FAKE_TEMP_ALLOWLIST_ENTRY; + } + return mFgsStartTempAllowList.getAllowedDurationAndReason(uid); } /** @@ -6010,13 +6045,13 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override - public void backgroundWhitelistUid(final int uid) { + public void backgroundAllowlistUid(final int uid) { if (Binder.getCallingUid() != Process.SYSTEM_UID) { - throw new SecurityException("Only the OS may call backgroundWhitelistUid()"); + throw new SecurityException("Only the OS may call backgroundAllowlistUid()"); } if (DEBUG_BACKGROUND_CHECK) { - Slog.i(TAG, "Adding uid " + uid + " to bg uid whitelist"); + Slog.i(TAG, "Adding uid " + uid + " to bg uid allowlist"); } synchronized (this) { synchronized (mProcLock) { @@ -6586,6 +6621,18 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override + public @ProcessCapability int getUidProcessCapabilities(int uid, String callingPackage) { + if (!hasUsageStatsPermission(callingPackage)) { + enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS, + "getUidProcessState"); + } + + synchronized (mProcLock) { + return mProcessList.getUidProcessCapabilityLOSP(uid); + } + } + + @Override public void registerUidObserver(IUidObserver observer, int which, int cutpoint, String callingPackage) { if (!hasUsageStatsPermission(callingPackage)) { @@ -7201,6 +7248,9 @@ public class ActivityManagerService extends IActivityManager.Stub final long memoryGrowthThreshold = Math.max(totalMemoryInKb / 100, MINIMUM_MEMORY_GROWTH_THRESHOLD); mProcessList.forEachLruProcessesLOSP(false, proc -> { + if (proc.getThread() == null) { + return; + } final ProcessProfileRecord pr = proc.mProfile; final ProcessStateRecord state = proc.mState; final int setProcState = state.getSetProcState(); @@ -8244,7 +8294,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (!TextUtils.isEmpty(packageName)) { final int uid = enforceDumpPermissionForPackage(packageName, userId, callingUid, "getHistoricalProcessExitReasons"); - if (uid != Process.INVALID_UID) { + if (uid != INVALID_UID) { mProcessList.mAppExitInfoTracker.getExitInfo( packageName, uid, pid, maxNum, results); tombstoneService.collectTombstones(results, uid, pid, maxNum); @@ -8278,7 +8328,7 @@ public class ActivityManagerService extends IActivityManager.Stub int enforceDumpPermissionForPackage(String packageName, int userId, int callingUid, String function) { final long identity = Binder.clearCallingIdentity(); - int uid = Process.INVALID_UID; + int uid = INVALID_UID; try { uid = mPackageManagerInt.getPackageUid(packageName, MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, userId); @@ -9170,6 +9220,8 @@ public class ActivityManagerService extends IActivityManager.Stub pw.println(ptw.tag); pw.print(" "); pw.print(ptw.type); + pw.print(" "); + pw.print(ptw.reasonCode); } } } @@ -10031,6 +10083,7 @@ public class ActivityManagerService extends IActivityManager.Stub ProcessList.PERSISTENT_SERVICE_ADJ, ProcessList.FOREGROUND_APP_ADJ, ProcessList.VISIBLE_APP_ADJ, ProcessList.PERCEPTIBLE_APP_ADJ, ProcessList.PERCEPTIBLE_LOW_APP_ADJ, + ProcessList.PERCEPTIBLE_MEDIUM_APP_ADJ, ProcessList.BACKUP_APP_ADJ, ProcessList.HEAVY_WEIGHT_APP_ADJ, ProcessList.SERVICE_ADJ, ProcessList.HOME_APP_ADJ, ProcessList.PREVIOUS_APP_ADJ, ProcessList.SERVICE_B_ADJ, ProcessList.CACHED_APP_MIN_ADJ @@ -10038,7 +10091,7 @@ public class ActivityManagerService extends IActivityManager.Stub static final String[] DUMP_MEM_OOM_LABEL = new String[] { "Native", "System", "Persistent", "Persistent Service", "Foreground", - "Visible", "Perceptible", "Perceptible Low", + "Visible", "Perceptible", "Perceptible Low", "Perceptible Medium", "Heavy Weight", "Backup", "A Services", "Home", "Previous", "B Services", "Cached" @@ -10046,7 +10099,7 @@ public class ActivityManagerService extends IActivityManager.Stub static final String[] DUMP_MEM_OOM_COMPACT_LABEL = new String[] { "native", "sys", "pers", "persvc", "fore", - "vis", "percept", "perceptl", + "vis", "percept", "perceptl", "perceptm", "heavy", "backup", "servicea", "home", "prev", "serviceb", "cached" @@ -10354,6 +10407,8 @@ public class ActivityManagerService extends IActivityManager.Stub } endTime = SystemClock.currentThreadTimeMillis(); hasSwapPss = mi.hasSwappedOutPss; + memtrackGraphics = mi.getOtherPrivate(Debug.MemoryInfo.OTHER_GRAPHICS); + memtrackGl = mi.getOtherPrivate(Debug.MemoryInfo.OTHER_GL); } else { reportType = ProcessStats.ADD_PSS_EXTERNAL; startTime = SystemClock.currentThreadTimeMillis(); @@ -10505,6 +10560,8 @@ public class ActivityManagerService extends IActivityManager.Stub if (!Debug.getMemoryInfo(st.pid, info)) { return; } + memtrackGraphics = info.getOtherPrivate(Debug.MemoryInfo.OTHER_GRAPHICS); + memtrackGl = info.getOtherPrivate(Debug.MemoryInfo.OTHER_GL); } else { long pss = Debug.getPss(st.pid, tmpLong, memtrackTmp); if (pss == 0) { @@ -12522,7 +12579,7 @@ public class ActivityManagerService extends IActivityManager.Stub BroadcastOptions brOptions = null; if (bOptions != null) { brOptions = new BroadcastOptions(bOptions); - if (brOptions.getTemporaryAppWhitelistDuration() > 0) { + if (brOptions.getTemporaryAppAllowlistDuration() > 0) { // See if the caller is allowed to do this. Note we are checking against // the actual real caller (not whoever provided the operation as say a // PendingIntent), because that who is actually supplied the arguments. @@ -13904,7 +13961,7 @@ public class ActivityManagerService extends IActivityManager.Stub } void noteUidProcessState(final int uid, final int state, - final @ActivityManager.ProcessCapability int capability) { + final @ProcessCapability int capability) { mBatteryStatsService.noteUidProcessState(uid, state); mAppOpsService.updateUidProcState(uid, state, capability); if (mTrackingAssociations) { @@ -13955,6 +14012,9 @@ public class ActivityManagerService extends IActivityManager.Stub final long uptimeSince = curUptime - mLastPowerCheckUptime; mLastPowerCheckUptime = curUptime; mProcessList.forEachLruProcessesLOSP(false, app -> { + if (app.getThread() == null) { + return; + } if (app.mState.getSetProcState() >= ActivityManager.PROCESS_STATE_HOME) { int cpuLimit; long checkDur = curUptime - app.mState.getWhenUnimportant(); @@ -14056,11 +14116,12 @@ public class ActivityManagerService extends IActivityManager.Stub mBatteryStatsService.reportExcessiveCpu(app.info.uid, app.processName, uptimeSince, cputimeUsed); app.getPkgList().forEachPackageProcessStats(holder -> { + final ProcessState state = holder.state; FrameworkStatsLog.write( FrameworkStatsLog.EXCESSIVE_CPU_USAGE_REPORTED, app.info.uid, processName, - holder.state.getPackage(), + state != null ? state.getPackage() : app.info.packageName, holder.appVersion); }); return true; @@ -14400,8 +14461,8 @@ public class ActivityManagerService extends IActivityManager.Stub */ @GuardedBy("this") void tempAllowlistForPendingIntentLocked(int callerPid, int callerUid, int targetUid, - long duration, int type, String tag) { - if (DEBUG_WHITELISTS) { + long duration, int type, @ReasonCode int reasonCode, String reason) { + if (DEBUG_ALLOWLISTS) { Slog.d(TAG, "tempAllowlistForPendingIntentLocked(" + callerPid + ", " + callerUid + ", " + targetUid + ", " + duration + ", " + type + ")"); } @@ -14420,7 +14481,7 @@ public class ActivityManagerService extends IActivityManager.Stub != PackageManager.PERMISSION_GRANTED && checkPermission(START_FOREGROUND_SERVICES_FROM_BACKGROUND, callerPid, callerUid) != PackageManager.PERMISSION_GRANTED) { - if (DEBUG_WHITELISTS) { + if (DEBUG_ALLOWLISTS) { Slog.d(TAG, "tempAllowlistForPendingIntentLocked() for target " + targetUid + ": pid " + callerPid + " is not allowed"); } @@ -14429,22 +14490,23 @@ public class ActivityManagerService extends IActivityManager.Stub } } - tempAllowlistUidLocked(targetUid, duration, tag, type); + tempAllowlistUidLocked(targetUid, duration, reasonCode, reason, type, callerUid); } /** * Allowlists {@code targetUid} to temporarily bypass Power Save mode. */ @GuardedBy("this") - void tempAllowlistUidLocked(int targetUid, long duration, String tag, int type) { + void tempAllowlistUidLocked(int targetUid, long duration, @ReasonCode int reasonCode, + String reason, int type, int callingUid) { synchronized (mProcLock) { mPendingTempAllowlist.put(targetUid, - new PendingTempAllowlist(targetUid, duration, tag, type)); + new PendingTempAllowlist(targetUid, duration, reasonCode, reason, type)); setUidTempAllowlistStateLSP(targetUid, true); mUiHandler.obtainMessage(PUSH_TEMP_ALLOWLIST_UI_MSG).sendToTarget(); - if (type == TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_ALLOWED) { - mFgsStartTempAllowList.add(targetUid, duration); + if (type == TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED) { + mFgsStartTempAllowList.add(targetUid, duration, reasonCode, reason, callingUid); } } } @@ -14470,7 +14532,7 @@ public class ActivityManagerService extends IActivityManager.Stub for (int i = 0; i < N; i++) { PendingTempAllowlist ptw = list[i]; mLocalDeviceIdleController.addPowerSaveTempWhitelistAppDirect(ptw.targetUid, - ptw.duration, ptw.type, true, ptw.tag); + ptw.duration, ptw.type, true, ptw.reasonCode, ptw.tag); } } @@ -15058,10 +15120,10 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override - public void setPendingIntentWhitelistDuration(IIntentSender target, IBinder whitelistToken, - long duration, int type) { - mPendingIntentController.setPendingIntentWhitelistDuration(target, whitelistToken, - duration, type); + public void setPendingIntentAllowlistDuration(IIntentSender target, IBinder allowlistToken, + long duration, int type, @ReasonCode int reasonCode, @Nullable String reason) { + mPendingIntentController.setPendingIntentAllowlistDuration(target, allowlistToken, + duration, type, reasonCode, reason); } @Override @@ -15071,32 +15133,32 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public void setPendingIntentAllowBgActivityStarts(IIntentSender target, - IBinder whitelistToken, int flags) { + IBinder allowlistToken, int flags) { if (!(target instanceof PendingIntentRecord)) { Slog.w(TAG, "setPendingIntentAllowBgActivityStarts():" + " not a PendingIntentRecord: " + target); return; } synchronized (ActivityManagerService.this) { - ((PendingIntentRecord) target).setAllowBgActivityStarts(whitelistToken, flags); + ((PendingIntentRecord) target).setAllowBgActivityStarts(allowlistToken, flags); } } @Override public void clearPendingIntentAllowBgActivityStarts(IIntentSender target, - IBinder whitelistToken) { + IBinder allowlistToken) { if (!(target instanceof PendingIntentRecord)) { Slog.w(TAG, "clearPendingIntentAllowBgActivityStarts():" + " not a PendingIntentRecord: " + target); return; } synchronized (ActivityManagerService.this) { - ((PendingIntentRecord) target).clearAllowBgActivityStarts(whitelistToken); + ((PendingIntentRecord) target).clearAllowBgActivityStarts(allowlistToken); } } @Override - public void setDeviceIdleWhitelist(int[] allAppids, int[] exceptIdleAppids) { + public void setDeviceIdleAllowlist(int[] allAppids, int[] exceptIdleAppids) { synchronized (ActivityManagerService.this) { synchronized (mProcLock) { mDeviceIdleAllowlist = allAppids; @@ -15106,17 +15168,19 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override - public void updateDeviceIdleTempWhitelist(int[] appids, int changingUid, boolean adding, - long durationMs, @TempAllowListType int type) { + public void updateDeviceIdleTempAllowlist(int[] appids, int changingUid, boolean adding, + long durationMs, @TempAllowListType int type, @ReasonCode int reasonCode, + @Nullable String reason, int callingUid) { synchronized (ActivityManagerService.this) { synchronized (mProcLock) { mDeviceIdleTempAllowlist = appids; if (adding) { - if (type == TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_ALLOWED) { - mFgsStartTempAllowList.add(changingUid, durationMs); + if (type == TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED) { + mFgsStartTempAllowList.add(changingUid, durationMs, reasonCode, reason, + callingUid); } + setAppIdTempAllowlistStateLSP(changingUid, adding); } - setAppIdTempAllowlistStateLSP(changingUid, adding); } } } @@ -15477,11 +15541,11 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override - public void tempWhitelistForPendingIntent(int callerPid, int callerUid, int targetUid, - long duration, int type, String tag) { + public void tempAllowlistForPendingIntent(int callerPid, int callerUid, int targetUid, + long duration, int type, @ReasonCode int reasonCode, String reason) { synchronized (ActivityManagerService.this) { ActivityManagerService.this.tempAllowlistForPendingIntentLocked( - callerPid, callerUid, targetUid, duration, type, tag); + callerPid, callerUid, targetUid, duration, type, reasonCode, reason); } } diff --git a/services/core/java/com/android/server/am/AppErrorDialog.java b/services/core/java/com/android/server/am/AppErrorDialog.java index 48222cb075cd..a9e557103966 100644 --- a/services/core/java/com/android/server/am/AppErrorDialog.java +++ b/services/core/java/com/android/server/am/AppErrorDialog.java @@ -18,10 +18,7 @@ package com.android.server.am; import static android.app.ActivityTaskManager.INVALID_TASK_ID; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.content.res.Resources; import android.os.Build; import android.os.Bundle; @@ -138,19 +135,6 @@ final class AppErrorDialog extends BaseErrorDialog implements View.OnClickListen findViewById(com.android.internal.R.id.customPanel).setVisibility(View.VISIBLE); } - @Override - public void onStart() { - super.onStart(); - getContext().registerReceiver(mReceiver, - new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); - } - - @Override - protected void onStop() { - super.onStop(); - getContext().unregisterReceiver(mReceiver); - } - private final Handler mHandler = new Handler() { public void handleMessage(Message msg) { setResult(msg.what); @@ -204,15 +188,6 @@ final class AppErrorDialog extends BaseErrorDialog implements View.OnClickListen } } - private final BroadcastReceiver mReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) { - cancel(); - } - } - }; - static class Data { AppErrorResult result; int taskId; diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java index e5a5cff409b3..3602f44cd785 100644 --- a/services/core/java/com/android/server/am/AppErrors.java +++ b/services/core/java/com/android/server/am/AppErrors.java @@ -29,6 +29,7 @@ import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_N import android.app.ActivityManager; import android.app.ActivityOptions; +import android.app.AnrController; import android.app.ApplicationErrorReport; import android.app.ApplicationExitInfo; import android.content.ActivityNotFoundException; @@ -1058,7 +1059,26 @@ class AppErrors { Settings.Secure.ANR_SHOW_BACKGROUND, 0, mService.mUserController.getCurrentUserId()) != 0; if (mService.mAtmInternal.canShowErrorDialogs() || showBackground) { - errState.getDialogController().showAnrDialogs(data); + AnrController anrController = errState.getDialogController().getAnrController(); + if (anrController == null) { + errState.getDialogController().showAnrDialogs(data); + } else { + String packageName = proc.info.packageName; + int uid = proc.info.uid; + boolean showDialog = anrController.onAnrDelayCompleted(packageName, uid); + + if (showDialog) { + Slog.d(TAG, "ANR delay completed. Showing ANR dialog for package: " + + packageName); + errState.getDialogController().showAnrDialogs(data); + } else { + Slog.d(TAG, "ANR delay completed. Cancelling ANR dialog for package: " + + packageName); + errState.setNotResponding(false); + errState.setNotRespondingReport(null); + errState.getDialogController().clearAnrDialogs(); + } + } } else { MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_APP_ANR, AppNotRespondingDialog.CANT_SHOW); diff --git a/services/core/java/com/android/server/am/AppNotRespondingDialog.java b/services/core/java/com/android/server/am/AppNotRespondingDialog.java index 77d2898842c6..b233a2ccc6e3 100644 --- a/services/core/java/com/android/server/am/AppNotRespondingDialog.java +++ b/services/core/java/com/android/server/am/AppNotRespondingDialog.java @@ -181,6 +181,11 @@ final class AppNotRespondingDialog extends BaseErrorDialog implements View.OnCli } }; + @Override + protected void closeDialog() { + mHandler.obtainMessage(FORCE_CLOSE).sendToTarget(); + } + static class Data { final ProcessRecord proc; final ApplicationInfo aInfo; diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java index c8630fa52973..f8494d8a7c04 100644 --- a/services/core/java/com/android/server/am/AppProfiler.java +++ b/services/core/java/com/android/server/am/AppProfiler.java @@ -1327,6 +1327,8 @@ public class AppProfiler { // Get a list of Stats that have vsize > 0 final List<ProcessCpuTracker.Stats> stats = getCpuStats(st -> st.vsize > 0); final int statsCount = stats.size(); + long totalMemtrackGraphics = 0; + long totalMemtrackGl = 0; for (int i = 0; i < statsCount; i++) { ProcessCpuTracker.Stats st = stats.get(i); long pss = Debug.getPss(st.pid, swaptrackTmp, memtrackTmp); @@ -1337,6 +1339,8 @@ public class AppProfiler { mi.pss = pss; mi.swapPss = swaptrackTmp[1]; mi.memtrack = memtrackTmp[0]; + totalMemtrackGraphics += memtrackTmp[1]; + totalMemtrackGl += memtrackTmp[2]; memInfos.add(mi); } } @@ -1345,20 +1349,18 @@ public class AppProfiler { long totalPss = 0; long totalSwapPss = 0; long totalMemtrack = 0; - long totalMemtrackGraphics = 0; - long totalMemtrackGl = 0; for (int i = 0, size = memInfos.size(); i < size; i++) { ProcessMemInfo mi = memInfos.get(i); if (mi.pss == 0) { mi.pss = Debug.getPss(mi.pid, swaptrackTmp, memtrackTmp); mi.swapPss = swaptrackTmp[1]; mi.memtrack = memtrackTmp[0]; + totalMemtrackGraphics += memtrackTmp[1]; + totalMemtrackGl += memtrackTmp[2]; } totalPss += mi.pss; totalSwapPss += mi.swapPss; totalMemtrack += mi.memtrack; - totalMemtrackGraphics += memtrackTmp[1]; - totalMemtrackGl += memtrackTmp[2]; } Collections.sort(memInfos, new Comparator<ProcessMemInfo>() { @Override public int compare(ProcessMemInfo lhs, ProcessMemInfo rhs) { diff --git a/services/core/java/com/android/server/am/AppWaitingForDebuggerDialog.java b/services/core/java/com/android/server/am/AppWaitingForDebuggerDialog.java index 3ce24712b42f..262e79521e8c 100644 --- a/services/core/java/com/android/server/am/AppWaitingForDebuggerDialog.java +++ b/services/core/java/com/android/server/am/AppWaitingForDebuggerDialog.java @@ -61,6 +61,11 @@ final class AppWaitingForDebuggerDialog extends BaseErrorDialog { public void onStop() { } + @Override + protected void closeDialog() { + /* Do nothing */ + } + private final Handler mHandler = new Handler() { public void handleMessage(Message msg) { switch (msg.what) { diff --git a/services/core/java/com/android/server/am/BaseErrorDialog.java b/services/core/java/com/android/server/am/BaseErrorDialog.java index aabb5877764e..7b5f2cd78947 100644 --- a/services/core/java/com/android/server/am/BaseErrorDialog.java +++ b/services/core/java/com/android/server/am/BaseErrorDialog.java @@ -16,16 +16,19 @@ package com.android.server.am; -import com.android.internal.R; - import android.app.AlertDialog; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.os.Handler; import android.os.Message; import android.view.KeyEvent; import android.view.WindowManager; import android.widget.Button; +import com.android.internal.R; + public class BaseErrorDialog extends AlertDialog { private static final int ENABLE_BUTTONS = 0; private static final int DISABLE_BUTTONS = 1; @@ -44,10 +47,19 @@ public class BaseErrorDialog extends AlertDialog { getWindow().setAttributes(attrs); } + @Override public void onStart() { super.onStart(); mHandler.sendEmptyMessage(DISABLE_BUTTONS); mHandler.sendMessageDelayed(mHandler.obtainMessage(ENABLE_BUTTONS), 1000); + getContext().registerReceiver(mReceiver, + new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); + } + + @Override + protected void onStop() { + super.onStop(); + getContext().unregisterReceiver(mReceiver); } public boolean dispatchKeyEvent(KeyEvent event) { @@ -84,4 +96,24 @@ public class BaseErrorDialog extends AlertDialog { } } }; + + /** + * Called when received ACTION_CLOSE_SYSTEM_DIALOGS. + */ + protected void closeDialog() { + if (mCancelable) { + cancel(); + } else { + dismiss(); + } + } + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) { + closeDialog(); + } + } + }; } diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index 34ff77494f94..29061930cd84 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -22,6 +22,7 @@ import static android.text.TextUtils.formatSimple; import static com.android.server.am.ActivityManagerDebugConfig.*; +import android.annotation.Nullable; import android.app.ActivityManager; import android.app.AppGlobals; import android.app.AppOpsManager; @@ -43,6 +44,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.PowerWhitelistManager; import android.os.PowerWhitelistManager.TempAllowListType; import android.os.Process; import android.os.RemoteException; @@ -903,8 +905,9 @@ public final class BroadcastQueue { return false; } - final void scheduleTempWhitelistLocked(int uid, long duration, BroadcastRecord r, - @TempAllowListType int type) { + final void scheduleTempAllowlistLocked(int uid, long duration, BroadcastRecord r, + @TempAllowListType int type, @PowerWhitelistManager.ReasonCode int reasonCode, + @Nullable String reason) { if (duration > Integer.MAX_VALUE) { duration = Integer.MAX_VALUE; } @@ -926,10 +929,11 @@ public final class BroadcastQueue { b.append(r.intent.getData()); } if (DEBUG_BROADCAST) { - Slog.v(TAG, "Broadcast temp whitelist uid=" + uid + " duration=" + duration + Slog.v(TAG, "Broadcast temp allowlist uid=" + uid + " duration=" + duration + " type=" + type + " : " + b.toString()); } - mService.tempAllowlistUidLocked(uid, duration, b.toString(), type); + mService.tempAllowlistUidLocked(uid, duration, reasonCode, b.toString(), type, + r.callingUid); } /** @@ -1332,10 +1336,12 @@ public final class BroadcastQueue { // r is guaranteed ordered at this point, so we know finishReceiverLocked() // will get a callback and handle the activity start token lifecycle. } - if (brOptions != null && brOptions.getTemporaryAppWhitelistDuration() > 0) { - scheduleTempWhitelistLocked(filter.owningUid, - brOptions.getTemporaryAppWhitelistDuration(), r, - brOptions.getTemporaryAppWhitelistType()); + if (brOptions != null && brOptions.getTemporaryAppAllowlistDuration() > 0) { + scheduleTempAllowlistLocked(filter.owningUid, + brOptions.getTemporaryAppAllowlistDuration(), r, + brOptions.getTemporaryAppAllowlistType(), + brOptions.getTemporaryAppAllowlistReasonCode(), + brOptions.getTemporaryAppAllowlistReason()); } } return; @@ -1619,11 +1625,13 @@ public final class BroadcastQueue { } final boolean isActivityCapable = - (brOptions != null && brOptions.getTemporaryAppWhitelistDuration() > 0); + (brOptions != null && brOptions.getTemporaryAppAllowlistDuration() > 0); if (isActivityCapable) { - scheduleTempWhitelistLocked(receiverUid, - brOptions.getTemporaryAppWhitelistDuration(), r, - brOptions.getTemporaryAppWhitelistType()); + scheduleTempAllowlistLocked(receiverUid, + brOptions.getTemporaryAppAllowlistDuration(), r, + brOptions.getTemporaryAppAllowlistType(), + brOptions.getTemporaryAppAllowlistReasonCode(), + brOptions.getTemporaryAppAllowlistReason()); } // Broadcast is being executed, its package can't be stopped. diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index a34163cd5fa2..c5f082a0f9e3 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -536,18 +536,6 @@ public final class CachedAppOptimizer { } /** - * Enable or disable the freezer. When enable == false all frozen processes are unfrozen, - * but aren't removed from the freezer. While in this state, processes can be added or removed - * by using Process.setProcessFrozen(), but they wouldn't be actually frozen until the freezer - * is enabled. If enable == true all processes in the freezer are frozen. - * - * @param enable Specify whether to enable (true) or disable (false) the freezer. - * - * @hide - */ - private static native void enableFreezerInternal(boolean enable); - - /** * Informs binder that a process is about to be frozen. If freezer is enabled on a process via * this method, this method will synchronously dispatch all pending transactions to the * specified pid. This method will not add significant latencies when unfreezing. @@ -590,10 +578,6 @@ public final class CachedAppOptimizer { if (state == '1' || state == '0') { supported = true; - // This is a workaround after reverting the cgroup v2 uid/pid hierarchy due to - // http://b/179006802. - // TODO: remove once the uid/pid hierarchy is restored - enableFreezerInternal(true); } else { Slog.e(TAG_AM, "unexpected value in cgroup.freeze"); } diff --git a/services/core/java/com/android/server/am/ErrorDialogController.java b/services/core/java/com/android/server/am/ErrorDialogController.java index ef135d55e43c..f23d309e424a 100644 --- a/services/core/java/com/android/server/am/ErrorDialogController.java +++ b/services/core/java/com/android/server/am/ErrorDialogController.java @@ -16,6 +16,8 @@ package com.android.server.am; +import android.annotation.Nullable; +import android.app.AnrController; import android.app.Dialog; import android.content.Context; @@ -57,6 +59,13 @@ final class ErrorDialogController { @GuardedBy("mProcLock") private AppWaitingForDebuggerDialog mWaitDialog; + /** + * ANR dialog controller + */ + @GuardedBy("mProcLock") + @Nullable + private AnrController mAnrController; + @GuardedBy("mProcLock") boolean hasCrashDialogs() { return mCrashDialogs != null; @@ -118,6 +127,7 @@ final class ErrorDialogController { } forAllDialogs(mAnrDialogs, Dialog::dismiss); mAnrDialogs = null; + mAnrController = null; } @GuardedBy("mProcLock") @@ -220,6 +230,17 @@ final class ErrorDialogController { }); } + @GuardedBy("mProcLock") + @Nullable + AnrController getAnrController() { + return mAnrController; + } + + @GuardedBy("mProcLock") + void setAnrController(AnrController controller) { + mAnrController = controller; + } + /** * Helper function to collect contexts from crashed app located displays. * diff --git a/services/core/java/com/android/server/am/FgsStartTempAllowList.java b/services/core/java/com/android/server/am/FgsStartTempAllowList.java index 4d8749c05294..1f897b5928ce 100644 --- a/services/core/java/com/android/server/am/FgsStartTempAllowList.java +++ b/services/core/java/com/android/server/am/FgsStartTempAllowList.java @@ -18,24 +18,53 @@ package com.android.server.am; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; +import android.annotation.Nullable; +import android.os.PowerWhitelistManager.ReasonCode; import android.os.SystemClock; import android.util.Slog; -import android.util.SparseLongArray; +import android.util.SparseArray; /** * List of uids that are temporarily allowed to start FGS from background. */ final class FgsStartTempAllowList { private static final int MAX_SIZE = 100; + + public static final class TempFgsAllowListEntry { + final long mExpirationTime; + final long mDuration; + final @ReasonCode int mReasonCode; + final String mReason; + final int mCallingUid; + + TempFgsAllowListEntry(long expirationTime, long duration, @ReasonCode int reasonCode, + String reason, int callingUid) { + mExpirationTime = expirationTime; + mDuration = duration; + mReasonCode = reasonCode; + mReason = reason; + mCallingUid = callingUid; + } + } + /** - * The key is the uid, the value is expiration elapse time in ms of this temp-allowed uid. + * The key is the uid, the value is a TempAllowListEntry. */ - private final SparseLongArray mTempAllowListFgs = new SparseLongArray(); + private final SparseArray<TempFgsAllowListEntry> mTempAllowListFgs = new SparseArray<>(); FgsStartTempAllowList() { } - void add(int uid, long duration) { + /** + * Add a uid and its duration with reason into the FGS temp-allowlist. + * @param uid + * @param duration temp-allowlisted duration in milliseconds. + * @param reason A human-readable reason for logging purposes. + * @param callingUid the callingUid that setup this temp allowlist, only valid when param adding + * is true. + */ + void add(int uid, long duration, @ReasonCode int reasonCode, @Nullable String reason, + int callingUid) { if (duration <= 0) { Slog.e(TAG_AM, "FgsStartTempAllowList bad duration:" + duration + " uid: " + uid); @@ -48,26 +77,36 @@ final class FgsStartTempAllowList { } final long now = SystemClock.elapsedRealtime(); for (int index = mTempAllowListFgs.size() - 1; index >= 0; index--) { - if (mTempAllowListFgs.valueAt(index) < now) { + if (mTempAllowListFgs.valueAt(index).mExpirationTime < now) { mTempAllowListFgs.removeAt(index); } } - final long existingExpirationTime = mTempAllowListFgs.get(uid, -1); + final TempFgsAllowListEntry existing = mTempAllowListFgs.get(uid); final long expirationTime = now + duration; - if (existingExpirationTime == -1 || existingExpirationTime < expirationTime) { - mTempAllowListFgs.put(uid, expirationTime); + if (existing == null || existing.mExpirationTime < expirationTime) { + mTempAllowListFgs.put(uid, + new TempFgsAllowListEntry(expirationTime, duration, reasonCode, + reason == null ? "" : reason, callingUid)); } } - boolean isAllowed(int uid) { + /** + * Is this uid temp-allowlisted to start FGS. + * @param uid + * @return If uid is in the temp-allowlist, return the {@link TempFgsAllowListEntry}; If not in + * temp-allowlist, return null. + */ + @Nullable + TempFgsAllowListEntry getAllowedDurationAndReason(int uid) { final int index = mTempAllowListFgs.indexOfKey(uid); if (index < 0) { - return false; - } else if (mTempAllowListFgs.valueAt(index) < SystemClock.elapsedRealtime()) { + return null; + } else if (mTempAllowListFgs.valueAt(index).mExpirationTime + < SystemClock.elapsedRealtime()) { mTempAllowListFgs.removeAt(index); - return false; + return null; } else { - return true; + return mTempAllowListFgs.valueAt(index); } } diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index 87cba54678c4..24953fc9c5d6 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -21,6 +21,7 @@ import static android.app.ActivityManager.PROCESS_CAPABILITY_ALL_IMPLICIT; import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_CAMERA; import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION; import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_MICROPHONE; +import static android.app.ActivityManager.PROCESS_CAPABILITY_NETWORK; import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE; import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP; @@ -41,6 +42,7 @@ import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND; import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA; import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION; import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE; +import static android.os.PowerWhitelistManager.REASON_DENIED; import static android.os.Process.SCHED_OTHER; import static android.os.Process.THREAD_GROUP_BACKGROUND; import static android.os.Process.THREAD_GROUP_DEFAULT; @@ -51,7 +53,6 @@ import static android.os.Process.setProcessGroup; import static android.os.Process.setThreadPriority; import static android.os.Process.setThreadScheduler; -import static com.android.server.am.ActiveServices.FGS_FEATURE_DENIED; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKUP; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LRU; @@ -1967,7 +1968,7 @@ public final class OomAdjuster { int clientProcState = cstate.getCurRawProcState(); // pass client's mAllowStartFgs to the app if client is not persistent process. - if (cstate.getAllowedStartFgs() != FGS_FEATURE_DENIED + if (cstate.getAllowedStartFgs() != REASON_DENIED && cstate.getMaxAdj() >= ProcessList.FOREGROUND_APP_ADJ) { state.setAllowStartFgs(cstate.getAllowedStartFgs()); } @@ -1981,6 +1982,21 @@ public final class OomAdjuster { capability |= cstate.getCurCapability(); } + // If an app has network capability by default + // (by having procstate <= BFGS), then the apps it binds to will get + // elevated to a high enough procstate anyway to get network unless they + // request otherwise, so don't propagate the network capability by default + // in this case unless they explicitly request it. + if ((cstate.getCurCapability() & PROCESS_CAPABILITY_NETWORK) != 0) { + if (clientProcState <= PROCESS_STATE_BOUND_FOREGROUND_SERVICE) { + if ((cr.flags & Context.BIND_ALLOW_NETWORK_ACCESS) != 0) { + capability |= PROCESS_CAPABILITY_NETWORK; + } + } else { + capability |= PROCESS_CAPABILITY_NETWORK; + } + } + if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) { // If the other app is cached for any reason, for purposes here // we are going to consider it empty. The specific cached state @@ -2048,6 +2064,10 @@ public final class OomAdjuster { && clientAdj <= ProcessList.PERCEPTIBLE_APP_ADJ && adj >= ProcessList.PERCEPTIBLE_LOW_APP_ADJ) { newAdj = ProcessList.PERCEPTIBLE_LOW_APP_ADJ; + } else if ((cr.flags & Context.BIND_ALMOST_PERCEPTIBLE) != 0 + && clientAdj < ProcessList.PERCEPTIBLE_APP_ADJ + && adj >= ProcessList.PERCEPTIBLE_MEDIUM_APP_ADJ) { + newAdj = ProcessList.PERCEPTIBLE_MEDIUM_APP_ADJ; } else if ((cr.flags&Context.BIND_NOT_VISIBLE) != 0 && clientAdj < ProcessList.PERCEPTIBLE_APP_ADJ && adj >= ProcessList.PERCEPTIBLE_APP_ADJ) { @@ -2117,13 +2137,13 @@ public final class OomAdjuster { if (enabled) { if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) { // TOP process passes all capabilities to the service. - capability |= PROCESS_CAPABILITY_ALL; + capability |= cstate.getCurCapability(); } else { // TOP process passes no capability to the service. } } else { // TOP process passes all capabilities to the service. - capability |= PROCESS_CAPABILITY_ALL; + capability |= cstate.getCurCapability(); } } } else if ((cr.flags & Context.BIND_IMPORTANT_BACKGROUND) == 0) { @@ -2448,20 +2468,20 @@ public final class OomAdjuster { case PROCESS_STATE_TOP: return PROCESS_CAPABILITY_ALL; case PROCESS_STATE_BOUND_TOP: - return PROCESS_CAPABILITY_NONE; + return PROCESS_CAPABILITY_NETWORK; case PROCESS_STATE_FOREGROUND_SERVICE: if (psr.hasForegroundServices()) { // Capability from FGS are conditional depending on foreground service type in // manifest file and the mAllowWhileInUsePermissionInFgs flag. - return PROCESS_CAPABILITY_NONE; + return PROCESS_CAPABILITY_NETWORK; } else { // process has no FGS, the PROCESS_STATE_FOREGROUND_SERVICE is from client. // the implicit capability could be removed in the future, client should use // BIND_INCLUDE_CAPABILITY flag. - return PROCESS_CAPABILITY_ALL_IMPLICIT; + return PROCESS_CAPABILITY_ALL_IMPLICIT | PROCESS_CAPABILITY_NETWORK; } case PROCESS_STATE_BOUND_FOREGROUND_SERVICE: - return PROCESS_CAPABILITY_NONE; + return PROCESS_CAPABILITY_NETWORK; default: return PROCESS_CAPABILITY_NONE; } diff --git a/services/core/java/com/android/server/am/PendingIntentController.java b/services/core/java/com/android/server/am/PendingIntentController.java index 42172bf7e1df..534bd84a91a3 100644 --- a/services/core/java/com/android/server/am/PendingIntentController.java +++ b/services/core/java/com/android/server/am/PendingIntentController.java @@ -36,6 +36,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.PowerWhitelistManager; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.UserHandle; @@ -300,15 +301,16 @@ public class PendingIntentController { } } - void setPendingIntentWhitelistDuration(IIntentSender target, IBinder whitelistToken, - long duration, int type) { + void setPendingIntentAllowlistDuration(IIntentSender target, IBinder allowlistToken, + long duration, int type, @PowerWhitelistManager.ReasonCode int reasonCode, + @Nullable String reason) { if (!(target instanceof PendingIntentRecord)) { Slog.w(TAG, "markAsSentFromNotification(): not a PendingIntentRecord: " + target); return; } synchronized (mLock) { - ((PendingIntentRecord) target).setWhitelistDurationLocked(whitelistToken, duration, - type); + ((PendingIntentRecord) target).setAllowlistDurationLocked(allowlistToken, duration, + type, reasonCode, reason); } } diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java index 0eb48f6da60d..51666acb8134 100644 --- a/services/core/java/com/android/server/am/PendingIntentRecord.java +++ b/services/core/java/com/android/server/am/PendingIntentRecord.java @@ -31,13 +31,14 @@ import android.content.Intent; import android.os.Binder; import android.os.Bundle; import android.os.IBinder; +import android.os.PowerWhitelistManager; +import android.os.PowerWhitelistManager.ReasonCode; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.TransactionTooLargeException; import android.os.UserHandle; import android.util.ArrayMap; import android.util.ArraySet; -import android.util.Pair; import android.util.Slog; import android.util.TimeUtils; @@ -67,7 +68,7 @@ public final class PendingIntentRecord extends IIntentSender.Stub { * milliseconds, Integer is allowlist type defined at * {@link android.os.PowerWhitelistManager.TempAllowListType} */ - private ArrayMap<IBinder, Pair<Long, Integer>> mWhitelistDuration; + private ArrayMap<IBinder, TempAllowListDuration> mAllowlistDuration; private RemoteCallbackList<IResultReceiver> mCancelCallbacks; private ArraySet<IBinder> mAllowBgActivityStartsForActivitySender = new ArraySet<>(); private ArraySet<IBinder> mAllowBgActivityStartsForBroadcastSender = new ArraySet<>(); @@ -214,6 +215,21 @@ public final class PendingIntentRecord extends IIntentSender.Stub { } } + static final class TempAllowListDuration { + long duration; + int type; + @ReasonCode int reasonCode; + @Nullable String reason; + + TempAllowListDuration(long _duration, int _type, @ReasonCode int _reasonCode, + String _reason) { + duration = _duration; + type = _type; + reasonCode = _reasonCode; + reason = _reason; + } + } + PendingIntentRecord(PendingIntentController _controller, Key _k, int _u) { controller = _controller; key = _k; @@ -221,18 +237,19 @@ public final class PendingIntentRecord extends IIntentSender.Stub { ref = new WeakReference<>(this); } - void setWhitelistDurationLocked(IBinder whitelistToken, long duration, int type) { + void setAllowlistDurationLocked(IBinder allowlistToken, long duration, int type, + @ReasonCode int reasonCode, @Nullable String reason) { if (duration > 0) { - if (mWhitelistDuration == null) { - mWhitelistDuration = new ArrayMap<>(); + if (mAllowlistDuration == null) { + mAllowlistDuration = new ArrayMap<>(); } - mWhitelistDuration.put(whitelistToken, new Pair(duration, type)); - } else if (mWhitelistDuration != null) { - mWhitelistDuration.remove(whitelistToken); - if (mWhitelistDuration.size() <= 0) { - mWhitelistDuration = null; + mAllowlistDuration.put(allowlistToken, + new TempAllowListDuration(duration, type, reasonCode, reason)); + } else if (mAllowlistDuration != null) { + mAllowlistDuration.remove(allowlistToken); + if (mAllowlistDuration.size() <= 0) { + mAllowlistDuration = null; } - } this.stringName = null; } @@ -280,25 +297,25 @@ public final class PendingIntentRecord extends IIntentSender.Stub { return listeners; } - public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken, + public void send(int code, Intent intent, String resolvedType, IBinder allowlistToken, IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) { - sendInner(code, intent, resolvedType, whitelistToken, finishedReceiver, + sendInner(code, intent, resolvedType, allowlistToken, finishedReceiver, requiredPermission, null, null, 0, 0, 0, options); } - public int sendWithResult(int code, Intent intent, String resolvedType, IBinder whitelistToken, + public int sendWithResult(int code, Intent intent, String resolvedType, IBinder allowlistToken, IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) { - return sendInner(code, intent, resolvedType, whitelistToken, finishedReceiver, + return sendInner(code, intent, resolvedType, allowlistToken, finishedReceiver, requiredPermission, null, null, 0, 0, 0, options); } - public int sendInner(int code, Intent intent, String resolvedType, IBinder whitelistToken, + public int sendInner(int code, Intent intent, String resolvedType, IBinder allowlistToken, IIntentReceiver finishedReceiver, String requiredPermission, IBinder resultTo, String resultWho, int requestCode, int flagsMask, int flagsValues, Bundle options) { if (intent != null) intent.setDefusable(true); if (options != null) options.setDefusable(true); - Pair<Long, Integer> duration = null; + TempAllowListDuration duration = null; Intent finalIntent = null; Intent[] allIntents = null; String[] allResolvedTypes = null; @@ -347,8 +364,8 @@ public final class PendingIntentRecord extends IIntentSender.Stub { mergedOptions.setCallerOptions(opts); } - if (mWhitelistDuration != null) { - duration = mWhitelistDuration.get(whitelistToken); + if (mAllowlistDuration != null) { + duration = mAllowlistDuration.get(allowlistToken); } if (key.type == ActivityManager.INTENT_SENDER_ACTIVITY @@ -377,7 +394,9 @@ public final class PendingIntentRecord extends IIntentSender.Stub { try { if (duration != null) { StringBuilder tag = new StringBuilder(64); - tag.append("pendingintent:"); + tag.append("setPendingIntentAllowlistDuration,reason:"); + tag.append(duration.reason == null ? "" : duration.reason); + tag.append(",pendingintent:"); UserHandle.formatUid(tag, callingUid); tag.append(":"); if (finalIntent.getAction() != null) { @@ -387,8 +406,8 @@ public final class PendingIntentRecord extends IIntentSender.Stub { } else if (finalIntent.getData() != null) { tag.append(finalIntent.getData().toSafeString()); } - controller.mAmInternal.tempWhitelistForPendingIntent(callingPid, callingUid, - uid, duration.first, duration.second, tag.toString()); + controller.mAmInternal.tempAllowlistForPendingIntent(callingPid, callingUid, + uid, duration.duration, duration.type, duration.reasonCode, tag.toString()); } boolean sendFinish = finishedReceiver != null; @@ -417,7 +436,8 @@ public final class PendingIntentRecord extends IIntentSender.Stub { allIntents, allResolvedTypes, resultTo, mergedOptions, userId, false /* validateIncomingUser */, this /* originatingPendingIntent */, - mAllowBgActivityStartsForActivitySender.contains(whitelistToken)); + mAllowBgActivityStartsForActivitySender.contains( + allowlistToken)); } else { res = controller.mAtmInternal.startActivityInPackage(uid, callingPid, callingUid, key.packageName, key.featureId, finalIntent, @@ -426,7 +446,7 @@ public final class PendingIntentRecord extends IIntentSender.Stub { false /* validateIncomingUser */, this /* originatingPendingIntent */, mAllowBgActivityStartsForActivitySender.contains( - whitelistToken)); + allowlistToken)); } } catch (RuntimeException e) { Slog.w(TAG, "Unable to send startActivity intent", e); @@ -439,8 +459,8 @@ public final class PendingIntentRecord extends IIntentSender.Stub { case ActivityManager.INTENT_SENDER_BROADCAST: try { final boolean allowedByToken = - mAllowBgActivityStartsForBroadcastSender.contains(whitelistToken); - final IBinder bgStartsToken = (allowedByToken) ? whitelistToken : null; + mAllowBgActivityStartsForBroadcastSender.contains(allowlistToken); + final IBinder bgStartsToken = (allowedByToken) ? allowlistToken : null; // If a completion callback has been requested, require // that the broadcast be delivered synchronously @@ -460,8 +480,8 @@ public final class PendingIntentRecord extends IIntentSender.Stub { case ActivityManager.INTENT_SENDER_FOREGROUND_SERVICE: try { final boolean allowedByToken = - mAllowBgActivityStartsForServiceSender.contains(whitelistToken); - final IBinder bgStartsToken = (allowedByToken) ? whitelistToken : null; + mAllowBgActivityStartsForServiceSender.contains(allowlistToken); + final IBinder bgStartsToken = (allowedByToken) ? allowlistToken : null; controller.mAmInternal.startServiceInPackage(uid, finalIntent, resolvedType, key.type == ActivityManager.INTENT_SENDER_FOREGROUND_SERVICE, @@ -533,18 +553,23 @@ public final class PendingIntentRecord extends IIntentSender.Stub { pw.print(prefix); pw.print("sent="); pw.print(sent); pw.print(" canceled="); pw.println(canceled); } - if (mWhitelistDuration != null) { + if (mAllowlistDuration != null) { pw.print(prefix); - pw.print("whitelistDuration="); - for (int i = 0; i < mWhitelistDuration.size(); i++) { + pw.print("allowlistDuration="); + for (int i = 0; i < mAllowlistDuration.size(); i++) { if (i != 0) { pw.print(", "); } - pw.print(Integer.toHexString(System.identityHashCode(mWhitelistDuration.keyAt(i)))); + TempAllowListDuration entry = mAllowlistDuration.valueAt(i); + pw.print(Integer.toHexString(System.identityHashCode(mAllowlistDuration.keyAt(i)))); pw.print(":"); - TimeUtils.formatDuration(mWhitelistDuration.valueAt(i).first, pw); + TimeUtils.formatDuration(entry.duration, pw); pw.print("/"); - pw.print(mWhitelistDuration.valueAt(i).second); + pw.print(entry.type); + pw.print("/"); + pw.print(PowerWhitelistManager.reasonCodeToString(entry.reasonCode)); + pw.print("/"); + pw.print(entry.reason); } pw.println(); } @@ -572,18 +597,23 @@ public final class PendingIntentRecord extends IIntentSender.Stub { } sb.append(' '); sb.append(key.typeName()); - if (mWhitelistDuration != null) { - sb.append( " (whitelist: "); - for (int i = 0; i < mWhitelistDuration.size(); i++) { + if (mAllowlistDuration != null) { + sb.append(" (allowlist: "); + for (int i = 0; i < mAllowlistDuration.size(); i++) { if (i != 0) { sb.append(","); } + TempAllowListDuration entry = mAllowlistDuration.valueAt(i); sb.append(Integer.toHexString(System.identityHashCode( - mWhitelistDuration.keyAt(i)))); + mAllowlistDuration.keyAt(i)))); sb.append(":"); - TimeUtils.formatDuration(mWhitelistDuration.valueAt(i).first, sb); + TimeUtils.formatDuration(entry.duration, sb); + sb.append("/"); + sb.append(entry.type); + sb.append("/"); + sb.append(PowerWhitelistManager.reasonCodeToString(entry.reasonCode)); sb.append("/"); - sb.append(mWhitelistDuration.valueAt(i).second); + sb.append(entry.reason); } sb.append(")"); } diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java index 165312352990..3258f8af0da2 100644 --- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java +++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java @@ -22,6 +22,7 @@ import static com.android.server.am.ActivityManagerService.MY_PID; import static com.android.server.am.ProcessRecord.TAG; import android.app.ActivityManager; +import android.app.AnrController; import android.app.ApplicationErrorReport; import android.app.ApplicationExitInfo; import android.content.ComponentName; @@ -418,10 +419,16 @@ class ProcessErrorStateRecord { // Retrieve max ANR delay from AnrControllers without the mService lock since the // controllers might in turn call into apps - long anrDialogDelayMs = mService.mActivityTaskManager.getMaxAnrDelayMillis(aInfo); - if (aInfo != null && aInfo.packageName != null && anrDialogDelayMs > 0) { - Slog.i(TAG, "Delaying ANR dialog for " + aInfo.packageName + " for " + anrDialogDelayMs - + "ms"); + AnrController anrController = mService.mActivityTaskManager.getAnrController(aInfo); + long anrDialogDelayMs = 0; + if (anrController != null) { + String packageName = aInfo.packageName; + int uid = aInfo.uid; + anrDialogDelayMs = anrController.getAnrDelayMillis(packageName, uid); + // Might execute an async binder call to a system app to show an interim + // ANR progress UI + anrController.onAnrDelayStarted(packageName, uid); + Slog.i(TAG, "ANR delay of " + anrDialogDelayMs + "ms started for " + packageName); } synchronized (mService) { @@ -440,6 +447,7 @@ class ProcessErrorStateRecord { // Set the app's notResponding state, and look up the errorReportReceiver makeAppNotRespondingLSP(activityShortComponentName, annotation != null ? "ANR " + annotation : "ANR", info.toString()); + mDialogController.setAnrController(anrController); } // Notify package manager service to possibly update package state diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index cb07a06d8340..38330fe770fb 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -16,6 +16,7 @@ package com.android.server.am; +import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE; import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY; import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT; import static android.app.ActivityThread.PROC_START_SEQ_IDENT; @@ -57,6 +58,7 @@ import static com.android.server.am.AppProfiler.TAG_PSS; import android.annotation.NonNull; import android.app.ActivityManager; +import android.app.ActivityManager.ProcessCapability; import android.app.ActivityThread; import android.app.AppGlobals; import android.app.AppProtoEnums; @@ -228,6 +230,11 @@ public final class ProcessList { // not so perceptible that it affects the user immediately if killed. static final int PERCEPTIBLE_LOW_APP_ADJ = 250; + // This is a process hosting services that are not perceptible to the user but the + // client (system) binding to it requested to treat it as if it is perceptible and avoid killing + // it if possible. + static final int PERCEPTIBLE_MEDIUM_APP_ADJ = 225; + // This is a process only hosting components that are perceptible to the // user, and we really want to avoid killing them, but they are not // immediately visible. An example is background music playback. @@ -1027,6 +1034,9 @@ public final class ProcessList { } else if (setAdj >= ProcessList.PERCEPTIBLE_LOW_APP_ADJ) { return buildOomTag("prcl ", "prcl", null, setAdj, ProcessList.PERCEPTIBLE_LOW_APP_ADJ, compact); + } else if (setAdj >= ProcessList.PERCEPTIBLE_MEDIUM_APP_ADJ) { + return buildOomTag("prcm ", "prcm", null, setAdj, + ProcessList.PERCEPTIBLE_MEDIUM_APP_ADJ, compact); } else if (setAdj >= ProcessList.PERCEPTIBLE_APP_ADJ) { return buildOomTag("prcp ", "prcp", null, setAdj, ProcessList.PERCEPTIBLE_APP_ADJ, compact); @@ -4395,6 +4405,7 @@ public final class ProcessList { printOomLevel(pw, "FOREGROUND_APP_ADJ", FOREGROUND_APP_ADJ); printOomLevel(pw, "VISIBLE_APP_ADJ", VISIBLE_APP_ADJ); printOomLevel(pw, "PERCEPTIBLE_APP_ADJ", PERCEPTIBLE_APP_ADJ); + printOomLevel(pw, "PERCEPTIBLE_MEDIUM_APP_ADJ", PERCEPTIBLE_MEDIUM_APP_ADJ); printOomLevel(pw, "PERCEPTIBLE_LOW_APP_ADJ", PERCEPTIBLE_LOW_APP_ADJ); printOomLevel(pw, "BACKUP_APP_ADJ", BACKUP_APP_ADJ); printOomLevel(pw, "HEAVY_WEIGHT_APP_ADJ", HEAVY_WEIGHT_APP_ADJ); @@ -4658,13 +4669,26 @@ public final class ProcessList { } } - /** Returns the uid's process state or PROCESS_STATE_NONEXISTENT if not running */ + /** + * Returns the uid's process state or {@link ActivityManager#PROCESS_STATE_NONEXISTENT} + * if not running + */ @GuardedBy(anyOf = {"mService", "mProcLock"}) int getUidProcStateLOSP(int uid) { UidRecord uidRec = mActiveUids.get(uid); return uidRec == null ? PROCESS_STATE_NONEXISTENT : uidRec.getCurProcState(); } + /** + * Returns the uid's process capability or {@link ActivityManager#PROCESS_CAPABILITY_NONE} + * if not running + */ + @GuardedBy(anyOf = {"mService", "mProcLock"}) + @ProcessCapability int getUidProcessCapabilityLOSP(int uid) { + UidRecord uidRec = mActiveUids.get(uid); + return uidRec == null ? PROCESS_CAPABILITY_NONE : uidRec.getCurCapability(); + } + /** Returns the UidRecord for the given uid, if it exists. */ @GuardedBy(anyOf = {"mService", "mProcLock"}) UidRecord getUidRecordLOSP(int uid) { @@ -4749,8 +4773,9 @@ public final class ProcessList { if (!UserHandle.isApp(uidRec.getUid()) || !uidRec.hasInternetPermission) { continue; } - // If process state is not changed, then there's nothing to do. - if (uidRec.getSetProcState() == uidRec.getCurProcState()) { + // If process state and capabilities are not changed, then there's nothing to do. + if (uidRec.getSetProcState() == uidRec.getCurProcState() + && uidRec.getSetCapability() == uidRec.getCurCapability()) { continue; } final int blockState = getBlockStateForUid(uidRec); diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java index 499fbcb0642d..6d783fc63901 100644 --- a/services/core/java/com/android/server/am/ProcessStateRecord.java +++ b/services/core/java/com/android/server/am/ProcessStateRecord.java @@ -23,22 +23,23 @@ import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE; import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.os.PowerWhitelistManager.REASON_BACKGROUND_ACTIVITY_PERMISSION; +import static android.os.PowerWhitelistManager.REASON_BACKGROUND_FGS_PERMISSION; +import static android.os.PowerWhitelistManager.REASON_COMPANION_DEVICE_MANAGER; +import static android.os.PowerWhitelistManager.REASON_DENIED; +import static android.os.PowerWhitelistManager.REASON_DEVICE_OWNER; +import static android.os.PowerWhitelistManager.REASON_PROFILE_OWNER; +import static android.os.PowerWhitelistManager.REASON_SYSTEM_ALERT_WINDOW_PERMISSION; +import static android.os.PowerWhitelistManager.REASON_SYSTEM_ALLOW_LISTED; +import static android.os.PowerWhitelistManager.REASON_SYSTEM_UID; +import static android.os.PowerWhitelistManager.ReasonCode; +import static android.os.PowerWhitelistManager.getReasonCodeFromProcState; +import static android.os.PowerWhitelistManager.reasonCodeToString; import static android.os.Process.NFC_UID; import static android.os.Process.ROOT_UID; import static android.os.Process.SHELL_UID; import static android.os.Process.SYSTEM_UID; -import static com.android.server.am.ActiveServices.FGS_FEATURE_ALLOWED_BY_BACKGROUND_ACTIVITY_PERMISSION; -import static com.android.server.am.ActiveServices.FGS_FEATURE_ALLOWED_BY_BACKGROUND_FGS_PERMISSION; -import static com.android.server.am.ActiveServices.FGS_FEATURE_ALLOWED_BY_COMPANION_APP; -import static com.android.server.am.ActiveServices.FGS_FEATURE_ALLOWED_BY_DEVICE_IDLE_ALLOW_LIST; -import static com.android.server.am.ActiveServices.FGS_FEATURE_ALLOWED_BY_DEVICE_OWNER; -import static com.android.server.am.ActiveServices.FGS_FEATURE_ALLOWED_BY_PROC_STATE; -import static com.android.server.am.ActiveServices.FGS_FEATURE_ALLOWED_BY_PROFILE_OWNER; -import static com.android.server.am.ActiveServices.FGS_FEATURE_ALLOWED_BY_SYSTEM_ALERT_WINDOW_PERMISSION; -import static com.android.server.am.ActiveServices.FGS_FEATURE_ALLOWED_BY_SYSTEM_UID; -import static com.android.server.am.ActiveServices.FGS_FEATURE_DENIED; -import static com.android.server.am.ActiveServices.fgsCodeToString; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ; import static com.android.server.am.ProcessRecord.TAG; @@ -329,7 +330,7 @@ final class ProcessStateRecord { * Does the process has permission to start FGS from background. */ @GuardedBy("mService") - private @ActiveServices.FgsFeatureRetCode int mAllowStartFgsByPermission; + private @ReasonCode int mAllowStartFgsByPermission = REASON_DENIED; /** * Can this process start FGS from background? @@ -337,7 +338,7 @@ final class ProcessStateRecord { * another process through service binding. */ @GuardedBy("mService") - private @ActiveServices.FgsFeatureRetCode int mAllowStartFgs; + private @ReasonCode int mAllowStartFgs = REASON_DENIED; /** * Debugging: primary thing impacting oom_adj. @@ -1152,44 +1153,47 @@ final class ProcessStateRecord { } @GuardedBy("mService") + int getAllowStartFgsState() { + return mAllowStartFgsState; + } + + @GuardedBy("mService") boolean isAllowedStartFgsState() { return mAllowStartFgsState <= PROCESS_STATE_BOUND_FOREGROUND_SERVICE; } @GuardedBy("mService") void setAllowStartFgsByPermission() { - int ret = FGS_FEATURE_DENIED; - if (ret == FGS_FEATURE_DENIED) { - boolean isSystem = false; - final int uid = UserHandle.getAppId(mApp.info.uid); - switch (uid) { - case ROOT_UID: - case SYSTEM_UID: - case NFC_UID: - case SHELL_UID: - isSystem = true; - break; - default: - isSystem = false; - break; - } + int ret = REASON_DENIED; + boolean isSystem = false; + final int uid = UserHandle.getAppId(mApp.info.uid); + switch (uid) { + case ROOT_UID: + case SYSTEM_UID: + case NFC_UID: + case SHELL_UID: + isSystem = true; + break; + default: + isSystem = false; + break; + } - if (isSystem) { - ret = FGS_FEATURE_ALLOWED_BY_SYSTEM_UID; - } + if (isSystem) { + ret = REASON_SYSTEM_UID; } - if (ret == FGS_FEATURE_DENIED) { + if (ret == REASON_DENIED) { if (ActivityManager.checkComponentPermission(START_ACTIVITIES_FROM_BACKGROUND, mApp.info.uid, -1, true) == PERMISSION_GRANTED) { - ret = FGS_FEATURE_ALLOWED_BY_BACKGROUND_ACTIVITY_PERMISSION; + ret = REASON_BACKGROUND_ACTIVITY_PERMISSION; } else if (ActivityManager.checkComponentPermission( START_FOREGROUND_SERVICES_FROM_BACKGROUND, mApp.info.uid, -1, true) == PERMISSION_GRANTED) { - ret = FGS_FEATURE_ALLOWED_BY_BACKGROUND_FGS_PERMISSION; + ret = REASON_BACKGROUND_FGS_PERMISSION; } else if (ActivityManager.checkComponentPermission(SYSTEM_ALERT_WINDOW, mApp.info.uid, -1, true) == PERMISSION_GRANTED) { - ret = FGS_FEATURE_ALLOWED_BY_SYSTEM_ALERT_WINDOW_PERMISSION; + ret = REASON_SYSTEM_ALERT_WINDOW_PERMISSION; } } mAllowStartFgs = mAllowStartFgsByPermission = ret; @@ -1197,59 +1201,65 @@ final class ProcessStateRecord { @GuardedBy("mService") void setAllowStartFgs() { - if (mAllowStartFgs != FGS_FEATURE_DENIED) { + if (mAllowStartFgs != REASON_DENIED) { return; } - if (mAllowStartFgs == FGS_FEATURE_DENIED) { + if (mAllowStartFgs == REASON_DENIED) { if (isAllowedStartFgsState()) { - mAllowStartFgs = FGS_FEATURE_ALLOWED_BY_PROC_STATE; + mAllowStartFgs = getReasonCodeFromProcState(mAllowStartFgsState); } } - if (mAllowStartFgs == FGS_FEATURE_DENIED) { + if (mAllowStartFgs == REASON_DENIED) { // Is the calling UID a device owner app? if (mService.mInternal != null) { if (mService.mInternal.isDeviceOwner(mApp.info.uid)) { - mAllowStartFgs = FGS_FEATURE_ALLOWED_BY_DEVICE_OWNER; + mAllowStartFgs = REASON_DEVICE_OWNER; } } } - if (mAllowStartFgs == FGS_FEATURE_DENIED) { + if (mAllowStartFgs == REASON_DENIED) { if (mService.mInternal != null) { final boolean isCompanionApp = mService.mInternal.isAssociatedCompanionApp( UserHandle.getUserId(mApp.info.uid), mApp.info.uid); if (isCompanionApp) { - mAllowStartFgs = FGS_FEATURE_ALLOWED_BY_COMPANION_APP; + mAllowStartFgs = REASON_COMPANION_DEVICE_MANAGER; } } } - if (mAllowStartFgs == FGS_FEATURE_DENIED) { + if (mAllowStartFgs == REASON_DENIED) { // Is the calling UID a profile owner app? if (mService.mInternal != null) { if (mService.mInternal.isProfileOwner(mApp.info.uid)) { - mAllowStartFgs = FGS_FEATURE_ALLOWED_BY_PROFILE_OWNER; + mAllowStartFgs = REASON_PROFILE_OWNER; } } } - if (mAllowStartFgs == FGS_FEATURE_DENIED) { + if (mAllowStartFgs == REASON_DENIED) { // uid is on DeviceIdleController's user/system allowlist // or AMS's FgsStartTempAllowList. - if (mService.isAllowlistedForFgsStartLOSP(mApp.info.uid)) { - mAllowStartFgs = FGS_FEATURE_ALLOWED_BY_DEVICE_IDLE_ALLOW_LIST; + FgsStartTempAllowList.TempFgsAllowListEntry entry = + mService.isAllowlistedForFgsStartLOSP(mApp.info.uid); + if (entry != null) { + if (entry == ActivityManagerService.FAKE_TEMP_ALLOWLIST_ENTRY) { + mAllowStartFgs = REASON_SYSTEM_ALLOW_LISTED; + } else { + mAllowStartFgs = entry.mReasonCode; + } } } } @GuardedBy("mService") - void setAllowStartFgs(@ActiveServices.FgsFeatureRetCode int allowStartFgs) { + void setAllowStartFgs(@ReasonCode int allowStartFgs) { mAllowStartFgs = allowStartFgs; } @GuardedBy("mService") - @ActiveServices.FgsFeatureRetCode int getAllowedStartFgs() { + @ReasonCode int getAllowedStartFgs() { return mAllowStartFgs; } @@ -1291,9 +1301,9 @@ final class ProcessStateRecord { pw.println(); pw.print(prefix); pw.print("allowStartFgsState="); pw.println(mAllowStartFgsState); - if (mAllowStartFgs != FGS_FEATURE_DENIED) { + if (mAllowStartFgs != REASON_DENIED) { pw.print(prefix); pw.print("allowStartFgs="); - pw.println(fgsCodeToString(mAllowStartFgs)); + pw.println(reasonCodeToString(mAllowStartFgs)); } if (mHasShownUi || mApp.mProfile.hasPendingUiClean()) { pw.print(prefix); pw.print("hasShownUi="); pw.print(mHasShownUi); diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java index 485087d29422..3ab95d131fad 100644 --- a/services/core/java/com/android/server/am/ServiceRecord.java +++ b/services/core/java/com/android/server/am/ServiceRecord.java @@ -18,6 +18,7 @@ package com.android.server.am; import static android.app.PendingIntent.FLAG_IMMUTABLE; import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; +import static android.os.PowerWhitelistManager.REASON_DENIED; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; @@ -36,6 +37,7 @@ import android.net.Uri; import android.os.Binder; import android.os.Build; import android.os.IBinder; +import android.os.PowerWhitelistManager; import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; @@ -101,7 +103,7 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN ProcessRecord isolatedProc; // keep track of isolated process, if requested ServiceState tracker; // tracking service execution, may be null ServiceState restartTracker; // tracking service restart - boolean whitelistManager; // any bindings to this service have BIND_ALLOW_WHITELIST_MANAGEMENT? + boolean allowlistManager; // any bindings to this service have BIND_ALLOW_WHITELIST_MANAGEMENT? boolean delayed; // are we waiting to start this service in the background? boolean fgRequired; // is the service required to go foreground after starting? boolean fgWaiting; // is a timeout for going foreground already scheduled? @@ -156,11 +158,14 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN // the most recent package that start/bind this service. String mRecentCallingPackage; + // the most recent uid that start/bind this service. + int mRecentCallingUid; // allow the service becomes foreground service? Service started from background may not be // allowed to become a foreground service. - @ActiveServices.FgsFeatureRetCode int mAllowStartForeground; + @PowerWhitelistManager.ReasonCode int mAllowStartForeground = REASON_DENIED; String mInfoAllowStartForeground; + FgsStartTempAllowList.TempFgsAllowListEntry mInfoTempFgsAllowListReason; boolean mLoggedInfoAllowStartForeground; String stringName; // caching of toString @@ -309,7 +314,7 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN if (isolatedProc != null) { isolatedProc.dumpDebug(proto, ServiceRecordProto.ISOLATED_PROC); } - proto.write(ServiceRecordProto.WHITELIST_MANAGER, whitelistManager); + proto.write(ServiceRecordProto.WHITELIST_MANAGER, allowlistManager); proto.write(ServiceRecordProto.DELAYED, delayed); if (isForeground || foregroundId != 0) { long fgToken = proto.start(ServiceRecordProto.FOREGROUND); @@ -410,8 +415,8 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN if (isolatedProc != null) { pw.print(prefix); pw.print("isolatedProc="); pw.println(isolatedProc); } - if (whitelistManager) { - pw.print(prefix); pw.print("whitelistManager="); pw.println(whitelistManager); + if (allowlistManager) { + pw.print(prefix); pw.print("allowlistManager="); pw.println(allowlistManager); } if (mIsAllowedBgActivityStartsByBinding) { pw.print(prefix); pw.print("mIsAllowedBgActivityStartsByBinding="); @@ -429,6 +434,8 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN pw.println(mAllowWhileInUsePermissionInFgs); pw.print(prefix); pw.print("recentCallingPackage="); pw.println(mRecentCallingPackage); + pw.print(prefix); pw.print("recentCallingUid="); + pw.println(mRecentCallingUid); pw.print(prefix); pw.print("allowStartForeground="); pw.println(mAllowStartForeground); pw.print(prefix); pw.print("infoAllowStartForeground="); @@ -894,13 +901,13 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN return false; } - public void updateWhitelistManager() { - whitelistManager = false; + public void updateAllowlistManager() { + allowlistManager = false; for (int conni=connections.size()-1; conni>=0; conni--) { ArrayList<ConnectionRecord> cr = connections.valueAt(conni); for (int i=0; i<cr.size(); i++) { if ((cr.get(i).flags&Context.BIND_ALLOW_WHITELIST_MANAGEMENT) != 0) { - whitelistManager = true; + allowlistManager = true; return; } } diff --git a/services/core/java/com/android/server/am/StrictModeViolationDialog.java b/services/core/java/com/android/server/am/StrictModeViolationDialog.java index 4cc1fc1b7d09..9dddd658575b 100644 --- a/services/core/java/com/android/server/am/StrictModeViolationDialog.java +++ b/services/core/java/com/android/server/am/StrictModeViolationDialog.java @@ -82,6 +82,11 @@ final class StrictModeViolationDialog extends BaseErrorDialog { DISMISS_TIMEOUT); } + @Override + protected void closeDialog() { + mHandler.obtainMessage(ACTION_OK).sendToTarget(); + } + private final Handler mHandler = new Handler() { public void handleMessage(Message msg) { synchronized (mService.mProcLock) { diff --git a/services/core/java/com/android/server/am/TEST_MAPPING b/services/core/java/com/android/server/am/TEST_MAPPING index 4061df4f3f62..03eddc9634d7 100644 --- a/services/core/java/com/android/server/am/TEST_MAPPING +++ b/services/core/java/com/android/server/am/TEST_MAPPING @@ -44,6 +44,23 @@ "exclude-annotation": "androidx.test.filters.FlakyTest" } ] + }, + { + "file_patterns": ["Battery[^/]*\\.java", "MeasuredEnergy[^/]*\\.java"], + "name": "FrameworksCoreTests", + "options": [ + { "include-filter": "com.android.internal.os.BatteryStatsTests" }, + { "exclude-annotation": "com.android.internal.os.SkipPresubmit" } + ] + }, + { + "file_patterns": ["Battery[^/]*\\.java", "MeasuredEnergy[^/]*\\.java"], + "name": "FrameworksServicesTests", + "options": [ + { "include-filter": "com.android.server.am.BatteryStatsServiceTest" }, + { "include-filter": "com.android.server.am.MeasuredEnergySnapshotTest" }, + { "include-filter": "com.android.server.am.BatteryExternalStatsWorkerTest" } + ] } ], "postsubmit": [ diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index a776458d63c5..44dcc205a9d0 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -339,7 +339,10 @@ public class AppOpsService extends IAppOpsService.Stub { SparseIntArray mProfileOwners; @GuardedBy("this") - private CheckOpsDelegate mCheckOpsDelegate; + private CheckOpsDelegate mAppOpsPolicy; + + @GuardedBy("this") + private CheckOpsDelegateDispatcher mCheckOpsDelegateDispatcher; /** * Reverse lookup for {@link AppOpsManager#opToSwitch(int)}. Initialized once and never @@ -1770,6 +1773,17 @@ public class AppOpsService extends IAppOpsService.Stub { mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); } + /** + * Sets a policy for handling app ops. + * + * @param appOpsPolicy The policy. + */ + public void setAppOpsPolicy(@Nullable CheckOpsDelegate appOpsPolicy) { + synchronized (AppOpsService.this) { + mAppOpsPolicy = appOpsPolicy; + } + } + public void packageRemoved(int uid, String packageName) { synchronized (this) { UidState uidState = mUidStates.get(uid); @@ -2868,13 +2882,19 @@ public class AppOpsService extends IAppOpsService.Stub { public CheckOpsDelegate getAppOpsServiceDelegate() { synchronized (this) { - return mCheckOpsDelegate; + return (mCheckOpsDelegateDispatcher != null) + ? mCheckOpsDelegateDispatcher.getCheckOpsDelegate() + : null; } } public void setAppOpsServiceDelegate(CheckOpsDelegate delegate) { synchronized (this) { - mCheckOpsDelegate = delegate; + if (delegate != null) { + mCheckOpsDelegateDispatcher = new CheckOpsDelegateDispatcher(delegate); + } else { + mCheckOpsDelegateDispatcher = null; + } } } @@ -2889,19 +2909,28 @@ public class AppOpsService extends IAppOpsService.Stub { } private int checkOperationInternal(int code, int uid, String packageName, boolean raw) { - final CheckOpsDelegate delegate; - synchronized (this) { - delegate = mCheckOpsDelegate; + final CheckOpsDelegate policy; + final CheckOpsDelegateDispatcher delegateDispatcher; + synchronized (AppOpsService.this) { + policy = mAppOpsPolicy; + delegateDispatcher = mCheckOpsDelegateDispatcher; } - if (delegate == null) { - return checkOperationImpl(code, uid, packageName, raw); + if (policy != null) { + if (delegateDispatcher != null) { + return policy.checkOperation(code, uid, packageName, raw, + delegateDispatcher::checkOperationImpl); + } else { + return policy.checkOperation(code, uid, packageName, raw, + AppOpsService.this::checkOperationImpl); + } + } else if (delegateDispatcher != null) { + delegateDispatcher.getCheckOpsDelegate().checkOperation(code, uid, + packageName, raw, AppOpsService.this::checkOperationImpl); } - return delegate.checkOperation(code, uid, packageName, raw, - AppOpsService.this::checkOperationImpl); + return checkOperationImpl(code, uid, packageName, raw); } - private int checkOperationImpl(int code, int uid, String packageName, - boolean raw) { + private int checkOperationImpl(int code, int uid, String packageName, boolean raw) { verifyIncomingOp(code); verifyIncomingPackage(packageName, UserHandle.getUserId(uid)); @@ -2956,15 +2985,25 @@ public class AppOpsService extends IAppOpsService.Stub { @Override public int checkAudioOperation(int code, int usage, int uid, String packageName) { - final CheckOpsDelegate delegate; - synchronized (this) { - delegate = mCheckOpsDelegate; + final CheckOpsDelegate policy; + final CheckOpsDelegateDispatcher delegateDispatcher; + synchronized (AppOpsService.this) { + policy = mAppOpsPolicy; + delegateDispatcher = mCheckOpsDelegateDispatcher; } - if (delegate == null) { - return checkAudioOperationImpl(code, usage, uid, packageName); + if (policy != null) { + if (delegateDispatcher != null) { + return policy.checkAudioOperation(code, usage, uid, packageName, + delegateDispatcher::checkAudioOperationImpl); + } else { + return policy.checkAudioOperation(code, usage, uid, packageName, + AppOpsService.this::checkAudioOperationImpl); + } + } else if (delegateDispatcher != null) { + delegateDispatcher.getCheckOpsDelegate().checkAudioOperation(code, usage, + uid, packageName, AppOpsService.this::checkAudioOperationImpl); } - return delegate.checkAudioOperation(code, usage, uid, packageName, - AppOpsService.this::checkAudioOperationImpl); + return checkAudioOperationImpl(code, usage, uid, packageName); } private int checkAudioOperationImpl(int code, int usage, int uid, String packageName) { @@ -3078,17 +3117,29 @@ public class AppOpsService extends IAppOpsService.Stub { @Override public int noteOperation(int code, int uid, String packageName, String attributionTag, boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage) { - final CheckOpsDelegate delegate; - synchronized (this) { - delegate = mCheckOpsDelegate; - } - if (delegate == null) { - return noteOperationImpl(code, uid, packageName, attributionTag, - shouldCollectAsyncNotedOp, message, shouldCollectMessage); + final CheckOpsDelegate policy; + final CheckOpsDelegateDispatcher delegateDispatcher; + synchronized (AppOpsService.this) { + policy = mAppOpsPolicy; + delegateDispatcher = mCheckOpsDelegateDispatcher; + } + if (policy != null) { + if (delegateDispatcher != null) { + return policy.noteOperation(code, uid, packageName, attributionTag, + shouldCollectAsyncNotedOp, message, shouldCollectMessage, + delegateDispatcher::noteOperationImpl); + } else { + return policy.noteOperation(code, uid, packageName, attributionTag, + shouldCollectAsyncNotedOp, message, shouldCollectMessage, + AppOpsService.this::noteOperationImpl); + } + } else if (delegateDispatcher != null) { + delegateDispatcher.getCheckOpsDelegate().noteOperation(code, uid, packageName, + attributionTag, shouldCollectAsyncNotedOp, message, shouldCollectMessage, + AppOpsService.this::noteOperationImpl); } - return delegate.noteOperation(code, uid, packageName, attributionTag, - shouldCollectAsyncNotedOp, message, shouldCollectMessage, - AppOpsService.this::noteOperationImpl); + return noteOperationImpl(code, uid, packageName, attributionTag, + shouldCollectAsyncNotedOp, message, shouldCollectMessage); } private int noteOperationImpl(int code, int uid, @Nullable String packageName, @@ -6589,7 +6640,6 @@ public class AppOpsService extends IAppOpsService.Stub { } } - /** * Async task for writing note op stack trace, op code, package name and version to file * More specifically, writes all the collected ops from {@link #mNoteOpCallerStacktraces} @@ -6726,4 +6776,34 @@ public class AppOpsService extends IAppOpsService.Stub { } } } + + private final class CheckOpsDelegateDispatcher { + private final @NonNull CheckOpsDelegate mCheckOpsDelegate; + + CheckOpsDelegateDispatcher(@NonNull CheckOpsDelegate checkOpsDelegate) { + mCheckOpsDelegate = checkOpsDelegate; + } + + public @NonNull CheckOpsDelegate getCheckOpsDelegate() { + return mCheckOpsDelegate; + } + + public int checkOperationImpl(int code, int uid, String packageName, boolean raw) { + return mCheckOpsDelegate.checkOperation(code, uid, packageName, raw, + AppOpsService.this::checkOperationImpl); + } + + public int checkAudioOperationImpl(int code, int usage, int uid, String packageName) { + return mCheckOpsDelegate.checkAudioOperation(code, usage, uid, packageName, + AppOpsService.this::checkAudioOperationImpl); + } + + public int noteOperationImpl(int code, int uid, @Nullable String packageName, + @Nullable String featureId, boolean shouldCollectAsyncNotedOp, + @Nullable String message, boolean shouldCollectMessage) { + return mCheckOpsDelegate.noteOperation(code, uid, packageName, featureId, + shouldCollectAsyncNotedOp, message, shouldCollectMessage, + AppOpsService.this::noteOperationImpl); + } + } } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index f5b94177a2d9..8363c9d203d5 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -6958,7 +6958,10 @@ public class AudioService extends IAudioService.Stub private void onSetVolumeIndexOnDevice(@NonNull DeviceVolumeUpdate update) { final VolumeStreamState streamState = mStreamStates[update.mStreamType]; if (update.hasVolumeIndex()) { - final int index = update.getVolumeIndex(); + int index = update.getVolumeIndex(); + if (!checkSafeMediaVolume(update.mStreamType, index, update.mDevice)) { + index = safeMediaVolumeIndex(update.mDevice); + } streamState.setIndex(index, update.mDevice, update.mCaller, // trusted as index is always validated before message is posted true /*hasModifyAudioSettings*/); diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java index 507600783aa4..2de709ebe71d 100644 --- a/services/core/java/com/android/server/clipboard/ClipboardService.java +++ b/services/core/java/com/android/server/clipboard/ClipboardService.java @@ -61,7 +61,6 @@ import android.widget.Toast; import com.android.server.LocalServices; import com.android.server.SystemService; -import com.android.server.SystemService.TargetUser; import com.android.server.UiThread; import com.android.server.contentcapture.ContentCaptureManagerInternal; import com.android.server.uri.UriGrantsManagerInternal; @@ -207,7 +206,7 @@ public class ClipboardService extends SystemService { new ClipData.Item(contents)); synchronized(mClipboards) { setPrimaryClipInternal(getClipboard(0), clip, - android.os.Process.SYSTEM_UID); + android.os.Process.SYSTEM_UID, null); } } }); @@ -247,6 +246,8 @@ public class ClipboardService extends SystemService { ClipData primaryClip; /** UID that set {@link #primaryClip}. */ int primaryClipUid = android.os.Process.NOBODY_UID; + /** Package of the app that set {@link #primaryClip}. */ + String mPrimaryClipPackage; final HashSet<String> activePermissionOwners = new HashSet<String>(); @@ -365,7 +366,7 @@ public class ClipboardService extends SystemService { return; } checkDataOwnerLocked(clip, intendingUid); - setPrimaryClipInternal(clip, intendingUid); + setPrimaryClipInternal(clip, intendingUid, callingPackage); } } @@ -378,7 +379,7 @@ public class ClipboardService extends SystemService { intendingUid, intendingUserId)) { return; } - setPrimaryClipInternal(null, intendingUid); + setPrimaryClipInternal(null, intendingUid, callingPackage); } } @@ -509,6 +510,11 @@ public class ClipboardService extends SystemService { } void setPrimaryClipInternal(@Nullable ClipData clip, int uid) { + setPrimaryClipInternal(clip, uid, null); + } + + private void setPrimaryClipInternal( + @Nullable ClipData clip, int uid, @Nullable String sourcePackage) { // Push clipboard to host, if any if (mHostClipboardMonitor != null) { if (clip == null) { @@ -524,7 +530,7 @@ public class ClipboardService extends SystemService { // Update this user final int userId = UserHandle.getUserId(uid); - setPrimaryClipInternal(getClipboard(userId), clip, uid); + setPrimaryClipInternal(getClipboard(userId), clip, uid, sourcePackage); // Update related users List<UserInfo> related = getRelatedProfiles(userId); @@ -558,7 +564,8 @@ public class ClipboardService extends SystemService { final boolean canCopyIntoProfile = !hasRestriction( UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, id); if (canCopyIntoProfile) { - setPrimaryClipInternal(getClipboard(id), clip, uid); + setPrimaryClipInternal( + getClipboard(id), clip, uid, sourcePackage); } } } @@ -568,6 +575,11 @@ public class ClipboardService extends SystemService { void setPrimaryClipInternal(PerUserClipboard clipboard, @Nullable ClipData clip, int uid) { + setPrimaryClipInternal(clipboard, clip, uid, null); + } + + private void setPrimaryClipInternal(PerUserClipboard clipboard, @Nullable ClipData clip, + int uid, @Nullable String sourcePackage) { revokeUris(clipboard); clipboard.activePermissionOwners.clear(); if (clip == null && clipboard.primaryClip == null) { @@ -576,8 +588,10 @@ public class ClipboardService extends SystemService { clipboard.primaryClip = clip; if (clip != null) { clipboard.primaryClipUid = uid; + clipboard.mPrimaryClipPackage = sourcePackage; } else { clipboard.primaryClipUid = android.os.Process.NOBODY_UID; + clipboard.mPrimaryClipPackage = null; } if (clip != null) { final ClipDescription description = clip.getDescription(); @@ -861,29 +875,34 @@ public class ClipboardService extends SystemService { && mAutofillInternal.isAugmentedAutofillServiceForUser(uid, userId)) { return; } - // Load the labels for the calling app and the app that set the clipboard content. - final long ident = Binder.clearCallingIdentity(); + + // Retrieve the app label of the source of the clip data + CharSequence sourceAppLabel = null; + if (clipboard.mPrimaryClipPackage != null) { + try { + sourceAppLabel = mPm.getApplicationLabel( + mPm.getApplicationInfo(clipboard.mPrimaryClipPackage, 0)); + } catch (PackageManager.NameNotFoundException e) { + // leave label as null + } + } + try { - final IPackageManager pm = AppGlobals.getPackageManager(); + CharSequence callingAppLabel = mPm.getApplicationLabel( + mPm.getApplicationInfo(callingPackage, 0)); String message; - final CharSequence callingLabel = mPm.getApplicationLabel( - pm.getApplicationInfo(callingPackage, 0, userId)); - final String[] packagesForUid = pm.getPackagesForUid(clipboard.primaryClipUid); - if (packagesForUid != null && packagesForUid.length > 0) { - final CharSequence clipLabel = mPm.getApplicationLabel( - pm.getApplicationInfo(packagesForUid[0], 0, - UserHandle.getUserId(clipboard.primaryClipUid))); - message = callingLabel + " pasted from " + clipLabel; + if (sourceAppLabel != null) { + message = callingAppLabel + " pasted from " + sourceAppLabel; } else { - message = callingLabel + " pasted from clipboard"; + message = callingAppLabel + " pasted from clipboard"; } Slog.i(TAG, message); - Toast.makeText(getContext(), UiThread.get().getLooper(), message, Toast.LENGTH_SHORT) - .show(); - } catch (RemoteException e) { - /* ignore */ - } finally { - Binder.restoreCallingIdentity(ident); + Binder.withCleanCallingIdentity(() -> + Toast.makeText(getContext(), UiThread.get().getLooper(), message, + Toast.LENGTH_SHORT) + .show()); + } catch (PackageManager.NameNotFoundException e) { + // do nothing } } } diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java index a62f67a743ad..30cbf2745638 100644 --- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java +++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java @@ -61,7 +61,10 @@ public abstract class BrightnessMappingStrategy { private static final Plog PLOG = Plog.createSystemPlog(TAG); @Nullable - public static BrightnessMappingStrategy create(Resources resources) { + public static BrightnessMappingStrategy create(Resources resources, + DisplayDeviceConfig displayDeviceConfig) { + + // Display independent values float[] luxLevels = getLuxLevels(resources.getIntArray( com.android.internal.R.array.config_autoBrightnessLevels)); int[] brightnessLevelsBacklight = resources.getIntArray( @@ -71,32 +74,22 @@ public abstract class BrightnessMappingStrategy { float autoBrightnessAdjustmentMaxGamma = resources.getFraction( com.android.internal.R.fraction.config_autoBrightnessAdjustmentMaxGamma, 1, 1); - - float[] nitsRange = getFloatArray(resources.obtainTypedArray( - com.android.internal.R.array.config_screenBrightnessNits)); - int[] backlightRange = resources.getIntArray( - com.android.internal.R.array.config_screenBrightnessBacklight); - long shortTermModelTimeout = resources.getInteger( com.android.internal.R.integer.config_autoBrightnessShortTermModelTimeout); - if (isValidMapping(nitsRange, backlightRange) + // Display dependent values - used for physical mapping strategy nits -> brightness + final float[] nitsRange = displayDeviceConfig.getNits(); + final float[] brightnessRange = displayDeviceConfig.getBrightness(); + + if (isValidMapping(nitsRange, brightnessRange) && isValidMapping(luxLevels, brightnessLevelsNits)) { - int minimumBacklight = resources.getInteger( - com.android.internal.R.integer.config_screenBrightnessSettingMinimum); - int maximumBacklight = resources.getInteger( - com.android.internal.R.integer.config_screenBrightnessSettingMaximum); - if (backlightRange[0] > minimumBacklight - || backlightRange[backlightRange.length - 1] < maximumBacklight) { - Slog.w(TAG, "Screen brightness mapping does not cover whole range of available " + - "backlight values, autobrightness functionality may be impaired."); - } + BrightnessConfiguration.Builder builder = new BrightnessConfiguration.Builder( luxLevels, brightnessLevelsNits); builder.setShortTermModelTimeoutMillis(shortTermModelTimeout); builder.setShortTermModelLowerLuxMultiplier(SHORT_TERM_MODEL_THRESHOLD_RATIO); builder.setShortTermModelUpperLuxMultiplier(SHORT_TERM_MODEL_THRESHOLD_RATIO); - return new PhysicalMappingStrategy(builder.build(), nitsRange, backlightRange, + return new PhysicalMappingStrategy(builder.build(), nitsRange, brightnessRange, autoBrightnessAdjustmentMaxGamma); } else if (isValidMapping(luxLevels, brightnessLevelsBacklight)) { return new SimpleMappingStrategy(luxLevels, brightnessLevelsBacklight, @@ -264,11 +257,11 @@ public abstract class BrightnessMappingStrategy { public abstract boolean setAutoBrightnessAdjustment(float adjustment); /** - * Converts the provided backlight value to nits if possible. + * Converts the provided brightness value to nits if possible. * - * Returns -1.0f if there's no available mapping for the backlight to nits. + * Returns -1.0f if there's no available mapping for the brightness to nits. */ - public abstract float convertToNits(int backlight); + public abstract float convertToNits(float brightness); /** * Adds a user interaction data point to the brightness mapping. @@ -603,7 +596,7 @@ public abstract class BrightnessMappingStrategy { } @Override - public float convertToNits(int backlight) { + public float convertToNits(float brightness) { return -1.0f; } @@ -701,37 +694,39 @@ public abstract class BrightnessMappingStrategy { // in nits. private Spline mBrightnessSpline; - // A spline mapping from nits to the corresponding backlight value, normalized to the range + // A spline mapping from nits to the corresponding brightness value, normalized to the range // [0, 1.0]. - private Spline mNitsToBacklightSpline; + private Spline mNitsToBrightnessSpline; + + // A spline mapping from the system brightness value, normalized to the range [0, 1.0], to + // a brightness in nits. + private Spline mBrightnessToNitsSpline; // The default brightness configuration. private final BrightnessConfiguration mDefaultConfig; - // A spline mapping from the device's backlight value, normalized to the range [0, 1.0], to - // a brightness in nits. - private Spline mBacklightToNitsSpline; - - private float[] mNits; - private int[] mBacklight; + private final float[] mNits; + private final float[] mBrightness; private boolean mBrightnessRangeAdjustmentApplied; - private float mMaxGamma; + private final float mMaxGamma; private float mAutoBrightnessAdjustment; private float mUserLux; private float mUserBrightness; public PhysicalMappingStrategy(BrightnessConfiguration config, float[] nits, - int[] backlight, float maxGamma) { - Preconditions.checkArgument(nits.length != 0 && backlight.length != 0, - "Nits and backlight arrays must not be empty!"); - Preconditions.checkArgument(nits.length == backlight.length, - "Nits and backlight arrays must be the same length!"); + float[] brightness, float maxGamma) { + + Preconditions.checkArgument(nits.length != 0 && brightness.length != 0, + "Nits and brightness arrays must not be empty!"); + + Preconditions.checkArgument(nits.length == brightness.length, + "Nits and brightness arrays must be the same length!"); Objects.requireNonNull(config); Preconditions.checkArrayElementsInRange(nits, 0, Float.MAX_VALUE, "nits"); - Preconditions.checkArrayElementsInRange(backlight, - PowerManager.BRIGHTNESS_OFF, PowerManager.BRIGHTNESS_ON, "backlight"); + Preconditions.checkArrayElementsInRange(brightness, + PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, "brightness"); mMaxGamma = maxGamma; mAutoBrightnessAdjustment = 0; @@ -739,7 +734,7 @@ public abstract class BrightnessMappingStrategy { mUserBrightness = -1; mNits = nits; - mBacklight = backlight; + mBrightness = brightness; computeNitsBrightnessSplines(mNits); mDefaultConfig = config; @@ -784,15 +779,15 @@ public abstract class BrightnessMappingStrategy { public float getBrightness(float lux, String packageName, @ApplicationInfo.Category int category) { float nits = mBrightnessSpline.interpolate(lux); - float backlight = mNitsToBacklightSpline.interpolate(nits); + float brightness = mNitsToBrightnessSpline.interpolate(nits); // Correct the brightness according to the current application and its category, but - // only if no user data point is set (as this will oevrride the user setting). + // only if no user data point is set (as this will override the user setting). if (mUserLux == -1) { - backlight = correctBrightness(backlight, packageName, category); + brightness = correctBrightness(brightness, packageName, category); } else if (mLoggingEnabled) { Slog.d(TAG, "user point set, correction not applied"); } - return backlight; + return brightness; } @Override @@ -817,8 +812,8 @@ public abstract class BrightnessMappingStrategy { } @Override - public float convertToNits(int backlight) { - return mBacklightToNitsSpline.interpolate(normalizeAbsoluteBrightness(backlight)); + public float convertToNits(float brightness) { + return mBrightnessToNitsSpline.interpolate(brightness); } @Override @@ -884,7 +879,8 @@ public abstract class BrightnessMappingStrategy { pw.println("PhysicalMappingStrategy"); pw.println(" mConfig=" + mConfig); pw.println(" mBrightnessSpline=" + mBrightnessSpline); - pw.println(" mNitsToBacklightSpline=" + mNitsToBacklightSpline); + pw.println(" mNitsToBrightnessSpline=" + mNitsToBrightnessSpline); + pw.println(" mBrightnessToNitsSpline=" + mBrightnessToNitsSpline); pw.println(" mMaxGamma=" + mMaxGamma); pw.println(" mAutoBrightnessAdjustment=" + mAutoBrightnessAdjustment); pw.println(" mUserLux=" + mUserLux); @@ -894,31 +890,25 @@ public abstract class BrightnessMappingStrategy { } private void computeNitsBrightnessSplines(float[] nits) { - final int len = nits.length; - float[] normalizedBacklight = new float[len]; - for (int i = 0; i < len; i++) { - normalizedBacklight[i] = normalizeAbsoluteBrightness(mBacklight[i]); - } - - mNitsToBacklightSpline = Spline.createSpline(nits, normalizedBacklight); - mBacklightToNitsSpline = Spline.createSpline(normalizedBacklight, nits); + mNitsToBrightnessSpline = Spline.createSpline(nits, mBrightness); + mBrightnessToNitsSpline = Spline.createSpline(mBrightness, nits); } private void computeSpline() { Pair<float[], float[]> defaultCurve = mConfig.getCurve(); float[] defaultLux = defaultCurve.first; float[] defaultNits = defaultCurve.second; - float[] defaultBacklight = new float[defaultNits.length]; - for (int i = 0; i < defaultBacklight.length; i++) { - defaultBacklight[i] = mNitsToBacklightSpline.interpolate(defaultNits[i]); + float[] defaultBrightness = new float[defaultNits.length]; + for (int i = 0; i < defaultBrightness.length; i++) { + defaultBrightness[i] = mNitsToBrightnessSpline.interpolate(defaultNits[i]); } - Pair<float[], float[]> curve = getAdjustedCurve(defaultLux, defaultBacklight, mUserLux, + Pair<float[], float[]> curve = getAdjustedCurve(defaultLux, defaultBrightness, mUserLux, mUserBrightness, mAutoBrightnessAdjustment, mMaxGamma); float[] lux = curve.first; - float[] backlight = curve.second; - float[] nits = new float[backlight.length]; + float[] brightness = curve.second; + float[] nits = new float[brightness.length]; for (int i = 0; i < nits.length; i++) { - nits[i] = mBacklightToNitsSpline.interpolate(backlight[i]); + nits[i] = mBrightnessToNitsSpline.interpolate(brightness[i]); } mBrightnessSpline = Spline.createSpline(lux, nits); } @@ -926,7 +916,7 @@ public abstract class BrightnessMappingStrategy { private float getUnadjustedBrightness(float lux) { Pair<float[], float[]> curve = mConfig.getCurve(); Spline spline = Spline.createSpline(curve.first, curve.second); - return mNitsToBacklightSpline.interpolate(spline.interpolate(lux)); + return mNitsToBrightnessSpline.interpolate(spline.interpolate(lux)); } private float correctBrightness(float brightness, String packageName, int category) { diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index 49328f1019c3..0071b2f558c4 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -18,9 +18,12 @@ package com.android.server.display; import android.annotation.NonNull; import android.content.Context; +import android.content.res.Resources; import android.os.Environment; import android.os.PowerManager; +import android.util.MathUtils; import android.util.Slog; +import android.util.Spline; import android.view.DisplayAddress; import com.android.internal.R; @@ -72,15 +75,31 @@ public class DisplayDeviceConfig { private final Context mContext; + // Nits and backlight values that are loaded from either the display device config file, or + // config.xml. These are the raw values and just used for the dumpsys + private float[] mRawNits; + private float[] mRawBacklight; + + // These arrays are calculated from the raw arrays, but clamped to contain values equal to and + // between mBacklightMinimum and mBacklightMaximum. These three arrays should all be the same + // length + // Nits array that is used to store the entire range of nits values that the device supports private float[] mNits; + // Backlight array holds the values that the HAL uses to display the corresponding nits values + private float[] mBacklight; + // Purely an array that covers the ranges of values 0.0 - 1.0, indicating the system brightness + // for the corresponding values above private float[] mBrightness; - private float mBrightnessMinimum = Float.NaN; - private float mBrightnessMaximum = Float.NaN; + + private float mBacklightMinimum = Float.NaN; + private float mBacklightMaximum = Float.NaN; private float mBrightnessDefault = Float.NaN; private float mBrightnessRampFastDecrease = Float.NaN; private float mBrightnessRampFastIncrease = Float.NaN; private float mBrightnessRampSlowDecrease = Float.NaN; private float mBrightnessRampSlowIncrease = Float.NaN; + private Spline mBrightnessToBacklightSpline; + private Spline mBacklightToBrightnessSpline; private List<String> mQuirks; private boolean mIsHighBrightnessModeEnabled = false; private HighBrightnessModeData mHbmData; @@ -167,7 +186,7 @@ public class DisplayDeviceConfig { } /** - * Return the brightness mapping nits array if one is defined in the configuration file. + * Return the brightness mapping nits array. * * @return The brightness mapping nits array. */ @@ -176,22 +195,40 @@ public class DisplayDeviceConfig { } /** - * Return the brightness mapping value array if one is defined in the configuration file. + * Return the brightness mapping backlight array. * - * @return The brightness mapping value array. + * @return The backlight mapping value array. */ - public float[] getBrightness() { - return mBrightness; + public float[] getBacklight() { + return mBacklight; } - public float getBrightnessMinimum() { - return mBrightnessMinimum; + /** + * Calculates the backlight value, as recognised by the HAL, from the brightness value + * given that the rest of the system deals with. + * + * @param brightness value on the framework scale of 0-1 + * @return backlight value on the HAL scale of 0-1 + */ + public float getBacklightFromBrightness(float brightness) { + return mBrightnessToBacklightSpline.interpolate(brightness); } - public float getBrightnessMaximum() { - return mBrightnessMaximum; + /** + * Return an array of equal length to backlight and nits, that covers the entire system + * brightness range of 0.0-1.0. + * + * @return brightness array + */ + public float[] getBrightness() { + return mBrightness; } + /** + * Return the default brightness on a scale of 0.0f - 1.0f + * + * @return default brightness + */ public float getBrightnessDefault() { return mBrightnessDefault; } @@ -237,10 +274,15 @@ public class DisplayDeviceConfig { @Override public String toString() { String str = "DisplayDeviceConfig{" - + "mBrightness=" + Arrays.toString(mBrightness) + + "mBacklight=" + Arrays.toString(mBacklight) + ", mNits=" + Arrays.toString(mNits) - + ", mBrightnessMinimum=" + mBrightnessMinimum - + ", mBrightnessMaximum=" + mBrightnessMaximum + + ", mRawBacklight=" + Arrays.toString(mRawBacklight) + + ", mRawNits=" + Arrays.toString(mRawNits) + + ", mBrightness=" + Arrays.toString(mBrightness) + + ", mBrightnessToBacklightSpline=" + mBrightnessToBacklightSpline + + ", mBacklightToBrightnessSpline=" + mBacklightToBrightnessSpline + + ", mBacklightMinimum=" + mBacklightMinimum + + ", mBacklightMaximum=" + mBacklightMaximum + ", mBrightnessDefault=" + mBrightnessDefault + ", mQuirks=" + mQuirks + ", isHbmEnabled=" + mIsHighBrightnessModeEnabled @@ -253,10 +295,6 @@ public class DisplayDeviceConfig { return str; } - private float getMaxBrightness() { - return mBrightness[mBrightness.length - 1]; - } - private static DisplayDeviceConfig getConfigFromSuffix(Context context, File baseDirectory, String suffixFormat, long idNumber) { @@ -264,7 +302,6 @@ public class DisplayDeviceConfig { final String filename = String.format(CONFIG_FILE_FORMAT, suffix); final File filePath = Environment.buildPath( baseDirectory, ETC_DIR, DISPLAY_CONFIG_DIR, filename); - if (filePath.exists()) { final DisplayDeviceConfig config = new DisplayDeviceConfig(context); config.initFromFile(filePath); @@ -299,9 +336,9 @@ public class DisplayDeviceConfig { try (InputStream in = new BufferedInputStream(new FileInputStream(configFile))) { final DisplayConfiguration config = XmlParser.read(in); if (config != null) { - loadBrightnessMap(config); loadBrightnessDefaultFromDdcXml(config); loadBrightnessConstraintsFromConfigXml(); + loadBrightnessMap(config); loadHighBrightnessModeData(config); loadQuirks(config); loadBrightnessRamps(config); @@ -318,13 +355,20 @@ public class DisplayDeviceConfig { // If no ddc exists, use config.xml loadBrightnessDefaultFromConfigXml(); loadBrightnessConstraintsFromConfigXml(); + loadBrightnessMapFromConfigXml(); loadBrightnessRampsFromConfigXml(); } private void initFromPmValues() { - mBrightnessMinimum = PowerManager.BRIGHTNESS_MIN; - mBrightnessMaximum = PowerManager.BRIGHTNESS_MAX; + // Set all to basic values + mBacklightMinimum = PowerManager.BRIGHTNESS_MIN; + mBacklightMaximum = PowerManager.BRIGHTNESS_MAX; mBrightnessDefault = BRIGHTNESS_DEFAULT; + mBrightnessRampFastDecrease = PowerManager.BRIGHTNESS_MAX; + mBrightnessRampFastIncrease = PowerManager.BRIGHTNESS_MAX; + mBrightnessRampSlowDecrease = PowerManager.BRIGHTNESS_MAX; + mBrightnessRampSlowIncrease = PowerManager.BRIGHTNESS_MAX; + setSimpleMappingStrategyValues(); } private void loadBrightnessDefaultFromDdcXml(DisplayConfiguration config) { @@ -364,24 +408,27 @@ public class DisplayDeviceConfig { final float max = mContext.getResources().getFloat(com.android.internal.R.dimen .config_screenBrightnessSettingMaximumFloat); if (min == INVALID_BRIGHTNESS_IN_CONFIG || max == INVALID_BRIGHTNESS_IN_CONFIG) { - mBrightnessMinimum = BrightnessSynchronizer.brightnessIntToFloat( + mBacklightMinimum = BrightnessSynchronizer.brightnessIntToFloat( mContext.getResources().getInteger(com.android.internal.R.integer .config_screenBrightnessSettingMinimum)); - mBrightnessMaximum = BrightnessSynchronizer.brightnessIntToFloat( + mBacklightMaximum = BrightnessSynchronizer.brightnessIntToFloat( mContext.getResources().getInteger(com.android.internal.R.integer .config_screenBrightnessSettingMaximum)); } else { - mBrightnessMinimum = min; - mBrightnessMaximum = max; + mBacklightMinimum = min; + mBacklightMaximum = max; } } private void loadBrightnessMap(DisplayConfiguration config) { final NitsMap map = config.getScreenBrightnessMap(); - // Map may not exist in config file + // Map may not exist in display device config if (map == null) { + loadBrightnessMapFromConfigXml(); return; } + + // Use the (preferred) display device config mapping final List<Point> points = map.getPoint(); final int size = points.size(); @@ -408,8 +455,123 @@ public class DisplayDeviceConfig { } ++i; } - mNits = nits; - mBrightness = backlight; + mRawNits = nits; + mRawBacklight = backlight; + constrainNitsAndBacklightArrays(); + } + + private void loadBrightnessMapFromConfigXml() { + // Use the config.xml mapping + final Resources res = mContext.getResources(); + final float[] sysNits = BrightnessMappingStrategy.getFloatArray(res.obtainTypedArray( + com.android.internal.R.array.config_screenBrightnessNits)); + final int[] sysBrightness = res.getIntArray( + com.android.internal.R.array.config_screenBrightnessBacklight); + final float[] sysBrightnessFloat = new float[sysBrightness.length]; + + for (int i = 0; i < sysBrightness.length; i++) { + sysBrightnessFloat[i] = BrightnessSynchronizer.brightnessIntToFloat( + sysBrightness[i]); + } + + // These arrays are allowed to be empty, we set null values so that + // BrightnessMappingStrategy will create a SimpleMappingStrategy instead. + if (sysBrightnessFloat.length == 0 || sysNits.length == 0) { + setSimpleMappingStrategyValues(); + return; + } + + mRawNits = sysNits; + mRawBacklight = sysBrightnessFloat; + constrainNitsAndBacklightArrays(); + } + + private void setSimpleMappingStrategyValues() { + // No translation from backlight to brightness should occur if we are using a + // SimpleMappingStrategy (ie they should be the same) so the splines are + // set to be linear, between 0.0 and 1.0 + mNits = null; + mBacklight = null; + float[] simpleMappingStrategyArray = new float[]{0.0f, 1.0f}; + mBrightnessToBacklightSpline = Spline.createSpline(simpleMappingStrategyArray, + simpleMappingStrategyArray); + mBacklightToBrightnessSpline = Spline.createSpline(simpleMappingStrategyArray, + simpleMappingStrategyArray); + } + + /** + * Change the nits and backlight arrays, so that they cover only the allowed backlight values + * Use the brightness minimum and maximum values to clamp these arrays. + */ + private void constrainNitsAndBacklightArrays() { + if (mRawBacklight[0] > mBacklightMinimum + || mRawBacklight[mRawBacklight.length - 1] < mBacklightMaximum + || mBacklightMinimum > mBacklightMaximum) { + throw new IllegalStateException("Min or max values are invalid" + + "; raw min=" + mRawBacklight[0] + + "; raw max=" + mRawBacklight[mRawBacklight.length - 1] + + "; backlight min=" + mBacklightMinimum + + "; backlight max=" + mBacklightMaximum); + } + + float[] newNits = new float[mRawBacklight.length]; + float[] newBacklight = new float[mRawBacklight.length]; + // Find the starting index of the clamped arrays. This may be less than the min so + // we'll need to clamp this value still when actually doing the remapping. + int newStart = 0; + for (int i = 0; i < mRawBacklight.length - 1; i++) { + if (mRawBacklight[i + 1] > mBacklightMinimum) { + newStart = i; + break; + } + } + + boolean isLastValue = false; + int newIndex = 0; + for (int i = newStart; i < mRawBacklight.length && !isLastValue; i++) { + newIndex = i - newStart; + final float newBacklightVal; + final float newNitsVal; + isLastValue = mRawBacklight[i] > mBacklightMaximum + || i >= mRawBacklight.length - 1; + // Clamp beginning and end to valid backlight values. + if (newIndex == 0) { + newBacklightVal = MathUtils.max(mRawBacklight[i], mBacklightMinimum); + newNitsVal = rawBacklightToNits(i, newBacklightVal); + } else if (isLastValue) { + newBacklightVal = MathUtils.min(mRawBacklight[i], mBacklightMaximum); + newNitsVal = rawBacklightToNits(i - 1, newBacklightVal); + } else { + newBacklightVal = mRawBacklight[i]; + newNitsVal = mRawNits[i]; + } + newBacklight[newIndex] = newBacklightVal; + newNits[newIndex] = newNitsVal; + } + mBacklight = Arrays.copyOf(newBacklight, newIndex + 1); + mNits = Arrays.copyOf(newNits, newIndex + 1); + createBacklightConversionSplines(); + } + + private float rawBacklightToNits(int i, float backlight) { + return MathUtils.map(mRawBacklight[i], mRawBacklight[i + 1], + mRawNits[i], mRawNits[i + 1], backlight); + } + + // This method creates a brightness spline that is of equal length with proportional increments + // to the backlight spline. The values of this array range from 0.0f to 1.0f instead of the + // potential constrained range that the backlight array covers + // These splines are used to convert from the system brightness value to the HAL backlight + // value + private void createBacklightConversionSplines() { + mBrightness = new float[mBacklight.length]; + for (int i = 0; i < mBrightness.length; i++) { + mBrightness[i] = MathUtils.map(mBacklight[0], + mBacklight[mBacklight.length - 1], + PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, mBacklight[i]); + } + mBrightnessToBacklightSpline = Spline.createSpline(mBrightness, mBacklight); + mBacklightToBrightnessSpline = Spline.createSpline(mBacklight, mBrightness); } private void loadQuirks(DisplayConfiguration config) { @@ -425,12 +587,14 @@ public class DisplayDeviceConfig { mIsHighBrightnessModeEnabled = hbm.getEnabled(); mHbmData = new HighBrightnessModeData(); mHbmData.minimumLux = hbm.getMinimumLux_all().floatValue(); - mHbmData.transitionPoint = hbm.getTransitionPoint_all().floatValue(); - if (mHbmData.transitionPoint >= getMaxBrightness()) { + float transitionPointBacklightScale = hbm.getTransitionPoint_all().floatValue(); + if (transitionPointBacklightScale >= mBacklightMaximum) { throw new IllegalArgumentException("HBM transition point invalid. " + mHbmData.transitionPoint + " is not less than " - + getMaxBrightness()); + + mBacklightMaximum); } + mHbmData.transitionPoint = + mBacklightToBrightnessSpline.interpolate(transitionPointBacklightScale); final HbmTiming hbmTiming = hbm.getTiming_all(); mHbmData.timeWindowMillis = hbmTiming.getTimeWindowSecs_all().longValue() * 1000; mHbmData.timeMaxMillis = hbmTiming.getTimeMaxSecs_all().longValue() * 1000; diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 62cf86b31180..86142bc1797d 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -190,12 +190,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // The dim screen brightness. private final float mScreenBrightnessDimConfig; - // The minimum allowed brightness. - private final float mScreenBrightnessRangeMinimum; - - // The maximum allowed brightness. - private final float mScreenBrightnessRangeMaximum; - private final float mScreenBrightnessDefault; // The minimum allowed brightness while in VR. @@ -443,8 +437,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call final Resources resources = context.getResources(); - final float screenBrightnessSettingMinimumFloat = clampAbsoluteBrightness( - pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM)); // DOZE AND DIM SETTINGS mScreenBrightnessDozeConfig = clampAbsoluteBrightness( @@ -453,10 +445,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DIM)); // NORMAL SCREEN SETTINGS - mScreenBrightnessRangeMinimum = - Math.min(screenBrightnessSettingMinimumFloat, mScreenBrightnessDimConfig); - mScreenBrightnessRangeMaximum = clampAbsoluteBrightness( - pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM)); mScreenBrightnessDefault = clampAbsoluteBrightness( mLogicalDisplay.getDisplayInfoLocked().brightnessDefault); @@ -481,18 +469,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call DisplayDeviceConfig displayDeviceConfig = logicalDisplay .getPrimaryDisplayDeviceLocked().getDisplayDeviceConfig(); - // TODO: (b/178183143) Ensure that the ddc is not null - if (displayDeviceConfig != null) { - mBrightnessRampRateFastDecrease = displayDeviceConfig.getBrightnessRampFastDecrease(); - mBrightnessRampRateFastIncrease = displayDeviceConfig.getBrightnessRampFastIncrease(); - mBrightnessRampRateSlowDecrease = displayDeviceConfig.getBrightnessRampSlowDecrease(); - mBrightnessRampRateSlowIncrease = displayDeviceConfig.getBrightnessRampSlowIncrease(); - } else { - mBrightnessRampRateFastDecrease = 1.0f; - mBrightnessRampRateFastIncrease = 1.0f; - mBrightnessRampRateSlowDecrease = 1.0f; - mBrightnessRampRateSlowIncrease = 1.0f; - } + mBrightnessRampRateFastDecrease = displayDeviceConfig.getBrightnessRampFastDecrease(); + mBrightnessRampRateFastIncrease = displayDeviceConfig.getBrightnessRampFastIncrease(); + mBrightnessRampRateSlowDecrease = displayDeviceConfig.getBrightnessRampSlowDecrease(); + mBrightnessRampRateSlowIncrease = displayDeviceConfig.getBrightnessRampSlowIncrease(); mSkipScreenOnBrightnessRamp = resources.getBoolean( com.android.internal.R.bool.config_skipScreenOnBrightnessRamp); @@ -545,12 +525,14 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call com.android.internal.R.string.config_displayLightSensorType); Sensor lightSensor = findDisplayLightSensor(lightSensorType); - mBrightnessMapper = BrightnessMappingStrategy.create(resources); + final DisplayDeviceConfig ddc = + logicalDisplay.getPrimaryDisplayDeviceLocked().getDisplayDeviceConfig(); + mBrightnessMapper = BrightnessMappingStrategy.create(resources, ddc); if (mBrightnessMapper != null) { mAutomaticBrightnessController = new AutomaticBrightnessController(this, handler.getLooper(), sensorManager, lightSensor, mBrightnessMapper, - lightSensorWarmUpTimeConfig, mScreenBrightnessRangeMinimum, - mScreenBrightnessRangeMaximum, dozeScaleFactor, lightSensorRate, + lightSensorWarmUpTimeConfig, PowerManager.BRIGHTNESS_MIN, + PowerManager.BRIGHTNESS_MAX, dozeScaleFactor, lightSensorRate, initialLightSensorRate, brighteningLightDebounce, darkeningLightDebounce, autoBrightnessResetAmbientLuxAfterWarmUp, ambientBrightnessThresholds, screenBrightnessThresholds, logicalDisplay, context); @@ -838,9 +820,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call noteScreenBrightness(mPowerState.getScreenBrightness()); // Initialize all of the brightness tracking state - final float brightness = convertToNits(BrightnessSynchronizer.brightnessFloatToInt( - mPowerState.getScreenBrightness())); - if (brightness >= 0.0f) { + final float brightness = convertToNits(mPowerState.getScreenBrightness()); + if (brightness >= PowerManager.BRIGHTNESS_MIN) { mBrightnessTracker.start(brightness); } @@ -1151,10 +1132,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // Apply dimming by at least some minimum amount when user activity // timeout is about to expire. if (mPowerRequest.policy == DisplayPowerRequest.POLICY_DIM) { - if (brightnessState > mScreenBrightnessRangeMinimum) { + if (brightnessState > PowerManager.BRIGHTNESS_MIN) { brightnessState = Math.max(Math.min(brightnessState - SCREEN_DIM_MINIMUM_REDUCTION_FLOAT, - mScreenBrightnessDimConfig), mScreenBrightnessRangeMinimum); + mScreenBrightnessDimConfig), PowerManager.BRIGHTNESS_MIN); mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_DIMMED); } if (!mAppliedDimming) { @@ -1168,12 +1149,11 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // If low power mode is enabled, scale brightness by screenLowPowerBrightnessFactor // as long as it is above the minimum threshold. if (mPowerRequest.lowPowerMode) { - if (brightnessState > mScreenBrightnessRangeMinimum) { + if (brightnessState > PowerManager.BRIGHTNESS_MIN) { final float brightnessFactor = Math.min(mPowerRequest.screenLowPowerBrightnessFactor, 1); final float lowPowerBrightnessFloat = (brightnessState * brightnessFactor); - brightnessState = Math.max(lowPowerBrightnessFloat, - mScreenBrightnessRangeMinimum); + brightnessState = Math.max(lowPowerBrightnessFloat, PowerManager.BRIGHTNESS_MIN); mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_LOW_POWER); } if (!mAppliedLowPower) { @@ -1258,9 +1238,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // slider event so notify as if the system changed the brightness. userInitiatedChange = false; } - notifyBrightnessChanged( - BrightnessSynchronizer.brightnessFloatToInt(brightnessState), - userInitiatedChange, hadUserBrightnessPoint); + notifyBrightnessChanged(brightnessState, userInitiatedChange, + hadUserBrightnessPoint); } } @@ -1487,17 +1466,17 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private float clampScreenBrightness(float value) { if (Float.isNaN(value)) { - return mScreenBrightnessRangeMinimum; + return PowerManager.BRIGHTNESS_MIN; } return MathUtils.constrain( - value, mScreenBrightnessRangeMinimum, mScreenBrightnessRangeMaximum); + value, PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX); } // Checks whether the brightness is within the valid brightness range, not including the off or // invalid states. private boolean isValidBrightnessValue(float brightnessState) { - return brightnessState >= mScreenBrightnessRangeMinimum - && brightnessState <= mScreenBrightnessRangeMaximum; + return brightnessState >= PowerManager.BRIGHTNESS_MIN + && brightnessState <= PowerManager.BRIGHTNESS_MAX; } private void animateScreenBrightness(float target, float rate) { @@ -1874,7 +1853,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call return true; } - private void notifyBrightnessChanged(int brightness, boolean userInitiated, + private void notifyBrightnessChanged(float brightness, boolean userInitiated, boolean hadUserDataPoint) { final float brightnessInNits = convertToNits(brightness); if (mPowerRequest.useAutoBrightness && brightnessInNits >= 0.0f @@ -1891,9 +1870,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } } - private float convertToNits(int backlight) { + private float convertToNits(float brightness) { if (mBrightnessMapper != null) { - return mBrightnessMapper.convertToNits(backlight); + return mBrightnessMapper.convertToNits(brightness); } else { return -1.0f; } @@ -1972,12 +1951,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call pw.println(); pw.println("Display Power Controller Configuration:"); - pw.println(" mScreenBrightnessRangeMinimum=" + mScreenBrightnessRangeMinimum); - pw.println(" mScreenBrightnessRangeMaximum=" + mScreenBrightnessRangeMaximum); pw.println(" mScreenBrightnessRangeDefault=" + mScreenBrightnessDefault); pw.println(" mScreenBrightnessDozeConfig=" + mScreenBrightnessDozeConfig); pw.println(" mScreenBrightnessDimConfig=" + mScreenBrightnessDimConfig); - pw.println(" mScreenBrightnessDefault=" + mScreenBrightnessDefault); pw.println(" mScreenBrightnessForVrRangeMinimum=" + mScreenBrightnessForVrRangeMinimum); pw.println(" mScreenBrightnessForVrRangeMaximum=" + mScreenBrightnessForVrRangeMaximum); pw.println(" mScreenBrightnessForVrDefault=" + mScreenBrightnessForVrDefault); @@ -2109,10 +2085,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } } - private static int clampAbsoluteBrightness(int value) { - return MathUtils.constrain(value, PowerManager.BRIGHTNESS_OFF, PowerManager.BRIGHTNESS_ON); - } - private static float clampAbsoluteBrightness(float value) { return MathUtils.constrain(value, PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX); diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index 5b2b3366b117..3709963b7caa 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -30,7 +30,6 @@ import android.os.Trace; import android.util.LongSparseArray; import android.util.Slog; import android.util.SparseArray; -import android.util.Spline; import android.view.Display; import android.view.DisplayAddress; import android.view.DisplayCutout; @@ -210,8 +209,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { private SurfaceControl.DisplayMode[] mSfDisplayModes; // The active display mode in SurfaceFlinger private SurfaceControl.DisplayMode mActiveSfDisplayMode; - private Spline mSystemBrightnessToNits; - private Spline mNitsToHalBrightness; + private DisplayDeviceConfig mDisplayDeviceConfig; private DisplayEventReceiver.FrameRateOverride[] mFrameRateOverrides = new DisplayEventReceiver.FrameRateOverride[0]; @@ -410,16 +408,13 @@ final class LocalDisplayAdapter extends DisplayAdapter { @Override public DisplayDeviceConfig getDisplayDeviceConfig() { if (mDisplayDeviceConfig == null) { - loadDisplayConfiguration(); + loadDisplayDeviceConfig(); } return mDisplayDeviceConfig; } - private void loadDisplayConfiguration() { - Spline nitsToHal = null; - Spline sysToNits = null; - - // Load the mapping from nits to HAL brightness range (display-device-config.xml) + private void loadDisplayDeviceConfig() { + // Load display device config final Context context = getOverlayContext(); mDisplayDeviceConfig = DisplayDeviceConfig.create(context, mPhysicalDisplayId, mIsDefaultDisplay); @@ -427,33 +422,9 @@ final class LocalDisplayAdapter extends DisplayAdapter { return; } + // Load brightness HWC quirk mBacklightAdapter.setForceSurfaceControl(mDisplayDeviceConfig.hasQuirk( DisplayDeviceConfig.QUIRK_CAN_SET_BRIGHTNESS_VIA_HWC)); - - final float[] halNits = mDisplayDeviceConfig.getNits(); - final float[] halBrightness = mDisplayDeviceConfig.getBrightness(); - if (halNits == null || halBrightness == null) { - return; - } - nitsToHal = Spline.createSpline(halNits, halBrightness); - - // Load the mapping from system brightness range to nits (config.xml) - final Resources res = context.getResources(); - final float[] sysNits = BrightnessMappingStrategy.getFloatArray(res.obtainTypedArray( - com.android.internal.R.array.config_screenBrightnessNits)); - final int[] sysBrightness = res.getIntArray( - com.android.internal.R.array.config_screenBrightnessBacklight); - if (sysNits.length == 0 || sysBrightness.length != sysNits.length) { - return; - } - final float[] sysBrightnessFloat = new float[sysBrightness.length]; - for (int i = 0; i < sysBrightness.length; i++) { - sysBrightnessFloat[i] = sysBrightness[i]; - } - sysToNits = Spline.createSpline(sysBrightnessFloat, sysNits); - - mNitsToHalBrightness = nitsToHal; - mSystemBrightnessToNits = sysToNits; } private boolean updateStaticInfo(SurfaceControl.StaticDisplayInfo info) { @@ -665,15 +636,9 @@ final class LocalDisplayAdapter extends DisplayAdapter { // The display is trusted since it is created by system. mInfo.flags |= DisplayDeviceInfo.FLAG_TRUSTED; - if (mDisplayDeviceConfig != null) { - mInfo.brightnessMinimum = mDisplayDeviceConfig.getBrightnessMinimum(); - mInfo.brightnessMaximum = mDisplayDeviceConfig.getBrightnessMaximum(); - mInfo.brightnessDefault = mDisplayDeviceConfig.getBrightnessDefault(); - } else { - mInfo.brightnessMinimum = PowerManager.BRIGHTNESS_MIN; - mInfo.brightnessMaximum = PowerManager.BRIGHTNESS_MAX; - mInfo.brightnessDefault = 0.5f; - } + mInfo.brightnessMinimum = PowerManager.BRIGHTNESS_MIN; + mInfo.brightnessMaximum = PowerManager.BRIGHTNESS_MAX; + mInfo.brightnessDefault = getDisplayDeviceConfig().getBrightnessDefault(); } return mInfo; } @@ -811,8 +776,8 @@ final class LocalDisplayAdapter extends DisplayAdapter { Trace.traceBegin(Trace.TRACE_TAG_POWER, "setDisplayBrightness(" + "id=" + physicalDisplayId + ", brightness=" + brightness + ")"); try { - brightness = displayBrightnessToHalBrightness(brightness); - mBacklightAdapter.setBrightness(brightness); + float backlight = brightnessToBacklight(brightness); + mBacklightAdapter.setBacklight(backlight); Trace.traceCounter(Trace.TRACE_TAG_POWER, "ScreenBrightness", BrightnessSynchronizer.brightnessFloatToInt(brightness)); @@ -821,35 +786,8 @@ final class LocalDisplayAdapter extends DisplayAdapter { } } - /** - * Converts brightness range from the framework's brightness space to the - * Hal brightness space if the HAL brightness space has been provided via - * a display device configuration file. - */ - private float displayBrightnessToHalBrightness(float brightness) { - // TODO: b/171380847 - This needs to be deprecated. The nits-to-brightness - // relationship should be specified in display-config OR config.xml, but not - // both, and no nits-space conversion should be necessary. - // - // Only do a conversion if there exists a unique system brightness and a - // unique HAL brightness-to-nits range defined. - if (mSystemBrightnessToNits == null || mNitsToHalBrightness == null) { - return brightness; - } - - // Sys brightness in this conversion is always specified in the old 1-255 - // range, so convert that here before the translation. - final float brightnessInt = - BrightnessSynchronizer.brightnessFloatToIntRange(brightness); - - if (BrightnessSynchronizer.floatEquals( - brightnessInt, PowerManager.BRIGHTNESS_OFF)) { - return PowerManager.BRIGHTNESS_OFF_FLOAT; - } - - final float nits = mSystemBrightnessToNits.interpolate(brightnessInt); - final float halBrightness = mNitsToHalBrightness.interpolate(nits); - return halBrightness; + private float brightnessToBacklight(float brightness) { + return getDisplayDeviceConfig().getBacklightFromBrightness(brightness); } }; } @@ -1338,11 +1276,12 @@ final class LocalDisplayAdapter extends DisplayAdapter { } } - void setBrightness(float brightness) { + // Set backlight within min and max backlight values + void setBacklight(float backlight) { if (mUseSurfaceControlBrightness || mForceSurfaceControl) { - mSurfaceControlProxy.setDisplayBrightness(mDisplayToken, brightness); + mSurfaceControlProxy.setDisplayBrightness(mDisplayToken, backlight); } else if (mBacklight != null) { - mBacklight.setBrightness(brightness); + mBacklight.setBrightness(backlight); } } diff --git a/services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java b/services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java index d514aab31e8d..62337c7df03b 100644 --- a/services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java +++ b/services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java @@ -46,7 +46,7 @@ import java.util.Set; private static final String ATTR_VALUE = "value"; /* package */ static class Config { - public long lastModifiedDate; + public long lastModifiedMillis; public final Set<String> updatedFontDirs = new ArraySet<>(); public final List<FontConfig.FontFamily> fontFamilies = new ArrayList<>(); } @@ -73,7 +73,7 @@ import java.util.Set; } else if (depth == 2) { switch (tag) { case TAG_LAST_MODIFIED_DATE: - out.lastModifiedDate = parseLongAttribute(parser, ATTR_VALUE, 0); + out.lastModifiedMillis = parseLongAttribute(parser, ATTR_VALUE, 0); break; case TAG_UPDATED_FONT_DIR: out.updatedFontDirs.add(getAttribute(parser, ATTR_VALUE)); @@ -101,7 +101,7 @@ import java.util.Set; out.startTag(null, TAG_ROOT); out.startTag(null, TAG_LAST_MODIFIED_DATE); - out.attribute(null, ATTR_VALUE, Long.toString(config.lastModifiedDate)); + out.attribute(null, ATTR_VALUE, Long.toString(config.lastModifiedMillis)); out.endTag(null, TAG_LAST_MODIFIED_DATE); for (String dir : config.updatedFontDirs) { out.startTag(null, TAG_UPDATED_FONT_DIR); diff --git a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java index 08ddc6ddf4ae..86dbe86f85ee 100644 --- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java +++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java @@ -112,7 +112,7 @@ final class UpdatableFontDir { private final File mConfigFile; private final File mTmpConfigFile; - private long mLastModifiedDate; + private long mLastModifiedMillis; private int mConfigVersion = 1; /** @@ -147,7 +147,7 @@ final class UpdatableFontDir { /* package */ void loadFontFileMap() { mFontFileInfoMap.clear(); mFontFamilyMap.clear(); - mLastModifiedDate = 0; + mLastModifiedMillis = 0; boolean success = false; try { PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config(); @@ -157,7 +157,7 @@ final class UpdatableFontDir { Slog.e(TAG, "Failed to load config xml file", e); return; } - mLastModifiedDate = config.lastModifiedDate; + mLastModifiedMillis = config.lastModifiedMillis; File[] dirs = mFilesDir.listFiles(); if (dirs == null) return; @@ -200,7 +200,7 @@ final class UpdatableFontDir { if (!success) { mFontFileInfoMap.clear(); mFontFamilyMap.clear(); - mLastModifiedDate = 0; + mLastModifiedMillis = 0; FileUtils.deleteContents(mFilesDir); } } @@ -211,7 +211,7 @@ final class UpdatableFontDir { FileUtils.deleteContents(mFilesDir); mFontFamilyMap.clear(); - mLastModifiedDate = Instant.now().getEpochSecond(); + mLastModifiedMillis = System.currentTimeMillis(); try (FileOutputStream fos = new FileOutputStream(mConfigFile)) { PersistentSystemFontConfig.writeToXml(fos, createPersistentConfig()); } catch (Exception e) { @@ -231,7 +231,7 @@ final class UpdatableFontDir { // Backup the mapping for rollback. ArrayMap<String, FontFileInfo> backupMap = new ArrayMap<>(mFontFileInfoMap); ArrayMap<String, FontConfig.FontFamily> backupFamilies = new ArrayMap<>(mFontFamilyMap); - long backupLastModifiedDate = mLastModifiedDate; + long backupLastModifiedDate = mLastModifiedMillis; boolean success = false; try { for (FontUpdateRequest request : requests) { @@ -247,7 +247,7 @@ final class UpdatableFontDir { } // Write config file. - mLastModifiedDate = Instant.now().getEpochSecond(); + mLastModifiedMillis = Instant.now().getEpochSecond(); try (FileOutputStream fos = new FileOutputStream(mTmpConfigFile)) { PersistentSystemFontConfig.writeToXml(fos, createPersistentConfig()); } catch (Exception e) { @@ -269,7 +269,7 @@ final class UpdatableFontDir { mFontFileInfoMap.putAll(backupMap); mFontFamilyMap.clear(); mFontFamilyMap.putAll(backupFamilies); - mLastModifiedDate = backupLastModifiedDate; + mLastModifiedMillis = backupLastModifiedDate; } } } @@ -527,7 +527,7 @@ final class UpdatableFontDir { private PersistentSystemFontConfig.Config createPersistentConfig() { PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config(); - config.lastModifiedDate = mLastModifiedDate; + config.lastModifiedMillis = mLastModifiedMillis; for (FontFileInfo info : mFontFileInfoMap.values()) { config.updatedFontDirs.add(info.getRandomizedFontDir().getName()); } @@ -560,7 +560,7 @@ final class UpdatableFontDir { // which will be used as a fallback font without being overridden. mergedFamilies.addAll(mFontFamilyMap.values()); return new FontConfig( - mergedFamilies, config.getAliases(), mLastModifiedDate, mConfigVersion); + mergedFamilies, config.getAliases(), mLastModifiedMillis, mConfigVersion); } /* package */ int getConfigVersion() { diff --git a/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java b/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java index a7f34ed85e0d..4c4c9783fab0 100644 --- a/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java +++ b/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java @@ -259,7 +259,7 @@ final class HotplugDetectionAction extends HdmiCecFeatureAction { } private void mayDisableSystemAudioAndARC(int address) { - if (HdmiUtils.isEligibleAddressForDevice(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM, address)) { + if (!HdmiUtils.isEligibleAddressForDevice(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM, address)) { return; } diff --git a/services/core/java/com/android/server/input/InputShellCommand.java b/services/core/java/com/android/server/input/InputShellCommand.java index d08980cfbf24..720be826fd38 100644 --- a/services/core/java/com/android/server/input/InputShellCommand.java +++ b/services/core/java/com/android/server/input/InputShellCommand.java @@ -29,6 +29,7 @@ import android.view.MotionEvent; import android.view.ViewConfiguration; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.HashMap; import java.util.Map; @@ -199,6 +200,8 @@ public class InputShellCommand extends ShellCommand { runRoll(inputSource, displayId); } else if ("motionevent".equals(arg)) { runMotionEvent(inputSource, displayId); + } else if ("keycombination".equals(arg)) { + runKeyCombination(inputSource, displayId); } else { handleDefaultCommands(arg); } @@ -224,7 +227,7 @@ public class InputShellCommand extends ShellCommand { out.println(); out.println("The commands and default sources are:"); out.println(" text <string> (Default: touchscreen)"); - out.println(" keyevent [--longpress] <key code number or name> ..." + out.println(" keyevent [--longpress|--doubletap] <key code number or name> ..." + " (Default: keyboard)"); out.println(" tap <x> <y> (Default: touchscreen)"); out.println(" swipe <x1> <y1> <x2> <y2> [duration(ms)]" @@ -234,6 +237,8 @@ public class InputShellCommand extends ShellCommand { out.println(" press (Default: trackball)"); out.println(" roll <dx> <dy> (Default: trackball)"); out.println(" motionevent <DOWN|UP|MOVE|CANCEL> <x> <y> (Default: touchscreen)"); + out.println(" keycombination <key code 1> <key code 2> ..." + + " (Default: keyboard)"); } } @@ -282,6 +287,14 @@ public class InputShellCommand extends ShellCommand { final boolean longpress = "--longpress".equals(arg); if (longpress) { arg = getNextArgRequired(); + } else { + final boolean doubleTap = "--doubletap".equals(arg); + if (doubleTap) { + arg = getNextArgRequired(); + final int keycode = KeyEvent.keyCodeFromString(arg); + sendKeyDoubleTap(inputSource, keycode, displayId); + return; + } } do { @@ -292,22 +305,32 @@ public class InputShellCommand extends ShellCommand { private void sendKeyEvent(int inputSource, int keyCode, boolean longpress, int displayId) { final long now = SystemClock.uptimeMillis(); - int repeatCount = 0; - KeyEvent event = new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyCode, repeatCount, + KeyEvent event = new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyCode, 0 /* repeatCount */, 0 /*metaState*/, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/, 0 /*flags*/, inputSource); event.setDisplayId(displayId); injectKeyEvent(event); if (longpress) { - repeatCount++; - injectKeyEvent(KeyEvent.changeTimeRepeat(event, now, repeatCount, + // Some long press behavior would check the event time, we set a new event time here. + final long nextEventTime = now + ViewConfiguration.getGlobalActionKeyTimeout(); + injectKeyEvent(KeyEvent.changeTimeRepeat(event, nextEventTime, 1 /* repeatCount */, KeyEvent.FLAG_LONG_PRESS)); } injectKeyEvent(KeyEvent.changeAction(event, KeyEvent.ACTION_UP)); } + private void sendKeyDoubleTap(int inputSource, int keyCode, int displayId) { + sendKeyEvent(inputSource, keyCode, false, displayId); + try { + Thread.sleep(ViewConfiguration.getDoubleTapMinTime()); + } catch (InterruptedException e) { + e.printStackTrace(); + } + sendKeyEvent(inputSource, keyCode, false, displayId); + } + private void runTap(int inputSource, int displayId) { inputSource = getSource(inputSource, InputDevice.SOURCE_TOUCHSCREEN); sendTap(inputSource, Float.parseFloat(getNextArgRequired()), @@ -440,4 +463,59 @@ public class InputShellCommand extends ShellCommand { final long now = SystemClock.uptimeMillis(); injectMotionEvent(inputSource, action, now, now, x, y, pressure, displayId); } + + private void runKeyCombination(int inputSource, int displayId) { + String arg = getNextArgRequired(); + ArrayList<Integer> keyCodes = new ArrayList<>(); + + while (arg != null) { + final int keyCode = KeyEvent.keyCodeFromString(arg); + if (keyCode == KeyEvent.KEYCODE_UNKNOWN) { + throw new IllegalArgumentException("Unknown keycode: " + arg); + } + keyCodes.add(keyCode); + arg = getNextArg(); + } + + // At least 2 keys. + if (keyCodes.size() < 2) { + throw new IllegalArgumentException("keycombination requires at least 2 keycodes"); + } + + sendKeyCombination(inputSource, keyCodes, displayId); + } + + private void injectKeyEventAsync(KeyEvent event) { + InputManager.getInstance().injectInputEvent(event, + InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); + } + + private void sendKeyCombination(int inputSource, ArrayList<Integer> keyCodes, int displayId) { + final long now = SystemClock.uptimeMillis(); + final int count = keyCodes.size(); + final KeyEvent[] events = new KeyEvent[count]; + for (int i = 0; i < count; i++) { + final KeyEvent event = new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyCodes.get(i), 0, + 0 /*metaState*/, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/, 0 /*flags*/, + inputSource); + event.setDisplayId(displayId); + events[i] = event; + } + + for (KeyEvent event: events) { + // Use async inject so interceptKeyBeforeQueueing or interceptKeyBeforeDispatching could + // handle keys. + injectKeyEventAsync(event); + } + + try { + Thread.sleep(ViewConfiguration.getTapTimeout()); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + for (KeyEvent event: events) { + injectKeyEventAsync(KeyEvent.changeAction(event, KeyEvent.ACTION_UP)); + } + } } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 1e6658930840..87e63ebf2651 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -5414,37 +5414,36 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread @ShellCommandResult private int onCommandWithSystemIdentity(@Nullable String cmd) { - if ("get-last-switch-user-id".equals(cmd)) { - return mService.getLastSwitchUserId(this); - } - - // For existing "adb shell ime <command>". - if ("ime".equals(cmd)) { - final String imeCommand = getNextArg(); - if (imeCommand == null || "help".equals(imeCommand) || "-h".equals(imeCommand)) { - onImeCommandHelp(); - return ShellCommandResult.SUCCESS; - } - switch (imeCommand) { - case "list": - return mService.handleShellCommandListInputMethods(this); - case "enable": - return mService.handleShellCommandEnableDisableInputMethod(this, true); - case "disable": - return mService.handleShellCommandEnableDisableInputMethod(this, false); - case "set": - return mService.handleShellCommandSetInputMethod(this); - case "reset": - return mService.handleShellCommandResetInputMethod(this); - case "tracing": - return mService.handleShellCommandTraceInputMethod(this); - default: - getOutPrintWriter().println("Unknown command: " + imeCommand); - return ShellCommandResult.FAILURE; + switch (TextUtils.emptyIfNull(cmd)) { + case "get-last-switch-user-id": + return mService.getLastSwitchUserId(this); + case "ime": { // For "adb shell ime <command>". + final String imeCommand = TextUtils.emptyIfNull(getNextArg()); + switch (imeCommand) { + case "": + case "-h": + case "help": + return onImeCommandHelp(); + case "list": + return mService.handleShellCommandListInputMethods(this); + case "enable": + return mService.handleShellCommandEnableDisableInputMethod(this, true); + case "disable": + return mService.handleShellCommandEnableDisableInputMethod(this, false); + case "set": + return mService.handleShellCommandSetInputMethod(this); + case "reset": + return mService.handleShellCommandResetInputMethod(this); + case "tracing": + return mService.handleShellCommandTraceInputMethod(this); + default: + getOutPrintWriter().println("Unknown command: " + imeCommand); + return ShellCommandResult.FAILURE; + } } + default: + return handleDefaultCommands(cmd); } - - return handleDefaultCommands(cmd); } @BinderThread @@ -5461,7 +5460,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - private void onImeCommandHelp() { + @BinderThread + @ShellCommandResult + private int onImeCommandHelp() { try (IndentingPrintWriter pw = new IndentingPrintWriter(getOutPrintWriter(), " ", 100)) { pw.println("ime <command>:"); @@ -5516,6 +5517,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub pw.decreaseIndent(); } + return ShellCommandResult.SUCCESS; } } diff --git a/services/core/java/com/android/server/location/HardwareActivityRecognitionProxy.java b/services/core/java/com/android/server/location/HardwareActivityRecognitionProxy.java index 7b400b6e0309..6ea4bd2b1d6d 100644 --- a/services/core/java/com/android/server/location/HardwareActivityRecognitionProxy.java +++ b/services/core/java/com/android/server/location/HardwareActivityRecognitionProxy.java @@ -17,7 +17,6 @@ package com.android.server.location; import android.annotation.Nullable; -import android.content.ComponentName; import android.content.Context; import android.hardware.location.ActivityRecognitionHardware; import android.hardware.location.IActivityRecognitionHardwareClient; @@ -27,6 +26,7 @@ import android.os.RemoteException; import android.util.Log; import com.android.server.ServiceWatcher; +import com.android.server.ServiceWatcher.BoundService; /** * Proxy class to bind GmsCore to the ActivityRecognitionHardware. @@ -82,7 +82,7 @@ public class HardwareActivityRecognitionProxy { return resolves; } - private void onBind(IBinder binder, ComponentName service) throws RemoteException { + private void onBind(IBinder binder, BoundService service) throws RemoteException { String descriptor = binder.getInterfaceDescriptor(); if (IActivityRecognitionHardwareWatcher.class.getCanonicalName().equals(descriptor)) { diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java index cbbc981d9ed6..57e9fc9cb719 100644 --- a/services/core/java/com/android/server/location/LocationManagerService.java +++ b/services/core/java/com/android/server/location/LocationManagerService.java @@ -63,6 +63,7 @@ import android.location.LastLocationRequest; import android.location.Location; import android.location.LocationManager; import android.location.LocationManagerInternal; +import android.location.LocationManagerInternal.OnProviderLocationTagsChangeListener; import android.location.LocationProvider; import android.location.LocationRequest; import android.location.LocationTime; @@ -82,6 +83,7 @@ import android.os.WorkSource.WorkChain; import android.provider.Settings; import android.stats.location.LocationStatsEnums; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.IndentingPrintWriter; import android.util.Log; @@ -255,6 +257,9 @@ public class LocationManagerService extends ILocationManager.Stub { final CopyOnWriteArrayList<LocationProviderManager> mProviderManagers = new CopyOnWriteArrayList<>(); + @GuardedBy("mLock") + private @Nullable OnProviderLocationTagsChangeListener mOnProviderLocationTagsChangeListener; + LocationManagerService(Context context, Injector injector, LocationEventLog eventLog) { mContext = context.createAttributionContext(ATTRIBUTION_TAG); mInjector = injector; @@ -319,8 +324,9 @@ public class LocationManagerService extends ILocationManager.Stub { Preconditions.checkState(getLocationProviderManager(manager.getName()) == null); manager.startManager(); + manager.setOnProviderLocationTagsChangeListener( + mOnProviderLocationTagsChangeListener); if (realProvider != null) { - // custom logic wrapping all non-passive providers if (manager != mPassiveManager) { boolean enableStationaryThrottling = Settings.Global.getInt( @@ -331,7 +337,6 @@ public class LocationManagerService extends ILocationManager.Stub { mInjector, realProvider, mEventLog); } } - manager.setRealProvider(realProvider); } mProviderManagers.add(manager); @@ -456,8 +461,9 @@ public class LocationManagerService extends ILocationManager.Stub { .setPowerUsage(Integer.parseInt(fragments[8])) .setAccuracy(Integer.parseInt(fragments[9])) .build(); - getOrAddLocationProviderManager(name).setMockProvider( - new MockLocationProvider(properties, CallerIdentity.fromContext(mContext))); + final LocationProviderManager manager = getOrAddLocationProviderManager(name); + manager.setMockProvider(new MockLocationProvider(properties, + CallerIdentity.fromContext(mContext), /*locationTags*/ null)); } } @@ -1147,15 +1153,16 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public void addTestProvider(String provider, ProviderProperties properties, - String packageName, String attributionTag) { + List<String> locationTags, String packageName, String attributionTag) { // unsafe is ok because app ops will verify the package name CallerIdentity identity = CallerIdentity.fromBinderUnsafe(packageName, attributionTag); if (!mInjector.getAppOpsHelper().noteOp(AppOpsManager.OP_MOCK_LOCATION, identity)) { return; } - getOrAddLocationProviderManager(provider).setMockProvider( - new MockLocationProvider(properties, identity)); + final LocationProviderManager manager = getOrAddLocationProviderManager(provider); + manager.setMockProvider(new MockLocationProvider(properties, identity, + (locationTags != null) ? new ArraySet<>(locationTags) : null)); } @Override @@ -1392,6 +1399,19 @@ public class LocationManagerService extends ILocationManager.Stub { return new LocationTime(location.getTime(), location.getElapsedRealtimeNanos()); } + + @Override + public void setOnProviderLocationTagsChangeListener( + @Nullable OnProviderLocationTagsChangeListener listener) { + synchronized (mLock) { + mOnProviderLocationTagsChangeListener = listener; + final int providerCount = mProviderManagers.size(); + for (int i = 0; i < providerCount; i++) { + final LocationProviderManager manager = mProviderManagers.get(i); + manager.setOnProviderLocationTagsChangeListener(listener); + } + } + } } private static class SystemInjector implements Injector { diff --git a/services/core/java/com/android/server/location/LocationShellCommand.java b/services/core/java/com/android/server/location/LocationShellCommand.java index f0dd8b559d64..21a9b0442b74 100644 --- a/services/core/java/com/android/server/location/LocationShellCommand.java +++ b/services/core/java/com/android/server/location/LocationShellCommand.java @@ -75,8 +75,8 @@ class LocationShellCommand extends BasicShellCommandHandler { case "add-test-provider": { String provider = getNextArgRequired(); ProviderProperties properties = parseTestProviderProviderProperties(); - mService.addTestProvider(provider, properties, mContext.getOpPackageName(), - mContext.getFeatureId()); + mService.addTestProvider(provider, properties, /*locationTags*/ null, + mContext.getOpPackageName(), mContext.getFeatureId()); return 0; } case "remove-test-provider": { diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java index 81c1e45504cb..dde45c4ab52a 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java @@ -295,7 +295,7 @@ public class ContextHubService extends IContextHubService.Stub { }; SensorPrivacyManager manager = SensorPrivacyManager.getInstance(mContext); manager.addSensorPrivacyListener( - SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE, listener); + SensorPrivacyManager.Sensors.MICROPHONE, listener); } } @@ -1079,8 +1079,8 @@ public class ContextHubService extends IContextHubService.Stub { */ private void sendMicrophoneDisableSettingUpdate() { SensorPrivacyManager manager = SensorPrivacyManager.getInstance(mContext); - boolean disabled = manager.isIndividualSensorPrivacyEnabled( - SensorPrivacyManager.INDIVIDUAL_SENSOR_MICROPHONE); + boolean disabled = manager.isSensorPrivacyEnabled( + SensorPrivacyManager.Sensors.MICROPHONE); Log.d(TAG, "Mic Disabled Setting: " + disabled); mContextHubWrapper.onMicrophoneDisableSettingChanged(disabled); } diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java index 9216a6b245a6..29da177ee4a1 100644 --- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java @@ -371,7 +371,8 @@ public class GnssLocationProvider extends AbstractLocationProvider implements public GnssLocationProvider(Context context, Injector injector, GnssNative gnssNative, GnssMetrics gnssMetrics) { - super(FgThread.getExecutor(), CallerIdentity.fromContext(context), PROPERTIES); + super(FgThread.getExecutor(), CallerIdentity.fromContext(context), PROPERTIES, + /*locationTags*/ null); mContext = context; mGnssNative = gnssNative; diff --git a/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java b/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java index 08deb8698608..9ff6e6bc8e32 100644 --- a/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java +++ b/services/core/java/com/android/server/location/provider/AbstractLocationProvider.java @@ -29,6 +29,7 @@ import com.android.internal.util.Preconditions; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Objects; +import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicReference; import java.util.function.UnaryOperator; @@ -67,7 +68,7 @@ public abstract class AbstractLocationProvider { * Default state value for a location provider that is disabled with no properties and an * empty provider package list. */ - public static final State EMPTY_STATE = new State(false, null, null); + public static final State EMPTY_STATE = new State(false, null, null, null); /** * The provider's allowed state. @@ -84,10 +85,14 @@ public abstract class AbstractLocationProvider { */ @Nullable public final CallerIdentity identity; - private State(boolean allowed, ProviderProperties properties, CallerIdentity identity) { + @Nullable public final Set<String> locationTags; + + private State(boolean allowed, ProviderProperties properties, CallerIdentity identity, + Set<String> locationTags) { this.allowed = allowed; this.properties = properties; this.identity = identity; + this.locationTags = locationTags; } /** @@ -97,7 +102,7 @@ public abstract class AbstractLocationProvider { if (allowed == this.allowed) { return this; } else { - return new State(allowed, properties, identity); + return new State(allowed, properties, identity, locationTags); } } @@ -108,7 +113,7 @@ public abstract class AbstractLocationProvider { if (Objects.equals(properties, this.properties)) { return this; } else { - return new State(allowed, properties, identity); + return new State(allowed, properties, identity, locationTags); } } @@ -119,10 +124,22 @@ public abstract class AbstractLocationProvider { if (Objects.equals(identity, this.identity)) { return this; } else { - return new State(allowed, properties, identity); + return new State(allowed, properties, identity, locationTags); } } + /** + * Returns a state the same as the current but with location tags set as specified. + */ + public State withLocationTags(@Nullable Set<String> locationTags) { + if (Objects.equals(locationTags, this.locationTags)) { + return this; + } else { + return new State(allowed, properties, identity, locationTags); + } + } + + @Override public boolean equals(Object o) { if (this == o) { @@ -133,12 +150,13 @@ public abstract class AbstractLocationProvider { } State state = (State) o; return allowed == state.allowed && properties == state.properties - && Objects.equals(identity, state.identity); + && Objects.equals(identity, state.identity) + && Objects.equals(locationTags, state.locationTags); } @Override public int hashCode() { - return Objects.hash(allowed, properties, identity); + return Objects.hash(allowed, properties, identity, locationTags); } } @@ -195,13 +213,14 @@ public abstract class AbstractLocationProvider { * An optional identity and properties may be provided to initialize the location provider. */ protected AbstractLocationProvider(Executor executor, @Nullable CallerIdentity identity, - @Nullable ProviderProperties properties) { + @Nullable ProviderProperties properties, @Nullable Set<String> locationTags) { Preconditions.checkArgument(identity == null || identity.getListenerId() == null); mExecutor = executor; mInternalState = new AtomicReference<>(new InternalState(null, State.EMPTY_STATE .withIdentity(identity) - .withProperties(properties))); + .withProperties(properties).withLocationTags(locationTags)) + ); mController = new Controller(); } @@ -281,6 +300,13 @@ public abstract class AbstractLocationProvider { } /** + * Call this method to report a change in provider location tags. + */ + protected void setLocationTags(@Nullable Set<String> locationTags) { + setState(state -> state.withLocationTags(locationTags)); + } + + /** * Call this method to report a new location. */ protected void reportLocation(LocationResult locationResult) { diff --git a/services/core/java/com/android/server/location/provider/DelegateLocationProvider.java b/services/core/java/com/android/server/location/provider/DelegateLocationProvider.java index a3ec867220dd..49f6e64a1e0c 100644 --- a/services/core/java/com/android/server/location/provider/DelegateLocationProvider.java +++ b/services/core/java/com/android/server/location/provider/DelegateLocationProvider.java @@ -40,7 +40,7 @@ class DelegateLocationProvider extends AbstractLocationProvider private boolean mInitialized = false; DelegateLocationProvider(Executor executor, AbstractLocationProvider delegate) { - super(executor, null, null); + super(executor, null, null, null); mDelegate = delegate; } diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java index 5a35d7f88ff3..c600663173a8 100644 --- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java +++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java @@ -29,6 +29,8 @@ import static android.os.PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF import static android.os.PowerManager.LOCATION_MODE_FOREGROUND_ONLY; import static android.os.PowerManager.LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF; import static android.os.PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF; +import static android.os.PowerWhitelistManager.REASON_LOCATION_PROVIDER; +import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED; import static com.android.server.location.LocationManagerService.D; import static com.android.server.location.LocationManagerService.TAG; @@ -52,6 +54,8 @@ import android.location.LastLocationRequest; import android.location.Location; import android.location.LocationManager; import android.location.LocationManagerInternal; +import android.location.LocationManagerInternal.LocationTagInfo; +import android.location.LocationManagerInternal.OnProviderLocationTagsChangeListener; import android.location.LocationManagerInternal.ProviderEnabledListener; import android.location.LocationRequest; import android.location.LocationResult; @@ -85,6 +89,7 @@ import android.util.TimeUtils; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; +import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.FgThread; import com.android.server.LocalServices; import com.android.server.location.LocationPermissions; @@ -117,6 +122,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.Objects; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Predicate; @@ -223,7 +229,10 @@ public class LocationProviderManager extends BroadcastOptions options = BroadcastOptions.makeBasic(); options.setDontSendToRestrictedApps(true); // allows apps to start a fg service in response to a location PI - options.setTemporaryAppWhitelistDuration(TEMPORARY_APP_ALLOWLIST_DURATION_MS); + options.setTemporaryAppAllowlist(TEMPORARY_APP_ALLOWLIST_DURATION_MS, + TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, + REASON_LOCATION_PROVIDER, + ""); Intent intent = new Intent().putExtra(KEY_LOCATION_CHANGED, locationResult.getLastLocation()); @@ -1288,6 +1297,9 @@ public class LocationProviderManager extends @GuardedBy("mLock") private @Nullable OnAlarmListener mDelayedRegister; + @GuardedBy("mLock") + private @Nullable OnProviderLocationTagsChangeListener mOnLocationTagsChangeListener; + public LocationProviderManager(Context context, Injector injector, LocationEventLog eventLog, String name, @Nullable PassiveLocationProviderManager passiveManager) { mContext = context; @@ -1448,6 +1460,19 @@ public class LocationProviderManager extends } } + /** + * Registers a listener for the location tags of the provider. + * + * @param listener The listener + */ + public void setOnProviderLocationTagsChangeListener( + @Nullable OnProviderLocationTagsChangeListener listener) { + Preconditions.checkArgument(mOnLocationTagsChangeListener == null || listener == null); + synchronized (mLock) { + mOnLocationTagsChangeListener = listener; + } + } + public void setMockProvider(@Nullable MockLocationProvider provider) { synchronized (mLock) { Preconditions.checkState(mState != STATE_STOPPED); @@ -2244,6 +2269,27 @@ public class LocationProviderManager extends if (oldState.allowed != newState.allowed) { onEnabledChanged(UserHandle.USER_ALL); } + + if (!Objects.equals(oldState.locationTags, newState.locationTags)) { + if (mOnLocationTagsChangeListener != null) { + if (oldState.identity != null) { + FgThread.getHandler().sendMessage(PooledLambda.obtainMessage( + OnProviderLocationTagsChangeListener::onLocationTagsChanged, + mOnLocationTagsChangeListener, new LocationTagInfo( + oldState.identity.getUid(), oldState.identity.getPackageName(), + Collections.emptySet()) + )); + } + if (newState.identity != null) { + FgThread.getHandler().sendMessage(PooledLambda.obtainMessage( + OnProviderLocationTagsChangeListener::onLocationTagsChanged, + mOnLocationTagsChangeListener, new LocationTagInfo( + newState.identity.getUid(), newState.identity.getPackageName(), + newState.locationTags) + )); + } + } + } } @GuardedBy("mLock") diff --git a/services/core/java/com/android/server/location/provider/MockLocationProvider.java b/services/core/java/com/android/server/location/provider/MockLocationProvider.java index 0d8f64377db0..7660f56f1580 100644 --- a/services/core/java/com/android/server/location/provider/MockLocationProvider.java +++ b/services/core/java/com/android/server/location/provider/MockLocationProvider.java @@ -28,6 +28,7 @@ import android.os.Bundle; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.Set; /** * A mock location provider used by LocationManagerService to implement test providers. @@ -38,9 +39,10 @@ public class MockLocationProvider extends AbstractLocationProvider { @Nullable private Location mLocation; - public MockLocationProvider(ProviderProperties properties, CallerIdentity identity) { + public MockLocationProvider(ProviderProperties properties, CallerIdentity identity, + @Nullable Set<String> locationTags) { // using a direct executor is ok because this class has no locks that could deadlock - super(DIRECT_EXECUTOR, identity, properties); + super(DIRECT_EXECUTOR, identity, properties, locationTags); } /** Sets the allowed state of this mock provider. */ diff --git a/services/core/java/com/android/server/location/provider/MockableLocationProvider.java b/services/core/java/com/android/server/location/provider/MockableLocationProvider.java index cb7264e55fa9..4ffa9a509a23 100644 --- a/services/core/java/com/android/server/location/provider/MockableLocationProvider.java +++ b/services/core/java/com/android/server/location/provider/MockableLocationProvider.java @@ -75,7 +75,7 @@ public class MockableLocationProvider extends AbstractLocationProvider { public MockableLocationProvider(Object ownerLock) { // using a direct executor is acceptable because all inbound calls are delegated to the // actual provider implementations which will use their own executors - super(DIRECT_EXECUTOR, null, null); + super(DIRECT_EXECUTOR, null, null, null); mOwnerLock = ownerLock; mRequest = ProviderRequest.EMPTY_REQUEST; } diff --git a/services/core/java/com/android/server/location/provider/PassiveLocationProvider.java b/services/core/java/com/android/server/location/provider/PassiveLocationProvider.java index a5758a37b983..ee9d35d21798 100644 --- a/services/core/java/com/android/server/location/provider/PassiveLocationProvider.java +++ b/services/core/java/com/android/server/location/provider/PassiveLocationProvider.java @@ -47,7 +47,8 @@ public class PassiveLocationProvider extends AbstractLocationProvider { public PassiveLocationProvider(Context context) { // using a direct executor is ok because this class has no locks that could deadlock - super(DIRECT_EXECUTOR, CallerIdentity.fromContext(context), PROPERTIES); + super(DIRECT_EXECUTOR, CallerIdentity.fromContext(context), PROPERTIES, + /*locationTags*/ null); setAllowed(true); } diff --git a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java index 4c97f645aac9..f00478a3488a 100644 --- a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java +++ b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java @@ -18,6 +18,7 @@ package com.android.server.location.provider.proxy; import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; +import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; @@ -32,10 +33,12 @@ import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; +import android.util.ArraySet; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.ArrayUtils; import com.android.server.ServiceWatcher; +import com.android.server.ServiceWatcher.BoundService; import com.android.server.location.provider.AbstractLocationProvider; import java.io.FileDescriptor; @@ -49,6 +52,9 @@ import java.util.Objects; */ public class ProxyLocationProvider extends AbstractLocationProvider { + private static final String KEY_LOCATION_TAGS = "android:location_allow_listed_tags"; + private static final String LOCATION_TAGS_SEPARATOR = ";"; + /** * Creates and registers this proxy. If no suitable service is available for the proxy, returns * null. @@ -84,7 +90,7 @@ public class ProxyLocationProvider extends AbstractLocationProvider { int nonOverlayPackageResId) { // safe to use direct executor since our locks are not acquired in a code path invoked by // our owning provider - super(DIRECT_EXECUTOR, null, null); + super(DIRECT_EXECUTOR, null, null, null); mContext = context; mServiceWatcher = new ServiceWatcher(context, action, this::onBind, @@ -94,22 +100,34 @@ public class ProxyLocationProvider extends AbstractLocationProvider { mRequest = ProviderRequest.EMPTY_REQUEST; } + private void updateLocationTagInfo(@NonNull BoundService boundService) { + if (boundService.metadata != null) { + final String tagsList = boundService.metadata.getString(KEY_LOCATION_TAGS); + if (tagsList != null) { + final String[] tags = tagsList.split(LOCATION_TAGS_SEPARATOR); + setLocationTags(new ArraySet<>(tags)); + } + } + } + private boolean checkServiceResolves() { return mServiceWatcher.checkServiceResolves(); } - private void onBind(IBinder binder, ComponentName service) throws RemoteException { + private void onBind(IBinder binder, BoundService boundService) throws RemoteException { ILocationProvider provider = ILocationProvider.Stub.asInterface(binder); synchronized (mLock) { mProxy = new Proxy(); - mService = service; + mService = boundService.component; provider.setLocationProviderManager(mProxy); ProviderRequest request = mRequest; if (!request.equals(ProviderRequest.EMPTY_REQUEST)) { provider.setRequest(request); } + + updateLocationTagInfo(boundService); } } diff --git a/services/core/java/com/android/server/locksettings/OWNERS b/services/core/java/com/android/server/locksettings/OWNERS index 08b8a8c106b7..7577ee5c9fde 100644 --- a/services/core/java/com/android/server/locksettings/OWNERS +++ b/services/core/java/com/android/server/locksettings/OWNERS @@ -1,3 +1,4 @@ jaggies@google.com kchyn@google.com rubinxu@google.com +xunchang@google.com diff --git a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java index 87e170ad12df..cb0a668871e0 100644 --- a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java +++ b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java @@ -29,6 +29,7 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.os.Handler; +import android.os.PowerWhitelistManager; import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; @@ -195,8 +196,9 @@ final class MediaButtonReceiverHolder { mediaButtonIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, callingPackageName); final BroadcastOptions options = BroadcastOptions.makeBasic(); - options.setTemporaryAppWhitelistDuration( - FGS_STARTS_TEMP_ALLOWLIST_DURATION_MS); + options.setTemporaryAppAllowlist(FGS_STARTS_TEMP_ALLOWLIST_DURATION_MS, + PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, + PowerWhitelistManager.REASON_MEDIA_BUTTON, ""); if (mPendingIntent != null) { if (DEBUG_KEY_EVENT) { Log.d(TAG, "Sending " + keyEvent + " to the last known PendingIntent " diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index ecf4438d8aca..2b9dd2d84ac6 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -882,9 +882,10 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); try { + // TODO: There shouldn't be a need to receive callback for all changes. mActivityManager.registerUidObserver(mUidObserver, ActivityManager.UID_OBSERVER_PROCSTATE|ActivityManager.UID_OBSERVER_GONE, - NetworkPolicyManager.FOREGROUND_THRESHOLD_STATE, "android"); + ActivityManager.PROCESS_STATE_UNKNOWN, "android"); mNetworkManager.registerObserver(mAlertObserver); } catch (RemoteException e) { // ignored; both services live in system_server diff --git a/services/core/java/com/android/server/net/NetworkStatsCollection.java b/services/core/java/com/android/server/net/NetworkStatsCollection.java index 6aefe41891f9..557fa8944445 100644 --- a/services/core/java/com/android/server/net/NetworkStatsCollection.java +++ b/services/core/java/com/android/server/net/NetworkStatsCollection.java @@ -54,6 +54,8 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; +import com.android.internal.util.FastDataInput; +import com.android.internal.util.FastDataOutput; import com.android.internal.util.FileRotator; import com.android.internal.util.IndentingPrintWriter; @@ -89,6 +91,9 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W /** File header magic number: "ANET" */ private static final int FILE_MAGIC = 0x414E4554; + /** Default buffer size from BufferedInputStream */ + private static final int BUFFER_SIZE = 8192; + private static final int VERSION_NETWORK_INIT = 1; private static final int VERSION_UID_INIT = 1; @@ -434,7 +439,8 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W @Override public void read(InputStream in) throws IOException { - read((DataInput) new DataInputStream(in)); + final FastDataInput dataIn = new FastDataInput(in, BUFFER_SIZE); + read(dataIn); } private void read(DataInput in) throws IOException { @@ -473,8 +479,9 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W @Override public void write(OutputStream out) throws IOException { - write((DataOutput) new DataOutputStream(out)); - out.flush(); + final FastDataOutput dataOut = new FastDataOutput(out, BUFFER_SIZE); + write(dataOut); + dataOut.flush(); } private void write(DataOutput out) throws IOException { diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index f8cb2e4cecfc..274344a445c3 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -64,6 +64,8 @@ import static android.media.AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY; import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE; import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL; import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL; +import static android.os.PowerWhitelistManager.REASON_NOTIFICATION_SERVICE; +import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED; import static android.os.UserHandle.USER_NULL; import static android.os.UserHandle.USER_SYSTEM; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING; @@ -6001,10 +6003,11 @@ public class NotificationManagerService extends SystemService { for (int i = 0; i < intentCount; i++) { PendingIntent pendingIntent = notification.allPendingIntents.valueAt(i); if (pendingIntent != null) { - am.setPendingIntentWhitelistDuration(pendingIntent.getTarget(), + am.setPendingIntentAllowlistDuration(pendingIntent.getTarget(), ALLOWLIST_TOKEN, duration, - BroadcastOptions.TEMPORARY_WHITELIST_TYPE_FOREGROUND_SERVICE_ALLOWED - ); + TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, + REASON_NOTIFICATION_SERVICE, + "NotificationManagerService"); am.setPendingIntentAllowBgActivityStarts(pendingIntent.getTarget(), ALLOWLIST_TOKEN, (FLAG_ACTIVITY_SENDER | FLAG_BROADCAST_SENDER | FLAG_SERVICE_SENDER)); diff --git a/services/core/java/com/android/server/pm/DataLoaderManagerService.java b/services/core/java/com/android/server/pm/DataLoaderManagerService.java index 308e815d7659..9a9b14c31314 100644 --- a/services/core/java/com/android/server/pm/DataLoaderManagerService.java +++ b/services/core/java/com/android/server/pm/DataLoaderManagerService.java @@ -171,7 +171,7 @@ public class DataLoaderManagerService extends SystemService { } } - private class DataLoaderServiceConnection implements ServiceConnection { + private class DataLoaderServiceConnection implements ServiceConnection, IBinder.DeathRecipient { final int mId; final IDataLoaderStatusListener mListener; IDataLoader mDataLoader; @@ -194,6 +194,13 @@ public class DataLoaderManagerService extends SystemService { mContext.unbindService(this); return; } + try { + service.linkToDeath(this, /*flags=*/0); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to link to DataLoader's death: " + mId, e); + onBindingDied(className); + return; + } callListener(IDataLoaderStatusListener.DATA_LOADER_BOUND); } @@ -218,6 +225,13 @@ public class DataLoaderManagerService extends SystemService { destroy(); } + @Override + public void binderDied() { + Slog.i(TAG, "DataLoader " + mId + " died"); + callListener(IDataLoaderStatusListener.DATA_LOADER_DESTROYED); + destroy(); + } + IDataLoader getDataLoader() { return mDataLoader; } diff --git a/services/core/java/com/android/server/pm/DumpState.java b/services/core/java/com/android/server/pm/DumpState.java index 380cdb10569b..6875b8a5abeb 100644 --- a/services/core/java/com/android/server/pm/DumpState.java +++ b/services/core/java/com/android/server/pm/DumpState.java @@ -56,6 +56,7 @@ public final class DumpState { private boolean mTitlePrinted; private boolean mFullPreferred; + private boolean mCheckIn; private String mTargetPackageName; @@ -118,4 +119,12 @@ public final class DumpState { public void setFullPreferred(boolean fullPreferred) { mFullPreferred = fullPreferred; } + + public boolean isCheckIn() { + return mCheckIn; + } + + public void setCheckIn(boolean checkIn) { + mCheckIn = checkIn; + } } diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 281283048a95..a5e28f164bc2 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -530,14 +530,6 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements throw new SecurityException("User restriction prevents installing"); } - if (params.dataLoaderParams != null - && mContext.checkCallingOrSelfPermission(Manifest.permission.USE_INSTALLER_V2) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("You need the " - + "com.android.permission.USE_INSTALLER_V2 permission " - + "to use a data loader"); - } - // INSTALL_REASON_ROLLBACK allows an app to be rolled back without requiring the ROLLBACK // capability; ensure if this is set as the install reason the app has one of the necessary // signature permissions to perform the rollback. diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 7bf3c5c1f4c9..460b2f2bf5c6 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -3547,14 +3547,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @Override public DataLoaderParamsParcel getDataLoaderParams() { - mContext.enforceCallingOrSelfPermission(Manifest.permission.USE_INSTALLER_V2, null); return params.dataLoaderParams != null ? params.dataLoaderParams.getData() : null; } @Override public void addFile(int location, String name, long lengthBytes, byte[] metadata, byte[] signature) { - mContext.enforceCallingOrSelfPermission(Manifest.permission.USE_INSTALLER_V2, null); if (!isDataLoaderInstallation()) { throw new IllegalStateException( "Cannot add files to non-data loader installation session."); @@ -3587,7 +3585,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @Override public void removeFile(int location, String name) { - mContext.enforceCallingOrSelfPermission(Manifest.permission.USE_INSTALLER_V2, null); if (!isDataLoaderInstallation()) { throw new IllegalStateException( "Cannot add files to non-data loader installation session."); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 16966d4de4e6..ec424f03ed75 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -1961,9 +1961,10 @@ public class PackageManagerService extends IPackageManager.Stub boolean isInstantAppInternal(String packageName, @UserIdInt int userId, int callingUid); boolean isInstantAppInternalBody(String packageName, @UserIdInt int userId, int callingUid); boolean isInstantAppResolutionAllowed(Intent intent, List<ResolveInfo> resolvedActivities, - int userId, boolean skipPackageCheck); + int userId, boolean skipPackageCheck, int flags); boolean isInstantAppResolutionAllowedBody(Intent intent, - List<ResolveInfo> resolvedActivities, int userId, boolean skipPackageCheck); + List<ResolveInfo> resolvedActivities, int userId, boolean skipPackageCheck, + int flags); boolean isPersistentPreferredActivitySetByDpm(Intent intent, int userId, String resolvedType, int flags); boolean isRecentsAccessingChildProfiles(int callingUid, int targetUserId); @@ -2322,7 +2323,7 @@ public class PackageManagerService extends IPackageManager.Stub result = filterIfNotSystemUser(mComponentResolver.queryActivities( intent, resolvedType, flags, userId), userId); addInstant = isInstantAppResolutionAllowed(intent, result, userId, - false /*skipPackageCheck*/); + false /*skipPackageCheck*/, flags); // Check for cross profile results. boolean hasNonNegativePriorityResult = hasNonNegativePriority(result); xpResolveInfo = queryCrossProfileIntents( @@ -2387,8 +2388,8 @@ public class PackageManagerService extends IPackageManager.Stub if (result == null || result.size() == 0) { // the caller wants to resolve for a particular package; however, there // were no installed results, so, try to find an ephemeral result - addInstant = isInstantAppResolutionAllowed( - intent, null /*result*/, userId, true /*skipPackageCheck*/); + addInstant = isInstantAppResolutionAllowed(intent, null /*result*/, userId, + true /*skipPackageCheck*/, flags); if (result == null) { result = new ArrayList<>(); } @@ -2603,10 +2604,22 @@ public class PackageManagerService extends IPackageManager.Stub final ArrayList<ResolveInfo> matchAllList = new ArrayList<>(); final ArrayList<ResolveInfo> undefinedList = new ArrayList<>(); + // Blocking instant apps is usually done in applyPostResolutionFilter, but since + // domain verification can resolve to a single result, which can be an instant app, + // it will then be filtered to an empty list in that method. Instead, do blocking + // here so that instant apps can be ignored for approval filtering and a lower + // priority result chosen instead. + final boolean blockInstant = intent.isWebIntent() && areWebInstantAppsDisabled(userId); + final int count = candidates.size(); // First, try to use approved apps. for (int n = 0; n < count; n++) { ResolveInfo info = candidates.get(n); + if (blockInstant && (info.isInstantAppAvailable + || isInstantApp(info.activityInfo.packageName, userId))) { + continue; + } + // Add to the special match all list (Browser use case) if (info.handleAllWebDataURI) { matchAllList.add(info); @@ -2618,7 +2631,7 @@ public class PackageManagerService extends IPackageManager.Stub // We'll want to include browser possibilities in a few cases boolean includeBrowser = false; - if (!DomainVerificationUtils.isDomainVerificationIntent(intent)) { + if (!DomainVerificationUtils.isDomainVerificationIntent(intent, matchFlags)) { result.addAll(undefinedList); // Maybe add one for the other profile. if (xpDomainInfo != null && xpDomainInfo.highestApprovalLevel @@ -2802,7 +2815,7 @@ public class PackageManagerService extends IPackageManager.Stub } result.highestApprovalLevel = Math.max(mDomainVerificationManager - .approvalLevelForDomain(ps, intent, riTargetUser.targetUserId), + .approvalLevelForDomain(ps, intent, flags, riTargetUser.targetUserId), result.highestApprovalLevel); } if (result != null && result.highestApprovalLevel @@ -3049,7 +3062,7 @@ public class PackageManagerService extends IPackageManager.Stub final String packageName = info.activityInfo.packageName; final PackageSetting ps = mSettings.getPackageLPr(packageName); if (ps.getInstantApp(userId)) { - if (hasAnyDomainApproval(mDomainVerificationManager, ps, intent, + if (hasAnyDomainApproval(mDomainVerificationManager, ps, intent, flags, userId)) { if (DEBUG_INSTANT) { Slog.v(TAG, "Instant app approved for intent; pkg: " @@ -3928,7 +3941,7 @@ public class PackageManagerService extends IPackageManager.Stub public boolean isInstantAppResolutionAllowed( Intent intent, List<ResolveInfo> resolvedActivities, int userId, - boolean skipPackageCheck) { + boolean skipPackageCheck, int flags) { if (mInstantAppResolverConnection == null) { return false; } @@ -3961,14 +3974,14 @@ public class PackageManagerService extends IPackageManager.Stub // Deny ephemeral apps if the user chose _ALWAYS or _ALWAYS_ASK for intent resolution. // Or if there's already an ephemeral app installed that handles the action return isInstantAppResolutionAllowedBody(intent, resolvedActivities, userId, - skipPackageCheck); + skipPackageCheck, flags); } // Deny ephemeral apps if the user chose _ALWAYS or _ALWAYS_ASK for intent resolution. // Or if there's already an ephemeral app installed that handles the action public boolean isInstantAppResolutionAllowedBody( Intent intent, List<ResolveInfo> resolvedActivities, int userId, - boolean skipPackageCheck) { + boolean skipPackageCheck, int flags) { final int count = (resolvedActivities == null ? 0 : resolvedActivities.size()); for (int n = 0; n < count; n++) { final ResolveInfo info = resolvedActivities.get(n); @@ -3977,7 +3990,7 @@ public class PackageManagerService extends IPackageManager.Stub if (ps != null) { // only check domain verification status if the app is not a browser if (!info.handleAllWebDataURI) { - if (hasAnyDomainApproval(mDomainVerificationManager, ps, intent, + if (hasAnyDomainApproval(mDomainVerificationManager, ps, intent, flags, userId)) { if (DEBUG_INSTANT) { Slog.v(TAG, "DENY instant app;" + " pkg: " + packageName @@ -4403,6 +4416,7 @@ public class PackageManagerService extends IPackageManager.Stub public void dump(int type, FileDescriptor fd, PrintWriter pw, DumpState dumpState) { final String packageName = dumpState.getTargetPackageName(); + final boolean checkin = dumpState.isCheckIn(); switch (type) { case DumpState.DUMP_VERSION: @@ -4415,6 +4429,56 @@ public class PackageManagerService extends IPackageManager.Stub break; } + case DumpState.DUMP_LIBS: + { + boolean printedHeader = false; + final int numSharedLibraries = mSharedLibraries.size(); + for (int index = 0; index < numSharedLibraries; index++) { + final String libName = mSharedLibraries.keyAt(index); + final WatchedLongSparseArray<SharedLibraryInfo> versionedLib = + mSharedLibraries.get(libName); + if (versionedLib == null) { + continue; + } + final int versionCount = versionedLib.size(); + for (int i = 0; i < versionCount; i++) { + SharedLibraryInfo libraryInfo = versionedLib.valueAt(i); + if (!checkin) { + if (!printedHeader) { + if (dumpState.onTitlePrinted()) { + pw.println(); + } + pw.println("Libraries:"); + printedHeader = true; + } + pw.print(" "); + } else { + pw.print("lib,"); + } + pw.print(libraryInfo.getName()); + if (libraryInfo.isStatic()) { + pw.print(" version=" + libraryInfo.getLongVersion()); + } + if (!checkin) { + pw.print(" -> "); + } + if (libraryInfo.getPath() != null) { + if (libraryInfo.isNative()) { + pw.print(" (so) "); + } else { + pw.print(" (jar) "); + } + pw.print(libraryInfo.getPath()); + } else { + pw.print(" (apk) "); + pw.print(libraryInfo.getPackageName()); + } + pw.println(); + } + } + break; + } + case DumpState.DUMP_PREFERRED_XML: { pw.flush(); @@ -4676,10 +4740,11 @@ public class PackageManagerService extends IPackageManager.Stub } } public boolean isInstantAppResolutionAllowedBody(Intent intent, - List<ResolveInfo> resolvedActivities, int userId, boolean skipPackageCheck) { + List<ResolveInfo> resolvedActivities, int userId, boolean skipPackageCheck, + int flags) { synchronized (mLock) { return super.isInstantAppResolutionAllowedBody(intent, resolvedActivities, userId, - skipPackageCheck); + skipPackageCheck, flags); } } public int getPackageUidInternal(String packageName, int flags, int userId, @@ -9167,6 +9232,11 @@ public class PackageManagerService extends IPackageManager.Stub */ @Override public String[] getPackagesForUid(int uid) { + final int callingUid = Binder.getCallingUid(); + final int userId = UserHandle.getUserId(uid); + enforceCrossUserOrProfilePermission(callingUid, userId, + /* requireFullPermission */ false, + /* checkShell */ false, "getPackagesForUid"); return snapshotComputer().getPackagesForUid(uid); } @@ -9470,20 +9540,20 @@ public class PackageManagerService extends IPackageManager.Stub private boolean isInstantAppResolutionAllowed( Intent intent, List<ResolveInfo> resolvedActivities, int userId, - boolean skipPackageCheck) { + boolean skipPackageCheck, int flags) { return liveComputer().isInstantAppResolutionAllowed( intent, resolvedActivities, userId, - skipPackageCheck); + skipPackageCheck, flags); } // Deny ephemeral apps if the user chose _ALWAYS or _ALWAYS_ASK for intent resolution. // Or if there's already an ephemeral app installed that handles the action private boolean isInstantAppResolutionAllowedBody( Intent intent, List<ResolveInfo> resolvedActivities, int userId, - boolean skipPackageCheck) { + boolean skipPackageCheck, int flags) { return liveComputer().isInstantAppResolutionAllowedBody( intent, resolvedActivities, userId, - skipPackageCheck); + skipPackageCheck, flags); } private void requestInstantAppResolutionPhaseTwo(AuxiliaryResolveInfo responseObj, @@ -9540,7 +9610,7 @@ public class PackageManagerService extends IPackageManager.Stub final String packageName = ri.activityInfo.packageName; final PackageSetting ps = mSettings.getPackageLPr(packageName); if (ps != null && hasAnyDomainApproval(mDomainVerificationManager, ps, - intent, userId)) { + intent, flags, userId)) { return ri; } } @@ -9597,8 +9667,9 @@ public class PackageManagerService extends IPackageManager.Stub */ private static boolean hasAnyDomainApproval( @NonNull DomainVerificationManagerInternal manager, @NonNull PackageSetting pkgSetting, - @NonNull Intent intent, @UserIdInt int userId) { - return manager.approvalLevelForDomain(pkgSetting, intent, userId) + @NonNull Intent intent, @PackageManager.ResolveInfoFlags int resolveInfoFlags, + @UserIdInt int userId) { + return manager.approvalLevelForDomain(pkgSetting, intent, resolveInfoFlags, userId) > DomainVerificationManagerInternal.APPROVAL_LEVEL_NONE; } @@ -22614,7 +22685,7 @@ public class PackageManagerService extends IPackageManager.Stub } final Intent intent = getHomeIntent(); final List<ResolveInfo> resolveInfos = queryIntentActivitiesInternal(intent, null, - PackageManager.GET_META_DATA, userId); + MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, userId); final ResolveInfo preferredResolveInfo = findPreferredActivityNotLocked( intent, null, 0, resolveInfos, 0, true, false, false, userId); final String packageName = preferredResolveInfo != null @@ -23699,8 +23770,6 @@ public class PackageManagerService extends IPackageManager.Stub if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return; DumpState dumpState = new DumpState(); - boolean checkin = false; - ArraySet<String> permissionNames = null; int opti = 0; @@ -23750,7 +23819,7 @@ public class PackageManagerService extends IPackageManager.Stub pw.println(" <package.name>: info about given package"); return; } else if ("--checkin".equals(opt)) { - checkin = true; + dumpState.setCheckIn(true); } else if ("--all-components".equals(opt)) { dumpState.setOptionEnabled(DumpState.OPTION_DUMP_ALL_COMPONENTS); } else if ("-f".equals(opt)) { @@ -23904,6 +23973,7 @@ public class PackageManagerService extends IPackageManager.Stub } final String packageName = dumpState.getTargetPackageName(); + final boolean checkin = dumpState.isCheckIn(); if (checkin) { pw.println("vers,1"); } @@ -23992,11 +24062,7 @@ public class PackageManagerService extends IPackageManager.Stub } if (dumpState.isDumping(DumpState.DUMP_LIBS) && packageName == null) { - // TODO: Move it to ComputerEngine once LongSparseArray<SharedLibraryInfo> is copied - // in snapshot. - synchronized (mLock) { - dumpSharedLibrariesLPr(pw, dumpState, checkin); - } + dump(DumpState.DUMP_LIBS, fd, pw, dumpState); } if (dumpState.isDumping(DumpState.DUMP_FEATURES) && packageName == null) { @@ -24337,53 +24403,6 @@ public class PackageManagerService extends IPackageManager.Stub } } - private void dumpSharedLibrariesLPr(PrintWriter pw, DumpState dumpState, boolean checkin) { - boolean printedHeader = false; - final int numSharedLibraries = mSharedLibraries.size(); - for (int index = 0; index < numSharedLibraries; index++) { - final String libName = mSharedLibraries.keyAt(index); - WatchedLongSparseArray<SharedLibraryInfo> versionedLib = mSharedLibraries.get(libName); - if (versionedLib == null) { - continue; - } - final int versionCount = versionedLib.size(); - for (int i = 0; i < versionCount; i++) { - SharedLibraryInfo libraryInfo = versionedLib.valueAt(i); - if (!checkin) { - if (!printedHeader) { - if (dumpState.onTitlePrinted()) { - pw.println(); - } - pw.println("Libraries:"); - printedHeader = true; - } - pw.print(" "); - } else { - pw.print("lib,"); - } - pw.print(libraryInfo.getName()); - if (libraryInfo.isStatic()) { - pw.print(" version=" + libraryInfo.getLongVersion()); - } - if (!checkin) { - pw.print(" -> "); - } - if (libraryInfo.getPath() != null) { - if (libraryInfo.isNative()) { - pw.print(" (so) "); - } else { - pw.print(" (jar) "); - } - pw.print(libraryInfo.getPath()); - } else { - pw.print(" (apk) "); - pw.print(libraryInfo.getPackageName()); - } - pw.println(); - } - } - } - // ------- apps on sdcard specific code ------- static final boolean DEBUG_SD_INSTALL = false; diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java index a83a3f81bc00..38e100e80cd3 100644 --- a/services/core/java/com/android/server/pm/PackageSettingBase.java +++ b/services/core/java/com/android/server/pm/PackageSettingBase.java @@ -787,6 +787,10 @@ public abstract class PackageSettingBase extends SettingBase { return firstInstallTime; } + public String getName() { + return name; + } + protected PackageSettingBase updateFrom(PackageSettingBase other) { super.copyFrom(other); setPath(other.getPath()); diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 24c27bedb9f7..c75dd27a383a 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -983,7 +983,11 @@ public class UserManagerService extends IUserManager.Stub { @Override public UserInfo getProfileParent(@UserIdInt int userId) { - checkManageUsersPermission("get the profile parent"); + if (!hasManageUsersOrPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)) { + throw new SecurityException( + "You need MANAGE_USERS or INTERACT_ACROSS_USERS permission to get the " + + "profile parent"); + } synchronized (mUsersLock) { return getProfileParentLU(userId); } @@ -1531,11 +1535,14 @@ public class UserManagerService extends IUserManager.Stub { @Override public String getUserName() { - if (!hasManageUsersOrPermission(android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED)) { - throw new SecurityException("You need MANAGE_USERS or GET_ACCOUNTS_PRIVILEGED " - + "permissions to: get user name"); + final int callingUid = Binder.getCallingUid(); + if (!hasManageOrCreateUsersPermission() + || hasPermissionGranted( + android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED, callingUid)) { + throw new SecurityException("You need MANAGE_USERS or CREATE_USERS or " + + "GET_ACCOUNTS_PRIVILEGED permissions to: get user name"); } - final int userId = UserHandle.getUserId(Binder.getCallingUid()); + final int userId = UserHandle.getUserId(callingUid); synchronized (mUsersLock) { UserInfo userInfo = userWithName(getUserInfoLU(userId)); return userInfo == null ? "" : userInfo.name; @@ -3287,7 +3294,7 @@ public class UserManagerService extends IUserManager.Stub { * as well as for {@link UserManager#USER_TYPE_FULL_RESTRICTED}. */ @Override - public UserInfo createProfileForUserWithThrow(String name, @NonNull String userType, + public UserInfo createProfileForUserWithThrow(@Nullable String name, @NonNull String userType, @UserInfoFlag int flags, @UserIdInt int userId, @Nullable String[] disallowedPackages) throws ServiceSpecificException { checkManageOrCreateUsersPermission(flags); @@ -3868,7 +3875,7 @@ public class UserManagerService extends IUserManager.Stub { * @hide */ @Override - public UserInfo createRestrictedProfileWithThrow(String name, int parentUserId) { + public UserInfo createRestrictedProfileWithThrow(@Nullable String name, int parentUserId) { checkManageOrCreateUsersPermission("setupRestrictedProfile"); final UserInfo user = createProfileForUserWithThrow( name, UserManager.USER_TYPE_FULL_RESTRICTED, 0, parentUserId, null); diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java index 44a2187aed8e..e3ccb7521b58 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -1277,12 +1277,7 @@ final class DefaultPermissionGrantPolicy { newFlags |= (flags & PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT); // If we are allowlisting the permission, update the exempt flag before grant. - // If the permission can't be allowlisted by an installer, skip it here because - // this is where the platform takes the role of the installer for exempting - // preinstalled apps. - if (whitelistRestrictedPermissions && pm.isPermissionRestricted(permission) - && !pm.getPermissionInfo(permission).isInstallerExemptIgnored()) { - + if (whitelistRestrictedPermissions && pm.isPermissionRestricted(permission)) { pm.updatePermissionFlags(permission, pkg, PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT, PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT, user); diff --git a/services/core/java/com/android/server/pm/permission/Permission.java b/services/core/java/com/android/server/pm/permission/Permission.java index 32bee5809b11..ac50f29fbf32 100644 --- a/services/core/java/com/android/server/pm/permission/Permission.java +++ b/services/core/java/com/android/server/pm/permission/Permission.java @@ -239,10 +239,6 @@ public final class Permission { return (mPermissionInfo.flags & PermissionInfo.FLAG_IMMUTABLY_RESTRICTED) != 0; } - public boolean isInstallerExemptIgnored() { - return (mPermissionInfo.flags & PermissionInfo.FLAG_INSTALLER_EXEMPT_IGNORED) != 0; - } - public boolean isSignature() { return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE) == PermissionInfo.PROTECTION_SIGNATURE; 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 7a936ec29498..2dfb6ff3e9a8 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -24,14 +24,12 @@ import static android.app.AppOpsManager.MODE_IGNORED; import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISALLOWED; import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISCOURAGED; import static android.content.pm.PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT; -import static android.content.pm.PackageManager.FLAG_PERMISSION_ALLOWLIST_ROLE; import static android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION; import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT; import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE; import static android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME; import static android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED; import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT; -import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT; import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT; import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT; import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED; @@ -771,10 +769,6 @@ public class PermissionManagerService extends IPermissionManager.Stub { isRuntimePermission = bp.isRuntime(); - if (bp.isInstallerExemptIgnored()) { - flagValues &= ~FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT; - } - final UidPermissionState uidState = getUidStateLocked(pkg, userId); if (uidState == null) { Slog.e(TAG, "Missing permissions state for " + packageName + " and user " + userId); @@ -1018,8 +1012,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { Preconditions.checkFlagsArgument(flags, PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE | PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM - | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER - | PackageManager.FLAG_PERMISSION_ALLOWLIST_ROLE); + | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER); Preconditions.checkArgumentNonNegative(userId, null); if (UserHandle.getCallingUserId() != userId) { @@ -1043,9 +1036,9 @@ public class PermissionManagerService extends IPermissionManager.Stub { final boolean isCallerInstallerOnRecord = mPackageManagerInt.isCallerInstallerOfRecord(pkg, callingUid); - if ((flags & (PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM - | PackageManager.FLAG_PERMISSION_ALLOWLIST_ROLE)) != 0 && !isCallerPrivileged) { - throw new SecurityException("Querying system or role allowlist requires " + if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) != 0 + && !isCallerPrivileged) { + throw new SecurityException("Querying system allowlist requires " + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS); } @@ -1087,9 +1080,6 @@ public class PermissionManagerService extends IPermissionManager.Stub { if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER) != 0) { queryFlags |= FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT; } - if ((flags & PackageManager.FLAG_PERMISSION_ALLOWLIST_ROLE) != 0) { - queryFlags |= FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT; - } ArrayList<String> allowlistedPermissions = null; @@ -1182,8 +1172,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { Preconditions.checkFlagsArgument(flags, PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE | PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM - | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER - | PackageManager.FLAG_PERMISSION_ALLOWLIST_ROLE); + | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER); Preconditions.checkArgument(Integer.bitCount(flags) == 1); Preconditions.checkArgumentNonNegative(userId, null); @@ -1209,10 +1198,8 @@ public class PermissionManagerService extends IPermissionManager.Stub { final boolean isCallerInstallerOnRecord = mPackageManagerInt.isCallerInstallerOfRecord(pkg, callingUid); - if ((flags & (PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM - | PackageManager.FLAG_PERMISSION_ALLOWLIST_ROLE)) != 0 - && !isCallerPrivileged) { - throw new SecurityException("Modifying system or role allowlist requires " + if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) != 0 && !isCallerPrivileged) { + throw new SecurityException("Modifying system allowlist requires " + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS); } @@ -3718,15 +3705,6 @@ public class PermissionManagerService extends IPermissionManager.Stub { } } break; - case FLAG_PERMISSION_ALLOWLIST_ROLE: { - mask |= FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT; - if (permissions != null && permissions.contains(permissionName)) { - newFlags |= FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT; - } else { - newFlags &= ~FLAG_PERMISSION_RESTRICTION_ROLE_EXEMPT; - } - } - break; } } diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java index 080de73ff933..e3cf67c34dad 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java @@ -42,6 +42,8 @@ public class DomainVerificationCollector { private static final Pattern DOMAIN_NAME_WITH_WILDCARD = Pattern.compile("(\\*\\.)?" + Patterns.DOMAIN_NAME.pattern()); + private static final int MAX_DOMAINS_BYTE_SIZE = 1024 * 1024; + @NonNull private final PlatformCompat mPlatformCompat; @@ -71,7 +73,7 @@ public class DomainVerificationCollector { * <li>- Only IntentFilter.SCHEME_HTTP and/or IntentFilter.SCHEME_HTTPS, * with no other schemes</li> * </ul> - * + * <p> * On prior versions of Android, Intent.CATEGORY_BROWSABLE was not a requirement, other * schemes were allowed, and setting autoVerify to true in any intent filter would implicitly * pretend that all intent filters were set to autoVerify="true". @@ -86,8 +88,8 @@ public class DomainVerificationCollector { } /** - * Effectively {@link #collectAllWebDomains(AndroidPackage)}, but requires - * {@link IntentFilter#getAutoVerify()} == true. + * Effectively {@link #collectAllWebDomains(AndroidPackage)}, but requires {@link + * IntentFilter#getAutoVerify()} == true. */ @NonNull public ArraySet<String> collectAutoVerifyDomains(@NonNull AndroidPackage pkg) { @@ -100,24 +102,21 @@ public class DomainVerificationCollector { boolean restrictDomains = DomainVerificationUtils.isChangeEnabled(mPlatformCompat, pkg, RESTRICT_DOMAINS); - ArraySet<String> domains = new ArraySet<>(); - if (restrictDomains) { - collectDomains(domains, pkg, checkAutoVerify); + return collectDomainsInternal(pkg, checkAutoVerify); } else { - collectDomainsLegacy(domains, pkg, checkAutoVerify); + return collectDomainsLegacy(pkg, checkAutoVerify); } - - return domains; } - /** @see #RESTRICT_DOMAINS */ - private void collectDomainsLegacy(@NonNull Set<String> domains, - @NonNull AndroidPackage pkg, boolean checkAutoVerify) { + /** + * @see #RESTRICT_DOMAINS + */ + private ArraySet<String> collectDomainsLegacy(@NonNull AndroidPackage pkg, + boolean checkAutoVerify) { if (!checkAutoVerify) { // Per-domain user selection state doesn't have a V1 equivalent on S, so just use V2 - collectDomains(domains, pkg, false); - return; + return collectDomainsInternal(pkg, false); } List<ParsedActivity> activities = pkg.getActivities(); @@ -140,39 +139,54 @@ public class DomainVerificationCollector { } if (!needsAutoVerify) { - return; + return new ArraySet<>(); } } - for (int activityIndex = 0; activityIndex < activitiesSize; activityIndex++) { + ArraySet<String> domains = new ArraySet<>(); + int totalSize = 0; + boolean underMaxSize = true; + for (int activityIndex = 0; activityIndex < activitiesSize && underMaxSize; + activityIndex++) { ParsedActivity activity = activities.get(activityIndex); List<ParsedIntentInfo> intents = activity.getIntents(); int intentsSize = intents.size(); - for (int intentIndex = 0; intentIndex < intentsSize; intentIndex++) { + for (int intentIndex = 0; intentIndex < intentsSize && underMaxSize; intentIndex++) { ParsedIntentInfo intent = intents.get(intentIndex); if (intent.handlesWebUris(false)) { int authorityCount = intent.countDataAuthorities(); for (int index = 0; index < authorityCount; index++) { String host = intent.getDataAuthority(index).getHost(); if (isValidHost(host)) { + totalSize += byteSizeOf(host); + underMaxSize = totalSize < MAX_DOMAINS_BYTE_SIZE; domains.add(host); } } } } } + + return domains; } - /** @see #RESTRICT_DOMAINS */ - private void collectDomains(@NonNull Set<String> domains, - @NonNull AndroidPackage pkg, boolean checkAutoVerify) { + /** + * @see #RESTRICT_DOMAINS + */ + private ArraySet<String> collectDomainsInternal(@NonNull AndroidPackage pkg, + boolean checkAutoVerify) { + ArraySet<String> domains = new ArraySet<>(); + int totalSize = 0; + boolean underMaxSize = true; + List<ParsedActivity> activities = pkg.getActivities(); int activitiesSize = activities.size(); - for (int activityIndex = 0; activityIndex < activitiesSize; activityIndex++) { + for (int activityIndex = 0; activityIndex < activitiesSize && underMaxSize; + activityIndex++) { ParsedActivity activity = activities.get(activityIndex); List<ParsedIntentInfo> intents = activity.getIntents(); int intentsSize = intents.size(); - for (int intentIndex = 0; intentIndex < intentsSize; intentIndex++) { + for (int intentIndex = 0; intentIndex < intentsSize && underMaxSize; intentIndex++) { ParsedIntentInfo intent = intents.get(intentIndex); if (checkAutoVerify && !intent.getAutoVerify()) { continue; @@ -198,14 +212,27 @@ public class DomainVerificationCollector { // app developer by declaring a separate intent-filter. This may not be worth // fixing. int authorityCount = intent.countDataAuthorities(); - for (int index = 0; index < authorityCount; index++) { + for (int index = 0; index < authorityCount && underMaxSize; index++) { String host = intent.getDataAuthority(index).getHost(); if (isValidHost(host)) { + totalSize += byteSizeOf(host); + underMaxSize = totalSize < MAX_DOMAINS_BYTE_SIZE; domains.add(host); } } } } + + return domains; + } + + /** + * Ballpark the size of domains to avoid a ridiculous amount of domains that could slow + * down client-server communication. + */ + private int byteSizeOf(String string) { + // Use the same method from core for the data objects so that restrictions are consistent + return android.content.pm.verify.domain.DomainVerificationUtils.estimatedByteSizeOf(string); } /** diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java index 275dd053fdde..ed37fa0da01f 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java @@ -185,6 +185,29 @@ public class DomainVerificationEnforcer { return !mCallback.filterAppAccess(packageName, callingUid, targetUserId); } + /** + * Querying for the owners of a domain. Because this API cannot filter the returned list of + * packages, enforces {@link android.Manifest.permission.QUERY_ALL_PACKAGES}, but also enforces + * {@link android.Manifest.permission.INTERACT_ACROSS_USERS} because each user has a different + * state. + */ + public void assertOwnerQuerent(int callingUid, @UserIdInt int callingUserId, + @UserIdInt int targetUserId) { + final int callingPid = Binder.getCallingPid(); + if (callingUserId != targetUserId) { + mContext.enforcePermission(android.Manifest.permission.INTERACT_ACROSS_USERS, + callingPid, callingUid, "Caller is not allowed to query other users"); + } + + mContext.enforcePermission(android.Manifest.permission.QUERY_ALL_PACKAGES, + callingPid, callingUid, "Caller " + callingUid + " does not hold " + + android.Manifest.permission.QUERY_ALL_PACKAGES); + + mContext.enforcePermission( + android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION, + callingPid, callingUid, "Caller is not allowed to query user selections"); + } + public interface Callback { /** * @return true if access to the given package should be filtered and the method failed as diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java index b6ea901ecd6b..9e22d82910df 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java @@ -310,7 +310,7 @@ public interface DomainVerificationManagerInternal extends DomainVerificationMan */ @ApprovalLevel int approvalLevelForDomain(@NonNull PackageSetting pkgSetting, @NonNull Intent intent, - @UserIdInt int userId); + @PackageManager.ResolveInfoFlags int resolveInfoFlags, @UserIdInt int userId); /** * @return the domain verification set ID for the given package, or null if the ID is diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java index 8aa63372b826..6f2810785c60 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java @@ -20,13 +20,14 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.verify.domain.DomainOwner; +import android.content.pm.verify.domain.DomainSet; +import android.content.pm.verify.domain.DomainVerificationInfo; import android.content.pm.verify.domain.DomainVerificationManager.InvalidDomainSetException; import android.content.pm.verify.domain.DomainVerificationManagerImpl; -import android.content.pm.verify.domain.DomainVerificationInfo; import android.content.pm.verify.domain.DomainVerificationUserSelection; import android.content.pm.verify.domain.IDomainVerificationManager; import android.os.ServiceSpecificException; -import android.util.ArraySet; import java.util.List; import java.util.UUID; @@ -61,11 +62,11 @@ class DomainVerificationManagerStub extends IDomainVerificationManager.Stub { } @Override - public void setDomainVerificationStatus(String domainSetId, List<String> domains, + public void setDomainVerificationStatus(String domainSetId, @NonNull DomainSet domainSet, int state) { try { mService.setDomainVerificationStatus(UUID.fromString(domainSetId), - new ArraySet<>(domains), state); + domainSet.getDomains(), state); } catch (Exception e) { throw rethrow(e); } @@ -82,11 +83,11 @@ class DomainVerificationManagerStub extends IDomainVerificationManager.Stub { } @Override - public void setDomainVerificationUserSelection(String domainSetId, List<String> domains, + public void setDomainVerificationUserSelection(String domainSetId, @NonNull DomainSet domainSet, boolean enabled, @UserIdInt int userId) { try { mService.setDomainVerificationUserSelection(UUID.fromString(domainSetId), - new ArraySet<>(domains), enabled, userId); + domainSet.getDomains(), enabled, userId); } catch (Exception e) { throw rethrow(e); } @@ -103,6 +104,17 @@ class DomainVerificationManagerStub extends IDomainVerificationManager.Stub { } } + @Nullable + @Override + public List<DomainOwner> getOwnersForDomain(@NonNull String domain, + @UserIdInt int userId) { + try { + return mService.getOwnersForDomain(domain, userId); + } catch (Exception e) { + throw rethrow(e); + } + } + private RuntimeException rethrow(Exception exception) throws RuntimeException { if (exception instanceof InvalidDomainSetException) { int packedErrorCode = DomainVerificationManagerImpl.ERROR_INVALID_DOMAIN_SET; diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java index 8e5aead05f96..b58c1ff374d5 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java @@ -17,6 +17,7 @@ package com.android.server.pm.verify.domain; import static java.util.Collections.emptyList; +import static java.util.Collections.emptySet; import android.annotation.NonNull; import android.annotation.Nullable; @@ -30,6 +31,7 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.pm.parsing.component.ParsedActivity; +import android.content.pm.verify.domain.DomainOwner; import android.content.pm.verify.domain.DomainVerificationInfo; import android.content.pm.verify.domain.DomainVerificationManager; import android.content.pm.verify.domain.DomainVerificationState; @@ -291,6 +293,8 @@ public class DomainVerificationService extends SystemService throws InvalidDomainSetException, NameNotFoundException { mEnforcer.assertApprovedVerifier(callingUid, mProxy); synchronized (mLock) { + List<String> verifiedDomains = new ArrayList<>(); + DomainVerificationPkgState pkgState = getAndValidateAttachedLocked(domainSetId, domains, true /* forAutoVerify */, callingUid, null /* userId */); ArrayMap<String, Integer> stateMap = pkgState.getStateMap(); @@ -301,8 +305,17 @@ public class DomainVerificationService extends SystemService continue; } + if (DomainVerificationManager.isStateVerified(state)) { + verifiedDomains.add(domain); + } + stateMap.put(domain, state); } + + int size = verifiedDomains.size(); + for (int index = 0; index < size; index++) { + removeUserSelectionsForDomain(verifiedDomains.get(index)); + } } mConnection.scheduleWriteSettings(); @@ -387,6 +400,20 @@ public class DomainVerificationService extends SystemService } } + private void removeUserSelectionsForDomain(@NonNull String domain) { + synchronized (mLock) { + final int size = mAttachedPkgStates.size(); + for (int index = 0; index < size; index++) { + DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index); + SparseArray<DomainVerificationUserState> array = pkgState.getUserSelectionStates(); + int arraySize = array.size(); + for (int arrayIndex = 0; arrayIndex < arraySize; arrayIndex++) { + array.valueAt(arrayIndex).removeHost(domain); + } + } + } + } + @Override public void setDomainVerificationLinkHandlingAllowed(@NonNull String packageName, boolean allowed) throws NameNotFoundException { @@ -470,19 +497,59 @@ public class DomainVerificationService extends SystemService InvalidDomainSetException.REASON_ID_INVALID); } + DomainVerificationPkgState pkgState = getAndValidateAttachedLocked(domainSetId, domains, + false /* forAutoVerify */, callingUid, userId); + DomainVerificationUserState userState = pkgState.getOrCreateUserSelectionState(userId); + + // Disable other packages if approving this one. Note that this check is only done for + // enabling. This allows an escape hatch in case multiple packages somehow get selected. + // They can be disabled without blocking in a circular dependency. if (enabled) { + // Cache the approved packages from the 1st pass because the search is expensive + ArrayMap<String, List<String>> domainToApprovedPackages = new ArrayMap<>(); + for (String domain : domains) { - if (!getApprovedPackages(domain, userId, APPROVAL_LEVEL_LEGACY_ALWAYS + 1, - mConnection::getPackageSettingLocked).first.isEmpty()) { + if (userState.getEnabledHosts().contains(domain)) { + continue; + } + + Pair<List<String>, Integer> packagesToLevel = getApprovedPackages(domain, + userId, APPROVAL_LEVEL_NONE + 1, mConnection::getPackageSettingLocked); + int highestApproval = packagesToLevel.second; + if (highestApproval > APPROVAL_LEVEL_SELECTION) { throw new InvalidDomainSetException(domainSetId, null, InvalidDomainSetException.REASON_UNABLE_TO_APPROVE); } + + domainToApprovedPackages.put(domain, packagesToLevel.first); + } + + // The removal for other packages must be done in a 2nd pass after it's determined + // that no higher priority owners exist for all of the domains in the set. + int mapSize = domainToApprovedPackages.size(); + for (int mapIndex = 0; mapIndex < mapSize; mapIndex++) { + String domain = domainToApprovedPackages.keyAt(mapIndex); + List<String> approvedPackages = domainToApprovedPackages.valueAt(mapIndex); + int approvedSize = approvedPackages.size(); + for (int approvedIndex = 0; approvedIndex < approvedSize; approvedIndex++) { + String approvedPackage = approvedPackages.get(approvedIndex); + DomainVerificationPkgState approvedPkgState = + mAttachedPkgStates.get(approvedPackage); + if (approvedPkgState == null) { + continue; + } + + DomainVerificationUserState approvedUserState = + approvedPkgState.getUserSelectionState(userId); + if (approvedUserState == null) { + continue; + } + + approvedUserState.removeHost(domain); + } } } - DomainVerificationPkgState pkgState = getAndValidateAttachedLocked(domainSetId, domains, - false /* forAutoVerify */, callingUid, userId); - DomainVerificationUserState userState = pkgState.getOrCreateUserSelectionState(userId); if (enabled) { userState.addHosts(domains); } else { @@ -600,28 +667,109 @@ public class DomainVerificationService extends SystemService throw DomainVerificationUtils.throwPackageUnavailable(packageName); } - ArrayMap<String, Boolean> hostToUserSelectionMap = new ArrayMap<>(); - - ArraySet<String> domains = mCollector.collectAllWebDomains(pkg); - int domainsSize = domains.size(); - for (int index = 0; index < domainsSize; index++) { - hostToUserSelectionMap.put(domains.valueAt(index), false); - } + ArraySet<String> webDomains = mCollector.collectAllWebDomains(pkg); + int webDomainsSize = webDomains.size(); + Map<String, Integer> domains = new ArrayMap<>(webDomainsSize); + ArrayMap<String, Integer> stateMap = pkgState.getStateMap(); DomainVerificationUserState userState = pkgState.getUserSelectionState(userId); - boolean linkHandlingAllowed = true; - if (userState != null) { - linkHandlingAllowed = userState.isLinkHandlingAllowed(); - ArraySet<String> enabledHosts = userState.getEnabledHosts(); - int hostsSize = enabledHosts.size(); - for (int index = 0; index < hostsSize; index++) { - hostToUserSelectionMap.put(enabledHosts.valueAt(index), true); + Set<String> enabledHosts = userState == null ? emptySet() : userState.getEnabledHosts(); + + for (int index = 0; index < webDomainsSize; index++) { + String host = webDomains.valueAt(index); + Integer state = stateMap.get(host); + + int domainState; + if (state != null && DomainVerificationManager.isStateVerified(state)) { + domainState = DomainVerificationUserSelection.DOMAIN_STATE_VERIFIED; + } else if (enabledHosts.contains(host)) { + domainState = DomainVerificationUserSelection.DOMAIN_STATE_SELECTED; + } else { + domainState = DomainVerificationUserSelection.DOMAIN_STATE_NONE; } + + domains.put(host, domainState); } + boolean linkHandlingAllowed = userState == null || userState.isLinkHandlingAllowed(); + return new DomainVerificationUserSelection(pkgState.getId(), packageName, - UserHandle.of(userId), linkHandlingAllowed, hostToUserSelectionMap); + UserHandle.of(userId), linkHandlingAllowed, domains); + } + } + + @NonNull + @Override + public List<DomainOwner> getOwnersForDomain(@NonNull String domain) { + return getOwnersForDomain(domain, mConnection.getCallingUserId()); + } + + public List<DomainOwner> getOwnersForDomain(@NonNull String domain, @UserIdInt int userId) { + mEnforcer.assertOwnerQuerent(mConnection.getCallingUid(), mConnection.getCallingUserId(), + userId); + + SparseArray<List<String>> levelToPackages = new SparseArray<>(); + + // First, collect the raw approval level values + synchronized (mLock) { + final int size = mAttachedPkgStates.size(); + for (int index = 0; index < size; index++) { + DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index); + String packageName = pkgState.getPackageName(); + PackageSetting pkgSetting = mConnection.getPackageSettingLocked(packageName); + if (pkgSetting == null) { + continue; + } + + int level = approvalLevelForDomain(pkgSetting, domain, userId, domain); + if (level <= APPROVAL_LEVEL_NONE) { + continue; + } + List<String> list = levelToPackages.get(level); + if (list == null) { + list = new ArrayList<>(); + levelToPackages.put(level, list); + } + list.add(packageName); + } + } + + final int size = levelToPackages.size(); + if (size == 0) { + return emptyList(); + } + + // Then sort them ascending by first installed time, with package name as the tie breaker + for (int index = 0; index < size; index++) { + levelToPackages.valueAt(index).sort((first, second) -> { + PackageSetting firstPkgSetting = mConnection.getPackageSettingLocked(first); + PackageSetting secondPkgSetting = mConnection.getPackageSettingLocked(second); + + long firstInstallTime = + firstPkgSetting == null ? -1L : firstPkgSetting.getFirstInstallTime(); + long secondInstallTime = + secondPkgSetting == null ? -1L : secondPkgSetting.getFirstInstallTime(); + + if (firstInstallTime != secondInstallTime) { + return (int) (firstInstallTime - secondInstallTime); + } + + return first.compareToIgnoreCase(second); + }); } + + List<DomainOwner> owners = new ArrayList<>(); + for (int index = 0; index < size; index++) { + int level = levelToPackages.keyAt(index); + boolean overrideable = level <= APPROVAL_LEVEL_SELECTION; + List<String> packages = levelToPackages.valueAt(index); + int packagesSize = packages.size(); + for (int packageIndex = 0; packageIndex < packagesSize; packageIndex++) { + owners.add(new DomainOwner(packages.get(packageIndex), overrideable)); + } + } + + return owners; } @NonNull @@ -634,7 +782,7 @@ public class DomainVerificationService extends SystemService @Override public void migrateState(@NonNull PackageSetting oldPkgSetting, @NonNull PackageSetting newPkgSetting) { - String pkgName = newPkgSetting.name; + String pkgName = newPkgSetting.getName(); boolean sendBroadcast; synchronized (mLock) { @@ -730,7 +878,7 @@ public class DomainVerificationService extends SystemService // gains or loses all domains. UUID domainSetId = newPkgSetting.getDomainSetId(); - String pkgName = newPkgSetting.name; + String pkgName = newPkgSetting.getName(); boolean sendBroadcast = true; @@ -1346,9 +1494,9 @@ public class DomainVerificationService extends SystemService @Override public int approvalLevelForDomain(@NonNull PackageSetting pkgSetting, @NonNull Intent intent, - @UserIdInt int userId) { - String packageName = pkgSetting.name; - if (!DomainVerificationUtils.isDomainVerificationIntent(intent)) { + @PackageManager.ResolveInfoFlags int resolveInfoFlags, @UserIdInt int userId) { + String packageName = pkgSetting.getName(); + if (!DomainVerificationUtils.isDomainVerificationIntent(intent, resolveInfoFlags)) { if (DEBUG_APPROVAL) { debugApproval(packageName, intent, userId, false, "not valid intent"); } @@ -1364,7 +1512,7 @@ public class DomainVerificationService extends SystemService */ private int approvalLevelForDomain(@NonNull PackageSetting pkgSetting, @NonNull String host, @UserIdInt int userId, @NonNull Object debugObject) { - String packageName = pkgSetting.name; + String packageName = pkgSetting.getName(); final AndroidPackage pkg = pkgSetting.getPkg(); // Should never be null, but if it is, skip this and assume that v2 is enabled diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java index 475d3a87b427..783aff6ccb55 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java @@ -40,10 +40,13 @@ public final class DomainVerificationUtils { throw new NameNotFoundException("Package " + packageName + " unavailable"); } - public static boolean isDomainVerificationIntent(Intent intent) { - return intent.isWebIntent() - && intent.hasCategory(Intent.CATEGORY_BROWSABLE) - && intent.hasCategory(Intent.CATEGORY_DEFAULT); + public static boolean isDomainVerificationIntent(Intent intent, int resolveInfoFlags) { + if (!intent.isWebIntent() || !intent.hasCategory(Intent.CATEGORY_BROWSABLE)) { + return false; + } + + return ((resolveInfoFlags & PackageManager.MATCH_DEFAULT_ONLY) != 0) + || intent.hasCategory(Intent.CATEGORY_DEFAULT); } static boolean isChangeEnabled(PlatformCompat platformCompat, AndroidPackage pkg, diff --git a/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java index 22468640800e..8fbb33afb6ca 100644 --- a/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java +++ b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java @@ -58,6 +58,11 @@ public class DomainVerificationUserState { return this; } + public DomainVerificationUserState removeHost(String host) { + mEnabledHosts.remove(host); + return this; + } + public DomainVerificationUserState removeHosts(@NonNull ArraySet<String> newHosts) { mEnabledHosts.removeAll(newHosts); return this; diff --git a/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java index a80406548719..2580a9725cb3 100644 --- a/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java +++ b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java @@ -16,6 +16,9 @@ package com.android.server.pm.verify.domain.proxy; +import static android.os.PowerWhitelistManager.REASON_DOMAIN_VERIFICATION_V1; +import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED; + import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; @@ -238,7 +241,8 @@ public class DomainVerificationProxyV1 implements DomainVerificationProxy { final long allowListTimeout = mConnection.getPowerSaveTempWhitelistAppDuration(); mConnection.getDeviceIdleInternal().addPowerSaveTempWhitelistApp(Process.myUid(), mVerifierComponent.getPackageName(), allowListTimeout, - UserHandle.USER_SYSTEM, true, "domain verification agent"); + UserHandle.USER_SYSTEM, true, REASON_DOMAIN_VERIFICATION_V1, + "domain verification agent"); int size = verifications.size(); for (int index = 0; index < size; index++) { @@ -261,7 +265,9 @@ public class DomainVerificationProxyV1 implements DomainVerificationProxy { .addFlags(Intent.FLAG_RECEIVER_FOREGROUND); final BroadcastOptions options = BroadcastOptions.makeBasic(); - options.setTemporaryAppWhitelistDuration(allowListTimeout); + options.setTemporaryAppAllowlist(allowListTimeout, + TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, + REASON_DOMAIN_VERIFICATION_V1, ""); mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM, null, options.toBundle()); } } diff --git a/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV2.java b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV2.java index 1ef06036021e..2ba17d3a86a5 100644 --- a/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV2.java +++ b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV2.java @@ -16,6 +16,9 @@ package com.android.server.pm.verify.domain.proxy; +import static android.os.PowerWhitelistManager.REASON_DOMAIN_VERIFICATION_V2; +import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.BroadcastOptions; @@ -69,11 +72,14 @@ public class DomainVerificationProxyV2 implements DomainVerificationProxy { final long allowListTimeout = mConnection.getPowerSaveTempWhitelistAppDuration(); final BroadcastOptions options = BroadcastOptions.makeBasic(); - options.setTemporaryAppWhitelistDuration(allowListTimeout); + options.setTemporaryAppAllowlist(allowListTimeout, + TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, + REASON_DOMAIN_VERIFICATION_V2, ""); mConnection.getDeviceIdleInternal().addPowerSaveTempWhitelistApp(Process.myUid(), mVerifierComponent.getPackageName(), allowListTimeout, - UserHandle.USER_SYSTEM, true, "domain verification agent"); + UserHandle.USER_SYSTEM, true, REASON_DOMAIN_VERIFICATION_V2, + "domain verification agent"); Intent intent = new Intent(Intent.ACTION_DOMAINS_NEED_VERIFICATION) .setComponent(mVerifierComponent) diff --git a/services/core/java/com/android/server/policy/AppOpsPolicy.java b/services/core/java/com/android/server/policy/AppOpsPolicy.java new file mode 100644 index 000000000000..c9653909adb6 --- /dev/null +++ b/services/core/java/com/android/server/policy/AppOpsPolicy.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.policy; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.AppOpsManager; +import android.app.AppOpsManagerInternal; +import android.location.LocationManagerInternal; +import android.util.ArrayMap; +import android.util.ArraySet; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.function.HeptFunction; +import com.android.internal.util.function.QuadFunction; +import com.android.server.LocalServices; + +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * This class defines policy for special behaviors around app ops. + */ +public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegate { + @NonNull + private final Object mLock = new Object(); + + /** + * The locking policy around the location tags is a bit special. Since we want to + * avoid grabbing the lock on every op note we are taking the approach where the + * read and write are being done via a thread-safe data structure such that the + * lookup/insert are single thread-safe calls. When we update the cached state we + * use a lock to ensure the update's lookup and store calls are done atomically, + * so multiple writers would not interleave. The tradeoff is we make is that the + * concurrent data structure would use boxing/unboxing of integers but this is + * preferred to locking. + */ + @GuardedBy("mLock - writes only - see above") + @NonNull + private final ConcurrentHashMap<Integer, ArrayMap<String, ArraySet<String>>> mLocationTags = + new ConcurrentHashMap(); + + public AppOpsPolicy() { + final LocationManagerInternal locationManagerInternal = LocalServices.getService( + LocationManagerInternal.class); + locationManagerInternal.setOnProviderLocationTagsChangeListener((providerTagInfo) -> { + synchronized (mLock) { + final int uid = providerTagInfo.getUid(); + // We make a copy of the per UID state to limit our mutation to one + // operation in the underlying concurrent data structure. + ArrayMap<String, ArraySet<String>> uidTags = mLocationTags.get(uid); + if (uidTags != null) { + uidTags = new ArrayMap<>(uidTags); + } + + final String packageName = providerTagInfo.getPackageName(); + ArraySet<String> packageTags = (uidTags != null) ? uidTags.get(packageName) : null; + if (packageTags != null) { + packageTags = new ArraySet<>(packageTags); + } + + final Set<String> providerTags = providerTagInfo.getTags(); + if (providerTags != null && !providerTags.isEmpty()) { + if (packageTags != null) { + packageTags.clear(); + packageTags.addAll(providerTags); + } else { + packageTags = new ArraySet<>(providerTags); + } + if (uidTags == null) { + uidTags = new ArrayMap<>(); + } + uidTags.put(packageName, packageTags); + mLocationTags.put(uid, uidTags); + } else if (uidTags != null) { + uidTags.remove(packageName); + if (!uidTags.isEmpty()) { + mLocationTags.put(uid, uidTags); + } else { + mLocationTags.remove(uid); + } + } + } + }); + } + + @Override + public int checkOperation(int code, int uid, String packageName, boolean raw, + QuadFunction<Integer, Integer, String, Boolean, Integer> superImpl) { + return superImpl.apply(code, uid, packageName, raw); + } + + @Override + public int checkAudioOperation(int code, int usage, int uid, String packageName, + QuadFunction<Integer, Integer, Integer, String, Integer> superImpl) { + return superImpl.apply(code, usage, uid, packageName); + } + + @Override + public int noteOperation(int code, int uid, @Nullable String packageName, + @Nullable String featureId, boolean shouldCollectAsyncNotedOp, @Nullable String message, + boolean shouldCollectMessage, @NonNull HeptFunction<Integer, Integer, String, String, + Boolean, String, Boolean, Integer> superImpl) { + if (isHandledOp(code)) { + // Only a single lookup from the underlying concurrent data structure + final ArrayMap<String, ArraySet<String>> uidTags = mLocationTags.get(uid); + if (uidTags != null) { + final ArraySet<String> packageTags = uidTags.get(packageName); + if (packageTags != null && packageTags.contains(featureId)) { + return superImpl.apply(resolveLocationOp(code), uid, packageName, featureId, + shouldCollectAsyncNotedOp, message, shouldCollectMessage); + } + } + } + return superImpl.apply(code, uid, packageName, featureId, shouldCollectAsyncNotedOp, + message, shouldCollectMessage); + } + + private static boolean isHandledOp(int code) { + switch (code) { + case AppOpsManager.OP_FINE_LOCATION: + case AppOpsManager.OP_COARSE_LOCATION: + return true; + } + return false; + } + + private static int resolveLocationOp(int code) { + switch (code) { + case AppOpsManager.OP_FINE_LOCATION: + return AppOpsManager.OP_FINE_LOCATION_SOURCE; + case AppOpsManager.OP_COARSE_LOCATION: + return AppOpsManager.OP_COARSE_LOCATION_SOURCE; + } + return code; + } +} diff --git a/services/core/java/com/android/server/policy/KeyCombinationManager.java b/services/core/java/com/android/server/policy/KeyCombinationManager.java index 84ac12497e71..7f55723cda0b 100644 --- a/services/core/java/com/android/server/policy/KeyCombinationManager.java +++ b/services/core/java/com/android/server/policy/KeyCombinationManager.java @@ -102,9 +102,11 @@ public class KeyCombinationManager { } /** - * Check if the key event could be triggered by combine key rule before dispatching to a window. + * Check if the key event could be intercepted by combination key rule before it is dispatched + * to a window. + * Return true if any active rule could be triggered by the key event, otherwise false. */ - void interceptKey(KeyEvent event, boolean interactive) { + boolean interceptKey(KeyEvent event, boolean interactive) { final boolean down = event.getAction() == KeyEvent.ACTION_DOWN; final int keyCode = event.getKeyCode(); final int count = mActiveRules.size(); @@ -117,9 +119,9 @@ public class KeyCombinationManager { // exceed time from first key down. forAllRules(mActiveRules, (rule)-> rule.cancel()); mActiveRules.clear(); - return; + return false; } else if (count == 0) { // has some key down but no active rule exist. - return; + return false; } } @@ -127,7 +129,7 @@ public class KeyCombinationManager { mDownTimes.put(keyCode, eventTime); } else { // ignore old key, maybe a repeat key. - return; + return false; } if (mDownTimes.size() == 1) { @@ -141,7 +143,7 @@ public class KeyCombinationManager { } else { // Ignore if rule already triggered. if (mTriggeredRule != null) { - return; + return true; } // check if second key can trigger rule, or remove the non-match rule. @@ -156,6 +158,7 @@ public class KeyCombinationManager { mActiveRules.clear(); if (mTriggeredRule != null) { mActiveRules.add(mTriggeredRule); + return true; } } } else { @@ -168,6 +171,7 @@ public class KeyCombinationManager { } } } + return false; } /** diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 4ccd57ddc977..1b192e43c7b8 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -73,6 +73,8 @@ import static android.view.WindowManagerGlobal.ADD_OKAY; import static android.view.WindowManagerGlobal.ADD_PERMISSION_DENIED; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_KEYCHORD_DELAY; +import static com.android.server.policy.SingleKeyGestureDetector.KEY_LONGPRESS; +import static com.android.server.policy.SingleKeyGestureDetector.KEY_VERYLONGPRESS; import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVERED; import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVER_ABSENT; import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_UNCOVERED; @@ -456,7 +458,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { volatile boolean mPowerKeyHandled; volatile boolean mBackKeyHandled; volatile boolean mBeganFromNonInteractive; - volatile int mPowerKeyPressCounter; volatile boolean mEndCallKeyHandled; volatile boolean mCameraGestureTriggeredDuringGoingToSleep; volatile boolean mGoingToSleep; @@ -497,7 +498,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { boolean mHasSoftInput = false; boolean mHapticTextHandleEnabled; boolean mUseTvRouting; - int mVeryLongPressTimeout; boolean mAllowStartActivityForLongPressOnPowerDuringSetup; MetricsLogger mLogger; boolean mWakeOnDpadKeyPress; @@ -520,8 +520,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { boolean mConsumeSearchKeyUp; boolean mPendingMetaAction; boolean mPendingCapsLockToggle; - int mMetaState; - int mInitialMetaState; // support for activating the lock screen while the screen is on private HashSet<Integer> mAllowLockscreenWhenOnDisplays = new HashSet<>(); @@ -597,14 +595,13 @@ public class PhoneWindowManager implements WindowManagerPolicy { private final com.android.internal.policy.LogDecelerateInterpolator mLogDecelerateInterpolator = new LogDecelerateInterpolator(100, 0); - private final MutableBoolean mTmpBoolean = new MutableBoolean(false); - private boolean mPerDisplayFocusEnabled = false; private volatile int mTopFocusedDisplayId = INVALID_DISPLAY; private int mPowerButtonSuppressionDelayMillis = POWER_BUTTON_SUPPRESSION_DELAY_DEFAULT_MILLIS; private KeyCombinationManager mKeyCombinationManager; + private SingleKeyGestureDetector mSingleKeyGestureDetector; private static final int MSG_DISPATCH_MEDIA_KEY_WITH_WAKE_LOCK = 3; private static final int MSG_DISPATCH_MEDIA_KEY_REPEAT_WITH_WAKE_LOCK = 4; @@ -615,10 +612,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { private static final int MSG_DISPATCH_SHOW_GLOBAL_ACTIONS = 10; private static final int MSG_HIDE_BOOT_MESSAGE = 11; private static final int MSG_LAUNCH_VOICE_ASSIST_WITH_WAKE_LOCK = 12; - private static final int MSG_POWER_DELAYED_PRESS = 13; - private static final int MSG_POWER_LONG_PRESS = 14; private static final int MSG_SHOW_PICTURE_IN_PICTURE_MENU = 15; - private static final int MSG_BACK_LONG_PRESS = 16; private static final int MSG_ACCESSIBILITY_SHORTCUT = 17; private static final int MSG_BUGREPORT_TV = 18; private static final int MSG_ACCESSIBILITY_TV = 19; @@ -626,8 +620,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { private static final int MSG_SYSTEM_KEY_PRESS = 21; private static final int MSG_HANDLE_ALL_APPS = 22; private static final int MSG_LAUNCH_ASSIST = 23; - private static final int MSG_POWER_VERY_LONG_PRESS = 25; - private static final int MSG_RINGER_TOGGLE_CHORD = 26; + private static final int MSG_RINGER_TOGGLE_CHORD = 24; private class PolicyHandler extends Handler { @Override @@ -668,22 +661,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { case MSG_LAUNCH_VOICE_ASSIST_WITH_WAKE_LOCK: launchVoiceAssistWithWakeLock(); break; - case MSG_POWER_DELAYED_PRESS: - powerPress((Long) msg.obj, msg.arg1 != 0, msg.arg2); - finishPowerKeyPress(); - break; - case MSG_POWER_LONG_PRESS: - powerLongPress((Long) msg.obj /* eventTime */); - break; - case MSG_POWER_VERY_LONG_PRESS: - powerVeryLongPress(); - break; case MSG_SHOW_PICTURE_IN_PICTURE_MENU: showPictureInPictureMenuInternal(); break; - case MSG_BACK_LONG_PRESS: - backLongPress(); - break; case MSG_ACCESSIBILITY_SHORTCUT: accessibilityShortcutActivated(); break; @@ -794,13 +774,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { } }; - private Runnable mPossibleVeryLongPressReboot = new Runnable() { - @Override - public void run() { - mActivityManagerInternal.prepareForPossibleShutdown(); - } - }; - private void handleRingerChordGesture() { if (mRingerToggleChord == VOLUME_HUSH_OFF) { return; @@ -840,28 +813,13 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - private void interceptBackKeyDown() { - mLogger.count("key_back_down", 1); - // Reset back key state for long press - mBackKeyHandled = false; - - if (hasLongPressOnBackBehavior()) { - Message msg = mHandler.obtainMessage(MSG_BACK_LONG_PRESS); - msg.setAsynchronous(true); - mHandler.sendMessageDelayed(msg, - ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout()); - } - } // returns true if the key was handled and should not be passed to the user - private boolean interceptBackKeyUp(KeyEvent event) { - mLogger.count("key_back_up", 1); + private boolean backKeyPress() { + mLogger.count("key_back_press", 1); // Cache handled state boolean handled = mBackKeyHandled; - // Reset back long press state - cancelPendingBackKeyAction(); - if (mHasFeatureWatch) { TelecomManager telecomManager = getTelecommService(); @@ -883,10 +841,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - if (mAutofillManagerInternal != null && event.getKeyCode() == KeyEvent.KEYCODE_BACK) { + if (mAutofillManagerInternal != null) { mHandler.sendMessage(mHandler.obtainMessage(MSG_DISPATCH_BACK_KEY_TO_AUTOFILL)); } - return handled; } @@ -896,11 +853,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { mPowerKeyWakeLock.acquire(); } - // Cancel multi-press detection timeout. - if (mPowerKeyPressCounter != 0) { - mHandler.removeMessages(MSG_POWER_DELAYED_PRESS); - } - mWindowManagerFuncs.onPowerKeyDown(interactive); // Stop ringing or end call if configured to do so when power is pressed. @@ -922,71 +874,20 @@ public class PhoneWindowManager implements WindowManagerPolicy { final boolean handledByPowerManager = mPowerManagerInternal.interceptPowerKeyDown(event); - GestureLauncherService gestureService = LocalServices.getService( - GestureLauncherService.class); - boolean gesturedServiceIntercepted = false; - if (gestureService != null) { - gesturedServiceIntercepted = gestureService.interceptPowerKeyDown(event, interactive, - mTmpBoolean); - if (mTmpBoolean.value && mRequestedOrGoingToSleep) { - mCameraGestureTriggeredDuringGoingToSleep = true; - } - } - // Inform the StatusBar; but do not allow it to consume the event. sendSystemKeyToStatusBarAsync(event.getKeyCode()); - schedulePossibleVeryLongPressReboot(); - // If the power key has still not yet been handled, then detect short // press, long press, or multi press and decide what to do. - mPowerKeyHandled = hungUp || gesturedServiceIntercepted + mPowerKeyHandled = mPowerKeyHandled || hungUp || handledByPowerManager || mKeyCombinationManager.isPowerKeyIntercepted(); if (!mPowerKeyHandled) { - if (interactive) { - // When interactive, we're already awake. - // Wait for a long press or for the button to be released to decide what to do. - if (hasLongPressOnPowerBehavior()) { - if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) { - powerLongPress(event.getEventTime()); - } else { - Message msg = mHandler.obtainMessage(MSG_POWER_LONG_PRESS, - event.getEventTime()); - msg.setAsynchronous(true); - mHandler.sendMessageDelayed(msg, - ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout()); - - if (hasVeryLongPressOnPowerBehavior()) { - Message longMsg = mHandler.obtainMessage(MSG_POWER_VERY_LONG_PRESS); - longMsg.setAsynchronous(true); - mHandler.sendMessageDelayed(longMsg, mVeryLongPressTimeout); - } - } - } - } else { + if (!interactive) { wakeUpFromPowerKey(event.getDownTime()); - if (mSupportLongPressPowerWhenNonInteractive && hasLongPressOnPowerBehavior()) { - if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) { - powerLongPress(event.getEventTime()); - } else { - Message msg = mHandler.obtainMessage(MSG_POWER_LONG_PRESS, - event.getEventTime()); - msg.setAsynchronous(true); - mHandler.sendMessageDelayed(msg, - ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout()); - - if (hasVeryLongPressOnPowerBehavior()) { - Message longMsg = mHandler.obtainMessage(MSG_POWER_VERY_LONG_PRESS); - longMsg.setAsynchronous(true); - mHandler.sendMessageDelayed(longMsg, mVeryLongPressTimeout); - } - } - mBeganFromNonInteractive = true; } else { final int maxCount = getMaxMultiPressPowerCount(); - if (maxCount <= 1) { mPowerKeyHandled = true; } else { @@ -994,68 +895,38 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } } + } else { + // handled by another power key policy. + if (!mSingleKeyGestureDetector.isKeyIntercepted(KEYCODE_POWER)) { + mSingleKeyGestureDetector.reset(); + } } } private void interceptPowerKeyUp(KeyEvent event, boolean interactive, boolean canceled) { final boolean handled = canceled || mPowerKeyHandled; - cancelPendingPowerKeyAction(); if (!handled) { if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) == 0) { // Abort possibly stuck animations only when power key up without long press case. mHandler.post(mWindowManagerFuncs::triggerAnimationFailsafe); } - - // Figure out how to handle the key now that it has been released. - mPowerKeyPressCounter += 1; - - final int maxCount = getMaxMultiPressPowerCount(); - final long eventTime = event.getDownTime(); - if (mPowerKeyPressCounter < maxCount) { - // This could be a multi-press. Wait a little bit longer to confirm. - // Continue holding the wake lock. - Message msg = mHandler.obtainMessage(MSG_POWER_DELAYED_PRESS, - interactive ? 1 : 0, mPowerKeyPressCounter, eventTime); - msg.setAsynchronous(true); - mHandler.sendMessageDelayed(msg, ViewConfiguration.getMultiPressTimeout()); - return; - } - - // No other actions. Handle it immediately. - powerPress(eventTime, interactive, mPowerKeyPressCounter); + } else { + // handled by single key or another power key policy. + mSingleKeyGestureDetector.reset(); + finishPowerKeyPress(); } - // Done. Reset our state. - finishPowerKeyPress(); } private void finishPowerKeyPress() { mBeganFromNonInteractive = false; - mPowerKeyPressCounter = 0; + mPowerKeyHandled = false; if (mPowerKeyWakeLock.isHeld()) { mPowerKeyWakeLock.release(); } } - private void cancelPendingPowerKeyAction() { - if (!mPowerKeyHandled) { - mPowerKeyHandled = true; - mHandler.removeMessages(MSG_POWER_LONG_PRESS); - } - if (hasVeryLongPressOnPowerBehavior()) { - mHandler.removeMessages(MSG_POWER_VERY_LONG_PRESS); - } - cancelPossibleVeryLongPressReboot(); - } - - private void cancelPendingBackKeyAction() { - if (!mBackKeyHandled) { - mBackKeyHandled = true; - mHandler.removeMessages(MSG_BACK_LONG_PRESS); - } - } - private void powerPress(long eventTime, boolean interactive, int count) { if (mDefaultDisplayPolicy.isScreenOnEarly() && !mDefaultDisplayPolicy.isScreenOnFully()) { Slog.i(TAG, "Suppressed redundant power key press while " @@ -1206,6 +1077,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { private void powerLongPress(long eventTime) { final int behavior = getResolvedLongPressOnPowerBehavior(); + switch (behavior) { case LONG_PRESS_POWER_NOTHING: break; @@ -1844,8 +1716,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { com.android.internal.R.integer.config_triplePressOnPowerBehavior); mShortPressOnSleepBehavior = mContext.getResources().getInteger( com.android.internal.R.integer.config_shortPressOnSleepBehavior); - mVeryLongPressTimeout = mContext.getResources().getInteger( - com.android.internal.R.integer.config_veryLongPressTimeout); mAllowStartActivityForLongPressOnPowerDuringSetup = mContext.getResources().getBoolean( com.android.internal.R.bool.config_allowStartActivityForLongPressOnPowerInSetup); @@ -1939,6 +1809,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } }); initKeyCombinationRules(); + initSingleKeyGestureRules(); } private void initKeyCombinationRules() { @@ -1951,7 +1822,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { new TwoKeysCombinationRule(KEYCODE_VOLUME_DOWN, KEYCODE_POWER) { @Override void execute() { - cancelPendingPowerKeyAction(); + mPowerKeyHandled = true; interceptScreenshotChord(); } @Override @@ -1986,7 +1857,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } @Override void execute() { - cancelPendingPowerKeyAction(); + mPowerKeyHandled = true; interceptRingerToggleChord(); } @Override @@ -2000,7 +1871,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { new TwoKeysCombinationRule(KEYCODE_BACK, KEYCODE_DPAD_DOWN) { @Override void execute() { - cancelPendingBackKeyAction(); + mBackKeyHandled = true; interceptAccessibilityGestureTv(); } @@ -2014,7 +1885,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { new TwoKeysCombinationRule(KEYCODE_DPAD_CENTER, KEYCODE_BACK) { @Override void execute() { - cancelPendingBackKeyAction(); + mBackKeyHandled = true; interceptBugreportGestureTv(); } @@ -2027,6 +1898,84 @@ public class PhoneWindowManager implements WindowManagerPolicy { } /** + * Rule for single power key gesture. + */ + private final class PowerKeyRule extends SingleKeyGestureDetector.SingleKeyRule { + PowerKeyRule(int gestures) { + super(KEYCODE_POWER, gestures); + } + + @Override + int getMaxMultiPressCount() { + return getMaxMultiPressPowerCount(); + } + + @Override + void onPress(long downTime) { + powerPress(downTime, true, 1 /*count*/); + finishPowerKeyPress(); + } + + @Override + void onLongPress(long downTime) { + powerLongPress(downTime); + } + + @Override + void onVeryLongPress(long downTime) { + mActivityManagerInternal.prepareForPossibleShutdown(); + powerVeryLongPress(); + } + + @Override + void onMultiPress(long downTime, int count) { + powerPress(downTime, true, count); + finishPowerKeyPress(); + } + } + + /** + * Rule for single back key gesture. + */ + private final class BackKeyRule extends SingleKeyGestureDetector.SingleKeyRule { + BackKeyRule(int gestures) { + super(KEYCODE_BACK, gestures); + } + + @Override + int getMaxMultiPressCount() { + return 1; + } + + @Override + void onPress(long downTime) { + mBackKeyHandled |= backKeyPress(); + } + + @Override + void onLongPress(long downTime) { + backLongPress(); + } + } + + private void initSingleKeyGestureRules() { + mSingleKeyGestureDetector = new SingleKeyGestureDetector(mContext); + + int powerKeyGestures = 0; + if (hasVeryLongPressOnPowerBehavior()) { + powerKeyGestures |= KEY_VERYLONGPRESS; + } + if (hasLongPressOnPowerBehavior()) { + powerKeyGestures |= KEY_LONGPRESS; + } + mSingleKeyGestureDetector.addRule(new PowerKeyRule(powerKeyGestures)); + + if (hasLongPressOnBackBehavior()) { + mSingleKeyGestureDetector.addRule(new BackKeyRule(KEY_LONGPRESS)); + } + } + + /** * Read values from config.xml that may be overridden depending on * the configuration of the device. * eg. Disable long press on home goes to recents on sw600dp. @@ -2547,6 +2496,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { final boolean down = event.getAction() == KeyEvent.ACTION_DOWN; final boolean canceled = event.isCanceled(); final int displayId = event.getDisplayId(); + final long key_consumed = -1; if (DEBUG_INPUT) { Log.d(TAG, "interceptKeyTi keyCode=" + keyCode + " down=" + down + " repeatCount=" @@ -2554,7 +2504,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } if (mKeyCombinationManager.isKeyConsumed(event)) { - return -1; + return key_consumed; } if ((flags & KeyEvent.FLAG_FALLBACK) == 0) { @@ -2575,205 +2525,250 @@ public class PhoneWindowManager implements WindowManagerPolicy { mPendingCapsLockToggle = false; } - // First we always handle the home key here, so applications - // can never break it, although if keyguard is on, we do let - // it handle it, because that gives us the correct 5 second - // timeout. - if (keyCode == KEYCODE_HOME) { - DisplayHomeButtonHandler handler = mDisplayHomeButtonHandlers.get(displayId); - if (handler == null) { - handler = new DisplayHomeButtonHandler(displayId); - mDisplayHomeButtonHandlers.put(displayId, handler); - } - return handler.handleHomeButton(focusedToken, event); - } else if (keyCode == KeyEvent.KEYCODE_MENU) { - // Hijack modified menu keys for debugging features - final int chordBug = KeyEvent.META_SHIFT_ON; - - if (down && repeatCount == 0) { - if (mEnableShiftMenuBugReports && (metaState & chordBug) == chordBug) { - Intent intent = new Intent(Intent.ACTION_BUG_REPORT); - mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, - null, null, null, 0, null, null); - return -1; + switch(keyCode) { + case KeyEvent.KEYCODE_HOME: + // First we always handle the home key here, so applications + // can never break it, although if keyguard is on, we do let + // it handle it, because that gives us the correct 5 second + // timeout. + DisplayHomeButtonHandler handler = mDisplayHomeButtonHandlers.get(displayId); + if (handler == null) { + handler = new DisplayHomeButtonHandler(displayId); + mDisplayHomeButtonHandlers.put(displayId, handler); } - } - } else if (keyCode == KeyEvent.KEYCODE_SEARCH) { - if (down) { - if (repeatCount == 0) { - mSearchKeyShortcutPending = true; - mConsumeSearchKeyUp = false; + return handler.handleHomeButton(focusedToken, event); + case KeyEvent.KEYCODE_MENU: + // Hijack modified menu keys for debugging features + final int chordBug = KeyEvent.META_SHIFT_ON; + + if (down && repeatCount == 0) { + if (mEnableShiftMenuBugReports && (metaState & chordBug) == chordBug) { + Intent intent = new Intent(Intent.ACTION_BUG_REPORT); + mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, + null, null, null, 0, null, null); + return key_consumed; + } } - } else { - mSearchKeyShortcutPending = false; - if (mConsumeSearchKeyUp) { - mConsumeSearchKeyUp = false; - return -1; + break; + case KeyEvent.KEYCODE_SEARCH: + if (down) { + if (repeatCount == 0) { + mSearchKeyShortcutPending = true; + mConsumeSearchKeyUp = false; + } + } else { + mSearchKeyShortcutPending = false; + if (mConsumeSearchKeyUp) { + mConsumeSearchKeyUp = false; + return key_consumed; + } } - } - return 0; - } else if (keyCode == KeyEvent.KEYCODE_APP_SWITCH) { - if (!keyguardOn) { - if (down && repeatCount == 0) { - preloadRecentApps(); - } else if (!down) { - toggleRecentApps(); + return 0; + case KeyEvent.KEYCODE_APP_SWITCH: + if (!keyguardOn) { + if (down && repeatCount == 0) { + preloadRecentApps(); + } else if (!down) { + toggleRecentApps(); + } } - } - return -1; - } else if (keyCode == KeyEvent.KEYCODE_N && event.isMetaPressed()) { - if (down) { - IStatusBarService service = getStatusBarService(); - if (service != null) { - try { - service.expandNotificationsPanel(); - } catch (RemoteException e) { - // do nothing. + return key_consumed; + case KeyEvent.KEYCODE_N: + if (down && event.isMetaPressed()) { + IStatusBarService service = getStatusBarService(); + if (service != null) { + try { + service.expandNotificationsPanel(); + } catch (RemoteException e) { + // do nothing. + } + return key_consumed; } } - } - } else if (keyCode == KeyEvent.KEYCODE_S && event.isMetaPressed() - && event.isCtrlPressed()) { - if (down && repeatCount == 0) { - int type = event.isShiftPressed() ? TAKE_SCREENSHOT_SELECTED_REGION - : TAKE_SCREENSHOT_FULLSCREEN; - mScreenshotRunnable.setScreenshotType(type); - mScreenshotRunnable.setScreenshotSource(SCREENSHOT_KEY_OTHER); - mHandler.post(mScreenshotRunnable); - return -1; - } - } else if (keyCode == KeyEvent.KEYCODE_SLASH && event.isMetaPressed()) { - if (down && repeatCount == 0 && !isKeyguardLocked()) { - toggleKeyboardShortcutsMenu(event.getDeviceId()); - } - } else if (keyCode == KeyEvent.KEYCODE_ASSIST) { - Slog.wtf(TAG, "KEYCODE_ASSIST should be handled in interceptKeyBeforeQueueing"); - return -1; - } else if (keyCode == KeyEvent.KEYCODE_VOICE_ASSIST) { - Slog.wtf(TAG, "KEYCODE_VOICE_ASSIST should be handled in interceptKeyBeforeQueueing"); - return -1; - } else if (keyCode == KeyEvent.KEYCODE_SYSRQ) { - if (down && repeatCount == 0) { - mScreenshotRunnable.setScreenshotType(TAKE_SCREENSHOT_FULLSCREEN); - mScreenshotRunnable.setScreenshotSource(SCREENSHOT_KEY_OTHER); - mHandler.post(mScreenshotRunnable); - } - return -1; - } else if (keyCode == KeyEvent.KEYCODE_BRIGHTNESS_UP - || keyCode == KeyEvent.KEYCODE_BRIGHTNESS_DOWN) { - if (down) { - int direction = keyCode == KeyEvent.KEYCODE_BRIGHTNESS_UP ? 1 : -1; - - // Disable autobrightness if it's on - int auto = Settings.System.getIntForUser( - mContext.getContentResolver(), - Settings.System.SCREEN_BRIGHTNESS_MODE, - Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, - UserHandle.USER_CURRENT_OR_SELF); - if (auto != 0) { - Settings.System.putIntForUser(mContext.getContentResolver(), + break; + case KeyEvent.KEYCODE_S: + if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) { + int type = event.isShiftPressed() ? TAKE_SCREENSHOT_SELECTED_REGION + : TAKE_SCREENSHOT_FULLSCREEN; + mScreenshotRunnable.setScreenshotType(type); + mScreenshotRunnable.setScreenshotSource(SCREENSHOT_KEY_OTHER); + mHandler.post(mScreenshotRunnable); + return key_consumed; + } + break; + case KeyEvent.KEYCODE_SLASH: + if (down && repeatCount == 0 && event.isMetaPressed() && !keyguardOn) { + toggleKeyboardShortcutsMenu(event.getDeviceId()); + return key_consumed; + } + break; + case KeyEvent.KEYCODE_ASSIST: + Slog.wtf(TAG, "KEYCODE_ASSIST should be handled in interceptKeyBeforeQueueing"); + return key_consumed; + case KeyEvent.KEYCODE_VOICE_ASSIST: + Slog.wtf(TAG, "KEYCODE_VOICE_ASSIST should be handled in" + + " interceptKeyBeforeQueueing"); + return key_consumed; + case KeyEvent.KEYCODE_SYSRQ: + if (down && repeatCount == 0) { + mScreenshotRunnable.setScreenshotType(TAKE_SCREENSHOT_FULLSCREEN); + mScreenshotRunnable.setScreenshotSource(SCREENSHOT_KEY_OTHER); + mHandler.post(mScreenshotRunnable); + } + return key_consumed; + case KeyEvent.KEYCODE_BRIGHTNESS_UP: + case KeyEvent.KEYCODE_BRIGHTNESS_DOWN: + if (down) { + int direction = keyCode == KeyEvent.KEYCODE_BRIGHTNESS_UP ? 1 : -1; + + // Disable autobrightness if it's on + int auto = Settings.System.getIntForUser( + mContext.getContentResolver(), Settings.System.SCREEN_BRIGHTNESS_MODE, Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, UserHandle.USER_CURRENT_OR_SELF); + if (auto != 0) { + Settings.System.putIntForUser(mContext.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS_MODE, + Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, + UserHandle.USER_CURRENT_OR_SELF); + } + float minFloat = mPowerManager.getBrightnessConstraint( + PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM); + float maxFloat = mPowerManager.getBrightnessConstraint( + PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM); + float stepFloat = (maxFloat - minFloat) / BRIGHTNESS_STEPS * direction; + float brightnessFloat = Settings.System.getFloatForUser( + mContext.getContentResolver(), Settings.System.SCREEN_BRIGHTNESS_FLOAT, + mContext.getDisplay().getBrightnessDefault(), + UserHandle.USER_CURRENT_OR_SELF); + brightnessFloat += stepFloat; + // Make sure we don't go beyond the limits. + brightnessFloat = Math.min(maxFloat, brightnessFloat); + brightnessFloat = Math.max(minFloat, brightnessFloat); + + Settings.System.putFloatForUser(mContext.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS_FLOAT, brightnessFloat, + UserHandle.USER_CURRENT_OR_SELF); + startActivityAsUser(new Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG), + UserHandle.CURRENT_OR_SELF); + } + return key_consumed; + case KeyEvent.KEYCODE_VOLUME_UP: + case KeyEvent.KEYCODE_VOLUME_DOWN: + case KeyEvent.KEYCODE_VOLUME_MUTE: + if (mUseTvRouting || mHandleVolumeKeysInWM) { + // On TVs or when the configuration is enabled, volume keys never + // go to the foreground app. + dispatchDirectAudioEvent(event); + return key_consumed; } - float minFloat = mPowerManager.getBrightnessConstraint( - PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM); - float maxFloat = mPowerManager.getBrightnessConstraint( - PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM); - float stepFloat = (maxFloat - minFloat) / BRIGHTNESS_STEPS * direction; - float brightnessFloat = Settings.System.getFloatForUser( - mContext.getContentResolver(), Settings.System.SCREEN_BRIGHTNESS_FLOAT, - mContext.getDisplay().getBrightnessDefault(), - UserHandle.USER_CURRENT_OR_SELF); - brightnessFloat += stepFloat; - // Make sure we don't go beyond the limits. - brightnessFloat = Math.min(maxFloat, brightnessFloat); - brightnessFloat = Math.max(minFloat, brightnessFloat); - - Settings.System.putFloatForUser(mContext.getContentResolver(), - Settings.System.SCREEN_BRIGHTNESS_FLOAT, brightnessFloat, - UserHandle.USER_CURRENT_OR_SELF); - startActivityAsUser(new Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG), - UserHandle.CURRENT_OR_SELF); - } - return -1; - } else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP - || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN - || keyCode == KeyEvent.KEYCODE_VOLUME_MUTE) { - if (mUseTvRouting || mHandleVolumeKeysInWM) { - // On TVs or when the configuration is enabled, volume keys never - // go to the foreground app. - dispatchDirectAudioEvent(event); - return -1; - } - // If the device is in VR mode and keys are "internal" (e.g. on the side of the - // device), then drop the volume keys and don't forward it to the application/dispatch - // the audio event. - if (mDefaultDisplayPolicy.isPersistentVrModeEnabled()) { - final InputDevice d = event.getDevice(); - if (d != null && !d.isExternal()) { - return -1; + // If the device is in VR mode and keys are "internal" (e.g. on the side of the + // device), then drop the volume keys and don't forward it to the + // application/dispatch the audio event. + if (mDefaultDisplayPolicy.isPersistentVrModeEnabled()) { + final InputDevice d = event.getDevice(); + if (d != null && !d.isExternal()) { + return key_consumed; + } } - } - } else if (keyCode == KeyEvent.KEYCODE_TAB && event.isMetaPressed()) { - // Pass through keyboard navigation keys. - return 0; - } else if (keyCode == KeyEvent.KEYCODE_ALL_APPS) { - if (!down) { - mHandler.removeMessages(MSG_HANDLE_ALL_APPS); - Message msg = mHandler.obtainMessage(MSG_HANDLE_ALL_APPS); - msg.setAsynchronous(true); - msg.sendToTarget(); - } - return -1; - } else if (keyCode == KeyEvent.KEYCODE_NOTIFICATION) { - if (!down) { - toggleNotificationPanel(); - } - return -1; - } + break; + case KeyEvent.KEYCODE_TAB: + if (event.isMetaPressed()) { + // Pass through keyboard navigation keys. + return 0; + } + // Display task switcher for ALT-TAB. + if (down && repeatCount == 0) { + if (mRecentAppsHeldModifiers == 0 && !keyguardOn && isUserSetupComplete()) { + final int shiftlessModifiers = + event.getModifiers() & ~KeyEvent.META_SHIFT_MASK; + if (KeyEvent.metaStateHasModifiers( + shiftlessModifiers, KeyEvent.META_ALT_ON)) { + mRecentAppsHeldModifiers = shiftlessModifiers; + showRecentApps(true); + return key_consumed; + } + } + } + break; + case KeyEvent.KEYCODE_ALL_APPS: + if (!down) { + mHandler.removeMessages(MSG_HANDLE_ALL_APPS); + Message msg = mHandler.obtainMessage(MSG_HANDLE_ALL_APPS); + msg.setAsynchronous(true); + msg.sendToTarget(); + } + return key_consumed; + case KeyEvent.KEYCODE_NOTIFICATION: + if (!down) { + toggleNotificationPanel(); + } + return key_consumed; - // Toggle Caps Lock on META-ALT. - boolean actionTriggered = false; - if (KeyEvent.isModifierKey(keyCode)) { - if (!mPendingCapsLockToggle) { - // Start tracking meta state for combo. - mInitialMetaState = mMetaState; - mPendingCapsLockToggle = true; - } else if (event.getAction() == KeyEvent.ACTION_UP) { - int altOnMask = mMetaState & KeyEvent.META_ALT_MASK; - int metaOnMask = mMetaState & KeyEvent.META_META_MASK; - - // Check for Caps Lock toggle - if ((metaOnMask != 0) && (altOnMask != 0)) { - // Check if nothing else is pressed - if (mInitialMetaState == (mMetaState ^ (altOnMask | metaOnMask))) { - // Handle Caps Lock Toggle + case KeyEvent.KEYCODE_SPACE: + // Handle keyboard layout switching. + if ((metaState & (KeyEvent.META_CTRL_MASK | KeyEvent.META_META_MASK)) == 0) { + return 0; + } + // Share the same behavior with KEYCODE_LANGUAGE_SWITCH. + case KeyEvent.KEYCODE_LANGUAGE_SWITCH: + if (down && repeatCount == 0) { + int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1; + mWindowManagerFuncs.switchKeyboardLayout(event.getDeviceId(), direction); + return key_consumed; + } + break; + case KeyEvent.KEYCODE_META_LEFT: + case KeyEvent.KEYCODE_META_RIGHT: + if (down) { + if (event.isAltPressed()) { + mPendingCapsLockToggle = true; + mPendingMetaAction = false; + } else { + mPendingCapsLockToggle = false; + mPendingMetaAction = true; + } + } else { + // Toggle Caps Lock on META-ALT. + if (mPendingCapsLockToggle) { mInputManagerInternal.toggleCapsLock(event.getDeviceId()); - actionTriggered = true; + mPendingCapsLockToggle = false; + } else if (mPendingMetaAction) { + launchAssistAction(Intent.EXTRA_ASSIST_INPUT_HINT_KEYBOARD, + event.getDeviceId(), + event.getEventTime()); + mPendingMetaAction = false; } } + return key_consumed; + case KeyEvent.KEYCODE_ALT_LEFT: + case KeyEvent.KEYCODE_ALT_RIGHT: + if (down) { + if (event.isMetaPressed()) { + mPendingCapsLockToggle = true; + mPendingMetaAction = false; + } else { + mPendingCapsLockToggle = false; + } + } else { + // hide recent if triggered by ALT-TAB. + if (mRecentAppsHeldModifiers != 0 + && (metaState & mRecentAppsHeldModifiers) == 0) { + mRecentAppsHeldModifiers = 0; + hideRecentApps(true, false); + return key_consumed; + } - // Always stop tracking when key goes up. - mPendingCapsLockToggle = false; - } - } - // Store current meta state to be able to evaluate it later. - mMetaState = metaState; - - if (actionTriggered) { - return -1; - } - - if (KeyEvent.isMetaKey(keyCode)) { - if (down) { - mPendingMetaAction = true; - } else if (mPendingMetaAction) { - launchAssistAction(Intent.EXTRA_ASSIST_INPUT_HINT_KEYBOARD, event.getDeviceId(), - event.getEventTime()); - } - return -1; + // Toggle Caps Lock on META-ALT. + if (mPendingCapsLockToggle) { + mInputManagerInternal.toggleCapsLock(event.getDeviceId()); + mPendingCapsLockToggle = false; + return key_consumed; + } + } + break; } // Shortcuts are invoked through Search+key, so intercept those here @@ -2803,7 +2798,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { + "SEARCH+" + KeyEvent.keyCodeToString(keyCode)); } } - return -1; + return key_consumed; } } @@ -2825,7 +2820,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { + "the activity to which it is registered was not found: " + "META+" + KeyEvent.keyCodeToString(keyCode), ex); } - return -1; + return key_consumed; } } } @@ -2844,39 +2839,13 @@ public class PhoneWindowManager implements WindowManagerPolicy { + "the activity to which it is registered was not found: " + "keyCode=" + keyCode + ", category=" + category, ex); } - return -1; - } - } - - // Display task switcher for ALT-TAB. - if (down && repeatCount == 0 && keyCode == KeyEvent.KEYCODE_TAB) { - if (mRecentAppsHeldModifiers == 0 && !keyguardOn && isUserSetupComplete()) { - final int shiftlessModifiers = event.getModifiers() & ~KeyEvent.META_SHIFT_MASK; - if (KeyEvent.metaStateHasModifiers(shiftlessModifiers, KeyEvent.META_ALT_ON)) { - mRecentAppsHeldModifiers = shiftlessModifiers; - showRecentApps(true); - return -1; - } + return key_consumed; } - } else if (!down && mRecentAppsHeldModifiers != 0 - && (metaState & mRecentAppsHeldModifiers) == 0) { - mRecentAppsHeldModifiers = 0; - hideRecentApps(true, false); - } - - // Handle keyboard language switching. - final boolean isCtrlOrMetaSpace = keyCode == KeyEvent.KEYCODE_SPACE - && (metaState & (KeyEvent.META_CTRL_MASK | KeyEvent.META_META_MASK)) != 0; - if (down && repeatCount == 0 - && (keyCode == KeyEvent.KEYCODE_LANGUAGE_SWITCH || isCtrlOrMetaSpace)) { - int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1; - mWindowManagerFuncs.switchKeyboardLayout(event.getDeviceId(), direction); - return -1; } if (isValidGlobalKey(keyCode) && mGlobalKeyManager.handleGlobalKey(mContext, keyCode, event)) { - return -1; + return key_consumed; } if (down) { @@ -2906,13 +2875,13 @@ public class PhoneWindowManager implements WindowManagerPolicy { } catch (RemoteException e) { mShortcutKeyServices.delete(shortcutCode); } - return -1; + return key_consumed; } } // Reserve all the META modifier combos for system behavior if ((metaState & KeyEvent.META_META_ON) != 0) { - return -1; + return key_consumed; } // Let the application handle the key. @@ -3550,8 +3519,21 @@ public class PhoneWindowManager implements WindowManagerPolicy { return result; } + // Alternate TV power to power key for Android TV device. + final HdmiControlManager hdmiControlManager = getHdmiControlManager(); + if (keyCode == KeyEvent.KEYCODE_TV_POWER && mHasFeatureLeanback + && (hdmiControlManager == null || !hdmiControlManager.shouldHandleTvPowerKey())) { + event = KeyEvent.obtain( + event.getDownTime(), event.getEventTime(), + event.getAction(), KeyEvent.KEYCODE_POWER, + event.getRepeatCount(), event.getMetaState(), + event.getDeviceId(), event.getScanCode(), + event.getFlags(), event.getSource(), event.getDisplayId(), null); + return interceptKeyBeforeQueueing(event, policyFlags); + } + if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) { - mKeyCombinationManager.interceptKey(event, interactive); + handleKeyGesture(event, interactive); } // Enable haptics if down and virtual key without multiple repetitions. If this is a hard @@ -3566,12 +3548,13 @@ public class PhoneWindowManager implements WindowManagerPolicy { switch (keyCode) { case KeyEvent.KEYCODE_BACK: { if (down) { - interceptBackKeyDown(); + mBackKeyHandled = false; } else { - boolean handled = interceptBackKeyUp(event); - + if (!hasLongPressOnBackBehavior()) { + mBackKeyHandled |= backKeyPress(); + } // Don't pass back press to app if we've already handled it via long press - if (handled) { + if (mBackKeyHandled) { result &= ~ACTION_PASS_TO_USER; } } @@ -3683,33 +3666,17 @@ public class PhoneWindowManager implements WindowManagerPolicy { case KeyEvent.KEYCODE_TV_POWER: { result &= ~ACTION_PASS_TO_USER; isWakeKey = false; // wake-up will be handled separately - HdmiControlManager hdmiControlManager = getHdmiControlManager(); - if (hdmiControlManager != null && hdmiControlManager.shouldHandleTvPowerKey()) { - if (down) { - hdmiControlManager.toggleAndFollowTvPower(); - } - } else if (mHasFeatureLeanback) { - KeyEvent fallbackEvent = KeyEvent.obtain( - event.getDownTime(), event.getEventTime(), - event.getAction(), KeyEvent.KEYCODE_POWER, - event.getRepeatCount(), event.getMetaState(), - event.getDeviceId(), event.getScanCode(), - event.getFlags(), event.getSource(), event.getDisplayId(), null); - if (down) { - interceptPowerKeyDown(fallbackEvent, interactive); - } else { - interceptPowerKeyUp(fallbackEvent, interactive, canceled); - } + if (down && hdmiControlManager != null) { + hdmiControlManager.toggleAndFollowTvPower(); } - // Ignore this key for any device that is not connected to a TV via HDMI and - // not an Android TV device. break; } case KeyEvent.KEYCODE_POWER: { EventLogTags.writeInterceptPower( KeyEvent.actionToString(event.getAction()), - mPowerKeyHandled ? 1 : 0, mPowerKeyPressCounter); + mPowerKeyHandled ? 1 : 0, + mSingleKeyGestureDetector.getKeyPressCounter(KeyEvent.KEYCODE_POWER)); // Any activity on the power button stops the accessibility shortcut result &= ~ACTION_PASS_TO_USER; isWakeKey = false; // wake-up will be handled separately @@ -3890,6 +3857,43 @@ public class PhoneWindowManager implements WindowManagerPolicy { return result; } + private void handleKeyGesture(KeyEvent event, boolean interactive) { + if (mKeyCombinationManager.interceptKey(event, interactive)) { + // handled by combo keys manager. + mSingleKeyGestureDetector.reset(); + return; + } + + if (event.getKeyCode() == KEYCODE_POWER && event.getAction() == KeyEvent.ACTION_DOWN) { + mPowerKeyHandled = handleCameraGesture(event, interactive); + if (mPowerKeyHandled) { + // handled by camera gesture. + mSingleKeyGestureDetector.reset(); + return; + } + } + + mSingleKeyGestureDetector.interceptKey(event); + } + + // The camera gesture will be detected by GestureLauncherService. + private boolean handleCameraGesture(KeyEvent event, boolean interactive) { + // camera gesture. + GestureLauncherService gestureService = LocalServices.getService( + GestureLauncherService.class); + if (gestureService == null) { + return false; + } + + final MutableBoolean outLaunched = new MutableBoolean(false); + final boolean gesturedServiceIntercepted = gestureService.interceptPowerKeyDown(event, + interactive, outLaunched); + if (outLaunched.value && mRequestedOrGoingToSleep) { + mCameraGestureTriggeredDuringGoingToSleep = true; + } + return gesturedServiceIntercepted; + } + /** * Handle statusbar expansion events. * @param event @@ -4889,15 +4893,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - private void schedulePossibleVeryLongPressReboot() { - mHandler.removeCallbacks(mPossibleVeryLongPressReboot); - mHandler.postDelayed(mPossibleVeryLongPressReboot, mVeryLongPressTimeout); - } - - private void cancelPossibleVeryLongPressReboot() { - mHandler.removeCallbacks(mPossibleVeryLongPressReboot); - } - // TODO (multidisplay): Support multiple displays in WindowManagerPolicy. private void updateScreenOffSleepToken(boolean acquire) { if (acquire) { diff --git a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java new file mode 100644 index 000000000000..3dafb0ce21ef --- /dev/null +++ b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java @@ -0,0 +1,356 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.policy; + +import android.annotation.IntDef; +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.Log; +import android.view.KeyEvent; +import android.view.ViewConfiguration; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; + +/** + * Detect single key gesture: press, long press, very long press and multi press. + * + * Call {@link #reset} if current {@link KeyEvent} has been handled by another policy + */ + +public final class SingleKeyGestureDetector { + private static final String TAG = "SingleKeyGesture"; + private static final boolean DEBUG = false; + + private static final int MSG_KEY_LONG_PRESS = 0; + private static final int MSG_KEY_VERY_LONG_PRESS = 1; + private static final int MSG_KEY_DELAYED_PRESS = 2; + + private final long mLongPressTimeout; + private final long mVeryLongPressTimeout; + + private volatile int mKeyPressCounter; + + private final ArrayList<SingleKeyRule> mRules = new ArrayList(); + private SingleKeyRule mActiveRule = null; + + // Key code of current key down event, reset when key up. + private int mDownKeyCode = KeyEvent.KEYCODE_UNKNOWN; + private volatile boolean mHandledByLongPress = false; + private final Handler mHandler; + private static final long MULTI_PRESS_TIMEOUT = ViewConfiguration.getMultiPressTimeout(); + + + /** Supported gesture flags */ + public static final int KEY_LONGPRESS = 1 << 1; + public static final int KEY_VERYLONGPRESS = 1 << 2; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "KEY_" }, value = { + KEY_LONGPRESS, + KEY_VERYLONGPRESS, + }) + public @interface KeyGestureFlag {} + + /** + * Rule definition for single keys gesture. + * E.g : define power key. + * <pre class="prettyprint"> + * SingleKeyRule rule = + * new SingleKeyRule(KEYCODE_POWER, KEY_LONGPRESS|KEY_VERYLONGPRESS) { + * int getMaxMultiPressCount() { // maximum multi press count. } + * void onPress(long downTime) { // short press behavior. } + * void onLongPress() { // long press behavior. } + * void onVeryLongPress() { // very long press behavior. } + * void onMultiPress(long downTime, int count) { // multi press behavior. } + * }; + * </pre> + */ + abstract static class SingleKeyRule { + private final int mKeyCode; + private final int mSupportedGestures; + + SingleKeyRule(int keyCode, @KeyGestureFlag int supportedGestures) { + mKeyCode = keyCode; + mSupportedGestures = supportedGestures; + } + + /** + * True if the rule could intercept the key. + */ + private boolean shouldInterceptKey(int keyCode) { + return keyCode == mKeyCode; + } + + /** + * True if the rule support long press. + */ + private boolean supportLongPress() { + return (mSupportedGestures & KEY_LONGPRESS) != 0; + } + + /** + * True if the rule support very long press. + */ + private boolean supportVeryLongPress() { + return (mSupportedGestures & KEY_VERYLONGPRESS) != 0; + } + + /** + * Maximum count of multi presses. + * Return 1 will trigger onPress immediately when {@link KeyEvent.ACTION_UP}. + * Otherwise trigger onMultiPress immediately when reach max count when + * {@link KeyEvent.ACTION_DOWN}. + */ + int getMaxMultiPressCount() { + return 1; + } + + /** + * Called when short press has been detected. + */ + abstract void onPress(long downTime); + /** + * Callback when multi press (>= 2) has been detected. + */ + void onMultiPress(long downTime, int count) {} + /** + * Callback when long press has been detected. + */ + void onLongPress(long downTime) {} + /** + * Callback when very long press has been detected. + */ + void onVeryLongPress(long downTime) {} + + @Override + public String toString() { + return "KeyCode = " + KeyEvent.keyCodeToString(mKeyCode) + + ", long press : " + supportLongPress() + + ", very Long press : " + supportVeryLongPress() + + ", max multi press count : " + getMaxMultiPressCount(); + } + } + + public SingleKeyGestureDetector(Context context) { + mLongPressTimeout = ViewConfiguration.get(context).getDeviceGlobalActionKeyTimeout(); + mVeryLongPressTimeout = context.getResources().getInteger( + com.android.internal.R.integer.config_veryLongPressTimeout); + mHandler = new KeyHandler(); + } + + void addRule(SingleKeyRule rule) { + mRules.add(rule); + } + + void interceptKey(KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_DOWN) { + interceptKeyDown(event); + } else { + interceptKeyUp(event); + } + } + + private void interceptKeyDown(KeyEvent event) { + final int keyCode = event.getKeyCode(); + // same key down. + if (mDownKeyCode == keyCode) { + if (mActiveRule != null && (event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0 + && !mHandledByLongPress) { + if (DEBUG) { + Log.i(TAG, "Long press Key " + KeyEvent.keyCodeToString(keyCode)); + } + mHandledByLongPress = true; + mHandler.removeMessages(MSG_KEY_LONG_PRESS); + mHandler.removeMessages(MSG_KEY_VERY_LONG_PRESS); + mActiveRule.onLongPress(event.getEventTime()); + } + return; + } + + // When a different key is pressed, stop processing gestures for the currently active key. + if (mDownKeyCode != KeyEvent.KEYCODE_UNKNOWN + || (mActiveRule != null && !mActiveRule.shouldInterceptKey(keyCode))) { + if (DEBUG) { + Log.i(TAG, "Press another key " + KeyEvent.keyCodeToString(keyCode)); + } + + reset(); + } + mDownKeyCode = keyCode; + + // Picks a new rule, return if no rule picked. + if (mActiveRule == null) { + final int count = mRules.size(); + for (int index = 0; index < count; index++) { + final SingleKeyRule rule = mRules.get(index); + if (rule.shouldInterceptKey(keyCode)) { + mActiveRule = rule; + break; + } + } + } + if (mActiveRule == null) { + return; + } + + final long eventTime = event.getEventTime(); + if (mKeyPressCounter == 0) { + if (mActiveRule.supportLongPress()) { + final Message msg = mHandler.obtainMessage(MSG_KEY_LONG_PRESS, keyCode, 0, + eventTime); + msg.setAsynchronous(true); + mHandler.sendMessageDelayed(msg, mLongPressTimeout); + } + + if (mActiveRule.supportVeryLongPress()) { + final Message msg = mHandler.obtainMessage(MSG_KEY_VERY_LONG_PRESS, keyCode, 0, + eventTime); + msg.setAsynchronous(true); + mHandler.sendMessageDelayed(msg, mVeryLongPressTimeout); + } + } else { + mHandler.removeMessages(MSG_KEY_LONG_PRESS); + mHandler.removeMessages(MSG_KEY_VERY_LONG_PRESS); + mHandler.removeMessages(MSG_KEY_DELAYED_PRESS); + + // Trigger multi press immediately when reach max count.( > 1) + if (mKeyPressCounter == mActiveRule.getMaxMultiPressCount() - 1) { + if (DEBUG) { + Log.i(TAG, "Trigger multi press " + mActiveRule.toString() + " for it" + + " reach the max count " + mKeyPressCounter); + } + mActiveRule.onMultiPress(eventTime, mKeyPressCounter + 1); + mKeyPressCounter = 0; + } + } + } + + private boolean interceptKeyUp(KeyEvent event) { + mHandler.removeMessages(MSG_KEY_LONG_PRESS); + mHandler.removeMessages(MSG_KEY_VERY_LONG_PRESS); + mDownKeyCode = KeyEvent.KEYCODE_UNKNOWN; + if (mActiveRule == null) { + return false; + } + + if (mHandledByLongPress) { + mHandledByLongPress = false; + return true; + } + + final long downTime = event.getDownTime(); + if (event.getKeyCode() == mActiveRule.mKeyCode) { + // Directly trigger short press when max count is 1. + if (mActiveRule.getMaxMultiPressCount() == 1) { + mActiveRule.onPress(downTime); + return true; + } + + // This could be a multi-press. Wait a little bit longer to confirm. + mKeyPressCounter++; + Message msg = mHandler.obtainMessage(MSG_KEY_DELAYED_PRESS, mActiveRule.mKeyCode, + mKeyPressCounter, downTime); + msg.setAsynchronous(true); + mHandler.sendMessageDelayed(msg, MULTI_PRESS_TIMEOUT); + return true; + } + reset(); + return false; + } + + int getKeyPressCounter(int keyCode) { + if (mActiveRule != null && mActiveRule.mKeyCode == keyCode) { + return mKeyPressCounter; + } else { + return 0; + } + } + + void reset() { + if (mActiveRule != null) { + if (mDownKeyCode != KeyEvent.KEYCODE_UNKNOWN) { + mHandler.removeMessages(MSG_KEY_LONG_PRESS); + mHandler.removeMessages(MSG_KEY_VERY_LONG_PRESS); + } + + if (mKeyPressCounter > 0) { + mHandler.removeMessages(MSG_KEY_DELAYED_PRESS); + mKeyPressCounter = 0; + } + mActiveRule = null; + } + + mHandledByLongPress = false; + mDownKeyCode = KeyEvent.KEYCODE_UNKNOWN; + } + + boolean isKeyIntercepted(int keyCode) { + if (mActiveRule != null && mActiveRule.shouldInterceptKey(keyCode)) { + return mHandledByLongPress; + } + return false; + } + + private class KeyHandler extends Handler { + KeyHandler() { + super(Looper.getMainLooper()); + } + + @Override + public void handleMessage(Message msg) { + if (mActiveRule == null) { + return; + } + final int keyCode = msg.arg1; + final long eventTime = (long) msg.obj; + switch(msg.what) { + case MSG_KEY_LONG_PRESS: + if (DEBUG) { + Log.i(TAG, "Detect long press " + KeyEvent.keyCodeToString(keyCode)); + } + mHandledByLongPress = true; + mActiveRule.onLongPress(eventTime); + break; + case MSG_KEY_VERY_LONG_PRESS: + if (DEBUG) { + Log.i(TAG, "Detect very long press " + + KeyEvent.keyCodeToString(keyCode)); + } + mHandledByLongPress = true; + mActiveRule.onVeryLongPress(eventTime); + break; + case MSG_KEY_DELAYED_PRESS: + if (DEBUG) { + Log.i(TAG, "Detect press " + KeyEvent.keyCodeToString(keyCode) + + ", count " + mKeyPressCounter); + } + if (mKeyPressCounter == 1) { + mActiveRule.onPress(eventTime); + } else { + mActiveRule.onMultiPress(eventTime, mKeyPressCounter); + } + mKeyPressCounter = 0; + break; + } + } + } +} diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 8c46445fac9b..bc117094dd68 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -1645,7 +1645,7 @@ public final class PowerManagerService extends SystemService } // Called from native code. - private void userActivityFromNative(long eventTime, int event, int flags) { + private void userActivityFromNative(long eventTime, int event, int displayId, int flags) { userActivityInternal(eventTime, event, flags, Process.SYSTEM_UID); } diff --git a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java index c4f29ea69218..ef0079e0c01f 100644 --- a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java +++ b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java @@ -16,6 +16,8 @@ package com.android.server.powerstats; +import static java.lang.System.currentTimeMillis; + import android.content.Context; import android.hardware.power.stats.Channel; import android.hardware.power.stats.EnergyConsumer; @@ -26,11 +28,14 @@ import android.hardware.power.stats.StateResidencyResult; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.os.SystemClock; import android.util.AtomicFile; import android.util.Slog; import android.util.proto.ProtoInputStream; import android.util.proto.ProtoOutputStream; +import com.android.internal.annotations.VisibleForTesting; + import com.android.server.powerstats.PowerStatsHALWrapper.IPowerStatsHALWrapper; import com.android.server.powerstats.ProtoStreamUtils.ChannelUtils; import com.android.server.powerstats.ProtoStreamUtils.EnergyConsumerResultUtils; @@ -61,6 +66,8 @@ public final class PowerStatsLogger extends Handler { protected static final int MSG_LOG_TO_DATA_STORAGE_LOW_FREQUENCY = 1; protected static final int MSG_LOG_TO_DATA_STORAGE_HIGH_FREQUENCY = 2; + // TODO(b/181240441): Add a listener to update the Wall clock baseline when changed + private final long mStartWallTime; private final PowerStatsDataStorage mPowerStatsMeterStorage; private final PowerStatsDataStorage mPowerStatsModelStorage; private final PowerStatsDataStorage mPowerStatsResidencyStorage; @@ -79,6 +86,8 @@ public final class PowerStatsLogger extends Handler { // Log power meter data. EnergyMeasurement[] energyMeasurements = mPowerStatsHALWrapper.readEnergyMeter(new int[0]); + EnergyMeasurementUtils.adjustTimeSinceBootToEpoch(energyMeasurements, + mStartWallTime); mPowerStatsMeterStorage.write( EnergyMeasurementUtils.getProtoBytes(energyMeasurements)); if (DEBUG) EnergyMeasurementUtils.print(energyMeasurements); @@ -86,6 +95,8 @@ public final class PowerStatsLogger extends Handler { // Log power model data without attribution data. EnergyConsumerResult[] ecrNoAttribution = mPowerStatsHALWrapper.getEnergyConsumed(new int[0]); + EnergyConsumerResultUtils.adjustTimeSinceBootToEpoch(ecrNoAttribution, + mStartWallTime); mPowerStatsModelStorage.write( EnergyConsumerResultUtils.getProtoBytes(ecrNoAttribution, false)); if (DEBUG) EnergyConsumerResultUtils.print(ecrNoAttribution); @@ -97,6 +108,8 @@ public final class PowerStatsLogger extends Handler { // Log power model data with attribution data. EnergyConsumerResult[] ecrAttribution = mPowerStatsHALWrapper.getEnergyConsumed(new int[0]); + EnergyConsumerResultUtils.adjustTimeSinceBootToEpoch(ecrAttribution, + mStartWallTime); mPowerStatsModelStorage.write( EnergyConsumerResultUtils.getProtoBytes(ecrAttribution, true)); if (DEBUG) EnergyConsumerResultUtils.print(ecrAttribution); @@ -108,6 +121,8 @@ public final class PowerStatsLogger extends Handler { // Log state residency data. StateResidencyResult[] stateResidencyResults = mPowerStatsHALWrapper.getStateResidency(new int[0]); + StateResidencyResultUtils.adjustTimeSinceBootToEpoch(stateResidencyResults, + mStartWallTime); mPowerStatsResidencyStorage.write( StateResidencyResultUtils.getProtoBytes(stateResidencyResults)); if (DEBUG) StateResidencyResultUtils.print(stateResidencyResults); @@ -293,12 +308,19 @@ public final class PowerStatsLogger extends Handler { return mDeleteResidencyDataOnBoot; } + @VisibleForTesting + public long getStartWallTime() { + return mStartWallTime; + } + public PowerStatsLogger(Context context, File dataStoragePath, String meterFilename, String meterCacheFilename, String modelFilename, String modelCacheFilename, String residencyFilename, String residencyCacheFilename, IPowerStatsHALWrapper powerStatsHALWrapper) { super(Looper.getMainLooper()); + mStartWallTime = currentTimeMillis() - SystemClock.elapsedRealtime(); + if (DEBUG) Slog.d(TAG, "mStartWallTime: " + mStartWallTime); mPowerStatsHALWrapper = powerStatsHALWrapper; mDataStoragePath = dataStoragePath; diff --git a/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java b/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java index 11b22a574476..746a09882f93 100644 --- a/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java +++ b/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java @@ -109,6 +109,18 @@ public class ProtoStreamUtils { } static class StateResidencyResultUtils { + public static void adjustTimeSinceBootToEpoch(StateResidencyResult[] stateResidencyResult, + long startWallTime) { + for (int i = 0; i < stateResidencyResult.length; i++) { + final int stateLength = stateResidencyResult[i].stateResidencyData.length; + for (int j = 0; j < stateLength; j++) { + final StateResidency stateResidencyData = + stateResidencyResult[i].stateResidencyData[j]; + stateResidencyData.lastEntryTimestampMs += startWallTime; + } + } + } + public static byte[] getProtoBytes(StateResidencyResult[] stateResidencyResult) { ProtoOutputStream pos = new ProtoOutputStream(); packProtoMessage(stateResidencyResult, pos); @@ -306,6 +318,13 @@ public class ProtoStreamUtils { } static class EnergyMeasurementUtils { + public static void adjustTimeSinceBootToEpoch(EnergyMeasurement[] energyMeasurement, + long startWallTime) { + for (int i = 0; i < energyMeasurement.length; i++) { + energyMeasurement[i].timestampMs += startWallTime; + } + } + public static byte[] getProtoBytes(EnergyMeasurement[] energyMeasurement) { ProtoOutputStream pos = new ProtoOutputStream(); packProtoMessage(energyMeasurement, pos); @@ -518,6 +537,13 @@ public class ProtoStreamUtils { } static class EnergyConsumerResultUtils { + public static void adjustTimeSinceBootToEpoch(EnergyConsumerResult[] energyConsumerResult, + long startWallTime) { + for (int i = 0; i < energyConsumerResult.length; i++) { + energyConsumerResult[i].timestampMs += startWallTime; + } + } + public static byte[] getProtoBytes(EnergyConsumerResult[] energyConsumerResult, boolean includeAttribution) { ProtoOutputStream pos = new ProtoOutputStream(); diff --git a/services/core/java/com/android/server/timedetector/DeviceConfig.java b/services/core/java/com/android/server/timedetector/DeviceConfig.java deleted file mode 100644 index 7b9ad0f531aa..000000000000 --- a/services/core/java/com/android/server/timedetector/DeviceConfig.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.server.timedetector; - -import static android.provider.DeviceConfig.NAMESPACE_SYSTEM_TIME; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.StringDef; - -import java.time.Duration; -import java.util.concurrent.Executor; - -/** - * A helper class for reading / monitoring the {@link - * android.provider.DeviceConfig#NAMESPACE_SYSTEM_TIME} namespace for server-configured flags. - */ -public final class DeviceConfig { - - /** - * An annotation used to indicate when a {@link - * android.provider.DeviceConfig#NAMESPACE_SYSTEM_TIME} key is required. - * - * <p>Note that the com.android.geotz module deployment of the Offline LocationTimeZoneProvider - * also shares the {@link android.provider.DeviceConfig#NAMESPACE_SYSTEM_TIME}, and uses the - * prefix "geotz_" on all of its key strings. - */ - @StringDef(prefix = "KEY_", value = { - KEY_FORCE_LOCATION_TIME_ZONE_DETECTION_ENABLED, - KEY_LOCATION_TIME_ZONE_DETECTION_ENABLED_DEFAULT, - KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS, - KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS, - KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS, - }) - @interface DeviceConfigKey {} - - /** - * The key to force location time zone detection on for a device. Only intended for use during - * release testing with droidfooders. The user can still disable the feature by turning off the - * master location switch, or disabling automatic time zone detection. - */ - @DeviceConfigKey - public static final String KEY_FORCE_LOCATION_TIME_ZONE_DETECTION_ENABLED = - "force_location_time_zone_detection_enabled"; - - /** - * The key for the default value used to determine whether location time zone detection is - * enabled when the user hasn't explicitly set it yet. - */ - @DeviceConfigKey - public static final String KEY_LOCATION_TIME_ZONE_DETECTION_ENABLED_DEFAULT = - "location_time_zone_detection_enabled_default"; - - /** - * The key for the minimum delay after location time zone detection has been enabled before the - * location time zone manager can report it is uncertain about the time zone. - */ - @DeviceConfigKey - public static final String KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS = - "location_time_zone_detection_uncertainty_delay_millis"; - - /** - * The key for the timeout passed to a location time zone provider that tells it how long it has - * to provide an explicit first suggestion without being declared uncertain. - */ - @DeviceConfigKey - public static final String KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS = - "ltpz_init_timeout_millis"; - - /** - * The key for the extra time added to {@link - * #KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS} by the location time zone - * manager before the location time zone provider will actually be declared uncertain. - */ - @DeviceConfigKey - public static final String KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS = - "ltpz_init_timeout_fuzz_millis"; - - /** Creates an instance. */ - public DeviceConfig() {} - - /** Adds a listener for the system_time namespace. */ - public void addListener( - @NonNull Executor handlerExecutor, @NonNull Runnable listener) { - android.provider.DeviceConfig.addOnPropertiesChangedListener( - NAMESPACE_SYSTEM_TIME, - handlerExecutor, - properties -> listener.run()); - } - - /** - * Returns a boolean value from {@link android.provider.DeviceConfig} from the system_time - * namespace, or {@code defaultValue} if there is no explicit value set. - */ - public boolean getBoolean(@DeviceConfigKey String key, boolean defaultValue) { - return android.provider.DeviceConfig.getBoolean(NAMESPACE_SYSTEM_TIME, key, defaultValue); - } - - /** - * Returns a positive duration from {@link android.provider.DeviceConfig} from the system_time - * namespace, or {@code defaultValue} if there is no explicit value set. - */ - @Nullable - public Duration getDurationFromMillis( - @DeviceConfigKey String key, @Nullable Duration defaultValue) { - long deviceConfigValue = - android.provider.DeviceConfig.getLong(NAMESPACE_SYSTEM_TIME, key, -1); - if (deviceConfigValue < 0) { - return defaultValue; - } - return Duration.ofMillis(deviceConfigValue); - } -} diff --git a/services/core/java/com/android/server/timedetector/ServerFlags.java b/services/core/java/com/android/server/timedetector/ServerFlags.java new file mode 100644 index 000000000000..8819b371b225 --- /dev/null +++ b/services/core/java/com/android/server/timedetector/ServerFlags.java @@ -0,0 +1,238 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.timedetector; + +import static android.provider.DeviceConfig.NAMESPACE_SYSTEM_TIME; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.StringDef; +import android.content.Context; +import android.provider.DeviceConfig; +import android.util.ArrayMap; + +import com.android.internal.annotations.GuardedBy; +import com.android.server.timezonedetector.ConfigurationChangeListener; +import com.android.server.timezonedetector.ServiceConfigAccessor; + +import java.time.Duration; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +/** + * A helper class for reading / monitoring the {@link DeviceConfig#NAMESPACE_SYSTEM_TIME} namespace + * for server-configured flags. + */ +public final class ServerFlags { + + private static final Optional<Boolean> OPTIONAL_TRUE = Optional.of(true); + private static final Optional<Boolean> OPTIONAL_FALSE = Optional.of(false); + + /** + * An annotation used to indicate when a {@link DeviceConfig#NAMESPACE_SYSTEM_TIME} key is + * required. + * + * <p>Note that the com.android.geotz module deployment of the Offline LocationTimeZoneProvider + * also shares the {@link DeviceConfig#NAMESPACE_SYSTEM_TIME}, and uses the + * prefix "geotz_" on all of its key strings. + */ + @StringDef(prefix = "KEY_", value = { + KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED, + KEY_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_ENABLED_OVERRIDE, + KEY_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_ENABLED_OVERRIDE, + KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS, + KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS, + KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS, + KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE, + KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT, + }) + @interface DeviceConfigKey {} + + /** + * Controls whether the location time zone manager service will started. Only observed if + * the device build is configured to support location-based time zone detection. See + * {@link ServiceConfigAccessor#isGeoTimeZoneDetectionFeatureSupportedInConfig()} and {@link + * ServiceConfigAccessor#isGeoTimeZoneDetectionFeatureSupported()}. + */ + @DeviceConfigKey + public static final String KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED = + "location_time_zone_detection_feature_supported"; + + /** + * The key for the server flag that can override the device config for whether the primary + * location time zone provider is enabled or disabled. + */ + @DeviceConfigKey + public static final String KEY_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_ENABLED_OVERRIDE = + "primary_location_time_zone_provider_enabled_override"; + + /** + * The key for the server flag that can override the device config for whether the secondary + * location time zone provider is enabled or disabled. + */ + @DeviceConfigKey + public static final String KEY_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_ENABLED_OVERRIDE = + "secondary_location_time_zone_provider_enabled_override"; + + /** + * The key for the minimum delay after location time zone detection has been enabled before the + * location time zone manager can report it is uncertain about the time zone. + */ + @DeviceConfigKey + public static final String KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS = + "location_time_zone_detection_uncertainty_delay_millis"; + + /** + * The key for the timeout passed to a location time zone provider that tells it how long it has + * to provide an explicit first suggestion without being declared uncertain. + */ + @DeviceConfigKey + public static final String KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS = + "ltpz_init_timeout_millis"; + + /** + * The key for the extra time added to {@link + * #KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS} by the location time zone + * manager before the location time zone provider will actually be declared uncertain. + */ + @DeviceConfigKey + public static final String KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS = + "ltpz_init_timeout_fuzz_millis"; + + /** + * The key for the server flag that can override location time zone detection being enabled for + * a user. Only intended for use during release testing with droidfooders. The user can still + * disable the feature by turning off the master location switch, or by disabling automatic time + * zone detection. + */ + @DeviceConfigKey + public static final String KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE = + "location_time_zone_detection_setting_enabled_override"; + + /** + * The key for the default value used to determine whether location time zone detection is + * enabled when the user hasn't explicitly set it yet. + */ + @DeviceConfigKey + public static final String KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT = + "location_time_zone_detection_setting_enabled_default"; + + @GuardedBy("mListeners") + private final ArrayMap<ConfigurationChangeListener, Set<String>> mListeners = new ArrayMap<>(); + + private static final Object SLOCK = new Object(); + + @GuardedBy("SLOCK") + @Nullable + private static ServerFlags sInstance; + + private ServerFlags(Context context) { + DeviceConfig.addOnPropertiesChangedListener( + NAMESPACE_SYSTEM_TIME, + context.getMainExecutor(), + this::handlePropertiesChanged); + } + + /** Returns the singleton instance. */ + public static ServerFlags getInstance(Context context) { + synchronized (SLOCK) { + if (sInstance == null) { + sInstance = new ServerFlags(context); + } + return sInstance; + } + } + + private void handlePropertiesChanged(@NonNull DeviceConfig.Properties properties) { + synchronized (mListeners) { + for (Map.Entry<ConfigurationChangeListener, Set<String>> listenerEntry + : mListeners.entrySet()) { + if (intersects(listenerEntry.getValue(), properties.getKeyset())) { + listenerEntry.getKey().onChange(); + } + } + } + } + + private static boolean intersects(@NonNull Set<String> one, @NonNull Set<String> two) { + for (String toFind : one) { + if (two.contains(toFind)) { + return true; + } + } + return false; + } + + /** + * Adds a listener for the system_time namespace that will trigger if any of the specified keys + * change. Listener callbacks are delivered on the main looper thread. + * + * <p>Note: Only for use by long-lived objects like other singletons. There is deliberately no + * associated remove method. + */ + public void addListener(@NonNull ConfigurationChangeListener listener, + @NonNull Set<String> keys) { + Objects.requireNonNull(listener); + Objects.requireNonNull(keys); + + synchronized (mListeners) { + mListeners.put(listener, keys); + } + } + + /** + * Returns an optional boolean value from {@link DeviceConfig} from the system_time + * namespace, returns {@link Optional#empty()} if there is no explicit value set. + */ + @NonNull + public Optional<Boolean> getOptionalBoolean(@DeviceConfigKey String key) { + String value = DeviceConfig.getProperty(NAMESPACE_SYSTEM_TIME, key); + return parseOptionalBoolean(value); + } + + @NonNull + private static Optional<Boolean> parseOptionalBoolean(@Nullable String value) { + if (value == null) { + return Optional.empty(); + } else { + return Boolean.parseBoolean(value) ? OPTIONAL_TRUE : OPTIONAL_FALSE; + } + } + + /** + * Returns a boolean value from {@link DeviceConfig} from the system_time + * namespace, or {@code defaultValue} if there is no explicit value set. + */ + public boolean getBoolean(@DeviceConfigKey String key, boolean defaultValue) { + return DeviceConfig.getBoolean(NAMESPACE_SYSTEM_TIME, key, defaultValue); + } + + /** + * Returns a positive duration from {@link DeviceConfig} from the system_time + * namespace, or {@code defaultValue} if there is no explicit value set. + */ + @Nullable + public Duration getDurationFromMillis( + @DeviceConfigKey String key, @Nullable Duration defaultValue) { + long deviceConfigValue = DeviceConfig.getLong(NAMESPACE_SYSTEM_TIME, key, -1); + if (deviceConfigValue < 0) { + return defaultValue; + } + return Duration.ofMillis(deviceConfigValue); + } +} diff --git a/services/core/java/com/android/server/timezonedetector/ConfigurationChangeListener.java b/services/core/java/com/android/server/timezonedetector/ConfigurationChangeListener.java index 4c7b1f38dd5a..aa8ad37815bf 100644 --- a/services/core/java/com/android/server/timezonedetector/ConfigurationChangeListener.java +++ b/services/core/java/com/android/server/timezonedetector/ConfigurationChangeListener.java @@ -17,10 +17,11 @@ package com.android.server.timezonedetector; /** - * A listener used to receive notification that time zone configuration has changed. + * A listener used to receive notification that configuration has / may have changed (depending on + * the usecase). */ @FunctionalInterface public interface ConfigurationChangeListener { - /** Called when the current user or a configuration value has changed. */ + /** Called when the configuration may have changed. */ void onChange(); } diff --git a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java index 1f73977444f8..3ae9d641e81c 100644 --- a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java +++ b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java @@ -33,26 +33,27 @@ import com.android.internal.util.Preconditions; import java.util.Objects; /** - * Holds all configuration values that affect time zone behavior and some associated logic, e.g. - * {@link #getAutoDetectionEnabledBehavior()}, {@link #getGeoDetectionEnabledBehavior()} and {@link - * #createCapabilitiesAndConfig()}. + * Holds configuration values that affect user-facing time zone behavior and some associated logic. + * Some configuration is global, some is user scoped, but this class deliberately doesn't make a + * distinction for simplicity. */ public final class ConfigurationInternal { - private final @UserIdInt int mUserId; - private final boolean mUserConfigAllowed; private final boolean mAutoDetectionSupported; private final boolean mGeoDetectionSupported; private final boolean mAutoDetectionEnabled; + private final @UserIdInt int mUserId; + private final boolean mUserConfigAllowed; private final boolean mLocationEnabled; private final boolean mGeoDetectionEnabled; private ConfigurationInternal(Builder builder) { - mUserId = builder.mUserId; - mUserConfigAllowed = builder.mUserConfigAllowed; mAutoDetectionSupported = builder.mAutoDetectionSupported; mGeoDetectionSupported = builder.mGeoDetectionSupported; mAutoDetectionEnabled = builder.mAutoDetectionEnabled; + + mUserId = builder.mUserId; + mUserConfigAllowed = builder.mUserConfigAllowed; mLocationEnabled = builder.mLocationEnabled; mGeoDetectionEnabled = builder.mGeoDetectionEnabled; // if mGeoDetectionSupported then mAutoDetectionSupported, i.e. mGeoDetectionSupported @@ -60,22 +61,6 @@ public final class ConfigurationInternal { Preconditions.checkState(mAutoDetectionSupported || !mGeoDetectionSupported); } - /** Returns the ID of the user this configuration is associated with. */ - public @UserIdInt int getUserId() { - return mUserId; - } - - /** Returns the handle of the user this configuration is associated with. */ - @NonNull - public UserHandle getUserHandle() { - return UserHandle.of(mUserId); - } - - /** Returns true if the user allowed to modify time zone configuration. */ - public boolean isUserConfigAllowed() { - return mUserConfigAllowed; - } - /** Returns true if the device supports any form of auto time zone detection. */ public boolean isAutoDetectionSupported() { return mAutoDetectionSupported; @@ -98,6 +83,22 @@ public final class ConfigurationInternal { return mAutoDetectionSupported && mAutoDetectionEnabled; } + /** Returns the ID of the user this configuration is associated with. */ + public @UserIdInt int getUserId() { + return mUserId; + } + + /** Returns the handle of the user this configuration is associated with. */ + @NonNull + public UserHandle getUserHandle() { + return UserHandle.of(mUserId); + } + + /** Returns true if the user allowed to modify time zone configuration. */ + public boolean isUserConfigAllowed() { + return mUserConfigAllowed; + } + /** Returns true if user's location can be used generally. */ public boolean isLocationEnabled() { return mLocationEnabled; @@ -283,7 +284,7 @@ public final class ConfigurationInternal { /** * Sets whether any form of automatic time zone detection is supported on this device. */ - public Builder setAutoDetectionSupported(boolean supported) { + public Builder setAutoDetectionFeatureSupported(boolean supported) { mAutoDetectionSupported = supported; return this; } @@ -291,7 +292,7 @@ public final class ConfigurationInternal { /** * Sets whether geolocation time zone detection is supported on this device. */ - public Builder setGeoDetectionSupported(boolean supported) { + public Builder setGeoDetectionFeatureSupported(boolean supported) { mGeoDetectionSupported = supported; return this; } diff --git a/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java b/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java index f52b9b1b1c58..e3caae9482d9 100644 --- a/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java +++ b/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java @@ -31,9 +31,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.database.ContentObserver; import android.location.LocationManager; -import android.net.ConnectivityManager; import android.os.Handler; -import android.os.HandlerExecutor; import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; @@ -42,10 +40,9 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.server.LocalServices; -import com.android.server.timedetector.DeviceConfig; import java.util.Objects; -import java.util.concurrent.Executor; +import java.util.Optional; /** * The real implementation of {@link TimeZoneDetectorStrategyImpl.Environment}. @@ -59,8 +56,7 @@ public final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Envir @NonNull private final Handler mHandler; @NonNull private final ContentResolver mCr; @NonNull private final UserManager mUserManager; - @NonNull private final DeviceConfig mDeviceConfig; - @NonNull private final boolean mGeoDetectionSupported; + @NonNull private final ServiceConfigAccessor mServiceConfigAccessor; @NonNull private final LocationManager mLocationManager; // @NonNull after setConfigChangeListener() is called. @@ -68,17 +64,16 @@ public final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Envir private ConfigurationChangeListener mConfigChangeListener; EnvironmentImpl(@NonNull Context context, @NonNull Handler handler, - @NonNull DeviceConfig deviceConfig, boolean geoDetectionSupported) { + @NonNull ServiceConfigAccessor serviceConfigAccessor) { mContext = Objects.requireNonNull(context); mHandler = Objects.requireNonNull(handler); - Executor handlerExecutor = new HandlerExecutor(mHandler); mCr = context.getContentResolver(); mUserManager = context.getSystemService(UserManager.class); mLocationManager = context.getSystemService(LocationManager.class); - mDeviceConfig = deviceConfig; - mGeoDetectionSupported = geoDetectionSupported; + mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor); - // Wire up the change listeners. All invocations are performed on the mHandler thread. + // Wire up the config change listeners. All invocations are performed on the mHandler + // thread. // Listen for the user changing / the user's location mode changing. IntentFilter filter = new IntentFilter(); @@ -112,13 +107,6 @@ public final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Envir handleConfigChangeOnHandlerThread(); } }, UserHandle.USER_ALL); - - // Add async callbacks for changes to server-side flags: some of the flags affect device / - // user config. All changes can be treated like a config change. If flags that affect config - // haven't changed then call will be a no-op. - mDeviceConfig.addListener( - handlerExecutor, - this::handleConfigChangeOnHandlerThread); } private void handleConfigChangeOnHandlerThread() { @@ -140,10 +128,12 @@ public final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Envir @Override public ConfigurationInternal getConfigurationInternal(@UserIdInt int userId) { return new ConfigurationInternal.Builder(userId) - .setUserConfigAllowed(isUserConfigAllowed(userId)) - .setAutoDetectionSupported(isAutoDetectionSupported()) - .setGeoDetectionSupported(isGeoDetectionSupported()) + .setAutoDetectionFeatureSupported( + mServiceConfigAccessor.isAutoDetectionFeatureSupported()) + .setGeoDetectionFeatureSupported( + mServiceConfigAccessor.isGeoTimeZoneDetectionFeatureSupported()) .setAutoDetectionEnabled(isAutoDetectionEnabled()) + .setUserConfigAllowed(isUserConfigAllowed(userId)) .setLocationEnabled(isLocationEnabled(userId)) .setGeoDetectionEnabled(isGeoDetectionEnabled(userId)) .build(); @@ -186,18 +176,19 @@ public final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Envir // time zone detection: if we wrote it down then we'd set the value explicitly, which would // prevent detecting "default" later. That might influence what happens on later releases // that support new types of auto detection on the same hardware. - if (isAutoDetectionSupported()) { + if (mServiceConfigAccessor.isAutoDetectionFeatureSupported()) { final boolean autoDetectionEnabled = configuration.isAutoDetectionEnabled(); setAutoDetectionEnabledIfRequired(autoDetectionEnabled); - // Avoid writing the geo detection enabled setting for devices that do not support geo - // time zone detection: if we wrote it down then we'd set the value explicitly, which - // would prevent detecting "default" later. That might influence what happens on later - // releases that support geo detection on the same hardware. - // Also avoid writing the geo detection enabled setting for devices that are currently - // force-enabled: otherwise we might overwrite a droidfood user's real setting - // permanently. - if (isGeoDetectionSupported() && !isGeoDetectionForceEnabled()) { + // Avoid writing the geo detection enabled setting for devices with settings that + // are currently overridden by server flags: otherwise we might overwrite a droidfood + // user's real setting permanently. + // Also avoid writing the geo detection enabled setting for devices that do not support + // geo time zone detection: if we wrote it down then we'd set the value explicitly, + // which would prevent detecting "default" later. That might influence what happens on + // later releases that start to support geo detection on the same hardware. + if (!mServiceConfigAccessor.getGeoDetectionSettingEnabledOverride().isPresent() + && mServiceConfigAccessor.isGeoTimeZoneDetectionFeatureSupported()) { final boolean geoTzDetectionEnabled = configuration.isGeoDetectionEnabled(); setGeoDetectionEnabledIfRequired(userId, geoTzDetectionEnabled); } @@ -209,14 +200,6 @@ public final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Envir return !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_DATE_TIME, userHandle); } - private boolean isAutoDetectionSupported() { - return deviceHasTelephonyNetwork() || isGeoDetectionSupported(); - } - - private boolean isGeoDetectionSupported() { - return mGeoDetectionSupported; - } - private boolean isAutoDetectionEnabled() { return Settings.Global.getInt(mCr, Settings.Global.AUTO_TIME_ZONE, 1 /* default */) > 0; } @@ -237,24 +220,20 @@ public final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Envir private boolean isGeoDetectionEnabled(@UserIdInt int userId) { // We may never use this, but it gives us a way to force location-based time zone detection - // on for testers (where their other settings allow). - boolean forceEnabled = isGeoDetectionForceEnabled(); - if (forceEnabled) { - return true; + // on/off for testers (but only where their other settings would allow them to turn it on + // for themselves). + Optional<Boolean> override = mServiceConfigAccessor.getGeoDetectionSettingEnabledOverride(); + if (override.isPresent()) { + return override.get(); } - final boolean geoDetectionEnabledByDefault = mDeviceConfig.getBoolean( - DeviceConfig.KEY_LOCATION_TIME_ZONE_DETECTION_ENABLED_DEFAULT, false); + final boolean geoDetectionEnabledByDefault = + mServiceConfigAccessor.isGeoDetectionEnabledForUsersByDefault(); return Settings.Secure.getIntForUser(mCr, Settings.Secure.LOCATION_TIME_ZONE_DETECTION_ENABLED, (geoDetectionEnabledByDefault ? 1 : 0) /* defaultValue */, userId) != 0; } - private boolean isGeoDetectionForceEnabled() { - return mDeviceConfig.getBoolean( - DeviceConfig.KEY_FORCE_LOCATION_TIME_ZONE_DETECTION_ENABLED, false); - } - private void setGeoDetectionEnabledIfRequired(@UserIdInt int userId, boolean enabled) { // See comment in setAutoDetectionEnabledIfRequired. http://b/171953500 if (isGeoDetectionEnabled(userId) != enabled) { @@ -262,10 +241,4 @@ public final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Envir enabled ? 1 : 0, userId); } } - - private boolean deviceHasTelephonyNetwork() { - // TODO b/150583524 Avoid the use of a deprecated API. - return mContext.getSystemService(ConnectivityManager.class) - .isNetworkSupported(ConnectivityManager.TYPE_MOBILE); - } } diff --git a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java new file mode 100644 index 000000000000..86c32f8d7b45 --- /dev/null +++ b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java @@ -0,0 +1,249 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.timezonedetector; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.content.res.Resources; +import android.net.ConnectivityManager; +import android.os.SystemProperties; +import android.util.ArraySet; + +import com.android.internal.R; +import com.android.internal.annotations.GuardedBy; +import com.android.server.timedetector.ServerFlags; + +import java.time.Duration; +import java.util.Collections; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +/** + * A singleton that provides access to service configuration for time zone detection. This hides how + * configuration is split between static, compile-time config and dynamic, server-pushed flags. It + * provides a rudimentary mechanism to signal when values have changed. + */ +public final class ServiceConfigAccessor { + + private static final Set<String> SERVER_FLAGS_KEYS_TO_WATCH = Collections.unmodifiableSet( + new ArraySet<>(new String[] { + ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED, + ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT, + ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE, + ServerFlags.KEY_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_ENABLED_OVERRIDE, + ServerFlags.KEY_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_ENABLED_OVERRIDE, + ServerFlags.KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS, + ServerFlags.KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS, + ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS + })); + + // TODO(b/179488561): Put this back to 5 minutes when primary provider is fully implemented + private static final Duration DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT = Duration.ofMinutes(1); + // TODO(b/179488561): Put this back to 1 minute when primary provider is fully implemented + private static final Duration DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ = + Duration.ofSeconds(20); + private static final Duration DEFAULT_PROVIDER_UNCERTAINTY_DELAY = Duration.ofMinutes(5); + + private static final Object SLOCK = new Object(); + + /** The singleton instance. Initialized once in {@link #getInstance(Context)}. */ + @GuardedBy("SLOCK") + @Nullable + private static ServiceConfigAccessor sInstance; + + @NonNull private final Context mContext; + + /** + * An ultimate "feature switch" for location-based time zone detection. If this is + * {@code false}, the device cannot support the feature without a config change or a reboot: + * This affects what services are started on boot to minimize expense when the feature is not + * wanted. + */ + private final boolean mGeoDetectionFeatureSupportedInConfig; + + @NonNull private final ServerFlags mServerFlags; + + private ServiceConfigAccessor(@NonNull Context context) { + mContext = Objects.requireNonNull(context); + + // The config value is expected to be the main feature flag. Platform developers can also + // force enable the feature using a persistent system property. Because system properties + // can change, this value is cached and only changes on reboot. + mGeoDetectionFeatureSupportedInConfig = context.getResources().getBoolean( + com.android.internal.R.bool.config_enableGeolocationTimeZoneDetection) + || SystemProperties.getBoolean( + "persist.sys.location_time_zone_detection_feature_supported", false); + + mServerFlags = ServerFlags.getInstance(mContext); + } + + /** Returns the singleton instance. */ + public static ServiceConfigAccessor getInstance(Context context) { + synchronized (SLOCK) { + if (sInstance == null) { + sInstance = new ServiceConfigAccessor(context); + } + return sInstance; + } + } + + /** + * Adds a listener that will be called server flags related to this class change. The callbacks + * are delivered on the main looper thread. + * + * <p>Note: Only for use by long-lived objects. There is deliberately no associated remove + * method. + */ + public void addListener(@NonNull ConfigurationChangeListener listener) { + mServerFlags.addListener(listener, SERVER_FLAGS_KEYS_TO_WATCH); + } + + /** Returns {@code true} if any form of automatic time zone detection is supported. */ + public boolean isAutoDetectionFeatureSupported() { + return deviceHasTelephonyNetwork() || isGeoTimeZoneDetectionFeatureSupported(); + } + + private boolean deviceHasTelephonyNetwork() { + // TODO b/150583524 Avoid the use of a deprecated API. + return mContext.getSystemService(ConnectivityManager.class) + .isNetworkSupported(ConnectivityManager.TYPE_MOBILE); + } + + /** + * Returns {@code true} if the location-based time zone detection feature can be supported on + * this device at all according to config. When {@code false}, implies that various other + * location-based settings will be turned off or rendered meaningless. Typically {@link + * #isGeoTimeZoneDetectionFeatureSupported()} should be used instead. + */ + public boolean isGeoTimeZoneDetectionFeatureSupportedInConfig() { + return mGeoDetectionFeatureSupportedInConfig; + } + + /** + * Returns {@code true} if the location-based time zone detection feature is supported on the + * device. This can be used during feature testing on builds that are capable of location time + * zone detection to enable / disable the feature for some users. + */ + public boolean isGeoTimeZoneDetectionFeatureSupported() { + return mGeoDetectionFeatureSupportedInConfig + && isGeoTimeZoneDetectionFeatureSupportedInternal(); + } + + private boolean isGeoTimeZoneDetectionFeatureSupportedInternal() { + final boolean defaultEnabled = true; + return mServerFlags.getBoolean( + ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED, + defaultEnabled); + } + + /** + * Returns {@code true} if the primary location time zone provider can be used. + */ + public boolean isPrimaryLocationTimeZoneProviderEnabled() { + return getPrimaryLocationTimeZoneProviderEnabledOverride() + .orElse(isPrimaryLocationTimeZoneProviderEnabledInConfig()); + } + + private boolean isPrimaryLocationTimeZoneProviderEnabledInConfig() { + int providerEnabledConfigId = R.bool.config_enablePrimaryLocationTimeZoneProvider; + return getConfigBoolean(providerEnabledConfigId); + } + + @NonNull + private Optional<Boolean> getPrimaryLocationTimeZoneProviderEnabledOverride() { + return mServerFlags.getOptionalBoolean( + ServerFlags.KEY_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_ENABLED_OVERRIDE); + } + + /** + * Returns {@code true} if the secondary location time zone provider can be used. + */ + public boolean isSecondaryLocationTimeZoneProviderEnabled() { + return getSecondaryLocationTimeZoneProviderEnabledOverride() + .orElse(isSecondaryLocationTimeZoneProviderEnabledInConfig()); + } + + private boolean isSecondaryLocationTimeZoneProviderEnabledInConfig() { + int providerEnabledConfigId = R.bool.config_enableSecondaryLocationTimeZoneProvider; + return getConfigBoolean(providerEnabledConfigId); + } + + @NonNull + private Optional<Boolean> getSecondaryLocationTimeZoneProviderEnabledOverride() { + return mServerFlags.getOptionalBoolean( + ServerFlags.KEY_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_ENABLED_OVERRIDE); + } + + /** + * Returns whether location time zone detection is enabled for users when there's no setting + * value. Intended for use during feature release testing to "opt-in" users that haven't shown + * an explicit preference. + */ + public boolean isGeoDetectionEnabledForUsersByDefault() { + return mServerFlags.getBoolean( + ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT, false); + } + + /** + * Returns whether location time zone detection is force enabled/disabled for users. Intended + * for use during feature release testing to force a given state. + */ + @NonNull + public Optional<Boolean> getGeoDetectionSettingEnabledOverride() { + return mServerFlags.getOptionalBoolean( + ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE); + } + + /** + * Returns the time to send to a location time zone provider that informs it how long it has + * to return its first time zone suggestion. + */ + @NonNull + public Duration getLocationTimeZoneProviderInitializationTimeout() { + return mServerFlags.getDurationFromMillis( + ServerFlags.KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS, + DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT); + } + + /** + * Returns the time added to {@link #getLocationTimeZoneProviderInitializationTimeout()} by the + * server before unilaterally declaring the provider is uncertain. + */ + @NonNull + public Duration getLocationTimeZoneProviderInitializationTimeoutFuzz() { + return mServerFlags.getDurationFromMillis( + ServerFlags.KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS, + DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ); + } + + /** + * Returns the time after uncertainty is detected by providers before the location time zone + * manager makes a suggestion to the time zone detector. + */ + @NonNull + public Duration getLocationTimeZoneUncertaintyDelay() { + return mServerFlags.getDurationFromMillis( + ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS, + DEFAULT_PROVIDER_UNCERTAINTY_DELAY); + } + + private boolean getConfigBoolean(int providerEnabledConfigId) { + Resources resources = mContext.getResources(); + return resources.getBoolean(providerEnabledConfigId); + } +} diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java index 203a8a4e02cc..cd220b164851 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java @@ -27,16 +27,21 @@ import android.annotation.NonNull; */ public interface TimeZoneDetectorInternal extends Dumpable.Container { - /** Adds a listener that will be invoked when time zone detection configuration is changed. */ - void addConfigurationListener(ConfigurationChangeListener listener); + /** Adds a listener that will be invoked when {@link ConfigurationInternal} may have changed. */ + void addConfigurationListener(@NonNull ConfigurationChangeListener listener); /** * Removes a listener previously added via {@link * #addConfigurationListener(ConfigurationChangeListener)}. */ - void removeConfigurationListener(ConfigurationChangeListener listener); + void removeConfigurationListener(@NonNull ConfigurationChangeListener listener); - /** Returns the {@link ConfigurationInternal} for the current user. */ + /** + * Returns a snapshot of the {@link ConfigurationInternal} for the current user. This is only a + * snapshot so callers must use {@link #addConfigurationListener(ConfigurationChangeListener)} + * to be notified when it changes. + */ + @NonNull ConfigurationInternal getCurrentUserConfigurationInternal(); /** diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java index bd71ddf67094..c20400ae7a4b 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java @@ -33,7 +33,6 @@ import android.os.IBinder; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ShellCallback; -import android.os.SystemProperties; import android.util.ArrayMap; import android.util.IndentingPrintWriter; import android.util.Slog; @@ -62,27 +61,6 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub static final String TAG = "time_zone_detector"; /** - * A "feature switch" for location-based time zone detection. If this is {@code false}. It is - * initialized and never refreshed; it affects what services are started on boot so consistency - * is important. - */ - @Nullable - private static Boolean sGeoLocationTimeZoneDetectionSupported; - - /** Returns {@code true} if the location-based time zone detection feature is enabled. */ - public static boolean isGeoLocationTimeZoneDetectionSupported(Context context) { - if (sGeoLocationTimeZoneDetectionSupported == null) { - // The config value is expected to be the main switch. Platform developers can also - // enable the feature using a persistent system property. - sGeoLocationTimeZoneDetectionSupported = context.getResources().getBoolean( - com.android.internal.R.bool.config_enableGeolocationTimeZoneDetection) - || SystemProperties.getBoolean( - "persist.sys.location_time_zone_detection_feature_enabled", false); - } - return sGeoLocationTimeZoneDetectionSupported; - } - - /** * Handles the service lifecycle for {@link TimeZoneDetectorService} and * {@link TimeZoneDetectorInternalImpl}. */ @@ -98,11 +76,10 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub Context context = getContext(); Handler handler = FgThread.getHandler(); - boolean geolocationTimeZoneDetectionSupported = - isGeoLocationTimeZoneDetectionSupported(context); + ServiceConfigAccessor serviceConfigAccessor = + ServiceConfigAccessor.getInstance(context); TimeZoneDetectorStrategy timeZoneDetectorStrategy = - TimeZoneDetectorStrategyImpl.create( - context, handler, geolocationTimeZoneDetectionSupported); + TimeZoneDetectorStrategyImpl.create(context, handler, serviceConfigAccessor); // Create and publish the local service for use by internal callers. TimeZoneDetectorInternal internal = @@ -330,7 +307,8 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub boolean isGeoTimeZoneDetectionSupported() { enforceManageTimeZoneDetectorPermission(); - return isGeoLocationTimeZoneDetectionSupported(mContext); + return ServiceConfigAccessor.getInstance(mContext) + .isGeoTimeZoneDetectionFeatureSupported(); } @Override diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java index 0b1d6d71ea7b..8266f121822e 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java @@ -75,17 +75,21 @@ import android.util.IndentingPrintWriter; public interface TimeZoneDetectorStrategy extends Dumpable, Dumpable.Container { /** - * Sets a listener that will be triggered whenever time zone detection configuration is + * Adds a listener that will be triggered whenever {@link ConfigurationInternal} may have * changed. */ void addConfigChangeListener(@NonNull ConfigurationChangeListener listener); - /** Returns the user's time zone configuration. */ + /** + * Returns a snapshot of the configuration that controls time zone detector behavior for the + * specified user. + */ @NonNull ConfigurationInternal getConfigurationInternal(@UserIdInt int userId); /** - * Returns the configuration that controls time zone detector behavior for the current user. + * Returns a snapshot of the configuration that controls time zone detector behavior for the + * current user. */ @NonNull ConfigurationInternal getCurrentUserConfigurationInternal(); diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java index c464b74ca726..d163a0e22320 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java @@ -38,7 +38,6 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import com.android.server.timedetector.DeviceConfig; import java.util.ArrayList; import java.util.List; @@ -204,11 +203,9 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat */ public static TimeZoneDetectorStrategyImpl create( @NonNull Context context, @NonNull Handler handler, - boolean geoDetectionSupported) { + @NonNull ServiceConfigAccessor serviceConfigAccessor) { - DeviceConfig deviceConfig = new DeviceConfig(); - EnvironmentImpl environment = new EnvironmentImpl( - context, handler, deviceConfig, geoDetectionSupported); + Environment environment = new EnvironmentImpl(context, handler, serviceConfigAccessor); return new TimeZoneDetectorStrategyImpl(environment); } diff --git a/services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java b/services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java index e463ee22452d..98e984d2c3ac 100644 --- a/services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java +++ b/services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java @@ -19,9 +19,9 @@ package com.android.server.timezonedetector.location; import android.annotation.NonNull; import com.android.server.LocalServices; -import com.android.server.timedetector.DeviceConfig; import com.android.server.timezonedetector.ConfigurationChangeListener; import com.android.server.timezonedetector.ConfigurationInternal; +import com.android.server.timezonedetector.ServiceConfigAccessor; import com.android.server.timezonedetector.TimeZoneDetectorInternal; import java.time.Duration; @@ -33,28 +33,19 @@ import java.util.Objects; */ class ControllerEnvironmentImpl extends LocationTimeZoneProviderController.Environment { - // TODO(b/179488561): Put this back to 5 minutes when primary provider is fully implemented - private static final Duration DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT = Duration.ofMinutes(1); - // TODO(b/179488561): Put this back to 5 minutes when primary provider is fully implemented - private static final Duration DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ = - Duration.ofSeconds(20); - private static final Duration DEFAULT_PROVIDER_UNCERTAINTY_DELAY = Duration.ofMinutes(5); - @NonNull private final TimeZoneDetectorInternal mTimeZoneDetectorInternal; - @NonNull private final LocationTimeZoneProviderController mController; - @NonNull private final DeviceConfig mDeviceConfig; + @NonNull private final ServiceConfigAccessor mServiceConfigAccessor; @NonNull private final ConfigurationChangeListener mConfigurationChangeListener; ControllerEnvironmentImpl(@NonNull ThreadingDomain threadingDomain, - @NonNull DeviceConfig deviceConfig, + @NonNull ServiceConfigAccessor serviceConfigAccessor, @NonNull LocationTimeZoneProviderController controller) { super(threadingDomain); - mController = Objects.requireNonNull(controller); - mDeviceConfig = Objects.requireNonNull(deviceConfig); + mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor); mTimeZoneDetectorInternal = LocalServices.getService(TimeZoneDetectorInternal.class); // Listen for configuration changes. - mConfigurationChangeListener = () -> mThreadingDomain.post(mController::onConfigChanged); + mConfigurationChangeListener = () -> mThreadingDomain.post(controller::onConfigChanged); mTimeZoneDetectorInternal.addConfigurationListener(mConfigurationChangeListener); } @@ -73,24 +64,18 @@ class ControllerEnvironmentImpl extends LocationTimeZoneProviderController.Envir @Override @NonNull Duration getProviderInitializationTimeout() { - return mDeviceConfig.getDurationFromMillis( - DeviceConfig.KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_MILLIS, - DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT); + return mServiceConfigAccessor.getLocationTimeZoneProviderInitializationTimeout(); } @Override @NonNull Duration getProviderInitializationTimeoutFuzz() { - return mDeviceConfig.getDurationFromMillis( - DeviceConfig.KEY_LOCATION_TIME_ZONE_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ_MILLIS, - DEFAULT_PROVIDER_INITIALIZATION_TIMEOUT_FUZZ); + return mServiceConfigAccessor.getLocationTimeZoneProviderInitializationTimeoutFuzz(); } @Override @NonNull Duration getUncertaintyDelay() { - return mDeviceConfig.getDurationFromMillis( - DeviceConfig.KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS, - DEFAULT_PROVIDER_UNCERTAINTY_DELAY); + return mServiceConfigAccessor.getLocationTimeZoneUncertaintyDelay(); } } diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java index 364eaf8dac04..0d1692a8781d 100644 --- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java +++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java @@ -21,11 +21,11 @@ import static android.app.time.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_DI import static android.app.time.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_NONE; import static android.app.time.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_SIMULATED; import static android.app.time.LocationTimeZoneManager.SECONDARY_PROVIDER_NAME; +import static android.app.time.LocationTimeZoneManager.SERVICE_NAME; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; -import android.content.res.Resources; import android.os.Binder; import android.os.Bundle; import android.os.Handler; @@ -43,9 +43,8 @@ import com.android.internal.util.DumpUtils; import com.android.internal.util.Preconditions; import com.android.server.FgThread; import com.android.server.SystemService; -import com.android.server.timedetector.DeviceConfig; +import com.android.server.timezonedetector.ServiceConfigAccessor; import com.android.server.timezonedetector.TimeZoneDetectorInternal; -import com.android.server.timezonedetector.TimeZoneDetectorService; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -96,28 +95,31 @@ public class LocationTimeZoneManagerService extends Binder { private LocationTimeZoneManagerService mService; + @NonNull + private final ServiceConfigAccessor mServerConfigAccessor; + public Lifecycle(@NonNull Context context) { super(Objects.requireNonNull(context)); + mServerConfigAccessor = ServiceConfigAccessor.getInstance(context); } @Override public void onStart() { Context context = getContext(); - if (TimeZoneDetectorService.isGeoLocationTimeZoneDetectionSupported(context)) { + if (mServerConfigAccessor.isGeoTimeZoneDetectionFeatureSupportedInConfig()) { mService = new LocationTimeZoneManagerService(context); // The service currently exposes no LocalService or Binder API, but it extends // Binder and is registered as a binder service so it can receive shell commands. - publishBinderService("location_time_zone_manager", mService); + publishBinderService(SERVICE_NAME, mService); } else { - Slog.i(TAG, getClass() + " is disabled"); + Slog.d(TAG, "Geo time zone detection feature is disabled in config"); } } @Override - public void onBootPhase(int phase) { - Context context = getContext(); - if (TimeZoneDetectorService.isGeoLocationTimeZoneDetectionSupported(context)) { + public void onBootPhase(@BootPhase int phase) { + if (mServerConfigAccessor.isGeoTimeZoneDetectionFeatureSupportedInConfig()) { if (phase == PHASE_SYSTEM_SERVICES_READY) { // The location service must be functioning after this boot phase. mService.onSystemReady(); @@ -159,6 +161,9 @@ public class LocationTimeZoneManagerService extends Binder { /** The shared lock from {@link #mThreadingDomain}. */ @NonNull private final Object mSharedLock; + @NonNull + private final ServiceConfigAccessor mServiceConfigAccessor; + // Lazily initialized. Can be null if the service has been stopped. @GuardedBy("mSharedLock") private ControllerImpl mLocationTimeZoneDetectorController; @@ -180,24 +185,38 @@ public class LocationTimeZoneManagerService extends Binder { mHandler = FgThread.getHandler(); mThreadingDomain = new HandlerThreadingDomain(mHandler); mSharedLock = mThreadingDomain.getLockObject(); + mServiceConfigAccessor = ServiceConfigAccessor.getInstance(mContext); } + // According to the SystemService docs: All lifecycle methods are called from the system + // server's main looper thread. void onSystemReady() { - // Called on an arbitrary thread during initialization. - synchronized (mSharedLock) { - // TODO(b/152744911): LocationManagerService watches for packages disappearing. Need to - // do anything here? + mServiceConfigAccessor.addListener(this::handleServiceConfigurationChangedOnMainThread); + } - // TODO(b/152744911): LocationManagerService watches for foreground app changes. Need to - // do anything here? - // TODO(b/152744911): LocationManagerService watches screen state. Need to do anything - // here? + private void handleServiceConfigurationChangedOnMainThread() { + // This method is called on the main thread, but service logic takes place on the threading + // domain thread, so we post the work there. + + // The way all service-level configuration changes are handled is to just restart this + // service - this is simple and effective, and service configuration changes should be rare. + mThreadingDomain.post(this::restartIfRequiredOnDomainThread); + } + + private void restartIfRequiredOnDomainThread() { + mThreadingDomain.assertCurrentThread(); + + synchronized (mSharedLock) { + // Stop and start the service, waiting until completion. + stopOnDomainThread(); + startOnDomainThread(); } } + // According to the SystemService docs: All lifecycle methods are called from the system + // server's main looper thread. void onSystemThirdPartyAppsCanStart() { - // Called on an arbitrary thread during initialization. We do not want to wait for - // completion as it would delay boot. + // Do not wait for completion as it would delay boot. final boolean waitForCompletion = false; startInternal(waitForCompletion); } @@ -205,6 +224,9 @@ public class LocationTimeZoneManagerService extends Binder { /** * Starts the service during server initialization or during tests after a call to * {@link #stop()}. + * + * <p>Because this method posts work to the {@code mThreadingDomain} thread and waits for + * completion, it cannot be called from the {@code mThreadingDomain} thread. */ void start() { enforceManageTimeZoneDetectorPermission(); @@ -214,28 +236,17 @@ public class LocationTimeZoneManagerService extends Binder { } /** - * Starts the service during server initialization or during tests after a call to - * {@link #stop()}. + * Starts the service during server initialization, if the configuration changes or during tests + * after a call to {@link #stop()}. * * <p>To avoid tests needing to sleep, when {@code waitForCompletion} is {@code true}, this * method will not return until all the system server components have started. + * + * <p>Because this method posts work to the {@code mThreadingDomain} thread, it cannot be + * called from the {@code mThreadingDomain} thread when {@code waitForCompletion} is true. */ private void startInternal(boolean waitForCompletion) { - Runnable runnable = () -> { - synchronized (mSharedLock) { - if (mLocationTimeZoneDetectorController == null) { - LocationTimeZoneProvider primary = createPrimaryProvider(); - LocationTimeZoneProvider secondary = createSecondaryProvider(); - mLocationTimeZoneDetectorController = - new ControllerImpl(mThreadingDomain, primary, secondary); - DeviceConfig deviceConfig = new DeviceConfig(); - mEnvironment = new ControllerEnvironmentImpl( - mThreadingDomain, deviceConfig, mLocationTimeZoneDetectorController); - ControllerCallbackImpl callback = new ControllerCallbackImpl(mThreadingDomain); - mLocationTimeZoneDetectorController.initialize(mEnvironment, callback); - } - } - }; + Runnable runnable = this::startOnDomainThread; if (waitForCompletion) { mThreadingDomain.postAndWait(runnable, BLOCKING_OP_WAIT_DURATION_MILLIS); } else { @@ -243,11 +254,38 @@ public class LocationTimeZoneManagerService extends Binder { } } + private void startOnDomainThread() { + mThreadingDomain.assertCurrentThread(); + + synchronized (mSharedLock) { + if (!mServiceConfigAccessor.isGeoTimeZoneDetectionFeatureSupported()) { + debugLog("Not starting " + SERVICE_NAME + ": it is disabled in service config"); + return; + } + + if (mLocationTimeZoneDetectorController == null) { + LocationTimeZoneProvider primary = createPrimaryProvider(); + LocationTimeZoneProvider secondary = createSecondaryProvider(); + + ControllerImpl controller = + new ControllerImpl(mThreadingDomain, primary, secondary); + ControllerEnvironmentImpl environment = new ControllerEnvironmentImpl( + mThreadingDomain, mServiceConfigAccessor, controller); + ControllerCallbackImpl callback = new ControllerCallbackImpl(mThreadingDomain); + controller.initialize(environment, callback); + + mEnvironment = environment; + mLocationTimeZoneDetectorController = controller; + } + } + } + + @NonNull private LocationTimeZoneProvider createPrimaryProvider() { LocationTimeZoneProviderProxy proxy; if (isProviderInSimulationMode(PRIMARY_PROVIDER_NAME)) { proxy = new SimulatedLocationTimeZoneProviderProxy(mContext, mThreadingDomain); - } else if (isProviderDisabled(PRIMARY_PROVIDER_NAME)) { + } else if (!isProviderEnabled(PRIMARY_PROVIDER_NAME)) { proxy = new NullLocationTimeZoneProviderProxy(mContext, mThreadingDomain); } else { proxy = new RealLocationTimeZoneProviderProxy( @@ -262,11 +300,12 @@ public class LocationTimeZoneManagerService extends Binder { return new BinderLocationTimeZoneProvider(mThreadingDomain, PRIMARY_PROVIDER_NAME, proxy); } + @NonNull private LocationTimeZoneProvider createSecondaryProvider() { LocationTimeZoneProviderProxy proxy; if (isProviderInSimulationMode(SECONDARY_PROVIDER_NAME)) { proxy = new SimulatedLocationTimeZoneProviderProxy(mContext, mThreadingDomain); - } else if (isProviderDisabled(SECONDARY_PROVIDER_NAME)) { + } else if (!isProviderEnabled(SECONDARY_PROVIDER_NAME)) { proxy = new NullLocationTimeZoneProviderProxy(mContext, mThreadingDomain); } else { proxy = new RealLocationTimeZoneProviderProxy( @@ -282,33 +321,27 @@ public class LocationTimeZoneManagerService extends Binder { } /** Used for bug triage and in tests to simulate provider events. */ - private boolean isProviderInSimulationMode(String providerName) { + private boolean isProviderInSimulationMode(@NonNull String providerName) { return isProviderModeOverrideSet(providerName, PROVIDER_MODE_OVERRIDE_SIMULATED); } - /** Used for bug triage, tests and experiments to remove a provider. */ - private boolean isProviderDisabled(String providerName) { - return !isProviderEnabledInConfig(providerName) - || isProviderModeOverrideSet(providerName, PROVIDER_MODE_OVERRIDE_DISABLED); - } + /** Used for bug triage, and by tests and experiments to remove a provider. */ + private boolean isProviderEnabled(@NonNull String providerName) { + if (isProviderModeOverrideSet(providerName, PROVIDER_MODE_OVERRIDE_DISABLED)) { + return false; + } - private boolean isProviderEnabledInConfig(String providerName) { - int providerEnabledConfigId; switch (providerName) { case PRIMARY_PROVIDER_NAME: { - providerEnabledConfigId = R.bool.config_enablePrimaryLocationTimeZoneProvider; - break; + return mServiceConfigAccessor.isPrimaryLocationTimeZoneProviderEnabled(); } case SECONDARY_PROVIDER_NAME: { - providerEnabledConfigId = R.bool.config_enableSecondaryLocationTimeZoneProvider; - break; + return mServiceConfigAccessor.isSecondaryLocationTimeZoneProviderEnabled(); } default: { throw new IllegalArgumentException(providerName); } } - Resources resources = mContext.getResources(); - return resources.getBoolean(providerEnabledConfigId); } private boolean isProviderModeOverrideSet(@NonNull String providerName, @NonNull String mode) { @@ -326,22 +359,29 @@ public class LocationTimeZoneManagerService extends Binder { } /** - * Stops the service for tests. To avoid tests needing to sleep, this method will not return - * until all the system server components have stopped. + * Stops the service for tests and other rare cases. To avoid tests needing to sleep, this + * method will not return until all the system server components have stopped. + * + * <p>Because this method posts work to the {@code mThreadingDomain} thread and waits it cannot + * be called from the {@code mThreadingDomain} thread. */ void stop() { enforceManageTimeZoneDetectorPermission(); - mThreadingDomain.postAndWait(() -> { - synchronized (mSharedLock) { - if (mLocationTimeZoneDetectorController != null) { - mLocationTimeZoneDetectorController.destroy(); - mLocationTimeZoneDetectorController = null; - mEnvironment.destroy(); - mEnvironment = null; - } + mThreadingDomain.postAndWait(this::stopOnDomainThread, BLOCKING_OP_WAIT_DURATION_MILLIS); + } + + private void stopOnDomainThread() { + mThreadingDomain.assertCurrentThread(); + + synchronized (mSharedLock) { + if (mLocationTimeZoneDetectorController != null) { + mLocationTimeZoneDetectorController.destroy(); + mLocationTimeZoneDetectorController = null; + mEnvironment.destroy(); + mEnvironment = null; } - }, BLOCKING_OP_WAIT_DURATION_MILLIS); + } } @Override diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java index b53150c729bc..bdf4a70a6a2b 100644 --- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java +++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java @@ -21,6 +21,7 @@ import static android.app.time.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_DI import static android.app.time.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_NONE; import static android.app.time.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_SIMULATED; import static android.app.time.LocationTimeZoneManager.SECONDARY_PROVIDER_NAME; +import static android.app.time.LocationTimeZoneManager.SERVICE_NAME; import static android.app.time.LocationTimeZoneManager.SHELL_COMMAND_DUMP_STATE; import static android.app.time.LocationTimeZoneManager.SHELL_COMMAND_RECORD_PROVIDER_STATES; import static android.app.time.LocationTimeZoneManager.SHELL_COMMAND_SEND_PROVIDER_TEST_COMMAND; @@ -102,7 +103,7 @@ class LocationTimeZoneManagerShellCommand extends ShellCommand { @Override public void onHelp() { final PrintWriter pw = getOutPrintWriter(); - pw.println("Location Time Zone Manager (location_time_zone_manager) commands for tests:"); + pw.printf("Location Time Zone Manager (%s) commands for tests:\n", SERVICE_NAME); pw.println(" help"); pw.println(" Print this help text."); pw.printf(" %s\n", SHELL_COMMAND_START); diff --git a/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java b/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java index 38211efc1c63..531c62c5b4e9 100644 --- a/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java +++ b/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java @@ -25,7 +25,6 @@ import static com.android.server.timezonedetector.location.LocationTimeZoneManag import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; -import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; @@ -41,6 +40,7 @@ import android.util.IndentingPrintWriter; import com.android.internal.annotations.GuardedBy; import com.android.server.ServiceWatcher; +import com.android.server.ServiceWatcher.BoundService; import java.util.Objects; import java.util.function.Predicate; @@ -123,7 +123,7 @@ class RealLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy { return resolves; } - private void onBind(IBinder binder, ComponentName componentName) { + private void onBind(IBinder binder, BoundService boundService) { mThreadingDomain.assertCurrentThread(); synchronized (mSharedLock) { diff --git a/services/core/java/com/android/server/timezonedetector/location/TimeZoneProviderRequest.java b/services/core/java/com/android/server/timezonedetector/location/TimeZoneProviderRequest.java index 14820319d9df..e8386bc22403 100644 --- a/services/core/java/com/android/server/timezonedetector/location/TimeZoneProviderRequest.java +++ b/services/core/java/com/android/server/timezonedetector/location/TimeZoneProviderRequest.java @@ -63,8 +63,6 @@ final class TimeZoneProviderRequest { return mSendUpdates; } - // TODO(b/152744911) - once there are a couple of implementations, decide whether this needs to - // be passed to the TimeZoneProviderService and remove if it is not useful. /** * Returns the maximum time that the provider is allowed to initialize before it is expected to * send an event of any sort. Only valid when {@link #sendUpdates()} is {@code true}. Failure to diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 5d3b9c197267..f40f4a98964a 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -40,8 +40,11 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.ROTATION_UNDEFINED; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.activityTypeToString; +import static android.app.WindowConfiguration.isSplitScreenWindowingMode; import static android.app.servertransaction.TransferSplashScreenViewStateItem.ATTACH_TO; import static android.app.servertransaction.TransferSplashScreenViewStateItem.HANDOVER_TO; import static android.content.Intent.ACTION_MAIN; @@ -209,6 +212,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerService.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND; import static com.android.server.wm.WindowManagerService.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING; import static com.android.server.wm.WindowManagerService.LETTERBOX_BACKGROUND_SOLID_COLOR; +import static com.android.server.wm.WindowManagerService.MIN_TASK_LETTERBOX_ASPECT_RATIO; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES; import static com.android.server.wm.WindowState.LEGACY_POLICY_VISIBILITY; @@ -557,7 +561,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A /** * The precomputed display insets for resolving configuration. It will be non-null if - * {@link #shouldUseSizeCompatMode} returns {@code true}. + * {@link #shouldCreateCompatDisplayInsets} returns {@code true}. */ private CompatDisplayInsets mCompatDisplayInsets; @@ -648,6 +652,18 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A */ private Rect mSizeCompatBounds; + // Whether this activity is in size compatibility mode because its bounds don't fit in parent + // naturally. + private boolean mInSizeCompatModeForBounds = false; + + // Whether this activity is letterboxed for fixed orientation. If letterboxed due to fixed + // orientation then aspect ratio restrictions are also already respected. + // This happens when an activity has fixed orientation which doesn't match orientation of the + // parent because a display is ignoring orientation request or fixed to user rotation. + // See WindowManagerService#getIgnoreOrientationRequest and + // WindowManagerService#getFixedToUserRotation for more context. + private boolean mIsLetterboxedForFixedOrientationAndAspectRatio = false; + // activity is not displayed? // TODO: rename to mNoDisplay @VisibleForTesting @@ -1296,9 +1312,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // TODO(b/36505427): Maybe this call should be moved inside // updateOverrideConfiguration() newTask.updateOverrideConfigurationFromLaunchBounds(); - // Make sure override configuration is up-to-date before using to create window - // controller. - updateSizeCompatMode(); // When an activity is started directly into a split-screen fullscreen root task, we // need to update the initial multi-window modes so that the callbacks are scheduled // correctly when the user leaves that mode. @@ -3624,7 +3637,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } // Reset the last saved PiP snap fraction on removal. - mDisplayContent.mPinnedStackControllerLocked.onActivityHidden(mActivityComponent); + mDisplayContent.mPinnedTaskControllerLocked.onActivityHidden(mActivityComponent); mWmService.mEmbeddedWindowController.onActivityRemoved(this); mRemovingFromDisplay = false; } @@ -4976,7 +4989,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A ProtoLog.v(WM_DEBUG_ADD_REMOVE, "notifyAppStopped: %s", this); mAppStopped = true; // Reset the last saved PiP snap fraction on app stop. - mDisplayContent.mPinnedStackControllerLocked.onActivityHidden(mActivityComponent); + mDisplayContent.mPinnedTaskControllerLocked.onActivityHidden(mActivityComponent); destroySurfaces(); // Remove any starting window that was added for this app if they are still around. removeStartingWindow(); @@ -6718,12 +6731,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } if (onDescendantOrientationChanged(this)) { - // The app is just becoming visible, and the parent Task has updated with the - // orientation request. Update the size compat mode. - updateSizeCompatMode(); - // WM Shell can override WM Core positioning (e.g. for letterboxing) so ensure - // that WM Shell is called when an activity becomes visible. Without this, WM Core - // will handle positioning instead of WM Shell when an app is reopened. + // WM Shell can show additional UI elements, e.g. a restart button for size compat mode + // so ensure that WM Shell is called when an activity becomes visible. task.dispatchTaskInfoChangedIfNeeded(/* force= */ true); } } @@ -6788,7 +6797,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * density than its parent or its bounds don't fit in parent naturally. */ boolean inSizeCompatMode() { - if (mCompatDisplayInsets == null || !shouldUseSizeCompatMode() + if (mInSizeCompatModeForBounds) { + return true; + } + if (mCompatDisplayInsets == null || !shouldCreateCompatDisplayInsets() // The orientation is different from parent when transforming. || isFixedRotationTransforming()) { return false; @@ -6798,70 +6810,30 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // The app bounds hasn't been computed yet. return false; } - final Configuration parentConfig = getParent().getConfiguration(); // Although colorMode, screenLayout, smallestScreenWidthDp are also fixed, generally these // fields should be changed with density and bounds, so here only compares the most // significant field. - if (parentConfig.densityDpi != getConfiguration().densityDpi) { - return true; - } - - final Rect parentAppBounds = parentConfig.windowConfiguration.getAppBounds(); - final int appWidth = appBounds.width(); - final int appHeight = appBounds.height(); - final int parentAppWidth = parentAppBounds.width(); - final int parentAppHeight = parentAppBounds.height(); - if (parentAppWidth == appWidth && parentAppHeight == appHeight) { - // Matched the parent bounds. - return false; - } - if (parentAppWidth > appWidth && parentAppHeight > appHeight) { - // Both sides are smaller than the parent. - return true; - } - if (parentAppWidth < appWidth || parentAppHeight < appHeight) { - // One side is larger than the parent. - return true; - } - - // The rest of the condition is that only one side is smaller than the parent, but it still - // needs to exclude the cases where the size is limited by the fixed aspect ratio. - if (info.maxAspectRatio > 0) { - final float aspectRatio = (0.5f + Math.max(appWidth, appHeight)) - / Math.min(appWidth, appHeight); - if (aspectRatio >= info.maxAspectRatio) { - // The current size has reached the max aspect ratio. - return false; - } - } - if (info.minAspectRatio > 0) { - // The activity should have at least the min aspect ratio, so this checks if the parent - // still has available space to provide larger aspect ratio. - final float parentAspectRatio = (0.5f + Math.max(parentAppWidth, parentAppHeight)) - / Math.min(parentAppWidth, parentAppHeight); - if (parentAspectRatio <= info.minAspectRatio) { - // The long side has reached the parent. - return false; - } - } - return true; + return parentConfig.densityDpi != getConfiguration().densityDpi; } /** * Indicates the activity will keep the bounds and screen configuration when it was first * launched, no matter how its parent changes. * + * <p>If {@true}, then {@link CompatDisplayInsets} will be created in {@link + * #resolveOverrideConfiguration} to "freeze" activity bounds and insets. + * * @return {@code true} if this activity is declared as non-resizable and fixed orientation or * aspect ratio. */ - boolean shouldUseSizeCompatMode() { + boolean shouldCreateCompatDisplayInsets() { if (info.supportsSizeChanges() != ActivityInfo.SIZE_CHANGES_UNSUPPORTED) { return false; } if (inMultiWindowMode() || getWindowConfiguration().hasWindowDecorCaption()) { final ActivityRecord root = task != null ? task.getRootActivity() : null; - if (root != null && root != this && !root.shouldUseSizeCompatMode()) { + if (root != null && root != this && !root.shouldCreateCompatDisplayInsets()) { // If the root activity doesn't use size compatibility mode, the activities above // are forced to be the same for consistent visual appearance. return false; @@ -6883,25 +6855,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } // TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer. - private void updateSizeCompatMode() { - if (mCompatDisplayInsets != null || !shouldUseSizeCompatMode()) { + private void updateCompatDisplayInsets(@Nullable Rect fixedOrientationBounds) { + if (mCompatDisplayInsets != null || !shouldCreateCompatDisplayInsets()) { // The override configuration is set only once in size compatibility mode. return; } - final Configuration parentConfig = getParent().getConfiguration(); - if (!hasProcess() && !isConfigurationCompatible(parentConfig)) { - // Don't compute when launching in fullscreen and the fixed orientation is not the - // current orientation. It is more accurately to compute the override bounds from - // the updated configuration after the fixed orientation is applied. - return; - } - - if (task == null || (!handlesOrientationChangeFromDescendant() - && task.getLastTaskBoundsComputeActivity() != this)) { - // Don't compute when Task hasn't computed its bounds for this app, because the Task can - // be letterboxed, and its bounds may not be accurate until then. - return; - } Configuration overrideConfig = getRequestedOverrideConfiguration(); final Configuration fullConfig = getConfiguration(); @@ -6924,17 +6882,18 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } // The role of CompatDisplayInsets is like the override bounds. - mCompatDisplayInsets = new CompatDisplayInsets(mDisplayContent, this); + mCompatDisplayInsets = + new CompatDisplayInsets(mDisplayContent, this, fixedOrientationBounds); } @VisibleForTesting void clearSizeCompatMode() { + mInSizeCompatModeForBounds = false; mSizeCompatScale = 1f; mSizeCompatBounds = null; mCompatDisplayInsets = null; - // Recompute from Task because letterbox can also happen on Task level. - task.onRequestedOverrideConfigurationChanged(task.getRequestedOverrideConfiguration()); + onRequestedOverrideConfigurationChanged(getRequestedOverrideConfiguration()); } @Override @@ -6969,23 +6928,41 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mTmpConfig.updateFrom(resolvedConfig); newParentConfiguration = mTmpConfig; } + + final int windowingMode = getWindowingMode(); + // TODO(b/181207944): Consider removing the if condition and always run + // resolveFixedOrientationConfiguration() since this should be applied for all cases. + if (isSplitScreenWindowingMode(windowingMode) + || windowingMode == WINDOWING_MODE_MULTI_WINDOW + || windowingMode == WINDOWING_MODE_FULLSCREEN) { + resolveFixedOrientationConfiguration(newParentConfiguration); + } + final Rect fixedOrientationBounds = isLetterboxedForFixedOrientationAndAspectRatio() + ? new Rect(resolvedConfig.windowConfiguration.getBounds()) : null; + if (mCompatDisplayInsets != null) { resolveSizeCompatModeConfiguration(newParentConfiguration); - } else { - if (inMultiWindowMode()) { - // We ignore activities' requested orientation in multi-window modes. Task level may - // take them into consideration when calculating bounds. - resolvedConfig.orientation = Configuration.ORIENTATION_UNDEFINED; - // If the activity has requested override bounds, the configuration needs to be - // computed accordingly. - if (!matchParentBounds()) { - task.computeConfigResourceOverrides(resolvedConfig, newParentConfiguration); - } - } else { - resolveFullscreenConfiguration(newParentConfiguration); + } else if (inMultiWindowMode()) { + // We ignore activities' requested orientation in multi-window modes. They may be + // taken into consideration in resolveFixedOrientationConfiguration call above. + resolvedConfig.orientation = Configuration.ORIENTATION_UNDEFINED; + // If the activity has requested override bounds, the configuration needs to be + // computed accordingly. + if (!matchParentBounds()) { + task.computeConfigResourceOverrides(resolvedConfig, newParentConfiguration); } + // If activity in fullscreen mode is letterboxed because of fixed orientation then bounds + // are already calculated in resolveFixedOrientationConfiguration. + } else if (!isLetterboxedForFixedOrientationAndAspectRatio()) { + resolveFullscreenConfiguration(newParentConfiguration); } + if (mVisibleRequested) { + updateCompatDisplayInsets(fixedOrientationBounds); + } + + // TODO(b/175212232): Consolidate position logic from each "resolve" method above here. + // Assign configuration sequence number into hierarchy because there is a different way than // ensureActivityConfiguration() in this class that uses configuration in WindowState during // layout traversals. @@ -6994,6 +6971,109 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } /** + * Whether this activity is letterboxed for fixed orientation. If letterboxed due to fixed + * orientation then aspect ratio restrictions are also already respected. + * + * <p>This happens when an activity has fixed orientation which doesn't match orientation of the + * parent because a display setting 'ignoreOrientationRequest' is set to true. See {@link + * WindowManagerService#getIgnoreOrientationRequest} for more context. + */ + boolean isLetterboxedForFixedOrientationAndAspectRatio() { + return mIsLetterboxedForFixedOrientationAndAspectRatio; + } + + /** + * Computes bounds (letterbox or pillarbox) when the parent doesn't handle the orientation + * change and the requested orientation is different from the parent. + * + * <p>If letterboxed due to fixed orientation then aspect ratio restrictions are also applied + * in this methiod. + */ + private void resolveFixedOrientationConfiguration(@NonNull Configuration newParentConfig) { + mIsLetterboxedForFixedOrientationAndAspectRatio = false; + if (handlesOrientationChangeFromDescendant()) { + // No need to letterbox because of fixed orientation. Display will handle + // fixed-orientation requests. + return; + } + + final Rect resolvedBounds = + getResolvedOverrideConfiguration().windowConfiguration.getBounds(); + final int parentOrientation = newParentConfig.orientation; + + // If the activity requires a different orientation (either by override or activityInfo), + // make it fit the available bounds by scaling down its bounds. + final int forcedOrientation = getRequestedConfigurationOrientation(); + if (forcedOrientation == ORIENTATION_UNDEFINED || forcedOrientation == parentOrientation) { + return; + } + + if (mCompatDisplayInsets != null && !mCompatDisplayInsets.mIsInFixedOrientationLetterbox) { + // App prefers to keep its original size. + // If the size compat is from previous fixed orientation letterboxing, we may want to + // have fixed orientation letterbox again, otherwise it will show the size compat + // restart button even if the restart bounds will be the same. + return; + } + + final Rect parentBounds = newParentConfig.windowConfiguration.getBounds(); + final int parentWidth = parentBounds.width(); + final int parentHeight = parentBounds.height(); + float aspect = Math.max(parentWidth, parentHeight) + / (float) Math.min(parentWidth, parentHeight); + + // Adjust the fixed orientation letterbox bounds to fit the app request aspect ratio in + // order to use the extra available space. + final float maxAspectRatio = info.maxAspectRatio; + final float minAspectRatio = info.minAspectRatio; + if (aspect > maxAspectRatio && maxAspectRatio != 0) { + aspect = maxAspectRatio; + } else if (aspect < minAspectRatio) { + aspect = minAspectRatio; + } + + // Override from config_letterboxAspectRatio or via ADB with set-letterbox-aspect-ratio. + // TODO(b/175212232): Rename getTaskLetterboxAspectRatio and all related methods since fixed + // orientation letterbox is on the activity level now. + final float letterboxAspectRatioOverride = mWmService.getTaskLetterboxAspectRatio(); + // Activity min/max aspect ratio restrictions will be respected by the activity-level + // letterboxing (size-compat mode). Therefore this override can control the maximum screen + // area that can be occupied by the app in the letterbox mode. + aspect = letterboxAspectRatioOverride > MIN_TASK_LETTERBOX_ASPECT_RATIO + ? letterboxAspectRatioOverride : aspect; + + // Store the current bounds to be able to revert to size compat mode values below if needed. + Rect mTmpFullBounds = new Rect(resolvedBounds); + if (forcedOrientation == ORIENTATION_LANDSCAPE) { + final int height = (int) Math.rint(parentWidth / aspect); + final int top = parentBounds.centerY() - height / 2; + resolvedBounds.set(parentBounds.left, top, parentBounds.right, top + height); + } else { + final int width = (int) Math.rint(parentHeight / aspect); + final int left = parentBounds.centerX() - width / 2; + resolvedBounds.set(left, parentBounds.top, left + width, parentBounds.bottom); + } + + if (mCompatDisplayInsets != null) { + mCompatDisplayInsets.getBoundsByRotation( + mTmpBounds, newParentConfig.windowConfiguration.getRotation()); + if (resolvedBounds.width() != mTmpBounds.width() + || resolvedBounds.height() != mTmpBounds.height()) { + // The app shouldn't be resized, we only do fixed orientation letterboxing if the + // compat bounds are also from the same fixed orientation letterbox. Otherwise, + // clear the fixed orientation bounds to show app in size compat mode. + resolvedBounds.set(mTmpFullBounds); + return; + } + } + + // Calculate app bounds using fixed orientation bounds because they will be needed later + // for comparison with size compat app bounds in {@link resolveSizeCompatModeConfiguration}. + task.computeConfigResourceOverrides(getResolvedOverrideConfiguration(), newParentConfig); + mIsLetterboxedForFixedOrientationAndAspectRatio = true; + } + + /** * Resolves the configuration of activity in fullscreen mode. If the bounds are restricted by * aspect ratio, the position will be centered horizontally in parent's app bounds to balance * the visual appearance. The policy of aspect ratio has higher priority than the requested @@ -7037,6 +7117,18 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A private void resolveSizeCompatModeConfiguration(Configuration newParentConfiguration) { final Configuration resolvedConfig = getResolvedOverrideConfiguration(); final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds(); + + // When an activity needs to be letterboxed because of fixed orientation, use fixed + // orientation bounds (stored in resolved bounds) instead of parent bounds since the + // activity will be displayed within them even if it is in size compat mode. They should be + // saved here before resolved bounds are overridden below. + final Rect containerBounds = isLetterboxedForFixedOrientationAndAspectRatio() + ? new Rect(resolvedBounds) + : newParentConfiguration.windowConfiguration.getBounds(); + final Rect containerAppBounds = isLetterboxedForFixedOrientationAndAspectRatio() + ? new Rect(getResolvedOverrideConfiguration().windowConfiguration.getAppBounds()) + : newParentConfiguration.windowConfiguration.getAppBounds(); + final int requestedOrientation = getRequestedConfigurationOrientation(); final boolean orientationRequested = requestedOrientation != ORIENTATION_UNDEFINED; final int orientation = orientationRequested @@ -7095,7 +7187,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Below figure is an example that puts an activity which was launched in a larger container // into a smaller container. // The outermost rectangle is the real display bounds. - // "@" is the parent app bounds. + // "@" is the container app bounds (parent bounds or fixed orientation bouds) // "#" is the {@code resolvedBounds} that applies to application. // "*" is the {@code mSizeCompatBounds} that used to show on screen if scaled. // ------------------------------ @@ -7111,19 +7203,18 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // The application is still layouted in "#" since it was launched, and it will be visually // scaled and positioned to "*". + final Rect resolvedAppBounds = resolvedConfig.windowConfiguration.getAppBounds(); + // Calculates the scale and offset to horizontal center the size compatibility bounds into // the region which is available to application. - final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds(); - final Rect parentAppBounds = newParentConfiguration.windowConfiguration.getAppBounds(); - final Rect resolvedAppBounds = resolvedConfig.windowConfiguration.getAppBounds(); final int contentW = resolvedAppBounds.width(); final int contentH = resolvedAppBounds.height(); - final int viewportW = parentAppBounds.width(); - final int viewportH = parentAppBounds.height(); + final int viewportW = containerAppBounds.width(); + final int viewportH = containerAppBounds.height(); // Only allow to scale down. mSizeCompatScale = (contentW <= viewportW && contentH <= viewportH) ? 1f : Math.min((float) viewportW / contentW, (float) viewportH / contentH); - final int screenTopInset = parentAppBounds.top - parentBounds.top; + final int screenTopInset = containerAppBounds.top - containerBounds.top; final boolean topNotAligned = screenTopInset != resolvedAppBounds.top - resolvedBounds.top; if (mSizeCompatScale != 1f || topNotAligned) { if (mSizeCompatBounds == null) { @@ -7143,8 +7234,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final int offsetX = getHorizontalCenterOffset( (int) viewportW, (int) (contentW * mSizeCompatScale)); // Above coordinates are in "@" space, now place "*" and "#" to screen space. - final int screenPosX = (fillContainer ? parentBounds.left : parentAppBounds.left) + offsetX; - final int screenPosY = parentBounds.top; + final int screenPosX = (fillContainer + ? containerBounds.left : containerAppBounds.left) + offsetX; + final int screenPosY = containerBounds.top; if (screenPosX != 0 || screenPosY != 0) { if (mSizeCompatBounds != null) { mSizeCompatBounds.offset(screenPosX, screenPosY); @@ -7154,6 +7246,52 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final int dy = screenPosY - resolvedBounds.top; offsetBounds(resolvedConfig, dx, dy); } + + mInSizeCompatModeForBounds = + isInSizeCompatModeForBounds(resolvedAppBounds, containerAppBounds); + } + + private boolean isInSizeCompatModeForBounds(final Rect appBounds, final Rect containerBounds) { + final int appWidth = appBounds.width(); + final int appHeight = appBounds.height(); + final int containerAppWidth = containerBounds.width(); + final int containerAppHeight = containerBounds.height(); + + if (containerAppWidth == appWidth && containerAppHeight == appHeight) { + // Matched the container bounds. + return false; + } + if (containerAppWidth > appWidth && containerAppHeight > appHeight) { + // Both sides are smaller than the container. + return true; + } + if (containerAppWidth < appWidth || containerAppHeight < appHeight) { + // One side is larger than the container. + return true; + } + + // The rest of the condition is that only one side is smaller than the container, but it + // still needs to exclude the cases where the size is limited by the fixed aspect ratio. + if (info.maxAspectRatio > 0) { + final float aspectRatio = (0.5f + Math.max(appWidth, appHeight)) + / Math.min(appWidth, appHeight); + if (aspectRatio >= info.maxAspectRatio) { + // The current size has reached the max aspect ratio. + return false; + } + } + if (info.minAspectRatio > 0) { + // The activity should have at least the min aspect ratio, so this checks if the + // container still has available space to provide larger aspect ratio. + final float containerAspectRatio = + (0.5f + Math.max(containerAppWidth, containerAppHeight)) + / Math.min(containerAppWidth, containerAppHeight); + if (containerAspectRatio <= info.minAspectRatio) { + // The long side has reached the parent. + return false; + } + } + return true; } /** @return The horizontal offset of putting the content in the center of viewport. */ @@ -7305,7 +7443,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final Task rootTask = getRootTask(); final float minAspectRatio = info.minAspectRatio; - if (task == null || rootTask == null || (inMultiWindowMode() && !shouldUseSizeCompatMode()) + if (task == null || rootTask == null + || (inMultiWindowMode() && !shouldCreateCompatDisplayInsets()) || (maxAspectRatio == 0 && minAspectRatio == 0) || isInVrUiMode(getConfiguration())) { // We don't enforce aspect ratio if the activity task is in multiwindow unless it @@ -7443,8 +7582,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (displayChanged) { mLastReportedDisplayId = newDisplayId; } - // TODO(b/36505427): Is there a better place to do this? - updateSizeCompatMode(); // Short circuit: if the two full configurations are equal (the common case), then there is // nothing to do. We test the full configuration instead of the global and merged override @@ -7723,11 +7860,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Reset the existing override configuration so it can be updated according to the latest // configuration. clearSizeCompatMode(); - if (mVisibleRequested) { - // Configuration will be ensured when becoming visible, so if it is already visible, - // then the manual update is needed. - updateSizeCompatMode(); - } if (!attachedToProcess()) { return; @@ -8134,8 +8266,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A private final int mHeight; /** Whether the {@link Task} windowingMode represents a floating window*/ final boolean mIsFloating; - /** Whether the {@link Task} is letterboxed when the unresizable activity is first shown. */ - final boolean mIsTaskLetterboxed; + /** + * Whether is letterboxed because of fixed orientation when the unresizable activity is + * first shown. + */ + final boolean mIsInFixedOrientationLetterbox; /** * The nonDecorInsets for each rotation. Includes the navigation bar and cutout insets. It * is used to compute the appBounds. @@ -8149,7 +8284,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final Rect[] mStableInsets = new Rect[4]; /** Constructs the environment to simulate the bounds behavior of the given container. */ - CompatDisplayInsets(DisplayContent display, ActivityRecord container) { + CompatDisplayInsets(DisplayContent display, ActivityRecord container, + @Nullable Rect fixedOrientationBounds) { mIsFloating = container.getWindowConfiguration().tasksAreFloating(); if (mIsFloating) { final Rect containerBounds = container.getWindowConfiguration().getBounds(); @@ -8162,24 +8298,34 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mNonDecorInsets[rotation] = emptyRect; mStableInsets[rotation] = emptyRect; } - mIsTaskLetterboxed = false; + mIsInFixedOrientationLetterbox = false; return; } final Task task = container.getTask(); - mIsTaskLetterboxed = task != null && task.isTaskLetterboxed(); + + mIsInFixedOrientationLetterbox = fixedOrientationBounds != null; // Store the bounds of the Task for the non-resizable activity to use in size compat // mode so that the activity will not be resized regardless the windowing mode it is // currently in. - final WindowContainer filledContainer = task != null ? task : display; - final Point dimensions = getRotationZeroDimensions(filledContainer); + // When an activity needs to be letterboxed because of fixed orientation, use fixed + // orientation bounds instead of task bounds since the activity will be displayed + // within these even if it is in size compat mode. + final Rect filledContainerBounds = mIsInFixedOrientationLetterbox + ? fixedOrientationBounds + : task != null ? task.getBounds() : display.getBounds(); + final int filledContainerRotation = task != null + ? task.getConfiguration().windowConfiguration.getRotation() + : display.getConfiguration().windowConfiguration.getRotation(); + final Point dimensions = getRotationZeroDimensions( + filledContainerBounds, filledContainerRotation); mWidth = dimensions.x; mHeight = dimensions.y; // Bounds of the filled container if it doesn't fill the display. final Rect unfilledContainerBounds = - filledContainer.getBounds().equals(display.getBounds()) ? null : new Rect(); + filledContainerBounds.equals(display.getBounds()) ? null : new Rect(); final DisplayPolicy policy = display.getDisplayPolicy(); for (int rotation = 0; rotation < 4; rotation++) { mNonDecorInsets[rotation] = new Rect(); @@ -8199,9 +8345,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // The insets is based on the display, but the container may be smaller than the // display, so update the insets to exclude parts that are not intersected with the // container. - unfilledContainerBounds.set(filledContainer.getBounds()); + unfilledContainerBounds.set(filledContainerBounds); display.rotateBounds( - filledContainer.getConfiguration().windowConfiguration.getRotation(), + filledContainerRotation, rotation, unfilledContainerBounds); updateInsetsForBounds(unfilledContainerBounds, dw, dh, mNonDecorInsets[rotation]); @@ -8214,9 +8360,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * the display is rotated, we can calculate the bounds by rotating the dimensions. * @see #getBoundsByRotation */ - private static Point getRotationZeroDimensions(WindowContainer container) { - final Rect bounds = container.getBounds(); - final int rotation = container.getConfiguration().windowConfiguration.getRotation(); + private static Point getRotationZeroDimensions(final Rect bounds, int rotation) { final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270); final int width = bounds.width(); final int height = bounds.height(); diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 60ca725b118e..6ce9048d79e2 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -1708,7 +1708,7 @@ class ActivityStarter { // If the activity being launched is the same as the one currently at the top, then // we need to check if it should only be launched once. - final Task topRootTask = mRootWindowContainer.getTopDisplayFocusedRootTask(); + final Task topRootTask = mPreferredTaskDisplayArea.getFocusedRootTask(); if (topRootTask != null) { startResult = deliverToCurrentTopIfNeeded(topRootTask, intentGrants); if (startResult != START_SUCCESS) { diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index b803fc37a421..af032c108538 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -2095,10 +2095,14 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } - /** @return the max ANR delay from all registered {@link AnrController} instances */ - public long getMaxAnrDelayMillis(ApplicationInfo info) { + /** + * @return the controller with the max ANR delay from all registered + * {@link AnrController} instances + */ + @Nullable + public AnrController getAnrController(ApplicationInfo info) { if (info == null || info.packageName == null) { - return 0; + return null; } final ArrayList<AnrController> controllers; @@ -2107,12 +2111,19 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } final String packageName = info.packageName; + final int uid = info.uid; long maxDelayMs = 0; + AnrController controllerWithMaxDelay = null; + for (AnrController controller : controllers) { - maxDelayMs = Math.max(maxDelayMs, controller.getAnrDelayMillis(packageName, info.uid)); + long delayMs = controller.getAnrDelayMillis(packageName, uid); + if (delayMs > 0 && delayMs > maxDelayMs) { + controllerWithMaxDelay = controller; + maxDelayMs = delayMs; + } } - maxDelayMs = Math.max(maxDelayMs, 0); - return maxDelayMs; + + return controllerWithMaxDelay; } @Override diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java index f505daa9b628..a7312b321e4d 100644 --- a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java +++ b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java @@ -67,39 +67,40 @@ import java.util.function.BiFunction; * .setImeContainer(imeContainer) * .setTaskDisplayAreas(rootTdaList); * - * // Build root hierarchy of front and rear DisplayAreaGroup. - * RootDisplayArea frontRoot = new RootDisplayArea(wmService, "FrontRoot", FEATURE_FRONT_ROOT); - * DisplayAreaPolicyBuilder.HierarchyBuilder frontGroupHierarchy = - * new DisplayAreaPolicyBuilder.HierarchyBuilder(frontRoot) + * // Build root hierarchy of first and second DisplayAreaGroup. + * RootDisplayArea firstRoot = new RootDisplayArea(wmService, "FirstRoot", FEATURE_FIRST_ROOT); + * DisplayAreaPolicyBuilder.HierarchyBuilder firstGroupHierarchy = + * new DisplayAreaPolicyBuilder.HierarchyBuilder(firstRoot) * // (Optional) .addFeature(...) - * .setTaskDisplayAreas(frontTdaList); + * .setTaskDisplayAreas(firstTdaList); * - * RootDisplayArea rearRoot = new RootDisplayArea(wmService, "RearRoot", FEATURE_REAR_ROOT); - * DisplayAreaPolicyBuilder.HierarchyBuilder rearGroupHierarchy = - * new DisplayAreaPolicyBuilder.HierarchyBuilder(rearRoot) + * RootDisplayArea secondRoot = new RootDisplayArea(wmService, "SecondRoot", + * FEATURE_REAR_ROOT); + * DisplayAreaPolicyBuilder.HierarchyBuilder secondGroupHierarchy = + * new DisplayAreaPolicyBuilder.HierarchyBuilder(secondRoot) * // (Optional) .addFeature(...) - * .setTaskDisplayAreas(rearTdaList); + * .setTaskDisplayAreas(secondTdaList); * * // Define the function to select root for window to attach. * BiFunction<WindowToken, Bundle, RootDisplayArea> selectRootForWindowFunc = - * (windowToken, bundle) -> { - * if (bundle == null) { + * (windowToken, options) -> { + * if (options == null) { * return root; * } * // OEMs need to define the condition. * if (...) { - * return frontRoot; + * return firstRoot; * } * if (...) { - * return rearRoot; + * return secondRoot; * } * return root; * }; * * return new DisplayAreaPolicyBuilder() * .setRootHierarchy(rootHierarchy) - * .addDisplayAreaGroupHierarchy(frontGroupHierarchy) - * .addDisplayAreaGroupHierarchy(rearGroupHierarchy) + * .addDisplayAreaGroupHierarchy(firstGroupHierarchy) + * .addDisplayAreaGroupHierarchy(secondGroupHierarchy) * .setSelectRootForWindowFunc(selectRootForWindowFunc) * .build(wmService, content); * </pre> @@ -110,12 +111,12 @@ import java.util.function.BiFunction; * - WindowedMagnification * - DisplayArea.Tokens (Wallpapers can be attached here) * - TaskDisplayArea - * - RootDisplayArea (FrontRoot) + * - RootDisplayArea (FirstRoot) * - DisplayArea.Tokens (Wallpapers can be attached here) * - TaskDisplayArea * - DisplayArea.Tokens (windows above Tasks up to IME can be attached here) * - DisplayArea.Tokens (windows above IME can be attached here) - * - RootDisplayArea (RearRoot) + * - RootDisplayArea (SecondRoot) * - DisplayArea.Tokens (Wallpapers can be attached here) * - TaskDisplayArea * - DisplayArea.Tokens (windows above Tasks up to IME can be attached here) @@ -133,14 +134,23 @@ import java.util.function.BiFunction; * {@link RootDisplayArea}. */ class DisplayAreaPolicyBuilder { + + /** + * Key to specify the {@link RootDisplayArea} to attach the window to. Should be used by the + * function passed in from {@link #setSelectRootForWindowFunc(BiFunction)} + */ + static final String KEY_ROOT_DISPLAY_AREA_ID = "root_display_area_id"; + @Nullable private HierarchyBuilder mRootHierarchyBuilder; - private ArrayList<HierarchyBuilder> mDisplayAreaGroupHierarchyBuilders = new ArrayList<>(); + private final ArrayList<HierarchyBuilder> mDisplayAreaGroupHierarchyBuilders = + new ArrayList<>(); /** * When a window is created, the policy will use this function, which takes window type and * options, to select the {@link RootDisplayArea} to place that window in. The selected root * can be either the one of the {@link #mRootHierarchyBuilder} or the one of any of the * {@link #mDisplayAreaGroupHierarchyBuilders}. + * @see DefaultSelectRootForWindowFunction as an example. **/ @Nullable private BiFunction<Integer, Bundle, RootDisplayArea> mSelectRootForWindowFunc; @@ -160,7 +170,10 @@ class DisplayAreaPolicyBuilder { return this; } - /** The policy will use this function to find the root to place windows in. */ + /** + * The policy will use this function to find the root to place windows in. + * @see DefaultSelectRootForWindowFunction as an example. + */ DisplayAreaPolicyBuilder setSelectRootForWindowFunc( BiFunction<Integer, Bundle, RootDisplayArea> selectRootForWindowFunc) { mSelectRootForWindowFunc = selectRootForWindowFunc; @@ -169,7 +182,7 @@ class DisplayAreaPolicyBuilder { /** * Makes sure the setting meets the requirement: - * 1. {@link mRootHierarchyBuilder} must be set. + * 1. {@link #mRootHierarchyBuilder} must be set. * 2. {@link RootDisplayArea} and {@link TaskDisplayArea} must have unique ids. * 3. {@link Feature} below the same {@link RootDisplayArea} must have unique ids. * 4. There must be exactly one {@link HierarchyBuilder} that contains the IME container. @@ -309,11 +322,57 @@ class DisplayAreaPolicyBuilder { hierarchyBuilder.build(); displayAreaGroupRoots.add(hierarchyBuilder.mRoot); } + // Use the default function if it is not specified otherwise. + if (mSelectRootForWindowFunc == null) { + mSelectRootForWindowFunc = new DefaultSelectRootForWindowFunction( + mRootHierarchyBuilder.mRoot, displayAreaGroupRoots); + } return new Result(wmService, mRootHierarchyBuilder.mRoot, displayAreaGroupRoots, mSelectRootForWindowFunc); } /** + * The default function to be used for finding {@link RootDisplayArea} for window to be attached + * to if there is no other function set through {@link #setSelectRootForWindowFunc(BiFunction)}. + * + * When a window is created with {@link Bundle} options, this function will select the + * {@link RootDisplayArea} based on the options. Returns {@link #mDisplayRoot} if there is no + * match found. + */ + private static class DefaultSelectRootForWindowFunction implements + BiFunction<Integer, Bundle, RootDisplayArea> { + final RootDisplayArea mDisplayRoot; + final List<RootDisplayArea> mDisplayAreaGroupRoots; + + DefaultSelectRootForWindowFunction(RootDisplayArea displayRoot, + List<RootDisplayArea> displayAreaGroupRoots) { + mDisplayRoot = displayRoot; + mDisplayAreaGroupRoots = Collections.unmodifiableList(displayAreaGroupRoots); + } + + @Override + public RootDisplayArea apply(Integer windowType, Bundle options) { + if (mDisplayAreaGroupRoots.isEmpty()) { + return mDisplayRoot; + } + + // Select the RootDisplayArea set in options. + if (options != null && options.containsKey(KEY_ROOT_DISPLAY_AREA_ID)) { + final int rootId = options.getInt(KEY_ROOT_DISPLAY_AREA_ID); + if (mDisplayRoot.mFeatureId == rootId) { + return mDisplayRoot; + } + for (int i = mDisplayAreaGroupRoots.size() - 1; i >= 0; i--) { + if (mDisplayAreaGroupRoots.get(i).mFeatureId == rootId) { + return mDisplayAreaGroupRoots.get(i); + } + } + } + return mDisplayRoot; + } + } + + /** * Builder to define {@link Feature} and {@link DisplayArea} hierarchy under a * {@link RootDisplayArea} */ @@ -672,15 +731,10 @@ class DisplayAreaPolicyBuilder { Result(WindowManagerService wmService, RootDisplayArea root, List<RootDisplayArea> displayAreaGroupRoots, - @Nullable BiFunction<Integer, Bundle, RootDisplayArea> - selectRootForWindowFunc) { + BiFunction<Integer, Bundle, RootDisplayArea> selectRootForWindowFunc) { super(wmService, root); mDisplayAreaGroupRoots = Collections.unmodifiableList(displayAreaGroupRoots); - mSelectRootForWindowFunc = selectRootForWindowFunc == null - // Always return the highest level root of the logical display when the func is - // not specified. - ? (type, options) -> mRoot - : selectRootForWindowFunc; + mSelectRootForWindowFunc = selectRootForWindowFunc; // Cache the default TaskDisplayArea for quick access. mDefaultTaskDisplayArea = mRoot.getItemFromTaskDisplayAreas(taskDisplayArea -> diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 2ac1d92be711..eff4ea6536bd 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -457,8 +457,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp /** Remove this display when animation on it has completed. */ private boolean mDeferredRemoval; - final DockedStackDividerController mDividerControllerLocked; - final PinnedStackController mPinnedStackControllerLocked; + final DockedTaskDividerController mDividerControllerLocked; + final PinnedTaskController mPinnedTaskControllerLocked; final ArrayList<WindowState> mTapExcludedWindows = new ArrayList<>(); /** A collection of windows that provide tap exclude regions inside of them. */ @@ -1008,8 +1008,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mDisplayPolicy.systemReady(); } mWindowCornerRadius = mDisplayPolicy.getWindowCornerRadius(); - mDividerControllerLocked = new DockedStackDividerController(this); - mPinnedStackControllerLocked = new PinnedStackController(mWmService, this); + mDividerControllerLocked = new DockedTaskDividerController(this); + mPinnedTaskControllerLocked = new PinnedTaskController(mWmService, this); final SurfaceControl.Builder b = mWmService.makeSurfaceBuilder(mSession) .setOpaque(true) @@ -1563,7 +1563,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // has it own policy for bounds, the activity bounds based on parent is unknown. return false; } - if (mPinnedStackControllerLocked.isPipActiveOrWindowingModeChanging()) { + if (mPinnedTaskControllerLocked.isPipActiveOrWindowingModeChanging()) { // Use normal rotation animation because seamless PiP rotation is not supported yet. return false; } @@ -2292,12 +2292,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } } - DockedStackDividerController getDockedDividerController() { + DockedTaskDividerController getDockedDividerController() { return mDividerControllerLocked; } - PinnedStackController getPinnedStackController() { - return mPinnedStackControllerLocked; + PinnedTaskController getPinnedTaskController() { + return mPinnedTaskControllerLocked; } /** @@ -2378,10 +2378,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp * for bounds calculations. */ void preOnConfigurationChanged() { - final PinnedStackController pinnedStackController = getPinnedStackController(); + final PinnedTaskController pinnedTaskController = getPinnedTaskController(); - if (pinnedStackController != null) { - getPinnedStackController().onConfigurationChanged(); + if (pinnedTaskController != null) { + getPinnedTaskController().onConfigurationChanged(); } } @@ -2925,7 +2925,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp final boolean imeVisible = imeWin != null && imeWin.isVisible() && imeWin.isDisplayed(); final int imeHeight = getInputMethodWindowVisibleHeight(); - mPinnedStackControllerLocked.setAdjustedForIme(imeVisible, imeHeight); + mPinnedTaskControllerLocked.setAdjustedForIme(imeVisible, imeHeight); } int getInputMethodWindowVisibleHeight() { @@ -3150,7 +3150,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } pw.println(); - mPinnedStackControllerLocked.dump(prefix, pw); + mPinnedTaskControllerLocked.dump(prefix, pw); pw.println(); mDisplayFrames.dump(prefix, pw); diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedTaskDividerController.java index de4bdaa57efa..fb9d06441536 100644 --- a/services/core/java/com/android/server/wm/DockedStackDividerController.java +++ b/services/core/java/com/android/server/wm/DockedTaskDividerController.java @@ -19,16 +19,16 @@ package com.android.server.wm; import android.graphics.Rect; /** - * Keeps information about the docked stack divider. + * Keeps information about the docked task divider. */ -public class DockedStackDividerController { +public class DockedTaskDividerController { private final DisplayContent mDisplayContent; private boolean mResizing; private final Rect mTouchRegion = new Rect(); - DockedStackDividerController(DisplayContent displayContent) { + DockedTaskDividerController(DisplayContent displayContent) { mDisplayContent = displayContent; } @@ -58,6 +58,6 @@ public class DockedStackDividerController { private void resetDragResizingChangeReported() { mDisplayContent.forAllWindows(WindowState::resetDragResizingChangeReported, - true /* traverseTopToBottom */ ); + true /* traverseTopToBottom */); } } diff --git a/services/core/java/com/android/server/wm/FactoryErrorDialog.java b/services/core/java/com/android/server/wm/FactoryErrorDialog.java index 88b5475cc3a2..afdf1ee4de7f 100644 --- a/services/core/java/com/android/server/wm/FactoryErrorDialog.java +++ b/services/core/java/com/android/server/wm/FactoryErrorDialog.java @@ -41,6 +41,11 @@ final class FactoryErrorDialog extends BaseErrorDialog { public void onStop() { } + @Override + protected void closeDialog() { + /* Do nothing */ + } + private final Handler mHandler = new Handler() { public void handleMessage(Message msg) { throw new RuntimeException("Rebooting from failed factory test"); diff --git a/services/core/java/com/android/server/wm/PinnedStackController.java b/services/core/java/com/android/server/wm/PinnedTaskController.java index 8fe2481f9eda..15e078b478b8 100644 --- a/services/core/java/com/android/server/wm/PinnedStackController.java +++ b/services/core/java/com/android/server/wm/PinnedTaskController.java @@ -29,18 +29,18 @@ import android.util.DisplayMetrics; import android.util.Log; import android.util.Slog; import android.view.DisplayInfo; -import android.view.IPinnedStackListener; +import android.view.IPinnedTaskListener; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; /** - * Holds the common state of the pinned stack between the system and SystemUI. If SystemUI ever + * Holds the common state of the pinned task between the system and SystemUI. If SystemUI ever * needs to be restarted, it will be notified with the last known state. * - * Changes to the pinned stack also flow through this controller, and generally, the system only - * changes the pinned stack bounds through this controller in two ways: + * Changes to the pinned task also flow through this controller, and generally, the system only + * changes the pinned task bounds through this controller in two ways: * * 1) When first entering PiP: the controller returns the valid bounds given, taking aspect ratio * and IME state into account. @@ -49,18 +49,18 @@ import java.util.List; * SystemUI adjustments (ie. expanded for menu, interaction, etc). * * Other changes in the system, including adjustment of IME, configuration change, and more are - * handled by SystemUI (similar to the docked stack divider). + * handled by SystemUI (similar to the docked task divider). */ -class PinnedStackController { +class PinnedTaskController { - private static final String TAG = TAG_WITH_CLASS_NAME ? "PinnedStackController" : TAG_WM; + private static final String TAG = TAG_WITH_CLASS_NAME ? "PinnedTaskController" : TAG_WM; private final WindowManagerService mService; private final DisplayContent mDisplayContent; - private IPinnedStackListener mPinnedStackListener; - private final PinnedStackListenerDeathHandler mPinnedStackListenerDeathHandler = - new PinnedStackListenerDeathHandler(); + private IPinnedTaskListener mPinnedTaskListener; + private final PinnedTaskListenerDeathHandler mPinnedTaskListenerDeathHandler = + new PinnedTaskListenerDeathHandler(); /** Whether the PiP is entering or leaving. */ private boolean mIsPipWindowingModeChanging; @@ -72,7 +72,7 @@ class PinnedStackController { private ArrayList<RemoteAction> mActions = new ArrayList<>(); private float mAspectRatio = -1f; - // Used to calculate stack bounds across rotations + // Used to calculate task bounds across rotations private final DisplayInfo mDisplayInfo = new DisplayInfo(); // The aspect ratio bounds of the PIP. @@ -85,19 +85,19 @@ class PinnedStackController { /** * Handler for the case where the listener dies. */ - private class PinnedStackListenerDeathHandler implements IBinder.DeathRecipient { + private class PinnedTaskListenerDeathHandler implements IBinder.DeathRecipient { @Override public void binderDied() { // Clean up the state if the listener dies - if (mPinnedStackListener != null) { - mPinnedStackListener.asBinder().unlinkToDeath(mPinnedStackListenerDeathHandler, 0); + if (mPinnedTaskListener != null) { + mPinnedTaskListener.asBinder().unlinkToDeath(mPinnedTaskListenerDeathHandler, 0); } - mPinnedStackListener = null; + mPinnedTaskListener = null; } } - PinnedStackController(WindowManagerService service, DisplayContent displayContent) { + PinnedTaskController(WindowManagerService service, DisplayContent displayContent) { mService = service; mDisplayContent = displayContent; mDisplayInfo.copyFrom(mDisplayContent.getDisplayInfo()); @@ -121,17 +121,17 @@ class PinnedStackController { } /** - * Registers a pinned stack listener. + * Registers a pinned task listener. */ - void registerPinnedStackListener(IPinnedStackListener listener) { + void registerPinnedTaskListener(IPinnedTaskListener listener) { try { - listener.asBinder().linkToDeath(mPinnedStackListenerDeathHandler, 0); - mPinnedStackListener = listener; + listener.asBinder().linkToDeath(mPinnedTaskListenerDeathHandler, 0); + mPinnedTaskListener = listener; notifyImeVisibilityChanged(mIsImeShowing, mImeHeight); notifyMovementBoundsChanged(false /* fromImeAdjustment */); notifyActionsChanged(mActions); } catch (RemoteException e) { - Log.e(TAG, "Failed to register pinned stack listener", e); + Log.e(TAG, "Failed to register pinned task listener", e); } } @@ -139,8 +139,8 @@ class PinnedStackController { * @return whether the given {@param aspectRatio} is valid. */ public boolean isValidPictureInPictureAspectRatio(float aspectRatio) { - return Float.compare(mMinAspectRatio, aspectRatio) <= 0 && - Float.compare(aspectRatio, mMaxAspectRatio) <= 0; + return Float.compare(mMinAspectRatio, aspectRatio) <= 0 + && Float.compare(aspectRatio, mMaxAspectRatio) <= 0; } /** Returns {@code true} if the PiP is on screen or is changing windowing mode. */ @@ -152,7 +152,7 @@ class PinnedStackController { return pinnedTask != null && pinnedTask.hasChild(); } - /** Sets whether a visible stack is changing from or to pinned mode. */ + /** Sets whether a visible task is changing from or to pinned mode. */ void setPipWindowingModeChanging(boolean isPipWindowingModeChanging) { mIsPipWindowingModeChanging = isPipWindowingModeChanging; } @@ -162,9 +162,9 @@ class PinnedStackController { * so that the default bounds will be returned for the next session. */ void onActivityHidden(ComponentName componentName) { - if (mPinnedStackListener == null) return; + if (mPinnedTaskListener == null) return; try { - mPinnedStackListener.onActivityHidden(componentName); + mPinnedTaskListener.onActivityHidden(componentName); } catch (RemoteException e) { Slog.e(TAG_WM, "Error delivering reset reentry fraction event.", e); } @@ -223,9 +223,9 @@ class PinnedStackController { * Notifies listeners that the PIP needs to be adjusted for the IME. */ private void notifyImeVisibilityChanged(boolean imeVisible, int imeHeight) { - if (mPinnedStackListener != null) { + if (mPinnedTaskListener != null) { try { - mPinnedStackListener.onImeVisibilityChanged(imeVisible, imeHeight); + mPinnedTaskListener.onImeVisibilityChanged(imeVisible, imeHeight); } catch (RemoteException e) { Slog.e(TAG_WM, "Error delivering bounds changed event.", e); } @@ -233,9 +233,9 @@ class PinnedStackController { } private void notifyAspectRatioChanged(float aspectRatio) { - if (mPinnedStackListener == null) return; + if (mPinnedTaskListener == null) return; try { - mPinnedStackListener.onAspectRatioChanged(aspectRatio); + mPinnedTaskListener.onAspectRatioChanged(aspectRatio); } catch (RemoteException e) { Slog.e(TAG_WM, "Error delivering aspect ratio changed event.", e); } @@ -245,9 +245,9 @@ class PinnedStackController { * Notifies listeners that the PIP actions have changed. */ private void notifyActionsChanged(List<RemoteAction> actions) { - if (mPinnedStackListener != null) { + if (mPinnedTaskListener != null) { try { - mPinnedStackListener.onActionsChanged(new ParceledListSlice(actions)); + mPinnedTaskListener.onActionsChanged(new ParceledListSlice(actions)); } catch (RemoteException e) { Slog.e(TAG_WM, "Error delivering actions changed event.", e); } @@ -259,11 +259,11 @@ class PinnedStackController { */ private void notifyMovementBoundsChanged(boolean fromImeAdjustment) { synchronized (mService.mGlobalLock) { - if (mPinnedStackListener == null) { + if (mPinnedTaskListener == null) { return; } try { - mPinnedStackListener.onMovementBoundsChanged(fromImeAdjustment); + mPinnedTaskListener.onMovementBoundsChanged(fromImeAdjustment); } catch (RemoteException e) { Slog.e(TAG_WM, "Error delivering actions changed event.", e); } @@ -271,7 +271,7 @@ class PinnedStackController { } void dump(String prefix, PrintWriter pw) { - pw.println(prefix + "PinnedStackController"); + pw.println(prefix + "PinnedTaskController"); pw.println(prefix + " mIsImeShowing=" + mIsImeShowing); pw.println(prefix + " mImeHeight=" + mImeHeight); pw.println(prefix + " mAspectRatio=" + mAspectRatio); diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 7238fd6fa747..7085156daa81 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -35,7 +35,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMAR import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.app.WindowConfiguration.activityTypeToString; -import static android.app.WindowConfiguration.isSplitScreenWindowingMode; import static android.app.WindowConfiguration.windowingModeToString; import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; @@ -148,7 +147,6 @@ import static com.android.server.wm.WindowContainerChildProto.TASK; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ROOT_TASK; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; -import static com.android.server.wm.WindowManagerService.MIN_TASK_LETTERBOX_ASPECT_RATIO; import static com.android.server.wm.WindowManagerService.dipToPixel; import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_BEFORE_ANIM; @@ -597,10 +595,6 @@ class Task extends WindowContainer<WindowContainer> { @Nullable private ActivityRecord mResumedActivity = null; - /** Last activity that is used to compute the Task bounds. */ - @Nullable - private ActivityRecord mLastTaskBoundsComputeActivity; - private boolean mForceShowForAllUsers; /** When set, will force the task to report as invisible. */ @@ -1496,11 +1490,6 @@ class Task extends WindowContainer<WindowContainer> { } void cleanUpActivityReferences(ActivityRecord r) { - // mLastTaskBoundsComputeActivity is set at leaf Task - if (mLastTaskBoundsComputeActivity == r) { - mLastTaskBoundsComputeActivity = null; - } - // mPausingActivity is set at leaf task if (mPausingActivity != null && mPausingActivity == r) { mPausingActivity = null; @@ -2267,7 +2256,7 @@ class Task extends WindowContainer<WindowContainer> { } if (pipChanging) { - mDisplayContent.getPinnedStackController().setPipWindowingModeChanging(true); + mDisplayContent.getPinnedTaskController().setPipWindowingModeChanging(true); // If the top activity is using fixed rotation, it should be changing from PiP to // fullscreen with display orientation change. Do not notify fullscreen task organizer // because the restoration of task surface and the transformation of activity surface @@ -2297,7 +2286,7 @@ class Task extends WindowContainer<WindowContainer> { } } finally { if (pipChanging) { - mDisplayContent.getPinnedStackController().setPipWindowingModeChanging(false); + mDisplayContent.getPinnedTaskController().setPipWindowingModeChanging(false); } } @@ -2863,7 +2852,6 @@ class Task extends WindowContainer<WindowContainer> { private void resolveLeafOnlyOverrideConfigs(Configuration newParentConfig, Rect previousBounds) { - mLastTaskBoundsComputeActivity = getTopNonFinishingActivity(false /* includeOverlays */); int windowingMode = getResolvedOverrideConfiguration().windowConfiguration.getWindowingMode(); @@ -2877,7 +2865,8 @@ class Task extends WindowContainer<WindowContainer> { getResolvedOverrideConfiguration().windowConfiguration.getBounds(); if (windowingMode == WINDOWING_MODE_FULLSCREEN) { - computeFullscreenBounds(outOverrideBounds, newParentConfig); + // Use empty bounds to indicate "fill parent". + outOverrideBounds.setEmpty(); // The bounds for fullscreen mode shouldn't be adjusted by minimal size. Otherwise if // the parent or display is smaller than the size, the content may be cropped. return; @@ -2888,21 +2877,6 @@ class Task extends WindowContainer<WindowContainer> { computeFreeformBounds(outOverrideBounds, newParentConfig); return; } - - if (isSplitScreenWindowingMode(windowingMode) - || windowingMode == WINDOWING_MODE_MULTI_WINDOW) { - // This is to compute whether the task should be letterboxed to handle non-resizable app - // in multi window. There is no split screen only logic. - computeLetterboxBounds(outOverrideBounds, newParentConfig); - } - } - - /** Computes bounds for {@link WindowConfiguration#WINDOWING_MODE_FULLSCREEN}. */ - @VisibleForTesting - void computeFullscreenBounds(@NonNull Rect outBounds, @NonNull Configuration newParentConfig) { - // In FULLSCREEN mode, always start with empty bounds to indicate "fill parent". - outBounds.setEmpty(); - computeLetterboxBounds(outBounds, newParentConfig); } /** Computes bounds for {@link WindowConfiguration#WINDOWING_MODE_FREEFORM}. */ @@ -2934,94 +2908,6 @@ class Task extends WindowContainer<WindowContainer> { } } - /** - * Computes bounds (letterbox or pillarbox) when the parent doesn't handle the orientation - * change and the requested orientation is different from the parent. - */ - private void computeLetterboxBounds(@NonNull Rect outBounds, - @NonNull Configuration newParentConfig) { - if (handlesOrientationChangeFromDescendant()) { - // No need to letterbox at task level. Display will handle fixed-orientation requests. - return; - } - - final int parentOrientation = newParentConfig.orientation; - // Use the top activity as the reference of orientation. Don't include overlays because - // it is usually not the actual content or just temporarily shown. - // E.g. ForcedResizableInfoActivity. - final ActivityRecord refActivity = getTopNonFinishingActivity(false /* includeOverlays */); - - // If the task or the reference activity requires a different orientation (either by - // override or activityInfo), make it fit the available bounds by scaling down its bounds. - final int overrideOrientation = getRequestedOverrideConfiguration().orientation; - final int forcedOrientation = - (overrideOrientation != ORIENTATION_UNDEFINED || refActivity == null) - ? overrideOrientation : refActivity.getRequestedConfigurationOrientation(); - if (forcedOrientation == ORIENTATION_UNDEFINED || forcedOrientation == parentOrientation) { - return; - } - - final ActivityRecord.CompatDisplayInsets compatDisplayInsets = - refActivity == null ? null : refActivity.getCompatDisplayInsets(); - if (compatDisplayInsets != null && !compatDisplayInsets.mIsTaskLetterboxed) { - // App prefers to keep its original size. - // If the size compat is from previous task letterboxing, we may want to have task - // letterbox again, otherwise it will show the size compat restart button even if the - // restart bounds will be the same. - return; - } - - final Rect parentBounds = newParentConfig.windowConfiguration.getBounds(); - final int parentWidth = parentBounds.width(); - final int parentHeight = parentBounds.height(); - float aspect = Math.max(parentWidth, parentHeight) - / (float) Math.min(parentWidth, parentHeight); - - // Adjust the Task letterbox bounds to fit the app request aspect ratio in order to use the - // extra available space. - if (refActivity != null) { - final float maxAspectRatio = refActivity.info.maxAspectRatio; - final float minAspectRatio = refActivity.info.minAspectRatio; - if (aspect > maxAspectRatio && maxAspectRatio != 0) { - aspect = maxAspectRatio; - } else if (aspect < minAspectRatio) { - aspect = minAspectRatio; - } - } - - // Override from config_letterboxAspectRatio or via ADB with set-letterbox-aspect-ratio. - final float letterboxAspectRatioOverride = mWmService.getTaskLetterboxAspectRatio(); - // Activity min/max aspect ratio restrictions will be respected by the activity-level - // letterboxing (size-compat mode). Therefore this override can control the maximum screen - // area that can be occupied by the app in the letterbox mode. - aspect = letterboxAspectRatioOverride > MIN_TASK_LETTERBOX_ASPECT_RATIO - ? letterboxAspectRatioOverride : aspect; - - // Store the current bounds to be able to revert to size compat mode values below if needed. - mTmpFullBounds.set(outBounds); - if (forcedOrientation == ORIENTATION_LANDSCAPE) { - final int height = (int) Math.rint(parentWidth / aspect); - final int top = parentBounds.centerY() - height / 2; - outBounds.set(parentBounds.left, top, parentBounds.right, top + height); - } else { - final int width = (int) Math.rint(parentHeight / aspect); - final int left = parentBounds.centerX() - width / 2; - outBounds.set(left, parentBounds.top, left + width, parentBounds.bottom); - } - - if (compatDisplayInsets != null) { - compatDisplayInsets.getBoundsByRotation( - mTmpBounds, newParentConfig.windowConfiguration.getRotation()); - if (outBounds.width() != mTmpBounds.width() - || outBounds.height() != mTmpBounds.height()) { - // The app shouldn't be resized, we only do task letterboxing if the compat bounds - // is also from the same task letterbox. Otherwise, clear the task bounds to show - // app in size compat mode. - outBounds.set(mTmpFullBounds); - } - } - } - Rect updateOverrideConfigurationFromLaunchBounds() { // If the task is controlled by another organized task, do not set override // configurations and let its parent (organized task) to control it; @@ -3036,11 +2922,6 @@ class Task extends WindowContainer<WindowContainer> { return bounds; } - @Nullable - ActivityRecord getLastTaskBoundsComputeActivity() { - return mLastTaskBoundsComputeActivity; - } - /** Updates the task's bounds and override configuration to match what is expected for the * input root task. */ void updateOverrideConfigurationForRootTask(Task inRootTask) { @@ -3939,12 +3820,6 @@ class Task extends WindowContainer<WindowContainer> { || activityType == ACTIVITY_TYPE_ASSISTANT; } - boolean isTaskLetterboxed() { - // No letterbox for multi window root task - return !matchParentBounds() - && (getWindowingMode() == WINDOWING_MODE_FULLSCREEN || !isRootTask()); - } - @Override boolean fillsParent() { // From the perspective of policy, we still want to report that this task fills parent @@ -7790,18 +7665,18 @@ class Task extends WindowContainer<WindowContainer> { return; } - final PinnedStackController pinnedStackController = - getDisplayContent().getPinnedStackController(); + final PinnedTaskController pinnedTaskController = + getDisplayContent().getPinnedTaskController(); - if (Float.compare(aspectRatio, pinnedStackController.getAspectRatio()) == 0) { + if (Float.compare(aspectRatio, pinnedTaskController.getAspectRatio()) == 0) { return; } // Notify the pinned stack controller about aspect ratio change. // This would result a callback delivered from SystemUI to WM to start animation, // if the bounds are ought to be altered due to aspect ratio change. - pinnedStackController.setAspectRatio( - pinnedStackController.isValidPictureInPictureAspectRatio(aspectRatio) + pinnedTaskController.setAspectRatio( + pinnedTaskController.isValidPictureInPictureAspectRatio(aspectRatio) ? aspectRatio : -1f); } @@ -7817,7 +7692,7 @@ class Task extends WindowContainer<WindowContainer> { return; } - getDisplayContent().getPinnedStackController().setActions(actions); + getDisplayContent().getPinnedTaskController().setActions(actions); } /** Returns true if a removal action is still being deferred. */ diff --git a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java index 9b7257057836..04ec4bd83511 100644 --- a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java +++ b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java @@ -130,8 +130,8 @@ class TaskChangeNotificationController { l.onActivityForcedResizable((String) m.obj, m.arg1, m.arg2); }; - private final TaskStackConsumer mNotifyActivityDismissingDockedStack = (l, m) -> { - l.onActivityDismissingDockedStack(); + private final TaskStackConsumer mNotifyActivityDismissingDockedTask = (l, m) -> { + l.onActivityDismissingDockedTask(); }; private final TaskStackConsumer mNotifyActivityLaunchOnSecondaryDisplayFailed = (l, m) -> { @@ -235,7 +235,7 @@ class TaskChangeNotificationController { forAllRemoteListeners(mNotifyActivityForcedResizable, msg); break; case NOTIFY_ACTIVITY_DISMISSING_DOCKED_ROOT_TASK_MSG: - forAllRemoteListeners(mNotifyActivityDismissingDockedStack, msg); + forAllRemoteListeners(mNotifyActivityDismissingDockedTask, msg); break; case NOTIFY_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED_MSG: forAllRemoteListeners(mNotifyActivityLaunchOnSecondaryDisplayFailed, msg); @@ -391,7 +391,7 @@ class TaskChangeNotificationController { void notifyActivityDismissingDockedRootTask() { mHandler.removeMessages(NOTIFY_ACTIVITY_DISMISSING_DOCKED_ROOT_TASK_MSG); final Message msg = mHandler.obtainMessage(NOTIFY_ACTIVITY_DISMISSING_DOCKED_ROOT_TASK_MSG); - forAllLocalListeners(mNotifyActivityDismissingDockedStack, msg); + forAllLocalListeners(mNotifyActivityDismissingDockedTask, msg); msg.sendToTarget(); } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 89d30408fe25..c0ccd81c9b15 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -230,7 +230,7 @@ import android.view.IDisplayWindowListener; import android.view.IDisplayWindowRotationController; import android.view.IInputFilter; import android.view.IOnKeyguardExitResult; -import android.view.IPinnedStackListener; +import android.view.IPinnedTaskListener; import android.view.IRecentsAnimationRunner; import android.view.IRotationWatcher; import android.view.IScrollCaptureCallbacks; @@ -253,6 +253,7 @@ import android.view.MagnificationSpec; import android.view.MotionEvent; import android.view.PointerIcon; import android.view.RemoteAnimationAdapter; +import android.view.ScrollCaptureResponse; import android.view.Surface; import android.view.SurfaceControl; import android.view.SurfaceSession; @@ -2393,15 +2394,9 @@ public class WindowManagerService extends IWindowManager.Stub } } - // We may be deferring layout passes at the moment, but since the client is interested - // in the new out values right now we need to force a layout. - mWindowPlacerLocked.performSurfacePlacement(true /* force */); - + // Create surfaceControl before surface placement otherwise layout will be skipped + // (because WS.isGoneForLayout() is true when there is no surface. if (shouldRelayout) { - Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: viewVisibility_1"); - - result = win.relayoutVisibleWindow(result); - try { result = createSurfaceControl(outSurfaceControl, result, win, winAnimator); } catch (Exception e) { @@ -2413,6 +2408,17 @@ public class WindowManagerService extends IWindowManager.Stub Binder.restoreCallingIdentity(origId); return 0; } + } + + // We may be deferring layout passes at the moment, but since the client is interested + // in the new out values right now we need to force a layout. + mWindowPlacerLocked.performSurfacePlacement(true /* force */); + + if (shouldRelayout) { + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: viewVisibility_1"); + + result = win.relayoutVisibleWindow(result); + if ((result & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) { focusMayChange = true; } @@ -2981,7 +2987,7 @@ public class WindowManagerService extends IWindowManager.Stub } boolean isValidPictureInPictureAspectRatio(DisplayContent displayContent, float aspectRatio) { - return displayContent.getPinnedStackController().isValidPictureInPictureAspectRatio( + return displayContent.getPinnedTaskController().isValidPictureInPictureAspectRatio( aspectRatio); } @@ -6880,7 +6886,7 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public void setDockedStackDividerTouchRegion(Rect touchRegion) { + public void setDockedTaskDividerTouchRegion(Rect touchRegion) { synchronized (mGlobalLock) { final DisplayContent dc = getDefaultDisplayContentLocked(); dc.getDockedDividerController().setTouchRegion(touchRegion); @@ -6905,10 +6911,9 @@ public class WindowManagerService extends IWindowManager.Stub return (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, displayMetrics); } - @Override - public void registerPinnedStackListener(int displayId, IPinnedStackListener listener) { + public void registerPinnedTaskListener(int displayId, IPinnedTaskListener listener) { if (!checkCallingPermission(REGISTER_WINDOW_MANAGER_LISTENERS, - "registerPinnedStackListener()")) { + "registerPinnedTaskListener()")) { return; } if (!mAtmService.mSupportsPictureInPicture) { @@ -6916,7 +6921,7 @@ public class WindowManagerService extends IWindowManager.Stub } synchronized (mGlobalLock) { final DisplayContent displayContent = mRoot.getDisplayContent(displayId); - displayContent.getPinnedStackController().registerPinnedStackListener(listener); + displayContent.getPinnedTaskController().registerPinnedTaskListener(listener); } } @@ -7155,12 +7160,14 @@ public class WindowManagerService extends IWindowManager.Stub } final long token = Binder.clearCallingIdentity(); try { + ScrollCaptureResponse.Builder responseBuilder = new ScrollCaptureResponse.Builder(); synchronized (mGlobalLock) { DisplayContent dc = mRoot.getDisplayContent(displayId); if (dc == null) { ProtoLog.e(WM_ERROR, "Invalid displayId for requestScrollCapture: %d", displayId); - callbacks.onUnavailable(); + responseBuilder.setDescription(String.format("bad displayId: %d", displayId)); + callbacks.onScrollCaptureResponse(responseBuilder.build()); return; } WindowState topWindow = null; @@ -7169,17 +7176,20 @@ public class WindowManagerService extends IWindowManager.Stub } WindowState targetWindow = dc.findScrollCaptureTargetWindow(topWindow, taskId); if (targetWindow == null) { - callbacks.onUnavailable(); + responseBuilder.setDescription("findScrollCaptureTargetWindow returned null"); + callbacks.onScrollCaptureResponse(responseBuilder.build()); return; } - // Forward to the window for handling. try { + // Forward to the window for handling, which will respond using the callback. targetWindow.mClient.requestScrollCapture(callbacks); } catch (RemoteException e) { ProtoLog.w(WM_ERROR, "requestScrollCapture: caught exception dispatching to window." + "token=%s", targetWindow.mClient.asBinder()); - callbacks.onUnavailable(); + responseBuilder.setWindowTitle(targetWindow.getName()); + responseBuilder.setDescription(String.format("caught exception: %s", e)); + callbacks.onScrollCaptureResponse(responseBuilder.build()); } } } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 63a083261614..9973664346f0 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -17,7 +17,6 @@ package com.android.server.wm; import static android.Manifest.permission.READ_FRAME_BUFFER; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT; @@ -426,8 +425,9 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } if (windowingMode > -1) { - if (mService.isInLockTaskMode() && windowingMode != WINDOWING_MODE_FULLSCREEN) { - throw new UnsupportedOperationException("Not supported to set non-fullscreen" + if (mService.isInLockTaskMode() + && WindowConfiguration.inMultiWindowMode(windowingMode)) { + throw new UnsupportedOperationException("Not supported to set multi-window" + " windowing mode during locked task mode."); } container.setWindowingMode(windowingMode); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 60e95e5cfd6e..1fc7041c0fe2 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -1224,7 +1224,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // But in docked we want to behave like fullscreen and behave as if the task // were given smaller bounds for the purposes of layout. Skip adjustments for // the root pinned task, they are handled separately in the - // PinnedStackController. + // PinnedTaskController. windowFrames.mContainingFrame.bottom = windowFrames.mParentFrame.bottom; } } @@ -3827,13 +3827,21 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP /** @return true when the window should be letterboxed. */ boolean isLetterboxedAppWindow() { // Fullscreen mode but doesn't fill display area. - return (!inMultiWindowMode() && !matchesDisplayAreaBounds()) - // Activity in size compat. - || (mActivityRecord != null && mActivityRecord.inSizeCompatMode()) - // Task letterboxed. - || (getTask() != null && getTask().isTaskLetterboxed()) - // Letterboxed for display cutout. - || isLetterboxedForDisplayCutout(); + if (!inMultiWindowMode() && !matchesDisplayAreaBounds()) { + return true; + } + if (mActivityRecord != null) { + // Activity in size compat. + if (mActivityRecord.inSizeCompatMode()) { + return true; + } + // Letterbox for fixed orientation. + if (mActivityRecord.isLetterboxedForFixedOrientationAndAspectRatio()) { + return true; + } + } + // Letterboxed for display cutout. + return isLetterboxedForDisplayCutout(); } /** Returns {@code true} if the window is letterboxed for the display cutout. */ diff --git a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp index f054e7c73015..8c6d084fba99 100644 --- a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp +++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp @@ -232,21 +232,6 @@ static void com_android_server_am_CachedAppOptimizer_compactProcess(JNIEnv*, job compactProcessOrFallback(pid, compactionFlags); } -static void com_android_server_am_CachedAppOptimizer_enableFreezerInternal( - JNIEnv *env, jobject clazz, jboolean enable) { - bool success = true; - - if (enable) { - success = SetTaskProfiles(0, {"FreezerEnabled"}, true); - } else { - success = SetTaskProfiles(0, {"FreezerDisabled"}, true); - } - - if (!success) { - jniThrowException(env, "java/lang/RuntimeException", "Unknown error"); - } -} - static void com_android_server_am_CachedAppOptimizer_freezeBinder( JNIEnv *env, jobject clazz, jint pid, jboolean freeze) { @@ -280,15 +265,19 @@ static jint com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo(JNIEnv static jstring com_android_server_am_CachedAppOptimizer_getFreezerCheckPath(JNIEnv* env, jobject clazz) { - return env->NewStringUTF(CGROUP_FREEZE_PATH); + std::string path; + + if (!getAttributePathForTask("FreezerState", getpid(), &path)) { + path = ""; + } + + return env->NewStringUTF(path.c_str()); } static const JNINativeMethod sMethods[] = { /* name, signature, funcPtr */ {"compactSystem", "()V", (void*)com_android_server_am_CachedAppOptimizer_compactSystem}, {"compactProcess", "(II)V", (void*)com_android_server_am_CachedAppOptimizer_compactProcess}, - {"enableFreezerInternal", "(Z)V", - (void*)com_android_server_am_CachedAppOptimizer_enableFreezerInternal}, {"freezeBinder", "(IZ)V", (void*)com_android_server_am_CachedAppOptimizer_freezeBinder}, {"getBinderFreezeInfo", "(I)I", (void*)com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo}, diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 21d57d8c189f..10705af9ac38 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -331,7 +331,7 @@ public: uint32_t policyFlags) override; bool dispatchUnhandledKey(const sp<IBinder>& token, const KeyEvent* keyEvent, uint32_t policyFlags, KeyEvent* outFallbackKeyEvent) override; - void pokeUserActivity(nsecs_t eventTime, int32_t eventType) override; + void pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) override; bool checkInjectEventsPermissionNonReentrant(int32_t injectorPid, int32_t injectorUid) override; void onPointerDownOutsideFocus(const sp<IBinder>& touchedToken) override; void setPointerCapture(bool enabled) override; @@ -1325,9 +1325,9 @@ bool NativeInputManager::dispatchUnhandledKey(const sp<IBinder>& token, return result; } -void NativeInputManager::pokeUserActivity(nsecs_t eventTime, int32_t eventType) { +void NativeInputManager::pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) { ATRACE_CALL(); - android_server_PowerManagerService_userActivity(eventTime, eventType); + android_server_PowerManagerService_userActivity(eventTime, eventType, displayId); } bool NativeInputManager::checkInjectEventsPermissionNonReentrant( diff --git a/services/core/jni/com_android_server_power_PowerManagerService.cpp b/services/core/jni/com_android_server_power_PowerManagerService.cpp index 63a6eedd9e66..9b7e27d891c4 100644 --- a/services/core/jni/com_android_server_power_PowerManagerService.cpp +++ b/services/core/jni/com_android_server_power_PowerManagerService.cpp @@ -103,7 +103,8 @@ static bool setPowerMode(Mode mode, bool enabled) { return result == power::HalResult::SUCCESSFUL; } -void android_server_PowerManagerService_userActivity(nsecs_t eventTime, int32_t eventType) { +void android_server_PowerManagerService_userActivity(nsecs_t eventTime, int32_t eventType, + int32_t displayId) { if (gPowerManagerServiceObj) { // Throttle calls into user activity by event type. // We're a little conservative about argument checking here in case the caller @@ -127,7 +128,7 @@ void android_server_PowerManagerService_userActivity(nsecs_t eventTime, int32_t env->CallVoidMethod(gPowerManagerServiceObj, gPowerManagerServiceClassInfo.userActivityFromNative, - nanoseconds_to_milliseconds(eventTime), eventType, 0); + nanoseconds_to_milliseconds(eventTime), eventType, displayId, 0); checkAndClearExceptionFromCallback(env, "userActivityFromNative"); } } @@ -285,7 +286,7 @@ int register_android_server_PowerManagerService(JNIEnv* env) { FIND_CLASS(clazz, "com/android/server/power/PowerManagerService"); GET_METHOD_ID(gPowerManagerServiceClassInfo.userActivityFromNative, clazz, - "userActivityFromNative", "(JII)V"); + "userActivityFromNative", "(JIII)V"); // Initialize for (int i = 0; i <= USER_ACTIVITY_EVENT_LAST; i++) { diff --git a/services/core/jni/com_android_server_power_PowerManagerService.h b/services/core/jni/com_android_server_power_PowerManagerService.h index a17fd650522b..a2f335c74870 100644 --- a/services/core/jni/com_android_server_power_PowerManagerService.h +++ b/services/core/jni/com_android_server_power_PowerManagerService.h @@ -24,7 +24,8 @@ namespace android { -extern void android_server_PowerManagerService_userActivity(nsecs_t eventTime, int32_t eventType); +extern void android_server_PowerManagerService_userActivity(nsecs_t eventTime, int32_t eventType, + int32_t displayId); } // namespace android diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 24f67b614757..524892b2ec4b 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -3404,8 +3404,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return; } Objects.requireNonNull(adminReceiver, "ComponentName is null"); - Preconditions.checkCallAuthorization(isAdb(getCallerIdentity()), - "Non-shell user attempted to call forceRemoveActiveAdmin"); + Preconditions.checkCallAuthorization(isAdb(getCallerIdentity()) + || hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS), + "Caller must be shell or hold MANAGE_PROFILE_AND_DEVICE_OWNERS to call " + + "forceRemoveActiveAdmin"); mInjector.binderWithCleanCallingIdentity(() -> { synchronized (getLockObject()) { if (!isAdminTestOnlyLocked(adminReceiver, userHandle)) { @@ -7582,8 +7584,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } private void sendProfileOwnerCommand(String action, Bundle extras, @UserIdInt int userId) { - sendActiveAdminCommand(action, extras, userId, - mOwners.getProfileOwnerComponent(userId)); + sendActiveAdminCommand(action, extras, userId, mOwners.getProfileOwnerComponent(userId)); } private void sendActiveAdminCommand(String action, Bundle extras, @@ -8111,7 +8112,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return null; } if (!callingUserOnly) { - Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity())); + Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()) + || hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)); } synchronized (getLockObject()) { if (!mOwners.hasDeviceOwner()) { @@ -12373,8 +12375,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { extras.putInt(DeviceAdminReceiver.EXTRA_OPERATION_SAFETY_REASON, reason); extras.putBoolean(DeviceAdminReceiver.EXTRA_OPERATION_SAFETY_STATE, isSafe); - sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_OPERATION_SAFETY_STATE_CHANGED, - extras); + if (mOwners.hasDeviceOwner()) { + sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_OPERATION_SAFETY_STATE_CHANGED, + extras); + } for (int profileOwnerId : mOwners.getProfileOwnerKeys()) { sendProfileOwnerCommand(DeviceAdminReceiver.ACTION_OPERATION_SAFETY_STATE_CHANGED, extras, profileOwnerId); @@ -12565,8 +12569,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public void clearSystemUpdatePolicyFreezePeriodRecord() { - Preconditions.checkCallAuthorization(isAdb(getCallerIdentity()), - "Non-shell user attempted to call clearSystemUpdatePolicyFreezePeriodRecord"); + Preconditions.checkCallAuthorization(isAdb(getCallerIdentity()) + || hasCallingOrSelfPermission(permission.CLEAR_FREEZE_PERIOD), + "Caller must be shell, or hold CLEAR_FREEZE_PERIOD permission to call " + + "clearSystemUpdatePolicyFreezePeriodRecord"); synchronized (getLockObject()) { // Print out current record to help diagnosed CTS failures Slog.i(LOG_TAG, "Clear freeze period record: " @@ -13509,7 +13515,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final CallerIdentity caller = getCallerIdentity(); // Only adb or system apps with the right permission can mark a profile owner on // organization-owned device. - if (!(isAdb(caller) || hasCallingPermission(permission.MARK_DEVICE_ORGANIZATION_OWNED))) { + if (!(isAdb(caller) || hasCallingPermission(permission.MARK_DEVICE_ORGANIZATION_OWNED) + || hasCallingPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS))) { throw new SecurityException( "Only the system can mark a profile owner of organization-owned device."); } @@ -13828,8 +13835,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public long forceSecurityLogs() { - Preconditions.checkCallAuthorization(isAdb(getCallerIdentity()), - "Non-shell user attempted to call forceSecurityLogs"); + Preconditions.checkCallAuthorization(isAdb(getCallerIdentity()) + || hasCallingOrSelfPermission(permission.FORCE_DEVICE_POLICY_MANAGER_LOGS), + "Caller must be shell or hold FORCE_DEVICE_POLICY_MANAGER_LOGS to call " + + "forceSecurityLogs"); if (!mInjector.securityLogGetLoggingEnabledProperty()) { throw new IllegalStateException("logging is not available"); } @@ -14349,8 +14358,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public long forceNetworkLogs() { - Preconditions.checkCallAuthorization(isAdb(getCallerIdentity()), - "Non-shell user attempted to call forceNetworkLogs"); + Preconditions.checkCallAuthorization(isAdb(getCallerIdentity()) + || hasCallingOrSelfPermission(permission.FORCE_DEVICE_POLICY_MANAGER_LOGS), + "Caller must be shell or hold FORCE_DEVICE_POLICY_MANAGER_LOGS to call " + + "forceNetworkLogs"); synchronized (getLockObject()) { if (!isNetworkLoggingEnabledInternalLocked()) { throw new IllegalStateException("logging is not available"); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java b/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java index 776b44445678..257fc640f93c 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/OneTimeSafetyChecker.java @@ -23,6 +23,8 @@ import android.app.admin.DevicePolicyManager.DevicePolicyOperation; import android.app.admin.DevicePolicyManager.OperationSafetyReason; import android.app.admin.DevicePolicyManagerInternal; import android.app.admin.DevicePolicySafetyChecker; +import android.os.Handler; +import android.os.Looper; import android.util.Slog; import com.android.internal.os.IResultReceiver; @@ -42,10 +44,15 @@ final class OneTimeSafetyChecker implements DevicePolicySafetyChecker { private static final String TAG = OneTimeSafetyChecker.class.getSimpleName(); + private static final long SELF_DESTRUCT_TIMEOUT_MS = 10_000; + private final DevicePolicyManagerService mService; private final DevicePolicySafetyChecker mRealSafetyChecker; private final @DevicePolicyOperation int mOperation; private final @OperationSafetyReason int mReason; + private final Handler mHandler = new Handler(Looper.getMainLooper()); + + private boolean mDone; OneTimeSafetyChecker(DevicePolicyManagerService service, @DevicePolicyOperation int operation, @OperationSafetyReason int reason) { @@ -53,7 +60,11 @@ final class OneTimeSafetyChecker implements DevicePolicySafetyChecker { mOperation = operation; mReason = reason; mRealSafetyChecker = service.getDevicePolicySafetyChecker(); - Slog.i(TAG, "Saving real DevicePolicySafetyChecker as " + mRealSafetyChecker); + Slog.i(TAG, "OneTimeSafetyChecker constructor: operation= " + operationToString(operation) + + ", reason=" + operationSafetyReasonToString(reason) + + ", realChecker=" + mRealSafetyChecker + + ", maxDuration=" + SELF_DESTRUCT_TIMEOUT_MS + "ms"); + mHandler.postDelayed(() -> selfDestruct(), SELF_DESTRUCT_TIMEOUT_MS); } @Override @@ -99,8 +110,21 @@ final class OneTimeSafetyChecker implements DevicePolicySafetyChecker { } private void disableSelf() { + if (mDone) { + Slog.w(TAG, "disableSelf(): already disabled"); + return; + } Slog.i(TAG, "restoring DevicePolicySafetyChecker to " + mRealSafetyChecker); mService.setDevicePolicySafetyCheckerUnchecked(mRealSafetyChecker); + mDone = true; + } + + private void selfDestruct() { + if (mDone) return; + + // Usually happens when a CTS failed before calling the DPM method that would clear it + Slog.e(TAG, "Self destructing " + this + ", as it was not automatically disabled"); + disableSelf(); } @Override diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 6bbd320017a7..a3d335340e9f 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -162,6 +162,7 @@ import com.android.server.pm.ShortcutService; import com.android.server.pm.UserManagerService; import com.android.server.pm.dex.SystemServerDexLoadReporter; import com.android.server.pm.verify.domain.DomainVerificationService; +import com.android.server.policy.AppOpsPolicy; import com.android.server.policy.PermissionPolicyService; import com.android.server.policy.PhoneWindowManager; import com.android.server.policy.role.RoleServicePlatformHelperImpl; @@ -2656,6 +2657,14 @@ public final class SystemServer implements Dumpable { } t.traceEnd(); + t.traceBegin("RegisterAppOpsPolicy"); + try { + mActivityManagerService.setAppOpsPolicy(new AppOpsPolicy()); + } catch (Throwable e) { + reportWtf("registering app ops policy", e); + } + t.traceEnd(); + // No dependency on Webview preparation in system server. But this should // be completed before allowing 3rd party final String WEBVIEW_PREPARATION = "WebViewFactoryPreparation"; diff --git a/services/net/Android.bp b/services/net/Android.bp index c68004a3591b..b01e42516358 100644 --- a/services/net/Android.bp +++ b/services/net/Android.bp @@ -52,6 +52,7 @@ java_library { libs: [ "unsupportedappusage", "framework-wifi-util-lib", + "framework-connectivity" ], static_libs: [ // All the classes in netd_aidl_interface must be jarjar so they do not conflict with the diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCoreApiTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCoreApiTest.kt index deb314764404..9447f390ada0 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCoreApiTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCoreApiTest.kt @@ -16,8 +16,9 @@ package com.android.server.pm.test.verify.domain -import android.content.pm.verify.domain.DomainVerificationRequest +import android.content.pm.verify.domain.DomainSet import android.content.pm.verify.domain.DomainVerificationInfo +import android.content.pm.verify.domain.DomainVerificationRequest import android.content.pm.verify.domain.DomainVerificationUserSelection import android.os.Parcel import android.os.Parcelable @@ -27,6 +28,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized import java.util.UUID +import kotlin.random.Random @RunWith(Parameterized::class) class DomainVerificationCoreApiTest { @@ -40,18 +42,25 @@ class DomainVerificationCoreApiTest { assertThat(value).containsExactlyEntriesIn(other) } + private val massiveSet by lazy { + val fragmentOf21 = ".com.example.test.app" + val list = mutableListOf("prefix$fragmentOf21") + var totalSize = 0 + // Slightly overshoot a size of 1MB + while (totalSize < (1024 * 512)) { + val nextValue = "${list.last()}$fragmentOf21" + totalSize += nextValue.length + list += nextValue + } + list.toSet() + } + @JvmStatic - @Parameterized.Parameters + @Parameterized.Parameters(name = "{0}") fun parameters() = arrayOf( Parameter( - initial = { - DomainVerificationRequest( - setOf( - "com.test.pkg.one", - "com.test.pkg.two" - ) - ) - }, + testName = "DomainVerificationRequest", + initial = { DomainVerificationRequest(massiveSet) }, unparcel = { DomainVerificationRequest.CREATOR.createFromParcel(it) }, assertion = { first, second -> assertAll<DomainVerificationRequest, Set<String>>(first, second, @@ -61,15 +70,12 @@ class DomainVerificationCoreApiTest { } ), Parameter( + testName = "DomainVerificationInfo", initial = { DomainVerificationInfo( UUID.fromString("703f6d34-6241-4cfd-8176-2e1d23355811"), "com.test.pkg", - mapOf( - "example.com" to 0, - "example.org" to 1, - "example.new" to 1000 - ) + massiveSet.withIndex().associate { it.value to it.index } ) }, unparcel = { DomainVerificationInfo.CREATOR.createFromParcel(it) }, @@ -86,17 +92,15 @@ class DomainVerificationCoreApiTest { } ), Parameter( + testName = "DomainVerificationUserSelection", initial = { DomainVerificationUserSelection( UUID.fromString("703f6d34-6241-4cfd-8176-2e1d23355811"), "com.test.pkg", UserHandle.of(10), true, - mapOf( - "example.com" to true, - "example.org" to false, - "example.new" to true - ) + massiveSet.withIndex() + .associate { it.value to (it.index % 3) } ) }, unparcel = { DomainVerificationUserSelection.CREATOR.createFromParcel(it) }, @@ -114,21 +118,35 @@ class DomainVerificationCoreApiTest { first, second, { it.isLinkHandlingAllowed }, { it.component4() }, IS_EQUAL_TO ) - assertAll<DomainVerificationUserSelection, Map<String, Boolean>>( - first, second, { it.hostToUserSelectionMap }, + assertAll<DomainVerificationUserSelection, Map<String, Int>>( + first, second, { it.hostToStateMap }, { it.component5() }, IS_MAP_EQUAL_TO ) } + ), + Parameter( + testName = "DomainSet", + initial = { DomainSet(massiveSet) }, + unparcel = { DomainSet.CREATOR.createFromParcel(it) }, + assertion = { first, second -> + assertAll<DomainSet, Set<String>>( + first, second, + { it.domains }, assertion = IS_EQUAL_TO + ) + } ) ) class Parameter<T : Parcelable>( + val testName: String, val initial: () -> T, val unparcel: (Parcel) -> T, private val assertion: (first: T, second: T) -> Unit ) { @Suppress("UNCHECKED_CAST") fun assert(first: Any, second: Any) = assertion(first as T, second as T) + + override fun toString() = testName } private fun <T> assertAll(vararg values: T, block: (value: T, other: T) -> Unit) { @@ -141,11 +159,17 @@ class DomainVerificationCoreApiTest { first: T, second: T, fieldValue: (T) -> V, - componentValue: (T) -> V, + componentValue: ((T) -> V)? = null, assertion: (value: V, other: V) -> Unit ) { - val values = arrayOf<Any>(fieldValue(first), fieldValue(second), - componentValue(first), componentValue(second)) + val values = mutableListOf<Any>(fieldValue(first), fieldValue(second)) + .apply { + componentValue?.let { + add(it(first)) + add(it(second)) + } + } + .toTypedArray() values.indices.drop(1).forEach { @Suppress("UNCHECKED_CAST") assertion(values[0] as V, values[it] as V) diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt index 2d23fb4990bf..89394837655a 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt @@ -244,6 +244,14 @@ class DomainVerificationEnforcerTest { service(Type.LEGACY_QUERENT, "getLegacyUserState") { getLegacyState(it.targetPackageName, it.userId) }, + service(Type.OWNER_QUERENT, "getOwnersForDomain") { + // Re-use package name, since the result itself isn't relevant + getOwnersForDomain(it.targetPackageName) + }, + service(Type.OWNER_QUERENT_USER, "getOwnersForDomainUserId") { + // Re-use package name, since the result itself isn't relevant + getOwnersForDomain(it.targetPackageName, it.userId) + }, ) } @@ -327,6 +335,7 @@ class DomainVerificationEnforcerTest { domainSetId ) ) { + whenever(getName()) { packageName } whenever(getPkg()) { mockPkg(packageName) } whenever(this.domainSetId) { domainSetId } whenever(userState) { @@ -357,6 +366,8 @@ class DomainVerificationEnforcerTest { Type.SELECTOR_USER -> approvedUserSelector(verifyCrossUser = true) Type.LEGACY_QUERENT -> legacyQuerent() Type.LEGACY_SELECTOR -> legacyUserSelector() + Type.OWNER_QUERENT -> ownerQuerent(verifyCrossUser = false) + Type.OWNER_QUERENT_USER -> ownerQuerent(verifyCrossUser = true) }.run { /*exhaust*/ } } @@ -628,6 +639,80 @@ class DomainVerificationEnforcerTest { runTestCases(callingUserId, notCallingUserId, throws = false) } + private fun ownerQuerent(verifyCrossUser: Boolean) { + val allowQueryAll = AtomicBoolean(false) + val allowUserSelection = AtomicBoolean(false) + val allowInteractAcrossUsers = AtomicBoolean(false) + val context: Context = mockThrowOnUnmocked { + initPermission( + allowQueryAll, + android.Manifest.permission.QUERY_ALL_PACKAGES + ) + initPermission( + allowUserSelection, + android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION + ) + initPermission( + allowInteractAcrossUsers, + android.Manifest.permission.INTERACT_ACROSS_USERS + ) + } + val target = params.construct(context) + + fun runTestCases(callingUserId: Int, targetUserId: Int, throws: Boolean) { + // Owner querent makes no distinction by UID + val allUids = INTERNAL_UIDS + VERIFIER_UID + NON_VERIFIER_UID + if (throws) { + allUids.forEach { + assertFails { + runMethod(target, it, visible = true, callingUserId, targetUserId) + } + } + } else { + allUids.forEach { + runMethod(target, it, visible = true, callingUserId, targetUserId) + } + } + } + + val callingUserId = 0 + val notCallingUserId = 1 + + runTestCases(callingUserId, callingUserId, throws = true) + if (verifyCrossUser) { + runTestCases(callingUserId, notCallingUserId, throws = true) + } + + allowQueryAll.set(true) + + runTestCases(callingUserId, callingUserId, throws = true) + if (verifyCrossUser) { + runTestCases(callingUserId, notCallingUserId, throws = true) + } + + allowUserSelection.set(true) + + runTestCases(callingUserId, callingUserId, throws = false) + if (verifyCrossUser) { + runTestCases(callingUserId, notCallingUserId, throws = true) + } + + allowQueryAll.set(false) + + runTestCases(callingUserId, callingUserId, throws = true) + if (verifyCrossUser) { + runTestCases(callingUserId, notCallingUserId, throws = true) + } + + allowQueryAll.set(true) + allowInteractAcrossUsers.set(true) + + runTestCases(callingUserId, callingUserId, throws = false) + if (verifyCrossUser) { + runTestCases(callingUserId, notCallingUserId, throws = false) + } + } + private fun Context.initPermission(boolean: AtomicBoolean, permission: String) { whenever(enforcePermission(eq(permission), anyInt(), anyInt(), anyString())) { if (!boolean.get()) { @@ -694,6 +779,12 @@ class DomainVerificationEnforcerTest { LEGACY_QUERENT, // Holding the legacy preferred apps permission - LEGACY_SELECTOR + LEGACY_SELECTOR, + + // Holding user setting permission, but not targeting a package + OWNER_QUERENT, + + // Holding user setting permission, but not targeting a package, but targeting cross user + OWNER_QUERENT_USER, } } diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerUserSelectionOverrideTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerUserSelectionOverrideTest.kt new file mode 100644 index 000000000000..48056a2b54d1 --- /dev/null +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerUserSelectionOverrideTest.kt @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm.test.verify.domain + +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.content.pm.parsing.component.ParsedActivity +import android.content.pm.parsing.component.ParsedIntentInfo +import android.content.pm.verify.domain.DomainVerificationManager +import android.content.pm.verify.domain.DomainVerificationUserSelection +import android.os.Build +import android.os.PatternMatcher +import android.os.Process +import android.util.ArraySet +import androidx.test.InstrumentationRegistry +import com.android.server.pm.PackageSetting +import com.android.server.pm.parsing.pkg.AndroidPackage +import com.android.server.pm.verify.domain.DomainVerificationService +import com.android.server.testutils.mockThrowOnUnmocked +import com.android.server.testutils.whenever +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.anyLong +import org.mockito.ArgumentMatchers.anyString +import java.util.UUID + +class DomainVerificationManagerUserSelectionOverrideTest { + + companion object { + private const val PKG_ONE = "com.test.one" + private const val PKG_TWO = "com.test.two" + private val UUID_ONE = UUID.fromString("1b041c96-8d37-4932-a858-561bfac5947c") + private val UUID_TWO = UUID.fromString("a3389c16-7f9f-4e86-85e3-500d1249c74c") + + private val DOMAIN_ONE = + DomainVerificationManagerUserSelectionOverrideTest::class.java.packageName + + private const val STATE_NONE = DomainVerificationUserSelection.DOMAIN_STATE_NONE + private const val STATE_SELECTED = DomainVerificationUserSelection.DOMAIN_STATE_SELECTED + private const val STATE_VERIFIED = DomainVerificationUserSelection.DOMAIN_STATE_VERIFIED + } + + private val pkg1 = mockPkgSetting(PKG_ONE, UUID_ONE) + private val pkg2 = mockPkgSetting(PKG_TWO, UUID_TWO) + + fun makeManager(): DomainVerificationManager = + DomainVerificationService(mockThrowOnUnmocked { + // Assume the test has every permission necessary + whenever(enforcePermission(anyString(), anyInt(), anyInt(), anyString())) + whenever(checkPermission(anyString(), anyInt(), anyInt())) { + PackageManager.PERMISSION_GRANTED + } + }, mockThrowOnUnmocked { + whenever(linkedApps) { ArraySet<String>() } + }, mockThrowOnUnmocked { + whenever(isChangeEnabled(anyLong(), any())) { true } + }).apply { + setConnection(mockThrowOnUnmocked { + whenever(filterAppAccess(anyString(), anyInt(), anyInt())) { false } + whenever(scheduleWriteSettings()) + + // Need to provide an internal UID so some permission checks are ignored + whenever(callingUid) { Process.ROOT_UID } + whenever(callingUserId) { 0 } + whenever(getPackageSettingLocked(PKG_ONE)) { pkg1 } + whenever(getPackageSettingLocked(PKG_TWO)) { pkg2 } + whenever(getPackageLocked(PKG_ONE)) { pkg1.getPkg() } + whenever(getPackageLocked(PKG_TWO)) { pkg2.getPkg() } + }) + addPackage(pkg1) + addPackage(pkg2) + + // Starting state for all tests is to have domain 1 enabled for the first package + setDomainVerificationUserSelection(UUID_ONE, setOf(DOMAIN_ONE), true) + + assertThat(stateFor(PKG_ONE, DOMAIN_ONE)).isEqualTo(STATE_SELECTED) + } + + fun mockPkgSetting(pkgName: String, domainSetId: UUID) = mockThrowOnUnmocked<PackageSetting> { + val pkg = mockThrowOnUnmocked<AndroidPackage> { + whenever(packageName) { pkgName } + whenever(targetSdkVersion) { Build.VERSION_CODES.S } + + val activityList = listOf( + ParsedActivity().apply { + addIntent( + ParsedIntentInfo().apply { + autoVerify = true + addAction(Intent.ACTION_VIEW) + addCategory(Intent.CATEGORY_BROWSABLE) + addCategory(Intent.CATEGORY_DEFAULT) + addDataScheme("http") + addDataScheme("https") + addDataPath("/sub", PatternMatcher.PATTERN_LITERAL) + addDataAuthority(DOMAIN_ONE, null) + } + ) + addIntent( + ParsedIntentInfo().apply { + autoVerify = true + addAction(Intent.ACTION_VIEW) + addCategory(Intent.CATEGORY_BROWSABLE) + addCategory(Intent.CATEGORY_DEFAULT) + addDataScheme("http") + addDataPath("/sub2", PatternMatcher.PATTERN_LITERAL) + addDataAuthority("example2.com", null) + } + ) + }, + ) + + whenever(activities) { activityList } + } + + whenever(getPkg()) { pkg } + whenever(getName()) { pkgName } + whenever(this.domainSetId) { domainSetId } + whenever(getInstantApp(anyInt())) { false } + whenever(firstInstallTime) { 0L } + } + + @Test + fun anotherPackageTakeoverSuccess() { + val manager = makeManager() + + // Attempt override by package 2 + manager.setDomainVerificationUserSelection(UUID_TWO, setOf(DOMAIN_ONE), true) + + // 1 loses approval + assertThat(manager.stateFor(PKG_ONE, DOMAIN_ONE)).isEqualTo(STATE_NONE) + + // 2 gains approval + assertThat(manager.stateFor(PKG_TWO, DOMAIN_ONE)).isEqualTo(STATE_SELECTED) + + // 2 is the only owner + assertThat(manager.getOwnersForDomain(DOMAIN_ONE).map { it.packageName }) + .containsExactly(PKG_TWO) + } + + @Test(expected = IllegalArgumentException::class) + fun anotherPackageTakeoverFailure() { + val manager = makeManager() + + // Verify 1 to give it a higher approval level + manager.setDomainVerificationStatus(UUID_ONE, setOf(DOMAIN_ONE), + DomainVerificationManager.STATE_SUCCESS) + assertThat(manager.stateFor(PKG_ONE, DOMAIN_ONE)).isEqualTo(STATE_VERIFIED) + assertThat(manager.getOwnersForDomain(DOMAIN_ONE).map { it.packageName }) + .containsExactly(PKG_ONE) + + // Attempt override by package 2 + manager.setDomainVerificationUserSelection(UUID_TWO, setOf(DOMAIN_ONE), true) + } + + private fun DomainVerificationManager.stateFor(pkgName: String, host: String) = + getDomainVerificationUserSelection(pkgName)!!.hostToStateMap[host] +} diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationModelExtensions.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationModelExtensions.kt index a76d8cee582c..439048ce51bb 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationModelExtensions.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationModelExtensions.kt @@ -34,7 +34,7 @@ operator fun DomainVerificationUserSelection.component1() = identifier operator fun DomainVerificationUserSelection.component2() = packageName operator fun DomainVerificationUserSelection.component3() = user operator fun DomainVerificationUserSelection.component4() = isLinkHandlingAllowed -operator fun DomainVerificationUserSelection.component5() = hostToUserSelectionMap +operator fun DomainVerificationUserSelection.component5() = hostToStateMap operator fun DomainVerificationPersistence.ReadResult.component1() = active operator fun DomainVerificationPersistence.ReadResult.component2() = restored diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt index 48518f4693dd..010eacf3f51f 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt @@ -237,6 +237,7 @@ class DomainVerificationSettingsMutationTest { TEST_UUID ) ) { + whenever(getName()) { TEST_PKG } whenever(getPkg()) { mockPkg() } whenever(domainSetId) { TEST_UUID } whenever(userState) { diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java index a382e85538cd..1c45203bb3c9 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java @@ -46,6 +46,7 @@ import static com.android.server.am.ProcessList.HEAVY_WEIGHT_APP_ADJ; import static com.android.server.am.ProcessList.HOME_APP_ADJ; import static com.android.server.am.ProcessList.PERCEPTIBLE_APP_ADJ; import static com.android.server.am.ProcessList.PERCEPTIBLE_LOW_APP_ADJ; +import static com.android.server.am.ProcessList.PERCEPTIBLE_MEDIUM_APP_ADJ; import static com.android.server.am.ProcessList.PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ; import static com.android.server.am.ProcessList.PERSISTENT_PROC_ADJ; import static com.android.server.am.ProcessList.PERSISTENT_SERVICE_ADJ; @@ -877,6 +878,39 @@ public class MockingOomAdjusterTests { @SuppressWarnings("GuardedBy") @Test + public void testUpdateOomAdj_DoOne_Service_MediumPerceptible() { + { + ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID, + MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); + ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, + MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); + bindService(app, client, null, Context.BIND_ALMOST_PERCEPTIBLE, mock(IBinder.class)); + client.mState.setMaxAdj(PERSISTENT_PROC_ADJ); + sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); + sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE); + + assertEquals(PERCEPTIBLE_MEDIUM_APP_ADJ, app.mState.getSetAdj()); + } + + { + ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID, + MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); + ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, + MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); + WindowProcessController wpc = client.getWindowProcessController(); + doReturn(true).when(wpc).isHeavyWeightProcess(); + bindService(app, client, null, Context.BIND_ALMOST_PERCEPTIBLE, mock(IBinder.class)); + client.mState.setMaxAdj(PERSISTENT_PROC_ADJ); + sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); + sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE); + doReturn(false).when(wpc).isHeavyWeightProcess(); + + assertEquals(PERCEPTIBLE_MEDIUM_APP_ADJ, app.mState.getSetAdj()); + } + } + + @SuppressWarnings("GuardedBy") + @Test public void testUpdateOomAdj_DoOne_Service_Other() { ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID, MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java index 728b97cc3968..18184b0a82af 100644 --- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java @@ -27,11 +27,14 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyFloat; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.when; import android.content.Context; import android.content.res.Resources; +import android.content.res.TypedArray; import android.os.Binder; import android.os.Handler; import android.os.IBinder; @@ -44,6 +47,7 @@ import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.dx.mockito.inline.extended.StaticMockitoSession; +import com.android.internal.R; import com.android.server.LocalServices; import com.android.server.display.LocalDisplayAdapter.BacklightAdapter; import com.android.server.lights.LightsManager; @@ -98,6 +102,9 @@ public class LocalDisplayAdapterTest { @Mock private LocalDisplayAdapter.SurfaceControlProxy mSurfaceControlProxy; + private static final float[] DISPLAY_RANGE_NITS = { 2.685f, 478.5f }; + private static final int[] BACKLIGHT_RANGE = { 1, 255 }; + private static final float[] BACKLIGHT_RANGE_ZERO_TO_ONE = { 0.0f, 1.0f }; @Before public void setUp() throws Exception { @@ -114,6 +121,18 @@ public class LocalDisplayAdapterTest { mListener, mInjector); spyOn(mAdapter); doReturn(mMockedContext).when(mAdapter).getOverlayContext(); + + TypedArray mockNitsRange = createFloatTypedArray(DISPLAY_RANGE_NITS); + when(mMockedResources.obtainTypedArray(R.array.config_screenBrightnessNits)) + .thenReturn(mockNitsRange); + when(mMockedResources.getIntArray(R.array.config_screenBrightnessBacklight)) + .thenReturn(BACKLIGHT_RANGE); + when(mMockedResources.getFloat(com.android.internal.R.dimen + .config_screenBrightnessSettingMinimumFloat)) + .thenReturn(BACKLIGHT_RANGE_ZERO_TO_ONE[0]); + when(mMockedResources.getFloat(com.android.internal.R.dimen + .config_screenBrightnessSettingMaximumFloat)) + .thenReturn(BACKLIGHT_RANGE_ZERO_TO_ONE[1]); } @After @@ -629,13 +648,13 @@ public class LocalDisplayAdapterTest { // Test as default display BacklightAdapter ba = new BacklightAdapter(displayToken, true /*isDefault*/, mSurfaceControlProxy); - ba.setBrightness(0.514f); + ba.setBacklight(0.514f); verify(mSurfaceControlProxy).setDisplayBrightness(displayToken, 0.514f); // Test as not default display BacklightAdapter ba2 = new BacklightAdapter(displayToken, false /*isDefault*/, mSurfaceControlProxy); - ba2.setBrightness(0.323f); + ba2.setBacklight(0.323f); verify(mSurfaceControlProxy).setDisplayBrightness(displayToken, 0.323f); } @@ -648,7 +667,7 @@ public class LocalDisplayAdapterTest { BacklightAdapter ba = new BacklightAdapter(displayToken, true /*isDefault*/, mSurfaceControlProxy); - ba.setBrightness(0.123f); + ba.setBacklight(0.123f); verify(mMockedBacklight).setBrightness(0.123f); } @@ -661,7 +680,7 @@ public class LocalDisplayAdapterTest { BacklightAdapter ba = new BacklightAdapter(displayToken, false /*isDefault*/, mSurfaceControlProxy); - ba.setBrightness(0.456f); + ba.setBacklight(0.456f); // Adapter does not forward any brightness in this case. verify(mMockedBacklight, never()).setBrightness(anyFloat()); @@ -864,4 +883,23 @@ public class LocalDisplayAdapterTest { } } + private TypedArray createFloatTypedArray(float[] vals) { + TypedArray mockArray = mock(TypedArray.class); + when(mockArray.length()).thenAnswer(invocation -> { + return vals.length; + }); + when(mockArray.getFloat(anyInt(), anyFloat())).thenAnswer(invocation -> { + final float def = (float) invocation.getArguments()[1]; + if (vals == null) { + return def; + } + int idx = (int) invocation.getArguments()[0]; + if (idx >= 0 && idx < vals.length) { + return vals[idx]; + } else { + return def; + } + }); + return mockArray; + } } diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java index 775276bd03f6..f7f592886473 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java @@ -19,6 +19,7 @@ package com.android.server.job.controllers; import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED; import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; @@ -611,6 +612,7 @@ public class ConnectivityControllerTest { private static NetworkCapabilities createCapabilities() { return new NetworkCapabilities().addCapability(NET_CAPABILITY_INTERNET) + .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED) .addCapability(NET_CAPABILITY_VALIDATED); } diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java index 68d6557880c4..c4c9ad088e45 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java @@ -352,7 +352,8 @@ public class LocationProviderManagerTest { @Test public void testGetLastLocation_ClearOnMockRemoval() { - MockLocationProvider mockProvider = new MockLocationProvider(PROPERTIES, PROVIDER_IDENTITY); + MockLocationProvider mockProvider = new MockLocationProvider(PROPERTIES, PROVIDER_IDENTITY, + null); mockProvider.setAllowed(true); mManager.setMockProvider(mockProvider); @@ -1048,7 +1049,7 @@ public class LocationProviderManagerTest { private final ArrayList<Runnable> mFlushCallbacks = new ArrayList<>(); TestProvider(ProviderProperties properties, CallerIdentity identity) { - super(DIRECT_EXECUTOR, identity, properties); + super(DIRECT_EXECUTOR, identity, properties, null); } public void setProviderAllowed(boolean allowed) { diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/MockableLocationProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/MockableLocationProviderTest.java index 07170dacb4da..e8a0bb51e20f 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/provider/MockableLocationProviderTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/MockableLocationProviderTest.java @@ -71,7 +71,8 @@ public class MockableLocationProviderTest { .setPowerUsage(POWER_USAGE_LOW) .setAccuracy(ACCURACY_FINE) .build(), - CallerIdentity.forTest(0, 1, "testpackage", "test")); + CallerIdentity.forTest(0, 1, "testpackage", "test"), + null); mProvider = new MockableLocationProvider(lock); mProvider.getController().setListener(mListener); diff --git a/services/tests/mockingservicestests/src/com/android/server/location/test/FakeProvider.java b/services/tests/mockingservicestests/src/com/android/server/location/test/FakeProvider.java index 775bdd580157..a1eadbe4a64f 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/test/FakeProvider.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/test/FakeProvider.java @@ -40,7 +40,7 @@ public class FakeProvider extends AbstractLocationProvider { private final FakeProviderInterface mFakeInterface; public FakeProvider(FakeProviderInterface fakeInterface) { - super(Runnable::run, null, null); + super(Runnable::run, null, null, null); mFakeInterface = fakeInterface; } diff --git a/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java index 488e5cdf33b9..1870df9ecf17 100644 --- a/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java @@ -30,6 +30,7 @@ import com.android.internal.os.BatteryStatsImpl; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @@ -59,6 +60,7 @@ public final class BatteryStatsServiceTest { } @Test + @Ignore("b/180015146") public void testAwaitCompletion() throws Exception { final CountDownLatch readyLatch = new CountDownLatch(2); final CountDownLatch startLatch = new CountDownLatch(1); 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 c3900e629c25..87100a63e35e 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -828,7 +828,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { * {@link DevicePolicyManager#forceRemoveActiveAdmin(ComponentName, int)} */ @Test - public void testForceRemoveActiveAdmin() throws Exception { + public void testForceRemoveActiveAdmin_nonShellCaller() throws Exception { mContext.callerPermissions.add(android.Manifest.permission.MANAGE_DEVICE_ADMINS); // Add admin. @@ -842,8 +842,53 @@ public class DevicePolicyManagerTest extends DpmTestBase { // Calling from a non-shell uid should fail with a SecurityException mContext.binder.callingUid = 123456; assertExpectException(SecurityException.class, - /* messageRegex =*/ "Non-shell user attempted to call", + /* messageRegex = */ null, () -> dpms.forceRemoveActiveAdmin(admin1, CALLER_USER_HANDLE)); + } + + /** + * Test for: + * {@link DevicePolicyManager#forceRemoveActiveAdmin(ComponentName, int)} + */ + @Test + public void testForceRemoveActiveAdmin_nonShellCallerWithPermission() throws Exception { + mContext.callerPermissions.add(android.Manifest.permission.MANAGE_DEVICE_ADMINS); + + // Add admin. + setupPackageInPackageManager(admin1.getPackageName(), + /* userId= */ CALLER_USER_HANDLE, + /* appId= */ 10138, + /* flags= */ ApplicationInfo.FLAG_TEST_ONLY); + dpm.setActiveAdmin(admin1, /* replace =*/ false); + assertThat(dpm.isAdminActive(admin1)).isTrue(); + + mContext.binder.callingUid = 123456; + mContext.callerPermissions.add( + android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS); + dpms.forceRemoveActiveAdmin(admin1, CALLER_USER_HANDLE); + + mContext.callerPermissions.add(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL); + // Verify + assertThat(dpm.isAdminActiveAsUser(admin1, CALLER_USER_HANDLE)).isFalse(); + verify(getServices().usageStatsManagerInternal).setActiveAdminApps( + null, CALLER_USER_HANDLE); + } + + /** + * Test for: + * {@link DevicePolicyManager#forceRemoveActiveAdmin(ComponentName, int)} + */ + @Test + public void testForceRemoveActiveAdmin_ShellCaller() throws Exception { + mContext.callerPermissions.add(android.Manifest.permission.MANAGE_DEVICE_ADMINS); + + // Add admin. + setupPackageInPackageManager(admin1.getPackageName(), + /* userId= */ CALLER_USER_HANDLE, + /* appId= */ 10138, + /* flags= */ ApplicationInfo.FLAG_TEST_ONLY); + dpm.setActiveAdmin(admin1, /* replace =*/ false); + assertThat(dpm.isAdminActive(admin1)).isTrue(); mContext.binder.callingUid = Process.SHELL_UID; dpms.forceRemoveActiveAdmin(admin1, CALLER_USER_HANDLE); diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java index 23a4c2f417c5..54825ee2745a 100644 --- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java @@ -64,7 +64,6 @@ public class AutomaticBrightnessControllerTest { @Mock HysteresisLevels mAmbientBrightnessThresholds; @Mock HysteresisLevels mScreenBrightnessThresholds; @Mock Handler mNoOpHandler; - @Mock DisplayDeviceConfig mDisplayDeviceConfig; @Mock DisplayDevice mDisplayDevice; private static final int LIGHT_SENSOR_WARMUP_TIME = 0; diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java index f0b4f1bec77b..285806b5dcd7 100644 --- a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java +++ b/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java @@ -88,7 +88,9 @@ public class BrightnessMappingStrategyTest { }; private static final float[] DISPLAY_RANGE_NITS = { 2.685f, 478.5f }; - private static final int[] BACKLIGHT_RANGE = { 1, 255 }; + private static final float[] DISPLAY_LEVELS_RANGE_NITS = { 13.25f, 478.5f }; + private static final float[] BACKLIGHT_RANGE_ZERO_TO_ONE = { 0.0f, 1.0f }; + private static final float[] DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT = { 0.03149606299f, 1.0f }; private static final float[] EMPTY_FLOAT_ARRAY = new float[0]; private static final int[] EMPTY_INT_ARRAY = new int[0]; @@ -114,25 +116,28 @@ public class BrightnessMappingStrategyTest { }; private static final Spline GAMMA_CORRECTION_SPLINE = Spline.createSpline( new float[] { 0.0f, 100.0f, 1000.0f, 2500.0f, 4000.0f, 4900.0f, 5000.0f }, - new float[] { 0.035f, 0.035f, 0.221f, 0.523f, 0.797f, 0.980f, 1.0f }); + new float[] { 0.0475f, 0.0475f, 0.2225f, 0.5140f, 0.8056f, 0.9805f, 1.0f }); @Test public void testSimpleStrategyMappingAtControlPoints() { Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT); - BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res); + DisplayDeviceConfig ddc = createDdc(); + BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc); assertNotNull("BrightnessMappingStrategy should not be null", simple); for (int i = 0; i < LUX_LEVELS.length; i++) { - final float expectedLevel = - (float) DISPLAY_LEVELS_BACKLIGHT[i] / PowerManager.BRIGHTNESS_ON; + final float expectedLevel = MathUtils.map(PowerManager.BRIGHTNESS_OFF + 1, + PowerManager.BRIGHTNESS_ON, PowerManager.BRIGHTNESS_MIN, + PowerManager.BRIGHTNESS_MAX, DISPLAY_LEVELS_BACKLIGHT[i]); assertEquals(expectedLevel, - simple.getBrightness(LUX_LEVELS[i]), 0.01f /*tolerance*/); + simple.getBrightness(LUX_LEVELS[i]), 0.0001f /*tolerance*/); } } @Test public void testSimpleStrategyMappingBetweenControlPoints() { Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT); - BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res); + DisplayDeviceConfig ddc = createDdc(); + BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc); assertNotNull("BrightnessMappingStrategy should not be null", simple); for (int i = 1; i < LUX_LEVELS.length; i++) { final float lux = (LUX_LEVELS[i - 1] + LUX_LEVELS[i]) / 2; @@ -146,66 +151,71 @@ public class BrightnessMappingStrategyTest { @Test public void testSimpleStrategyIgnoresNewConfiguration() { Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT); - BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res); + DisplayDeviceConfig ddc = createDdc(); + BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc); - final int N = LUX_LEVELS.length; final float[] lux = { 0f, 1f }; final float[] nits = { 0, PowerManager.BRIGHTNESS_ON }; BrightnessConfiguration config = new BrightnessConfiguration.Builder(lux, nits) .build(); strategy.setBrightnessConfiguration(config); - assertNotEquals(1.0f, strategy.getBrightness(1f), 0.01 /*tolerance*/); + assertNotEquals(1.0f, strategy.getBrightness(1f), 0.0001f /*tolerance*/); } @Test public void testSimpleStrategyIgnoresNullConfiguration() { Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT); - BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res); + DisplayDeviceConfig ddc = createDdc(); + BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc); strategy.setBrightnessConfiguration(null); final int N = DISPLAY_LEVELS_BACKLIGHT.length; final float expectedBrightness = (float) DISPLAY_LEVELS_BACKLIGHT[N - 1] / PowerManager.BRIGHTNESS_ON; assertEquals(expectedBrightness, - strategy.getBrightness(LUX_LEVELS[N - 1]), 0.01 /*tolerance*/); + strategy.getBrightness(LUX_LEVELS[N - 1]), 0.0001f /*tolerance*/); } @Test public void testPhysicalStrategyMappingAtControlPoints() { - Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_NITS, - DISPLAY_RANGE_NITS, BACKLIGHT_RANGE); - BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res); + Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_NITS); + DisplayDeviceConfig ddc = createDdc(); + BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc); assertNotNull("BrightnessMappingStrategy should not be null", physical); for (int i = 0; i < LUX_LEVELS.length; i++) { - final float expectedLevel = DISPLAY_LEVELS_NITS[i] / DISPLAY_RANGE_NITS[1]; + final float expectedLevel = MathUtils.map(DISPLAY_RANGE_NITS[0], DISPLAY_RANGE_NITS[1], + DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT[0], + DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT[1], + DISPLAY_LEVELS_NITS[i]); assertEquals(expectedLevel, - physical.getBrightness(LUX_LEVELS[i]), 0.01f /*tolerance*/); + physical.getBrightness(LUX_LEVELS[i]), + 0.0001f /*tolerance*/); } } @Test public void testPhysicalStrategyMappingBetweenControlPoints() { - Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_NITS, - DISPLAY_RANGE_NITS, BACKLIGHT_RANGE); - BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res); + Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_NITS); + DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE); + BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc); assertNotNull("BrightnessMappingStrategy should not be null", physical); - Spline backlightToBrightness = - Spline.createSpline(toFloatArray(BACKLIGHT_RANGE), DISPLAY_RANGE_NITS); + Spline brightnessToNits = + Spline.createSpline(BACKLIGHT_RANGE_ZERO_TO_ONE, DISPLAY_RANGE_NITS); for (int i = 1; i < LUX_LEVELS.length; i++) { - final float lux = (LUX_LEVELS[i - 1] + LUX_LEVELS[i]) / 2; - final float backlight = physical.getBrightness(lux) * PowerManager.BRIGHTNESS_ON; - final float nits = backlightToBrightness.interpolate(backlight); - assertTrue("Desired brightness should be between adjacent control points.", + final float lux = (LUX_LEVELS[i - 1] + LUX_LEVELS[i]) / 2.0f; + final float brightness = physical.getBrightness(lux); + final float nits = brightnessToNits.interpolate(brightness); + assertTrue("Desired brightness should be between adjacent control points: " + nits, nits > DISPLAY_LEVELS_NITS[i - 1] && nits < DISPLAY_LEVELS_NITS[i]); } } @Test public void testPhysicalStrategyUsesNewConfigurations() { - Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_NITS, - DISPLAY_RANGE_NITS, BACKLIGHT_RANGE); - BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res); + Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_NITS); + DisplayDeviceConfig ddc = createDdc(); + BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc); final float[] lux = { 0f, 1f }; final float[] nits = { @@ -216,46 +226,53 @@ public class BrightnessMappingStrategyTest { BrightnessConfiguration config = new BrightnessConfiguration.Builder(lux, nits) .build(); strategy.setBrightnessConfiguration(config); - assertEquals(1.0f, strategy.getBrightness(1f), 0.01 /*tolerance*/); + assertEquals(1.0f, strategy.getBrightness(1f), 0.0001f /*tolerance*/); // Check that null returns us to the default configuration. strategy.setBrightnessConfiguration(null); final int N = DISPLAY_LEVELS_NITS.length; final float expectedBrightness = DISPLAY_LEVELS_NITS[N - 1] / DISPLAY_RANGE_NITS[1]; assertEquals(expectedBrightness, - strategy.getBrightness(LUX_LEVELS[N - 1]), 0.01f /*tolerance*/); + strategy.getBrightness(LUX_LEVELS[N - 1]), 0.0001f /*tolerance*/); } @Test public void testPhysicalStrategyRecalculateSplines() { - Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_NITS, DISPLAY_RANGE_NITS, - BACKLIGHT_RANGE); - BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res); + Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_NITS); + DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS); + BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc); float[] adjustedNits50p = new float[DISPLAY_RANGE_NITS.length]; for (int i = 0; i < DISPLAY_RANGE_NITS.length; i++) { adjustedNits50p[i] = DISPLAY_RANGE_NITS[i] * 0.5f; } // Default is unadjusted - assertEquals(2.685f, strategy.convertToNits(BACKLIGHT_RANGE[0]), 0.01f /* tolerance */); - assertEquals(478.5f, strategy.convertToNits(BACKLIGHT_RANGE[1]), 0.01f /* tolerance */); + assertEquals(DISPLAY_RANGE_NITS[0], strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[0]), + 0.0001f /* tolerance */); + assertEquals(DISPLAY_RANGE_NITS[1], strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[1]), + 0.0001f /* tolerance */); // When adjustment is turned on, adjustment array is used strategy.recalculateSplines(true, adjustedNits50p); - assertEquals(1.3425f, strategy.convertToNits(BACKLIGHT_RANGE[0]), 0.01f /* tolerance */); - assertEquals(239.25f, strategy.convertToNits(BACKLIGHT_RANGE[1]), 0.01f /* tolerance */); + assertEquals(DISPLAY_RANGE_NITS[0] / 2, + strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[0]), 0.0001f /* tolerance */); + assertEquals(DISPLAY_RANGE_NITS[1] / 2, + strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[1]), 0.0001f /* tolerance */); // When adjustment is turned off, adjustment array is ignored strategy.recalculateSplines(false, adjustedNits50p); - assertEquals(2.685f, strategy.convertToNits(BACKLIGHT_RANGE[0]), 0.01f /* tolerance */); - assertEquals(478.5f, strategy.convertToNits(BACKLIGHT_RANGE[1]), 0.01f /* tolerance */); + assertEquals(DISPLAY_RANGE_NITS[0], strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[0]), + 0.0001f /* tolerance */); + assertEquals(DISPLAY_RANGE_NITS[1], strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[1]), + 0.0001f /* tolerance */); } @Test public void testDefaultStrategyIsPhysical() { Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT, - DISPLAY_LEVELS_NITS, DISPLAY_RANGE_NITS, BACKLIGHT_RANGE); - BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res); + DISPLAY_LEVELS_NITS); + DisplayDeviceConfig ddc = createDdc(); + BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc); assertTrue(strategy instanceof BrightnessMappingStrategy.PhysicalMappingStrategy); } @@ -266,15 +283,15 @@ public class BrightnessMappingStrategyTest { int tmp = lux[idx]; lux[idx] = lux[idx+1]; lux[idx+1] = tmp; - Resources res = createResources(lux, DISPLAY_LEVELS_NITS, - DISPLAY_RANGE_NITS, BACKLIGHT_RANGE); - BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res); + Resources res = createResources(lux, DISPLAY_LEVELS_NITS); + DisplayDeviceConfig ddc = createDdc(); + BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc); assertNull(strategy); // And make sure we get the same result even if it's monotone but not increasing. lux[idx] = lux[idx+1]; - res = createResources(lux, DISPLAY_LEVELS_NITS, DISPLAY_RANGE_NITS, BACKLIGHT_RANGE); - strategy = BrightnessMappingStrategy.create(res); + res = createResources(lux, DISPLAY_LEVELS_NITS); + strategy = BrightnessMappingStrategy.create(res, ddc); assertNull(strategy); } @@ -285,13 +302,13 @@ public class BrightnessMappingStrategyTest { // Make sure it's strictly increasing so that the only failure is the differing array // lengths lux[lux.length - 1] = lux[lux.length - 2] + 1; - Resources res = createResources(lux, DISPLAY_LEVELS_NITS, - DISPLAY_RANGE_NITS, BACKLIGHT_RANGE); - BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res); + Resources res = createResources(lux, DISPLAY_LEVELS_NITS); + DisplayDeviceConfig ddc = createDdc(); + BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc); assertNull(strategy); res = createResources(lux, DISPLAY_LEVELS_BACKLIGHT); - strategy = BrightnessMappingStrategy.create(res); + strategy = BrightnessMappingStrategy.create(res, ddc); assertNull(strategy); // Extra backlight level @@ -299,43 +316,45 @@ public class BrightnessMappingStrategyTest { DISPLAY_LEVELS_BACKLIGHT, DISPLAY_LEVELS_BACKLIGHT.length+1); backlight[backlight.length - 1] = backlight[backlight.length - 2] + 1; res = createResources(LUX_LEVELS, backlight); - strategy = BrightnessMappingStrategy.create(res); + strategy = BrightnessMappingStrategy.create(res, ddc); assertNull(strategy); // Extra nits level final float[] nits = Arrays.copyOf(DISPLAY_RANGE_NITS, DISPLAY_LEVELS_NITS.length+1); nits[nits.length - 1] = nits[nits.length - 2] + 1; - res = createResources(LUX_LEVELS, nits, DISPLAY_RANGE_NITS, BACKLIGHT_RANGE); - strategy = BrightnessMappingStrategy.create(res); + res = createResources(LUX_LEVELS, nits); + strategy = BrightnessMappingStrategy.create(res, ddc); assertNull(strategy); } @Test public void testPhysicalStrategyRequiresNitsMapping() { Resources res = createResources(LUX_LEVELS, EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/, - DISPLAY_LEVELS_NITS, EMPTY_FLOAT_ARRAY /*nitsRange*/, BACKLIGHT_RANGE); - BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res); + DISPLAY_LEVELS_NITS); + DisplayDeviceConfig ddc = createDdc(EMPTY_FLOAT_ARRAY /*nitsRange*/); + BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc); assertNull(physical); res = createResources(LUX_LEVELS, EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/, - DISPLAY_LEVELS_NITS, DISPLAY_RANGE_NITS, EMPTY_INT_ARRAY /*backlightRange*/); - physical = BrightnessMappingStrategy.create(res); + DISPLAY_LEVELS_NITS); + physical = BrightnessMappingStrategy.create(res, ddc); assertNull(physical); res = createResources(LUX_LEVELS, EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/, - DISPLAY_LEVELS_NITS, EMPTY_FLOAT_ARRAY /*nitsRange*/, - EMPTY_INT_ARRAY /*backlightRange*/); - physical = BrightnessMappingStrategy.create(res); + DISPLAY_LEVELS_NITS); + physical = BrightnessMappingStrategy.create(res, ddc); assertNull(physical); } @Test public void testStrategiesAdaptToUserDataPoint() { - Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_NITS, - DISPLAY_RANGE_NITS, BACKLIGHT_RANGE); - assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res)); + Resources res = createResources(LUX_LEVELS, EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/, + DISPLAY_LEVELS_NITS); + DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE); + assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res, ddc)); + ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE); res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT); - assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res)); + assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res, ddc)); } private static void assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy strategy) { @@ -351,7 +370,7 @@ public class BrightnessMappingStrategyTest { // Then make sure that all control points after the middle lux level are also set to max... for (int i = idx; i < LUX_LEVELS.length; i++) { - assertEquals(strategy.getBrightness(LUX_LEVELS[idx]), 1.0, 0.01 /*tolerance*/); + assertEquals(strategy.getBrightness(LUX_LEVELS[idx]), 1.0, 0.0001f /*tolerance*/); } // ...and that all control points before the middle lux level are strictly less than the @@ -369,12 +388,12 @@ public class BrightnessMappingStrategyTest { strategy.clearUserDataPoints(); for (int i = 0; i < LUX_LEVELS.length; i++) { assertEquals(initialBrightnessLevels[i], strategy.getBrightness(LUX_LEVELS[i]), - 0.01 /*tolerance*/); + 0.0001f /*tolerance*/); } // Now set the middle of the lux range to something just above the minimum. float minBrightness = strategy.getBrightness(LUX_LEVELS[0]); - strategy.addUserDataPoint(LUX_LEVELS[idx], minBrightness + 0.01f); + strategy.addUserDataPoint(LUX_LEVELS[idx], minBrightness + 0.0001f); // Then make sure the curve is still monotonic. prevBrightness = 0f; @@ -389,31 +408,21 @@ public class BrightnessMappingStrategyTest { // be true assuming that there are more than two lux levels in the curve since we picked a // brightness just barely above the minimum for the middle of the curve. minBrightness = (float) MathUtils.pow(minBrightness, MAXIMUM_GAMMA); // Gamma correction. - assertEquals(minBrightness, strategy.getBrightness(LUX_LEVELS[0]), 0.01 /*tolerance*/); - } - - private static float[] toFloatArray(int[] vals) { - float[] newVals = new float[vals.length]; - for (int i = 0; i < vals.length; i++) { - newVals[i] = (float) vals[i]; - } - return newVals; + assertEquals(minBrightness, strategy.getBrightness(LUX_LEVELS[0]), 0.0001f /*tolerance*/); } private Resources createResources(int[] luxLevels, int[] brightnessLevelsBacklight) { return createResources(luxLevels, brightnessLevelsBacklight, - EMPTY_FLOAT_ARRAY /*brightnessLevelsNits*/, EMPTY_FLOAT_ARRAY /*nitsRange*/, - EMPTY_INT_ARRAY /*backlightRange*/); + EMPTY_FLOAT_ARRAY /*brightnessLevelsNits*/); } - private Resources createResources(int[] luxLevels, float[] brightnessLevelsNits, - float[] nitsRange, int[] backlightRange) { + private Resources createResources(int[] luxLevels, float[] brightnessLevelsNits) { return createResources(luxLevels, EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/, - brightnessLevelsNits, nitsRange, backlightRange); + brightnessLevelsNits); } private Resources createResources(int[] luxLevels, int[] brightnessLevelsBacklight, - float[] brightnessLevelsNits, float[] nitsRange, int[] backlightRange) { + float[] brightnessLevelsNits) { Resources mockResources = mock(Resources.class); // For historical reasons, the lux levels resource implicitly defines the first point as 0, // so we need to chop it off of the array the mock resource object returns. @@ -430,15 +439,6 @@ public class BrightnessMappingStrategyTest { com.android.internal.R.array.config_autoBrightnessDisplayValuesNits)) .thenReturn(mockBrightnessLevelNits); - TypedArray mockNitsRange = createFloatTypedArray(nitsRange); - when(mockResources.obtainTypedArray( - com.android.internal.R.array.config_screenBrightnessNits)) - .thenReturn(mockNitsRange); - - when(mockResources.getIntArray( - com.android.internal.R.array.config_screenBrightnessBacklight)) - .thenReturn(backlightRange); - when(mockResources.getInteger( com.android.internal.R.integer.config_screenBrightnessSettingMinimum)) .thenReturn(1); @@ -451,6 +451,21 @@ public class BrightnessMappingStrategyTest { return mockResources; } + private DisplayDeviceConfig createDdc() { + return createDdc(DISPLAY_RANGE_NITS); + } + + private DisplayDeviceConfig createDdc(float[] nitsArray) { + return createDdc(nitsArray, DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT); + } + + private DisplayDeviceConfig createDdc(float[] nitsArray, float[] backlightArray) { + DisplayDeviceConfig mockDdc = mock(DisplayDeviceConfig.class); + when(mockDdc.getNits()).thenReturn(nitsArray); + when(mockDdc.getBrightness()).thenReturn(backlightArray); + return mockDdc; + } + private TypedArray createFloatTypedArray(float[] vals) { TypedArray mockArray = mock(TypedArray.class); when(mockArray.length()).thenAnswer(invocation -> { @@ -488,21 +503,22 @@ public class BrightnessMappingStrategyTest { final float y1 = GAMMA_CORRECTION_SPLINE.interpolate(x1); final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2); final float y3 = GAMMA_CORRECTION_SPLINE.interpolate(x3); - Resources resources = createResources(GAMMA_CORRECTION_LUX, GAMMA_CORRECTION_NITS, - DISPLAY_LEVELS_NITS, DISPLAY_LEVELS_BACKLIGHT); - BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources); + + Resources resources = createResources(GAMMA_CORRECTION_LUX, GAMMA_CORRECTION_NITS); + DisplayDeviceConfig ddc = createDdc(); + BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc); // Let's start with a validity check: - assertEquals(y1, strategy.getBrightness(x1), 0.01f /* tolerance */); - assertEquals(y2, strategy.getBrightness(x2), 0.01f /* tolerance */); - assertEquals(y3, strategy.getBrightness(x3), 0.01f /* tolerance */); + assertEquals(y1, strategy.getBrightness(x1), 0.0001f /* tolerance */); + assertEquals(y2, strategy.getBrightness(x2), 0.0001f /* tolerance */); + assertEquals(y3, strategy.getBrightness(x3), 0.0001f /* tolerance */); // OK, let's roll: float gamma = 0.5f; strategy.addUserDataPoint(x2, (float) MathUtils.pow(y2, gamma)); - assertEquals(MathUtils.pow(y1, gamma), strategy.getBrightness(x1), 0.01f /* tolerance */); - assertEquals(MathUtils.pow(y2, gamma), strategy.getBrightness(x2), 0.01f /* tolerance */); - assertEquals(MathUtils.pow(y3, gamma), strategy.getBrightness(x3), 0.01f /* tolerance */); - // The adjustment should be +0.63 (manual calculation). - assertEquals(+0.63f, strategy.getAutoBrightnessAdjustment(), 0.01f /* tolerance */); + assertEquals(MathUtils.pow(y1, gamma), strategy.getBrightness(x1), 0.0001f /* tolerance */); + assertEquals(MathUtils.pow(y2, gamma), strategy.getBrightness(x2), 0.0001f /* tolerance */); + assertEquals(MathUtils.pow(y3, gamma), strategy.getBrightness(x3), 0.0001f /* tolerance */); + // The adjustment should be +0.6308 (manual calculation). + assertEquals(+0.6308f, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */); } @Test @@ -516,39 +532,39 @@ public class BrightnessMappingStrategyTest { final float y1 = GAMMA_CORRECTION_SPLINE.interpolate(x1); final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2); final float y3 = GAMMA_CORRECTION_SPLINE.interpolate(x3); - Resources resources = createResources(GAMMA_CORRECTION_LUX, GAMMA_CORRECTION_NITS, - DISPLAY_LEVELS_NITS, DISPLAY_LEVELS_BACKLIGHT); - BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources); + Resources resources = createResources(GAMMA_CORRECTION_LUX, GAMMA_CORRECTION_NITS); + DisplayDeviceConfig ddc = createDdc(); + BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc); // Validity check: - assertEquals(y1, strategy.getBrightness(x1), 0.01f /* tolerance */); - assertEquals(y2, strategy.getBrightness(x2), 0.01f /* tolerance */); - assertEquals(y3, strategy.getBrightness(x3), 0.01f /* tolerance */); + assertEquals(y1, strategy.getBrightness(x1), 0.0001f /* tolerance */); + assertEquals(y2, strategy.getBrightness(x2), 0.0001f /* tolerance */); + assertEquals(y3, strategy.getBrightness(x3), 0.0001f /* tolerance */); // Let's roll: float gamma = 0.25f; final float minGamma = 1.0f / MAXIMUM_GAMMA; strategy.addUserDataPoint(x2, (float) MathUtils.pow(y2, gamma)); - assertEquals(MathUtils.pow(y1, minGamma), strategy.getBrightness(x1), - 0.01f /* tolerance */); - assertEquals(MathUtils.pow(y2, gamma), strategy.getBrightness(x2), - 0.01f /* tolerance */); - assertEquals(MathUtils.pow(y3, minGamma), strategy.getBrightness(x3), - 0.01f /* tolerance */); + assertEquals(MathUtils.pow(y1, minGamma), + strategy.getBrightness(x1), 0.0001f /* tolerance */); + assertEquals(MathUtils.pow(y2, gamma), + strategy.getBrightness(x2), 0.0001f /* tolerance */); + assertEquals(MathUtils.pow(y3, minGamma), + strategy.getBrightness(x3), 0.0001f /* tolerance */); // The adjustment should be +1.0 (maximum adjustment). - assertEquals(+1.0f, strategy.getAutoBrightnessAdjustment(), 0.01f /* tolerance */); + assertEquals(+1.0f, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */); } @Test public void testGammaCorrectionExtremeChangeAtCenter() { // Extreme changes (e.g. setting brightness to 0.0 or 1.0) can't be gamma corrected, so we // just make sure the adjustment reflects the change. - Resources resources = createResources(GAMMA_CORRECTION_LUX, GAMMA_CORRECTION_NITS, - DISPLAY_LEVELS_NITS, DISPLAY_LEVELS_BACKLIGHT); - BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources); - assertEquals(0.0f, strategy.getAutoBrightnessAdjustment(), 0.01f /* tolerance */); + Resources resources = createResources(GAMMA_CORRECTION_LUX, GAMMA_CORRECTION_NITS); + DisplayDeviceConfig ddc = createDdc(); + BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc); + assertEquals(0.0f, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */); strategy.addUserDataPoint(2500, 1.0f); - assertEquals(+1.0f, strategy.getAutoBrightnessAdjustment(), 0.01f /* tolerance */); + assertEquals(+1.0f, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */); strategy.addUserDataPoint(2500, 0.0f); - assertEquals(-1.0f, strategy.getAutoBrightnessAdjustment(), 0.01f /* tolerance */); + assertEquals(-1.0f, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */); } @Test @@ -562,28 +578,28 @@ public class BrightnessMappingStrategyTest { final float y0 = GAMMA_CORRECTION_SPLINE.interpolate(x0); final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2); final float y4 = GAMMA_CORRECTION_SPLINE.interpolate(x4); - Resources resources = createResources(GAMMA_CORRECTION_LUX, GAMMA_CORRECTION_NITS, - DISPLAY_LEVELS_NITS, DISPLAY_LEVELS_BACKLIGHT); - BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources); + Resources resources = createResources(GAMMA_CORRECTION_LUX, GAMMA_CORRECTION_NITS); + DisplayDeviceConfig ddc = createDdc(); + BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc); // Validity, as per tradition: - assertEquals(y0, strategy.getBrightness(x0), 0.01f /* tolerance */); - assertEquals(y2, strategy.getBrightness(x2), 0.01f /* tolerance */); - assertEquals(y4, strategy.getBrightness(x4), 0.01f /* tolerance */); + assertEquals(y0, strategy.getBrightness(x0), 0.0001f /* tolerance */); + assertEquals(y2, strategy.getBrightness(x2), 0.0001f /* tolerance */); + assertEquals(y4, strategy.getBrightness(x4), 0.0001f /* tolerance */); // Rollin': float adjustment = 0.3f; float gamma = (float) MathUtils.pow(MAXIMUM_GAMMA, -adjustment); strategy.addUserDataPoint(x0, y0 + adjustment); - assertEquals(y0 + adjustment, strategy.getBrightness(x0), 0.01f /* tolerance */); - assertEquals(MathUtils.pow(y2, gamma), strategy.getBrightness(x2), 0.01f /* tolerance */); - assertEquals(MathUtils.pow(y4, gamma), strategy.getBrightness(x4), 0.01f /* tolerance */); - assertEquals(adjustment, strategy.getAutoBrightnessAdjustment(), 0.01f /* tolerance */); + assertEquals(y0 + adjustment, strategy.getBrightness(x0), 0.0001f /* tolerance */); + assertEquals(MathUtils.pow(y2, gamma), strategy.getBrightness(x2), 0.0001f /* tolerance */); + assertEquals(MathUtils.pow(y4, gamma), strategy.getBrightness(x4), 0.0001f /* tolerance */); + assertEquals(adjustment, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */); // Similarly, if we set a user data point at (x4, 1.0), the adjustment should be 1 - y4. adjustment = 1.0f - y4; gamma = (float) MathUtils.pow(MAXIMUM_GAMMA, -adjustment); strategy.addUserDataPoint(x4, 1.0f); - assertEquals(MathUtils.pow(y0, gamma), strategy.getBrightness(x0), 0.01f /* tolerance */); - assertEquals(MathUtils.pow(y2, gamma), strategy.getBrightness(x2), 0.01f /* tolerance */); - assertEquals(1.0f, strategy.getBrightness(x4), 0.01f /* tolerance */); - assertEquals(adjustment, strategy.getAutoBrightnessAdjustment(), 0.01f /* tolerance */); + assertEquals(MathUtils.pow(y0, gamma), strategy.getBrightness(x0), 0.0001f /* tolerance */); + assertEquals(MathUtils.pow(y2, gamma), strategy.getBrightness(x2), 0.0001f /* tolerance */); + assertEquals(1.0f, strategy.getBrightness(x4), 0.0001f /* tolerance */); + assertEquals(adjustment, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */); } } diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/PersistentSystemFontConfigTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/PersistentSystemFontConfigTest.java index 27fce3c37fd9..912da9414165 100644 --- a/services/tests/servicestests/src/com/android/server/graphics/fonts/PersistentSystemFontConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/PersistentSystemFontConfigTest.java @@ -45,7 +45,7 @@ public final class PersistentSystemFontConfigTest { public void testWriteRead() throws Exception { long expectedModifiedDate = 1234567890; PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config(); - config.lastModifiedDate = expectedModifiedDate; + config.lastModifiedMillis = expectedModifiedDate; config.updatedFontDirs.add("~~abc"); config.updatedFontDirs.add("~~def"); @@ -65,7 +65,7 @@ public final class PersistentSystemFontConfigTest { PersistentSystemFontConfig.Config another = new PersistentSystemFontConfig.Config(); PersistentSystemFontConfig.loadFromXml(bais, another); - assertThat(another.lastModifiedDate).isEqualTo(expectedModifiedDate); + assertThat(another.lastModifiedMillis).isEqualTo(expectedModifiedDate); assertThat(another.updatedFontDirs).containsExactly("~~abc", "~~def"); assertThat(another.fontFamilies).containsExactly(fontFamily); } @@ -82,7 +82,7 @@ public final class PersistentSystemFontConfigTest { new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))) { PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config(); PersistentSystemFontConfig.loadFromXml(bais, config); - assertThat(config.lastModifiedDate).isEqualTo(0); + assertThat(config.lastModifiedMillis).isEqualTo(0); } } diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java index 7771afc8c7f1..843296e31800 100644 --- a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java +++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java @@ -151,7 +151,7 @@ public final class UpdatableFontDirTest { FakeFontFileParser parser = new FakeFontFileParser(); FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config(); - config.lastModifiedDate = expectedModifiedDate; + config.lastModifiedMillis = expectedModifiedDate; writeConfig(config, mConfigFile); UpdatableFontDir dirForPreparation = new UpdatableFontDir( mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, @@ -507,7 +507,7 @@ public final class UpdatableFontDirTest { File readonlyFile = new File(readonlyDir, "readonly_config.xml"); PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config(); - config.lastModifiedDate = expectedModifiedDate; + config.lastModifiedMillis = expectedModifiedDate; writeConfig(config, readonlyFile); assertThat(readonlyDir.setWritable(false, false)).isTrue(); 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 7d7af03ecd3d..74bf4f5da70d 100644 --- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java @@ -115,7 +115,6 @@ import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkPolicy; -import android.net.NetworkPolicyManager; import android.net.NetworkState; import android.net.NetworkStats; import android.net.NetworkStatsHistory; @@ -386,8 +385,7 @@ public class NetworkPolicyManagerServiceTest { Log.d(TAG, "set mUidObserver to " + mUidObserver); return null; } - }).when(mActivityManager).registerUidObserver(any(), anyInt(), - eq(NetworkPolicyManager.FOREGROUND_THRESHOLD_STATE), any(String.class)); + }).when(mActivityManager).registerUidObserver(any(), anyInt(), anyInt(), any(String.class)); mFutureIntent = newRestrictBackgroundChangedFuture(); mService = new NetworkPolicyManagerService(mServiceContext, mActivityManager, diff --git a/services/tests/servicestests/src/com/android/server/pm/OWNERS b/services/tests/servicestests/src/com/android/server/pm/OWNERS index d825dfd7cf00..e15b5f57069c 100644 --- a/services/tests/servicestests/src/com/android/server/pm/OWNERS +++ b/services/tests/servicestests/src/com/android/server/pm/OWNERS @@ -1 +1,3 @@ include /services/core/java/com/android/server/pm/OWNERS + +per-file *Shortcut* = file:/core/java/android/content/pm/SHORTCUT_OWNERS diff --git a/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java index ddbe81c81d6d..26b34fdd4e04 100644 --- a/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java @@ -318,7 +318,8 @@ public class PowerStatsServiceTest { assertTrue(pssProto.energyMeasurement.length == ENERGY_METER_COUNT); for (int i = 0; i < pssProto.energyMeasurement.length; i++) { assertTrue(pssProto.energyMeasurement[i].id == i); - assertTrue(pssProto.energyMeasurement[i].timestampMs == i); + assertTrue(pssProto.energyMeasurement[i].timestampMs == + i + mPowerStatsLogger.getStartWallTime()); assertTrue(pssProto.energyMeasurement[i].durationMs == i); assertTrue(pssProto.energyMeasurement[i].energyUws == i); } @@ -359,7 +360,8 @@ public class PowerStatsServiceTest { assertTrue(pssProto.energyConsumerResult.length == ENERGY_CONSUMER_COUNT); for (int i = 0; i < pssProto.energyConsumerResult.length; i++) { assertTrue(pssProto.energyConsumerResult[i].id == i); - assertTrue(pssProto.energyConsumerResult[i].timestampMs == i); + assertTrue(pssProto.energyConsumerResult[i].timestampMs == + i + mPowerStatsLogger.getStartWallTime()); assertTrue(pssProto.energyConsumerResult[i].energyUws == i); assertTrue(pssProto.energyConsumerResult[i].attribution.length == ENERGY_CONSUMER_ATTRIBUTION_COUNT); @@ -420,7 +422,8 @@ public class PowerStatsServiceTest { assertTrue(stateResidency.id == j); assertTrue(stateResidency.totalTimeInStateMs == j); assertTrue(stateResidency.totalStateEntryCount == j); - assertTrue(stateResidency.lastEntryTimestampMs == j); + assertTrue(stateResidency.lastEntryTimestampMs == + j + mPowerStatsLogger.getStartWallTime()); } } } diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/OWNERS b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/OWNERS index 816bc6bba639..33385afbdfd6 100644 --- a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/OWNERS +++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/OWNERS @@ -1 +1 @@ -include /core/java/android/media/soundtrigger/OWNERS +include /media/aidl/android/media/soundtrigger_middleware/OWNERS diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java index 682a80c9b297..5d2755221288 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java @@ -46,8 +46,8 @@ public class ConfigurationInternalTest { public void test_unrestricted() { ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID) .setUserConfigAllowed(true) - .setAutoDetectionSupported(true) - .setGeoDetectionSupported(true) + .setAutoDetectionFeatureSupported(true) + .setGeoDetectionFeatureSupported(true) .setAutoDetectionEnabled(true) .setLocationEnabled(true) .setGeoDetectionEnabled(true) @@ -108,8 +108,8 @@ public class ConfigurationInternalTest { public void test_restricted() { ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID) .setUserConfigAllowed(false) - .setAutoDetectionSupported(true) - .setGeoDetectionSupported(true) + .setAutoDetectionFeatureSupported(true) + .setGeoDetectionFeatureSupported(true) .setAutoDetectionEnabled(true) .setLocationEnabled(true) .setGeoDetectionEnabled(true) @@ -170,8 +170,8 @@ public class ConfigurationInternalTest { public void test_autoDetectNotSupported() { ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID) .setUserConfigAllowed(true) - .setAutoDetectionSupported(false) - .setGeoDetectionSupported(false) + .setAutoDetectionFeatureSupported(false) + .setGeoDetectionFeatureSupported(false) .setAutoDetectionEnabled(true) .setLocationEnabled(true) .setGeoDetectionEnabled(true) @@ -232,8 +232,8 @@ public class ConfigurationInternalTest { public void test_geoDetectNotSupported() { ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID) .setUserConfigAllowed(true) - .setAutoDetectionSupported(true) - .setGeoDetectionSupported(false) + .setAutoDetectionFeatureSupported(true) + .setGeoDetectionFeatureSupported(false) .setAutoDetectionEnabled(true) .setLocationEnabled(true) .setGeoDetectionEnabled(true) diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java index d2452eaee657..14e0bbd6fa42 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java @@ -364,8 +364,8 @@ public class TimeZoneDetectorServiceTest { // the tests. final boolean geoDetectionEnabled = autoDetectionEnabled; return new ConfigurationInternal.Builder(ARBITRARY_USER_ID) - .setAutoDetectionSupported(true) - .setGeoDetectionSupported(true) + .setAutoDetectionFeatureSupported(true) + .setGeoDetectionFeatureSupported(true) .setUserConfigAllowed(true) .setAutoDetectionEnabled(autoDetectionEnabled) .setLocationEnabled(geoDetectionEnabled) diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java index c8dba5f40882..f1f8b2f5e81a 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java @@ -90,8 +90,8 @@ public class TimeZoneDetectorStrategyImplTest { private static final ConfigurationInternal CONFIG_INT_USER_RESTRICTED_AUTO_DISABLED = new ConfigurationInternal.Builder(USER_ID) .setUserConfigAllowed(false) - .setAutoDetectionSupported(true) - .setGeoDetectionSupported(true) + .setAutoDetectionFeatureSupported(true) + .setGeoDetectionFeatureSupported(true) .setAutoDetectionEnabled(false) .setLocationEnabled(true) .setGeoDetectionEnabled(false) @@ -100,8 +100,8 @@ public class TimeZoneDetectorStrategyImplTest { private static final ConfigurationInternal CONFIG_INT_USER_RESTRICTED_AUTO_ENABLED = new ConfigurationInternal.Builder(USER_ID) .setUserConfigAllowed(false) - .setAutoDetectionSupported(true) - .setGeoDetectionSupported(true) + .setAutoDetectionFeatureSupported(true) + .setGeoDetectionFeatureSupported(true) .setAutoDetectionEnabled(true) .setLocationEnabled(true) .setGeoDetectionEnabled(true) @@ -110,8 +110,8 @@ public class TimeZoneDetectorStrategyImplTest { private static final ConfigurationInternal CONFIG_INT_AUTO_DETECT_NOT_SUPPORTED = new ConfigurationInternal.Builder(USER_ID) .setUserConfigAllowed(true) - .setAutoDetectionSupported(false) - .setGeoDetectionSupported(false) + .setAutoDetectionFeatureSupported(false) + .setGeoDetectionFeatureSupported(false) .setAutoDetectionEnabled(false) .setLocationEnabled(true) .setGeoDetectionEnabled(false) @@ -120,8 +120,8 @@ public class TimeZoneDetectorStrategyImplTest { private static final ConfigurationInternal CONFIG_INT_AUTO_SUPPORTED_GEO_NOT_SUPPORTED = new ConfigurationInternal.Builder(USER_ID) .setUserConfigAllowed(true) - .setAutoDetectionSupported(true) - .setGeoDetectionSupported(false) + .setAutoDetectionFeatureSupported(true) + .setGeoDetectionFeatureSupported(false) .setAutoDetectionEnabled(true) .setLocationEnabled(true) .setGeoDetectionEnabled(true) @@ -130,8 +130,8 @@ public class TimeZoneDetectorStrategyImplTest { private static final ConfigurationInternal CONFIG_INT_AUTO_DISABLED_GEO_DISABLED = new ConfigurationInternal.Builder(USER_ID) .setUserConfigAllowed(true) - .setAutoDetectionSupported(true) - .setGeoDetectionSupported(true) + .setAutoDetectionFeatureSupported(true) + .setGeoDetectionFeatureSupported(true) .setAutoDetectionEnabled(false) .setLocationEnabled(true) .setGeoDetectionEnabled(false) @@ -139,8 +139,8 @@ public class TimeZoneDetectorStrategyImplTest { private static final ConfigurationInternal CONFIG_INT_AUTO_ENABLED_GEO_DISABLED = new ConfigurationInternal.Builder(USER_ID) - .setAutoDetectionSupported(true) - .setGeoDetectionSupported(true) + .setAutoDetectionFeatureSupported(true) + .setGeoDetectionFeatureSupported(true) .setUserConfigAllowed(true) .setAutoDetectionEnabled(true) .setLocationEnabled(true) @@ -149,8 +149,8 @@ public class TimeZoneDetectorStrategyImplTest { private static final ConfigurationInternal CONFIG_INT_AUTO_ENABLED_GEO_ENABLED = new ConfigurationInternal.Builder(USER_ID) - .setAutoDetectionSupported(true) - .setGeoDetectionSupported(true) + .setAutoDetectionFeatureSupported(true) + .setGeoDetectionFeatureSupported(true) .setUserConfigAllowed(true) .setAutoDetectionEnabled(true) .setLocationEnabled(true) diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java index d319488ba73b..8280cdcb18c4 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java @@ -44,8 +44,8 @@ final class TestSupport { @UserIdInt int userId, boolean geoDetectionEnabled) { return new ConfigurationInternal.Builder(userId) .setUserConfigAllowed(true) - .setAutoDetectionSupported(true) - .setGeoDetectionSupported(true) + .setAutoDetectionFeatureSupported(true) + .setGeoDetectionFeatureSupported(true) .setAutoDetectionEnabled(true) .setLocationEnabled(true) .setGeoDetectionEnabled(geoDetectionEnabled) diff --git a/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java new file mode 100644 index 000000000000..3025a95be98c --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.policy; + +import static android.view.KeyEvent.ACTION_DOWN; +import static android.view.KeyEvent.ACTION_UP; +import static android.view.KeyEvent.KEYCODE_POWER; + +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + +import static com.android.server.policy.SingleKeyGestureDetector.KEY_LONGPRESS; +import static com.android.server.policy.SingleKeyGestureDetector.KEY_VERYLONGPRESS; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import android.app.Instrumentation; +import android.content.Context; +import android.os.SystemClock; +import android.view.KeyEvent; +import android.view.ViewConfiguration; + +import org.junit.Before; +import org.junit.Test; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * Test class for {@link SingleKeyGestureDetector}. + * + * Build/Install/Run: + * atest WmTests:SingleKeyGestureTests + */ +public class SingleKeyGestureTests { + private SingleKeyGestureDetector mDetector; + + private int mMaxMultiPressPowerCount = 2; + + private CountDownLatch mShortPressed = new CountDownLatch(1); + private CountDownLatch mLongPressed = new CountDownLatch(1); + private CountDownLatch mVeryLongPressed = new CountDownLatch(1); + private CountDownLatch mMultiPressed = new CountDownLatch(1); + + private final Instrumentation mInstrumentation = getInstrumentation(); + private final Context mContext = mInstrumentation.getTargetContext(); + private long mWaitTimeout; + private long mLongPressTime; + private long mVeryLongPressTime; + + @Before + public void setUp() { + mDetector = new SingleKeyGestureDetector(mContext); + initSingleKeyGestureRules(); + mWaitTimeout = ViewConfiguration.getMultiPressTimeout() + 50; + mLongPressTime = ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout() + 50; + mVeryLongPressTime = mContext.getResources().getInteger( + com.android.internal.R.integer.config_veryLongPressTimeout) + 50; + } + + private void initSingleKeyGestureRules() { + mDetector.addRule(new SingleKeyGestureDetector.SingleKeyRule(KEYCODE_POWER, + KEY_LONGPRESS | KEY_VERYLONGPRESS) { + @Override + int getMaxMultiPressCount() { + return mMaxMultiPressPowerCount; + } + @Override + public void onPress(long downTime) { + mShortPressed.countDown(); + } + + @Override + void onLongPress(long downTime) { + mLongPressed.countDown(); + } + + @Override + void onVeryLongPress(long downTime) { + mVeryLongPressed.countDown(); + } + + @Override + void onMultiPress(long downTime, int count) { + mMultiPressed.countDown(); + assertEquals(mMaxMultiPressPowerCount, count); + } + }); + } + + private void pressKey(long eventTime, int keyCode, long pressTime) { + final KeyEvent keyDown = new KeyEvent(eventTime, eventTime, ACTION_DOWN, + keyCode, 0 /* repeat */, 0 /* metaState */); + mDetector.interceptKey(keyDown); + + // keep press down. + try { + Thread.sleep(pressTime); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + eventTime += pressTime; + final KeyEvent keyUp = new KeyEvent(eventTime, eventTime, ACTION_UP, + keyCode, 0 /* repeat */, 0 /* metaState */); + + mDetector.interceptKey(keyUp); + } + + @Test + public void testShortPress() throws InterruptedException { + final long eventTime = SystemClock.uptimeMillis(); + pressKey(eventTime, KEYCODE_POWER, 0 /* pressTime */); + assertTrue(mShortPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); + } + + @Test + public void testLongPress() throws InterruptedException { + final long eventTime = SystemClock.uptimeMillis(); + pressKey(eventTime, KEYCODE_POWER, mLongPressTime); + assertTrue(mLongPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); + } + + @Test + public void testVeryLongPress() throws InterruptedException { + final long eventTime = SystemClock.uptimeMillis(); + pressKey(eventTime, KEYCODE_POWER, mVeryLongPressTime); + assertTrue(mVeryLongPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); + } + + @Test + public void testMultiPress() throws InterruptedException { + final long eventTime = SystemClock.uptimeMillis(); + pressKey(eventTime, KEYCODE_POWER, 0 /* pressTime */); + pressKey(eventTime, KEYCODE_POWER, 0 /* pressTime */); + assertTrue(mMultiPressed.await(mWaitTimeout, TimeUnit.MILLISECONDS)); + } +} 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 488e629f5790..990927b704fa 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -555,9 +555,10 @@ public class ActivityRecordTests extends WindowTestsBase { activity.setRequestedOrientation( isScreenPortrait ? SCREEN_ORIENTATION_PORTRAIT : SCREEN_ORIENTATION_LANDSCAPE); - // Asserts it has orientation derived from bounds. - assertEquals(isScreenPortrait ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT, + // Asserts it has orientation derived requested orientation (fixed orientation letterbox). + assertEquals(isScreenPortrait ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE, activity.getConfiguration().orientation); + assertTrue(activity.isLetterboxedForFixedOrientationAndAspectRatio()); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java index d13e4dcaf9fd..7df17fd4b3c6 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java @@ -38,6 +38,7 @@ import static android.window.DisplayAreaOrganizer.FEATURE_WINDOWED_MAGNIFICATION import static com.android.server.wm.DisplayArea.Type.ABOVE_TASKS; import static com.android.server.wm.DisplayAreaPolicyBuilder.Feature; +import static com.android.server.wm.DisplayAreaPolicyBuilder.KEY_ROOT_DISPLAY_AREA_ID; import static com.google.common.truth.Truth.assertThat; @@ -595,6 +596,17 @@ public class DisplayAreaPolicyBuilderTest { assertThat(token.isDescendantOf(mRoot)).isTrue(); assertThat(token.isDescendantOf(mGroupRoot1)).isFalse(); assertThat(token.isDescendantOf(mGroupRoot2)).isFalse(); + + // When the window has options for target root id, attach it to the target root. + final Bundle options = new Bundle(); + options.putInt(KEY_ROOT_DISPLAY_AREA_ID, mGroupRoot2.mFeatureId); + final WindowToken token2 = new WindowToken(mWms, mock(IBinder.class), + TYPE_STATUS_BAR, true /* persistOnEmpty */, mDisplayContent, + true /* ownerCanManageAppTokens */, false /* roundedCornerOverlay */, + false /* fromClientToken */, options); + policy.addWindow(token2); + + assertThat(token2.isDescendantOf(mGroupRoot2)).isTrue(); } @Test 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 c216bfa90571..137cf6523caf 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -1444,7 +1444,7 @@ public class DisplayContentTests extends WindowTestsBase { // Assume the animation of PipTaskOrganizer is done and then commit fullscreen to task. pinnedTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN); displayContent.continueUpdateOrientationForDiffOrienLaunchingApp(); - assertFalse(displayContent.getPinnedStackController().isPipActiveOrWindowingModeChanging()); + assertFalse(displayContent.getPinnedTaskController().isPipActiveOrWindowingModeChanging()); assertEquals(pinnedConfigOrientation, displayConfig.orientation); clearInvocations(mWm); @@ -1455,7 +1455,7 @@ public class DisplayContentTests extends WindowTestsBase { assertFalse(displayContent.hasTopFixedRotationLaunchingApp()); verify(mWm, atLeastOnce()).startFreezingDisplay(anyInt(), anyInt(), any(), anyInt()); assertEquals(homeConfigOrientation, displayConfig.orientation); - assertTrue(displayContent.getPinnedStackController().isPipActiveOrWindowingModeChanging()); + assertTrue(displayContent.getPinnedTaskController().isPipActiveOrWindowingModeChanging()); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java index f91c9d0e9853..e9c356d6c6c4 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java @@ -171,7 +171,7 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase { final Rect activityBounds = new Rect(mFirstActivity.getBounds()); // DAG is portrait (860x1200), so Task and Activity fill DAG. - assertThat(mFirstTask.isTaskLetterboxed()).isFalse(); + assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse(); assertThat(mFirstActivity.inSizeCompatMode()).isFalse(); assertThat(taskBounds).isEqualTo(dagBounds); assertThat(activityBounds).isEqualTo(taskBounds); @@ -194,8 +194,8 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase { final Rect activityConfigBounds = new Rect(mFirstActivity.getConfiguration().windowConfiguration.getBounds()); - // DAG is landscape (1200x860), Task fills parent - assertThat(mFirstTask.isTaskLetterboxed()).isFalse(); + // DAG is landscape (1200x860), no fixed orientation letterbox + assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse(); assertThat(mFirstActivity.inSizeCompatMode()).isTrue(); assertThat(newDagBounds.width()).isEqualTo(dagBounds.height()); assertThat(newDagBounds.height()).isEqualTo(dagBounds.width()); @@ -211,7 +211,7 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase { } @Test - public void testLaunchLandscapeApp_taskIsLetterboxInDisplayAreaGroup() { + public void testLaunchLandscapeApp_activityIsLetterboxForFixedOrientationInDisplayAreaGroup() { mFirstRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda); @@ -221,17 +221,18 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase { final Rect taskBounds = new Rect(mFirstTask.getBounds()); final Rect activityBounds = new Rect(mFirstActivity.getBounds()); - // DAG is portrait (860x1200), so Task is letterbox (860x[860x860/1200=616]) - assertThat(mFirstTask.isTaskLetterboxed()).isTrue(); + // DAG is portrait (860x1200), and activity is letterboxed for fixed orientation + // (860x[860x860/1200=616]). Task fills DAG. + assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isTrue(); assertThat(mFirstActivity.inSizeCompatMode()).isFalse(); - assertThat(taskBounds.width()).isEqualTo(dagBounds.width()); - assertThat(taskBounds.height()) + assertThat(taskBounds).isEqualTo(dagBounds); + assertThat(activityBounds.width()).isEqualTo(dagBounds.width()); + assertThat(activityBounds.height()) .isEqualTo(dagBounds.width() * dagBounds.width() / dagBounds.height()); - assertThat(activityBounds).isEqualTo(taskBounds); } @Test - public void testLaunchLandscapeApp_taskLetterboxBecomesActivityLetterboxAfterRotation() { + public void testLaunchLandscapeApp_fixedOrientationLetterboxBecomesSizeCompatAfterRotation() { mFirstRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda); @@ -245,9 +246,8 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase { final Rect newTaskBounds = new Rect(mFirstTask.getBounds()); final Rect newActivityBounds = new Rect(mFirstActivity.getBounds()); - // DAG is landscape (1200x860), Task fills parent - // Task letterbox size - assertThat(mFirstTask.isTaskLetterboxed()).isFalse(); + // DAG is landscape (1200x860), no fixed orientation letterbox + assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse(); assertThat(mFirstActivity.inSizeCompatMode()).isTrue(); assertThat(newDagBounds.width()).isEqualTo(dagBounds.height()); assertThat(newDagBounds.height()).isEqualTo(dagBounds.width()); @@ -311,7 +311,7 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase { } @Test - public void testResizableFixedOrientationApp_taskLevelLetterboxing() { + public void testResizableFixedOrientationApp_fixedOrientationLetterboxing() { mFirstRoot.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */); mSecondRoot.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */); @@ -324,7 +324,7 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase { assertThat(mDisplay.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_LANDSCAPE); assertThat(mFirstRoot.getConfiguration().orientation).isEqualTo(ORIENTATION_PORTRAIT); assertThat(mFirstActivity.getConfiguration().orientation).isEqualTo(ORIENTATION_PORTRAIT); - assertThat(mFirstTask.isTaskLetterboxed()).isFalse(); + assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse(); assertThat(mFirstActivity.inSizeCompatMode()).isFalse(); // Launch portrait on second DAG @@ -336,13 +336,13 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase { assertThat(mDisplay.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_PORTRAIT); assertThat(mSecondRoot.getConfiguration().orientation).isEqualTo(ORIENTATION_LANDSCAPE); assertThat(mSecondActivity.getConfiguration().orientation).isEqualTo(ORIENTATION_LANDSCAPE); - assertThat(mSecondTask.isTaskLetterboxed()).isFalse(); + assertThat(mSecondActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse(); assertThat(mSecondActivity.inSizeCompatMode()).isFalse(); // First activity is letterboxed in portrait as requested. assertThat(mFirstRoot.getConfiguration().orientation).isEqualTo(ORIENTATION_LANDSCAPE); assertThat(mFirstActivity.getConfiguration().orientation).isEqualTo(ORIENTATION_PORTRAIT); - assertThat(mFirstTask.isTaskLetterboxed()).isTrue(); + assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isTrue(); assertThat(mFirstActivity.inSizeCompatMode()).isFalse(); } diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index e843dd71381f..b73c66407874 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -23,6 +23,7 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; +import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.view.Surface.ROTATION_180; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; @@ -43,7 +44,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.same; @@ -126,6 +126,7 @@ public class SizeCompatTests extends WindowTestsBase { // Put app window into freeform and then make it a compat app. final Rect bounds = new Rect(100, 100, 400, 600); mTask.setBounds(bounds); + prepareUnresizable(mActivity, -1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT); assertEquals(bounds, mActivity.getBounds()); @@ -194,12 +195,7 @@ public class SizeCompatTests extends WindowTestsBase { new TestDisplayContent.Builder(mAtm, 1000, 2000) .setDensityDpi(200).build(); - mActivity = new ActivityBuilder(mAtm) - .setTask(mTask) - .setResizeMode(RESIZE_MODE_UNRESIZEABLE) - .setMaxAspectRatio(1.5f) - .build(); - mActivity.mVisibleRequested = true; + prepareUnresizable(mActivity, 1.5f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED); final Rect originalBounds = new Rect(mActivity.getBounds()); final int originalDpi = mActivity.getConfiguration().densityDpi; @@ -566,18 +562,18 @@ public class SizeCompatTests extends WindowTestsBase { .setResizeMode(ActivityInfo.RESIZE_MODE_UNRESIZEABLE) .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) .build(); - assertTrue(activity.shouldUseSizeCompatMode()); + assertTrue(activity.shouldCreateCompatDisplayInsets()); // The non-resizable activity should not be size compat because it is on a resizable task // in multi-window mode. mTask.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM); - assertFalse(activity.shouldUseSizeCompatMode()); + assertFalse(activity.shouldCreateCompatDisplayInsets()); // The non-resizable activity should not be size compat because the display support // changing windowing mode from fullscreen to freeform. mTask.mDisplayContent.setDisplayWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM); mTask.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN); - assertFalse(activity.shouldUseSizeCompatMode()); + assertFalse(activity.shouldCreateCompatDisplayInsets()); } @Test @@ -597,7 +593,7 @@ public class SizeCompatTests extends WindowTestsBase { SizeCompatTests.class.getName())) .setUid(android.os.Process.myUid()) .build(); - assertFalse(activity.shouldUseSizeCompatMode()); + assertFalse(activity.shouldCreateCompatDisplayInsets()); } @Test @@ -653,7 +649,7 @@ public class SizeCompatTests extends WindowTestsBase { } @Test - public void testDisplayIgnoreOrientationRequest_fixedOrientationAppLaunchedInTaskLetterbox() { + public void testDisplayIgnoreOrientationRequest_fixedOrientationAppLaunchedLetterbox() { // Set up a display in landscape and ignoring orientation request. setUpDisplaySizeWithApp(2800, 1400); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); @@ -662,7 +658,6 @@ public class SizeCompatTests extends WindowTestsBase { prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT); final Rect displayBounds = new Rect(mActivity.mDisplayContent.getBounds()); - final Rect taskBounds = new Rect(mTask.getBounds()); final Rect activityBounds = new Rect(mActivity.getBounds()); // Display shouldn't be rotated. @@ -670,19 +665,19 @@ public class SizeCompatTests extends WindowTestsBase { mActivity.mDisplayContent.getLastOrientation()); assertTrue(displayBounds.width() > displayBounds.height()); - // App should launch in task level letterboxing. - assertTrue(mTask.isTaskLetterboxed()); + // App should launch in fixed orientation letterbox. + assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertFalse(mActivity.inSizeCompatMode()); - assertEquals(taskBounds, activityBounds); - // Task bounds should be 700x1400 with the ratio as the display. - assertEquals(displayBounds.height(), taskBounds.height()); + // Activity bounds should be 700x1400 with the ratio as the display. + assertEquals(displayBounds.height(), activityBounds.height()); assertEquals(displayBounds.height() * displayBounds.height() / displayBounds.width(), - taskBounds.width()); + activityBounds.width()); } @Test - public void testDisplayIgnoreOrientationRequest_taskLetterboxBecameSizeCompatAfterRotate() { + public void + testDisplayIgnoreOrientationRequest_orientationLetterboxBecameSizeCompatAfterRotate() { // Set up a display in landscape and ignoring orientation request. setUpDisplaySizeWithApp(2800, 1400); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); @@ -700,7 +695,7 @@ public class SizeCompatTests extends WindowTestsBase { assertTrue(displayBounds.width() < displayBounds.height()); // App should be in size compat. - assertFalse(mTask.isTaskLetterboxed()); + assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertScaled(); assertEquals(activityBounds.width(), newActivityBounds.width()); assertEquals(activityBounds.height(), newActivityBounds.height()); @@ -719,7 +714,7 @@ public class SizeCompatTests extends WindowTestsBase { Rect activityBounds = new Rect(mActivity.getBounds()); // App should launch in fullscreen. - assertFalse(mTask.isTaskLetterboxed()); + assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertFalse(mActivity.inSizeCompatMode()); assertEquals(displayBounds, activityBounds); @@ -731,7 +726,7 @@ public class SizeCompatTests extends WindowTestsBase { assertTrue(displayBounds.width() > displayBounds.height()); // App should be in size compat. - assertFalse(mTask.isTaskLetterboxed()); + assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertScaled(); // App bounds should be 700x1400 with the ratio as the display. @@ -741,7 +736,7 @@ public class SizeCompatTests extends WindowTestsBase { } @Test - public void testDisplayIgnoreOrientationRequest_newLaunchedOrientationAppInTaskLetterbox() { + public void testDisplayIgnoreOrientationRequest_newLaunchedOrientationAppInLetterbox() { // Set up a display in landscape and ignoring orientation request. setUpDisplaySizeWithApp(2800, 1400); final DisplayContent display = mActivity.mDisplayContent; @@ -750,7 +745,7 @@ public class SizeCompatTests extends WindowTestsBase { // Portrait fixed app without max aspect. prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT); - assertTrue(mTask.isTaskLetterboxed()); + assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertFalse(mActivity.inSizeCompatMode()); // Launch another portrait fixed app. @@ -765,19 +760,19 @@ public class SizeCompatTests extends WindowTestsBase { // Update with new activity requested orientation and recompute bounds with no previous // size compat cache. verify(mTask).onDescendantOrientationChanged(same(newActivity)); - verify(mTask).computeFullscreenBounds(any(), any()); final Rect displayBounds = new Rect(display.getBounds()); final Rect taskBounds = new Rect(mTask.getBounds()); final Rect newActivityBounds = new Rect(newActivity.getBounds()); - // Task and app bounds should be 700x1400 with the ratio as the display. - assertTrue(mTask.isTaskLetterboxed()); + // Task and display bounds should be equal while activity should be letterboxed and + // has 700x1400 bounds with the ratio as the display. + assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertFalse(newActivity.inSizeCompatMode()); - assertEquals(taskBounds, newActivityBounds); - assertEquals(displayBounds.height(), taskBounds.height()); + assertEquals(taskBounds, displayBounds); + assertEquals(displayBounds.height(), newActivityBounds.height()); assertEquals(displayBounds.height() * displayBounds.height() / displayBounds.width(), - taskBounds.width()); + newActivityBounds.width()); } @Test @@ -790,7 +785,7 @@ public class SizeCompatTests extends WindowTestsBase { // Portrait fixed app without max aspect. prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT); - assertTrue(mTask.isTaskLetterboxed()); + assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertFalse(mActivity.inSizeCompatMode()); // Launch another portrait fixed app with max aspect ratio as 1.3. @@ -806,21 +801,20 @@ public class SizeCompatTests extends WindowTestsBase { // Update with new activity requested orientation and recompute bounds with no previous // size compat cache. verify(mTask).onDescendantOrientationChanged(same(newActivity)); - verify(mTask).computeFullscreenBounds(any(), any()); final Rect displayBounds = new Rect(display.getBounds()); final Rect taskBounds = new Rect(mTask.getBounds()); final Rect newActivityBounds = new Rect(newActivity.getBounds()); - // Task bounds should be (1400 / 1.3 = 1076)x1400 with the app requested ratio. - assertTrue(mTask.isTaskLetterboxed()); - assertEquals(displayBounds.height(), taskBounds.height()); - assertEquals((long) Math.rint(taskBounds.height() / newActivity.info.maxAspectRatio), - taskBounds.width()); + // Task bounds should fill parent bounds. + assertEquals(displayBounds, taskBounds); - // App bounds should be fullscreen in Task bounds. + // Activity bounds should be (1400 / 1.3 = 1076)x1400 with the app requested ratio. + assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertFalse(newActivity.inSizeCompatMode()); - assertEquals(taskBounds, newActivityBounds); + assertEquals(displayBounds.height(), newActivityBounds.height()); + assertEquals((long) Math.rint(newActivityBounds.height() / newActivity.info.maxAspectRatio), + newActivityBounds.width()); } @Test @@ -834,26 +828,23 @@ public class SizeCompatTests extends WindowTestsBase { prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT); clearInvocations(mActivity); - assertTrue(mTask.isTaskLetterboxed()); + assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertFalse(mActivity.inSizeCompatMode()); - assertEquals(mTask.getLastTaskBoundsComputeActivity(), mActivity); // Rotate display to portrait. rotateDisplay(mActivity.mDisplayContent, ROTATION_90); // App should be in size compat. - assertFalse(mTask.isTaskLetterboxed()); + assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertScaled(); - assertEquals(mTask.getLastTaskBoundsComputeActivity(), mActivity); final Rect activityBounds = new Rect(mActivity.getBounds()); mTask.resumeTopActivityUncheckedLocked(null /* prev */, null /* options */); // App still in size compat, and the bounds don't change. verify(mActivity, never()).clearSizeCompatMode(); - assertFalse(mTask.isTaskLetterboxed()); + assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertScaled(); - assertEquals(mTask.getLastTaskBoundsComputeActivity(), mActivity); assertEquals(activityBounds, mActivity.getBounds()); } @@ -867,22 +858,22 @@ public class SizeCompatTests extends WindowTestsBase { // Portrait fixed app. prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT); - // In Task letterbox - assertTrue(mTask.isTaskLetterboxed()); + // In fixed orientation letterbox + assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertFalse(mActivity.inSizeCompatMode()); // Rotate display to portrait. rotateDisplay(display, ROTATION_90); // App should be in size compat. - assertFalse(mTask.isTaskLetterboxed()); + assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertScaled(); // Rotate display to landscape. rotateDisplay(display, ROTATION_180); // In Task letterbox - assertTrue(mTask.isTaskLetterboxed()); + assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertFalse(mActivity.inSizeCompatMode()); } @@ -898,22 +889,22 @@ public class SizeCompatTests extends WindowTestsBase { // Landscape fixed app. prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_LANDSCAPE); - // In Task letterbox - assertTrue(mTask.isTaskLetterboxed()); + // In fixed orientation letterbox + assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertFalse(mActivity.inSizeCompatMode()); // Rotate display to portrait. rotateDisplay(display, ROTATION_90); // App should be in size compat. - assertFalse(mTask.isTaskLetterboxed()); + assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertScaled(); // Rotate display to landscape. rotateDisplay(display, ROTATION_180); - // In Task letterbox - assertTrue(mTask.isTaskLetterboxed()); + // In fixed orientation letterbox + assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertFalse(mActivity.inSizeCompatMode()); } @@ -972,21 +963,21 @@ public class SizeCompatTests extends WindowTestsBase { addWindowToActivity(mActivity); mActivity.mRootWindowContainer.performSurfacePlacement(); - // Split screen is also in portrait [1000,1400], so Task should be in letterbox, and - // activity fills task. - assertEquals(ORIENTATION_LANDSCAPE, mTask.getConfiguration().orientation); + // Split screen is also in portrait [1000,1400], so activty should be in fixed orientation + // letterbox. + assertEquals(ORIENTATION_PORTRAIT, mTask.getConfiguration().orientation); assertEquals(ORIENTATION_LANDSCAPE, mActivity.getConfiguration().orientation); assertFitted(); - assertTrue(mTask.isTaskLetterboxed()); + assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); - // Letterbox should fill the gap between the split screen and the letterboxed task. + // Letterbox should fill the gap between the split screen and the letterboxed activity. final Rect primarySplitBounds = new Rect(organizer.mPrimary.getBounds()); - final Rect letterboxedTaskBounds = new Rect(mTask.getBounds()); - assertTrue(primarySplitBounds.contains(letterboxedTaskBounds)); - assertEquals(new Rect(letterboxedTaskBounds.left - primarySplitBounds.left, - letterboxedTaskBounds.top - primarySplitBounds.top, - primarySplitBounds.right - letterboxedTaskBounds.right, - primarySplitBounds.bottom - letterboxedTaskBounds.bottom), + final Rect letterboxedBounds = new Rect(mActivity.getBounds()); + assertTrue(primarySplitBounds.contains(letterboxedBounds)); + assertEquals(new Rect(letterboxedBounds.left - primarySplitBounds.left, + letterboxedBounds.top - primarySplitBounds.top, + primarySplitBounds.right - letterboxedBounds.right, + primarySplitBounds.bottom - letterboxedBounds.bottom), mActivity.getLetterboxInsets()); } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java index 0eb8c8d2e58a..d853b930af11 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java @@ -266,8 +266,9 @@ public class TaskRecordTests extends WindowTestsBase { root.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT); assertEquals(root, task.getRootActivity()); assertEquals(SCREEN_ORIENTATION_PORTRAIT, task.getRootActivity().getOrientation()); - assertThat(task.getBounds().width()).isLessThan(task.getBounds().height()); - assertEquals(fullScreenBounds.height(), task.getBounds().height()); + // Portrait orientation is enforced on activity level. Task should fill fullscreen bounds. + assertThat(task.getBounds().height()).isLessThan(task.getBounds().width()); + assertEquals(fullScreenBounds, task.getBounds()); // Top activity gets used final ActivityRecord top = new ActivityBuilder(mAtm).setTask(task).setParentTask(stack) @@ -286,8 +287,11 @@ public class TaskRecordTests extends WindowTestsBase { // Fix the display orientation to portrait which is 90 degrees for the test display. dr.setUserRotation(USER_ROTATION_FREE, ROTATION_90); - assertThat(task.getBounds().width()).isGreaterThan(task.getBounds().height()); - assertEquals(fullScreenBoundsPort.width(), task.getBounds().width()); + // Fixed orientation request should be resolved on activity level. Task fills display + // bounds. + assertThat(task.getBounds().height()).isGreaterThan(task.getBounds().width()); + assertThat(top.getBounds().width()).isGreaterThan(top.getBounds().height()); + assertEquals(fullScreenBoundsPort, task.getBounds()); // in FREEFORM, no constraint final Rect freeformBounds = new Rect(display.getBounds()); @@ -297,10 +301,11 @@ public class TaskRecordTests extends WindowTestsBase { task.setBounds(freeformBounds); assertEquals(freeformBounds, task.getBounds()); - // FULLSCREEN letterboxes bounds + // FULLSCREEN letterboxes bounds on activity level, no constraint on task level. stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN); - assertThat(task.getBounds().width()).isGreaterThan(task.getBounds().height()); - assertEquals(fullScreenBoundsPort.width(), task.getBounds().width()); + assertThat(task.getBounds().height()).isGreaterThan(task.getBounds().width()); + assertThat(top.getBounds().width()).isGreaterThan(top.getBounds().height()); + assertEquals(fullScreenBoundsPort, task.getBounds()); // FREEFORM restores bounds as before stack.setWindowingMode(WINDOWING_MODE_FREEFORM); @@ -327,9 +332,10 @@ public class TaskRecordTests extends WindowTestsBase { assertEquals(fullScreenBounds, task.getBounds()); - // Setting app to fixed portrait fits within parent + // Setting app to fixed portrait fits within parent on activity level. Task fills parent. root.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT); - assertThat(task.getBounds().width()).isLessThan(task.getBounds().height()); + assertThat(root.getBounds().width()).isLessThan(root.getBounds().height()); + assertEquals(task.getBounds(), fullScreenBounds); assertEquals(SCREEN_ORIENTATION_PORTRAIT, task.getOrientation()); } @@ -424,7 +430,8 @@ public class TaskRecordTests extends WindowTestsBase { // to the input bounds. final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task).build(); final ActivityRecord.CompatDisplayInsets compatIntsets = - new ActivityRecord.CompatDisplayInsets(display, activity); + new ActivityRecord.CompatDisplayInsets( + display, activity, /* fixedOrientationBounds= */ null); task.computeConfigResourceOverrides(inOutConfig, parentConfig, compatIntsets); assertEquals(largerLandscapeBounds, inOutConfig.windowConfiguration.getAppBounds()); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java index 1c0f640e1a9c..c3eb5c49cea0 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java @@ -257,24 +257,4 @@ public class TaskTests extends WindowTestsBase { task.resolveOverrideConfiguration(parentConfig); assertThat(resolvedOverride.getWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED); } - - @Test - public void testCleanUpActivityReferences_clearLastTaskBoundsComputeActivity() { - final Task rootTask = createTaskStackOnDisplay(mDisplayContent); - final Task leafTask = createTaskInStack(rootTask, 0 /* userId */); - final ActivityRecord activity2 = createActivityRecord(mDisplayContent, leafTask); - final ActivityRecord activity1 = createActivityRecord(mDisplayContent, leafTask); - activity1.finishing = false; - leafTask.resolveOverrideConfiguration(rootTask.getConfiguration()); - - assertEquals(activity1, leafTask.getLastTaskBoundsComputeActivity()); - - leafTask.cleanUpActivityReferences(activity2); - - assertNotNull(leafTask.getLastTaskBoundsComputeActivity()); - - leafTask.cleanUpActivityReferences(activity1); - - assertNull(leafTask.getLastTaskBoundsComputeActivity()); - } } 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 896969548af3..51aec65f7285 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -251,7 +251,7 @@ public class WindowStateTests extends WindowTestsBase { // Simulate the window is in split screen primary stack and the current state is // minimized and home stack is resizable, so that we should ignore input for the stack. - final DockedStackDividerController controller = + final DockedTaskDividerController controller = mDisplayContent.getDockedDividerController(); final Task stack = createTaskStackOnDisplay(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, mDisplayContent); diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index 8628f89a49c1..dce63ebb0889 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -76,6 +76,7 @@ import android.util.ArraySet; import android.util.Log; import android.util.Slog; +import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IHotwordRecognitionStatusCallback; import com.android.internal.app.IVoiceActionCheckCallback; @@ -643,6 +644,10 @@ public class VoiceInteractionManagerService extends SystemService { } ComponentName findAvailRecognizer(String prefPackage, int userHandle) { + if (prefPackage == null) { + prefPackage = getDefaultRecognizer(); + } + List<ResolveInfo> available = mContext.getPackageManager().queryIntentServicesAsUser( new Intent(RecognitionService.SERVICE_INTERFACE), @@ -670,6 +675,12 @@ public class VoiceInteractionManagerService extends SystemService { } } + @Nullable + public String getDefaultRecognizer() { + String recognizer = mContext.getString(R.string.config_systemSpeechRecognizer); + return TextUtils.isEmpty(recognizer) ? null : recognizer; + } + ComponentName getCurRecognizer(int userHandle) { String curRecognizer = Settings.Secure.getStringForUser( mContext.getContentResolver(), diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java index 5cf8de8f9078..f20ee7e56d05 100644 --- a/telecomm/java/android/telecom/InCallService.java +++ b/telecomm/java/android/telecom/InCallService.java @@ -138,6 +138,24 @@ import java.util.List; * } * } * } + * + * </pre> + * <p id="companionInCallService"> + * <h3>Access to InCallService for Wearable Devices</h3> + * <ol> + * If your app is a third-party companion app and wants to access InCallService APIs, what your + * app could do are: + * <p> + * <ol> + * <li> Declare MANAGE_ONGOING_CALLS permission in your manifest + * <li> Associate with a physical wearable device via the + * {@link android.companion.CompanionDeviceManager} API as a companion app. See: + * https://developer.android.com/guide/topics/connectivity/companion-device-pairing + * <li> Implement this InCallService with BIND_INCALL_SERVICE permission + * </ol> + * </ol> + * <p> + * * </pre> * <p id="incomingCallNotification"> * <h3>Showing the Incoming Call Notification</h3> diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index 472d63946ebc..17749e8b0a8f 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -1702,22 +1702,22 @@ public class TelecomManager { } /** - * Returns whether the caller has {@link InCallService} access for companion apps. - * - * A companion app is an app associated with a physical wearable device via the - * {@link android.companion.CompanionDeviceManager} API. + * Returns whether the caller has {@link android.Manifest.permission#MANAGE_ONGOING_CALLS} + * permission. The permission can be obtained by associating with a physical wearable device + * via the {@link android.companion.CompanionDeviceManager} API as a companion app. If the + * caller app has the permission, it has {@link InCallService} access to manage ongoing calls. * * @return {@code true} if the caller has {@link InCallService} access for * companion app; {@code false} otherwise. */ - public boolean hasCompanionInCallServiceAccess() { + public boolean hasManageOngoingCallsPermission() { ITelecomService service = getTelecomService(); if (service != null) { try { - return service.hasCompanionInCallServiceAccess( + return service.hasManageOngoingCallsPermission( mContext.getOpPackageName()); } catch (RemoteException e) { - Log.e(TAG, "RemoteException calling hasCompanionInCallServiceAccess().", e); + Log.e(TAG, "RemoteException calling hasManageOngoingCallsPermission().", e); if (!isSystemProcess()) { e.rethrowAsRuntimeException(); } diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl index 88ef1b09f6b1..eb106b50f69d 100644 --- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl +++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl @@ -179,9 +179,9 @@ interface ITelecomService { boolean isInCall(String callingPackage, String callingFeatureId); /** - * @see TelecomServiceImpl#hasCompanionInCallServiceAccess + * @see TelecomServiceImpl#hasManageOngoingCallsPermission */ - boolean hasCompanionInCallServiceAccess(String callingPackage); + boolean hasManageOngoingCallsPermission(String callingPackage); /** * @see TelecomServiceImpl#isInManagedCall diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 3d43d031868d..b52f49190679 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -33,7 +33,11 @@ import android.os.RemoteException; import android.service.carrier.CarrierService; import android.telecom.TelecomManager; import android.telephony.ims.ImsReasonInfo; +import android.telephony.ims.ImsRegistrationAttributes; import android.telephony.ims.ImsSsData; +import android.telephony.ims.SipDelegateManager; +import android.telephony.ims.feature.MmTelFeature; +import android.telephony.ims.feature.RcsFeature; import com.android.internal.telephony.ICarrierConfigLoader; import com.android.telephony.Rlog; @@ -1717,8 +1721,14 @@ public class CarrierConfigManager { * Configs used for APN setup. */ public static final class Apn { - /** Prefix of all Apn.KEY_* constants. */ - private static final String KEY_PREFIX = "apn."; + /** + * Prefix of all Apn.KEY_* constants. + * + * @deprecated Since KEY_PREFIX is unnecessary to public, it will modify to private + * next android generation. + */ + @Deprecated + public static final String KEY_PREFIX = "apn."; /** IPv4 internet protocol */ public static final String PROTOCOL_IPV4 = "IP"; @@ -3666,14 +3676,13 @@ public class CarrierConfigManager { /** Prefix of all ImsServiceEntitlement.KEY_* constants. */ public static final String KEY_PREFIX = "imsserviceentitlement."; - /** The address of the entitlement configuration server. */ - public static final String KEY_AES_URL_STRING = KEY_PREFIX + "aes_url_string"; - + public static final String KEY_ENTITLEMENT_SERVER_URL_STRING = + KEY_PREFIX + "entitlement_server_url_string"; private static PersistableBundle getDefaults() { PersistableBundle defaults = new PersistableBundle(); - defaults.putString(KEY_AES_URL_STRING, ""); + defaults.putString(KEY_ENTITLEMENT_SERVER_URL_STRING, ""); return defaults; } } @@ -4001,6 +4010,43 @@ public class CarrierConfigManager { KEY_PREFIX + "enable_presence_publish_bool"; /** + * Each string in this array contains a mapping between the service-id and version portion + * of the service-description element and the associated IMS feature tag(s) that are + * associated with each element (see RCC.07 Table 7). + * <p> + * Each string contains 3 parts, which define the mapping between service-description and + * feature tag(s) that must be present in the IMS REGISTER for the RCS service to be + * published as part of the RCS PUBLISH procedure: + * [service-id]|[version]|[desc]|[feature_tag];[feature_tag];... + * <ul> + * <li>[service-id]: the service-id element associated with the RCS capability.</li> + * <li>[version]: The version element associated with that service-id</li> + * <li>[desc]: The optional desecription element associated with that service-id</li> + * <li>[feature_tag];[feature_tag]: The list of all feature tags associated with this + * capability that MUST ALL be present in the IMS registration for this this + * capability to be published to the network.</li> + * </ul> + * <p> + * Features managed by the framework will be considered capable when the ImsService reports + * that those services are capable via the + * {@link MmTelFeature#notifyCapabilitiesStatusChanged(MmTelFeature.MmTelCapabilities)} or + * {@link RcsFeature#notifyCapabilitiesStatusChanged(RcsFeature.RcsImsCapabilities)} APIs. + * For RCS services not managed by the framework, the capability of these services are + * determined by looking at the feature tags associated with the IMS registration using the + * {@link ImsRegistrationAttributes} API and mapping them to the service-description map. + * <p> + * The framework contains a default value of this key, which is based off of RCC.07 + * specification. Capabilities based of carrier extensions may be added to this list on a + * carrier-by-carrier basis as required in order to support additional services in the + * PUBLISH. If this list contains a service-id and version that overlaps with the default, + * it will override the framework default. + * @hide + */ + @SystemApi + public static final String KEY_PUBLISH_SERVICE_DESC_FEATURE_TAG_MAP_OVERRIDE_STRING_ARRAY = + KEY_PREFIX + "publish_service_desc_feature_tag_map_override_string_array"; + + /** * Flag indicating whether or not this carrier supports the exchange of phone numbers with * the carrier's RCS presence server in order to retrieve the RCS capabilities of requested * contacts used in the RCS User Capability Exchange (UCE) procedure. See RCC.71, section 3 @@ -4059,6 +4105,8 @@ public class CarrierConfigManager { defaults.putInt(KEY_WIFI_OFF_DEFERRING_TIME_MILLIS_INT, 4000); defaults.putBoolean(KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL, false); defaults.putBoolean(KEY_ENABLE_PRESENCE_PUBLISH_BOOL, false); + defaults.putStringArray(KEY_PUBLISH_SERVICE_DESC_FEATURE_TAG_MAP_OVERRIDE_STRING_ARRAY, + new String[] {}); defaults.putBoolean(KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL, false); defaults.putBoolean(KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL, false); defaults.putBoolean(KEY_ENABLE_PRESENCE_GROUP_SUBSCRIBE_BOOL, true); diff --git a/telephony/java/android/telephony/SignalStrengthUpdateRequest.java b/telephony/java/android/telephony/SignalStrengthUpdateRequest.java index af67ed279fab..fe7e5976b132 100644 --- a/telephony/java/android/telephony/SignalStrengthUpdateRequest.java +++ b/telephony/java/android/telephony/SignalStrengthUpdateRequest.java @@ -187,7 +187,7 @@ public final class SignalStrengthUpdateRequest implements Parcelable { return mIsSystemThresholdReportingRequestedWhileIdle; } - /* + /** * @return the live token of the request * * @hide diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index f64f4283b66a..61e809b55031 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -29,6 +29,7 @@ import android.annotation.IntDef; import android.annotation.LongDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresFeature; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; @@ -8579,6 +8580,9 @@ public class TelephonyManager { */ @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + @RequiresFeature( + enforcement = "android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported", + value = TelephonyManager.CAPABILITY_ALLOWED_NETWORK_TYPES_USED) @SystemApi public boolean setAllowedNetworkTypes(@NetworkTypeBitMask long allowedNetworkTypes) { try { @@ -8670,6 +8674,9 @@ public class TelephonyManager { */ @SystemApi @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + @RequiresFeature( + enforcement = "android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported", + value = TelephonyManager.CAPABILITY_ALLOWED_NETWORK_TYPES_USED) public void setAllowedNetworkTypesForReason(@AllowedNetworkTypesReason int reason, @NetworkTypeBitMask long allowedNetworkTypes) { if (!isValidAllowedNetworkTypesReason(reason)) { @@ -8708,6 +8715,9 @@ public class TelephonyManager { */ @SystemApi @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + @RequiresFeature( + enforcement = "android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported", + value = TelephonyManager.CAPABILITY_ALLOWED_NETWORK_TYPES_USED) public @NetworkTypeBitMask long getAllowedNetworkTypesForReason( @AllowedNetworkTypesReason int reason) { if (!isValidAllowedNetworkTypesReason(reason)) { diff --git a/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java b/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java index 5eb75e762fc9..bdf628b4d339 100644 --- a/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java +++ b/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.annotation.StringDef; import android.annotation.SystemApi; import android.net.Uri; +import android.os.Build; import android.os.Parcel; import android.os.Parcelable; @@ -39,6 +40,15 @@ import java.util.List; public final class RcsContactPresenceTuple implements Parcelable { /** + * The service ID used to indicate that service discovery via presence is available. + * <p> + * See RCC.07 v5.0 specification for more information. + * @hide + */ + public static final String SERVICE_ID_PRESENCE = + "org.3gpp.urn:urn-7:3gpp-application.ims.iari.rcse.dp"; + + /** * The service ID used to indicate that MMTEL service is available. * <p> * See the GSMA RCC.07 specification for more information. @@ -329,6 +339,13 @@ public final class RcsContactPresenceTuple implements Parcelable { public @NonNull @DuplexMode List<String> getUnsupportedDuplexModes() { return Collections.unmodifiableList(mUnsupportedDuplexModeList); } + + @Override + public String toString() { + return "servCaps{" + "a=" + mIsAudioCapable + ", v=" + mIsVideoCapable + + ", supported=" + mSupportedDuplexModeList + ", unsupported=" + + mUnsupportedDuplexModeList + '}'; + } } /** @@ -487,4 +504,36 @@ public final class RcsContactPresenceTuple implements Parcelable { public @Nullable ServiceCapabilities getServiceCapabilities() { return mServiceCapabilities; } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder("{"); + if (Build.IS_ENG) { + builder.append("u="); + builder.append(mContactUri); + } else { + builder.append("u="); + builder.append(mContactUri != null ? "XXX" : "null"); + } + builder.append(", id="); + builder.append(mServiceId); + builder.append(", v="); + builder.append(mServiceVersion); + builder.append(", s="); + builder.append(mStatus); + if (mTimestamp != null) { + builder.append(", timestamp="); + builder.append(mTimestamp); + } + if (mServiceDescription != null) { + builder.append(", servDesc="); + builder.append(mServiceDescription); + } + if (mServiceCapabilities != null) { + builder.append(", servCaps="); + builder.append(mServiceCapabilities); + } + builder.append("}"); + return builder.toString(); + } } diff --git a/telephony/java/android/telephony/ims/RcsContactUceCapability.java b/telephony/java/android/telephony/ims/RcsContactUceCapability.java index fe855023f5d0..9299fed1e27d 100644 --- a/telephony/java/android/telephony/ims/RcsContactUceCapability.java +++ b/telephony/java/android/telephony/ims/RcsContactUceCapability.java @@ -21,6 +21,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.net.Uri; +import android.os.Build; import android.os.Parcel; import android.os.Parcelable; @@ -344,4 +345,39 @@ public final class RcsContactUceCapability implements Parcelable { public @NonNull Uri getContactUri() { return mContactUri; } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder("RcsContactUceCapability"); + if (mCapabilityMechanism == CAPABILITY_MECHANISM_PRESENCE) { + builder.append("(presence) {"); + } else if (mCapabilityMechanism == CAPABILITY_MECHANISM_OPTIONS) { + builder.append("(options) {"); + } else { + builder.append("(?) {"); + } + if (Build.IS_ENG) { + builder.append("uri="); + builder.append(mContactUri); + } else { + builder.append("uri (isNull)="); + builder.append(mContactUri != null ? "XXX" : "null"); + } + builder.append(", sourceType="); + builder.append(mSourceType); + builder.append(", requestResult="); + builder.append(mRequestResult); + + if (mCapabilityMechanism == CAPABILITY_MECHANISM_PRESENCE) { + builder.append(", presenceTuples={"); + builder.append(mPresenceTuples); + builder.append("}"); + } else if (mCapabilityMechanism == CAPABILITY_MECHANISM_OPTIONS) { + builder.append(", featureTags={"); + builder.append(mFeatureTags); + builder.append("}"); + } + + return builder.toString(); + } } diff --git a/telephony/java/android/telephony/ims/stub/ImsEcbmImplBase.java b/telephony/java/android/telephony/ims/stub/ImsEcbmImplBase.java index 5f8e93d02a00..8ad40ed1032c 100644 --- a/telephony/java/android/telephony/ims/stub/ImsEcbmImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsEcbmImplBase.java @@ -57,10 +57,10 @@ public class ImsEcbmImplBase { } else if (listener != null && mListener == null) { mListener = listener; } else { - // Fail fast here instead of silently overwriting the listener to another - // listener due to another connection connecting. - throw new IllegalStateException("ImsEcbmImplBase: Listener already set by " - + "another connection."); + // Warn that the listener is being replaced while active + Log.w(TAG, "setListener is being called when there is already an active " + + "listener"); + mListener = listener; } } } diff --git a/telephony/java/android/telephony/ims/stub/ImsMultiEndpointImplBase.java b/telephony/java/android/telephony/ims/stub/ImsMultiEndpointImplBase.java index 8e961acc7b36..ec1c7b3a92a8 100644 --- a/telephony/java/android/telephony/ims/stub/ImsMultiEndpointImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsMultiEndpointImplBase.java @@ -62,10 +62,10 @@ public class ImsMultiEndpointImplBase { } else if (listener != null && mListener == null) { mListener = listener; } else { - // Fail fast here instead of silently overwriting the listener to another - // listener due to another connection connecting. - throw new IllegalStateException("ImsMultiEndpointImplBase: Listener already" - + " set by another connection."); + // Warn that the listener is being replaced while active + Log.w(TAG, "setListener is being called when there is already an active " + + "listener"); + mListener = listener; } } } diff --git a/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java b/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java index 83b89aa8e814..eb3e8ed5a8e4 100644 --- a/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java @@ -224,11 +224,10 @@ public class ImsUtImplBase { } else if (listener != null && mUtListener == null) { mUtListener = new ImsUtListener(listener); } else { - // This is a limitation of the current API surface, there can only be one - // listener connected. Fail fast instead of silently overwriting the other - // listener. - throw new IllegalStateException("ImsUtImplBase#setListener: listener already " - + "set by another connected interface!"); + // Warn that the listener is being replaced while active + Log.w(TAG, "setListener is being called when there is already an active " + + "listener"); + mUtListener = new ImsUtListener(listener); } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt index 7d29cdd2b5c5..a8b7b057fe24 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt @@ -21,8 +21,6 @@ import com.android.server.wm.flicker.helpers.WindowUtils import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper.Companion.NAV_BAR_LAYER_NAME import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper.Companion.STATUS_BAR_WINDOW_NAME -const val APP_PAIR_SPLIT_DIVIDER = "AppPairSplitDivider" -const val DOCKED_STACK_DIVIDER = "DockedStackDivider" const val WALLPAPER_TITLE = "Wallpaper" fun FlickerTestParameter.statusBarWindowIsAlwaysVisible() { @@ -64,9 +62,9 @@ fun FlickerTestParameter.wallpaperWindowBecomesVisible() { fun FlickerTestParameter.wallpaperWindowBecomesInvisible() { assertWm { - this.showsBelowAppWindow("Wallpaper") + this.showsBelowAppWindow(WALLPAPER_TITLE) .then() - .hidesBelowAppWindow("Wallpaper") + .hidesBelowAppWindow(WALLPAPER_TITLE) } } @@ -103,19 +101,19 @@ fun FlickerTestParameter.noUncoveredRegions( if (allStates) { assertLayers { if (startingBounds == endingBounds) { - this.coversAtLeastRegion(startingBounds) + this.coversAtLeast(startingBounds) } else { - this.coversAtLeastRegion(startingBounds) + this.coversAtLeast(startingBounds) .then() - .coversAtLeastRegion(endingBounds) + .coversAtLeast(endingBounds) } } } else { assertLayersStart { - this.coversAtLeastRegion(startingBounds) + this.coversAtLeast(startingBounds) } assertLayersEnd { - this.coversAtLeastRegion(endingBounds) + this.coversAtLeast(endingBounds) } } } @@ -124,15 +122,15 @@ fun FlickerTestParameter.noUncoveredRegions( fun FlickerTestParameter.navBarLayerIsAlwaysVisible(rotatesScreen: Boolean = false) { if (rotatesScreen) { assertLayers { - this.showsLayer(NAV_BAR_LAYER_NAME) + this.isVisible(NAV_BAR_LAYER_NAME) .then() - .hidesLayer(NAV_BAR_LAYER_NAME) + .isInvisible(NAV_BAR_LAYER_NAME) .then() - .showsLayer(NAV_BAR_LAYER_NAME) + .isVisible(NAV_BAR_LAYER_NAME) } } else { assertLayers { - this.showsLayer(NAV_BAR_LAYER_NAME) + this.isVisible(NAV_BAR_LAYER_NAME) } } } @@ -141,15 +139,15 @@ fun FlickerTestParameter.navBarLayerIsAlwaysVisible(rotatesScreen: Boolean = fal fun FlickerTestParameter.statusBarLayerIsAlwaysVisible(rotatesScreen: Boolean = false) { if (rotatesScreen) { assertLayers { - this.showsLayer(STATUS_BAR_WINDOW_NAME) + this.isVisible(STATUS_BAR_WINDOW_NAME) .then() - hidesLayer(STATUS_BAR_WINDOW_NAME) + .isInvisible(STATUS_BAR_WINDOW_NAME) .then() - .showsLayer(STATUS_BAR_WINDOW_NAME) + .isVisible(STATUS_BAR_WINDOW_NAME) } } else { assertLayers { - this.showsLayer(STATUS_BAR_WINDOW_NAME) + this.isVisible(STATUS_BAR_WINDOW_NAME) } } } @@ -163,10 +161,10 @@ fun FlickerTestParameter.navBarLayerRotatesAndScales( val endingPos = WindowUtils.getNavigationBarPosition(endRotation) assertLayersStart { - this.hasVisibleRegion(NAV_BAR_LAYER_NAME, startingPos) + this.coversExactly(startingPos, NAV_BAR_LAYER_NAME) } assertLayersEnd { - this.hasVisibleRegion(NAV_BAR_LAYER_NAME, endingPos) + this.coversExactly(endingPos, NAV_BAR_LAYER_NAME) } } @@ -179,10 +177,10 @@ fun FlickerTestParameter.statusBarLayerRotatesScales( val endingPos = WindowUtils.getStatusBarPosition(endRotation) assertLayersStart { - this.hasVisibleRegion(STATUS_BAR_WINDOW_NAME, startingPos) + this.coversExactly(startingPos, STATUS_BAR_WINDOW_NAME) } assertLayersEnd { - this.hasVisibleRegion(STATUS_BAR_WINDOW_NAME, endingPos) + this.coversExactly(endingPos, STATUS_BAR_WINDOW_NAME) } } @@ -197,39 +195,41 @@ fun FlickerTestParameter.visibleLayersShownMoreThanOneConsecutiveEntry( fun FlickerTestParameter.appLayerReplacesWallpaperLayer(appName: String) { assertLayers { - this.showsLayer("Wallpaper") + this.isVisible(WALLPAPER_TITLE) .then() - .replaceVisibleLayer("Wallpaper", appName) + .isInvisible(WALLPAPER_TITLE) + .isVisible(appName) } } fun FlickerTestParameter.wallpaperLayerReplacesAppLayer(testApp: IAppHelper) { assertLayers { - this.showsLayer(testApp.getPackage()) + this.isVisible(testApp.getPackage()) .then() - .replaceVisibleLayer(testApp.getPackage(), WALLPAPER_TITLE) + .isInvisible(testApp.getPackage()) + .isVisible(WALLPAPER_TITLE) } } fun FlickerTestParameter.layerAlwaysVisible(packageName: String) { assertLayers { - this.showsLayer(packageName) + this.isVisible(packageName) } } fun FlickerTestParameter.layerBecomesVisible(packageName: String) { assertLayers { - this.hidesLayer(packageName) + this.isInvisible(packageName) .then() - .showsLayer(packageName) + .isVisible(packageName) } } fun FlickerTestParameter.layerBecomesInvisible(packageName: String) { assertLayers { - this.showsLayer(packageName) + this.isVisible(packageName) .then() - .hidesLayer(packageName) + .isInvisible(packageName) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt index 38a88d372da4..26afb794bb06 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt @@ -153,31 +153,11 @@ class CloseImeAutoOpenWindowToHomeTest(private val testSpec: FlickerTestParamete @Presubmit @Test - fun navBarLayerIsAlwaysVisible() { - Assume.assumeFalse(testSpec.isRotated) - testSpec.navBarLayerIsAlwaysVisible() - } - - @FlakyTest - @Test - fun navBarLayerIsAlwaysVisible_Flaky() { - Assume.assumeTrue(testSpec.isRotated) - testSpec.navBarLayerIsAlwaysVisible() - } + fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() @Presubmit @Test - fun statusBarLayerIsAlwaysVisible() { - Assume.assumeFalse(testSpec.isRotated) - testSpec.statusBarLayerIsAlwaysVisible() - } - - @FlakyTest - @Test - fun statusBarLayerIsAlwaysVisible_Flaky() { - Assume.assumeTrue(testSpec.isRotated) - testSpec.statusBarLayerIsAlwaysVisible() - } + fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible() @Presubmit @Test diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt index 476708c42c4c..2c4c627a444d 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt @@ -17,9 +17,8 @@ package com.android.server.wm.flicker.ime import android.app.Instrumentation -import android.platform.test.annotations.Postsubmit +import android.platform.test.annotations.Presubmit import android.view.Surface -import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import androidx.test.platform.app.InstrumentationRegistry import com.android.server.wm.flicker.FlickerBuilderProvider @@ -85,55 +84,55 @@ class CloseImeWindowToAppTest(private val testSpec: FlickerTestParameter) { } } - @Postsubmit + @Presubmit @Test fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() - @Postsubmit + @Presubmit @Test fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() - @Postsubmit + @Presubmit @Test fun visibleWindowsShownMoreThanOneConsecutiveEntry() = testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry(listOf(IME_WINDOW_TITLE)) - @Postsubmit + @Presubmit @Test fun imeAppWindowIsAlwaysVisible() = testSpec.imeAppWindowIsAlwaysVisible(testApp) - @Postsubmit + @Presubmit @Test fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() - @Postsubmit + @Presubmit @Test fun statusBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() - @Postsubmit + @Presubmit @Test fun noUncoveredRegions() = testSpec.noUncoveredRegions(testSpec.config.startRotation) - @FlakyTest + @Presubmit @Test fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation) - @FlakyTest + @Presubmit @Test fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation) - @Postsubmit + @Presubmit @Test fun visibleLayersShownMoreThanOneConsecutiveEntry() = testSpec.visibleLayersShownMoreThanOneConsecutiveEntry() - @Postsubmit + @Presubmit @Test fun imeLayerBecomesInvisible() = testSpec.imeLayerBecomesInvisible() - @Postsubmit + @Presubmit @Test fun imeAppLayerIsAlwaysVisible() = testSpec.imeAppLayerIsAlwaysVisible(testApp) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt index ac140f505076..2bcdcd9c8219 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt @@ -17,7 +17,7 @@ package com.android.server.wm.flicker.ime import android.app.Instrumentation -import android.platform.test.annotations.Postsubmit +import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice @@ -91,63 +91,54 @@ class CloseImeWindowToHomeTest(private val testSpec: FlickerTestParameter) { } } - @Postsubmit + @Presubmit @Test fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() - @Postsubmit + @Presubmit @Test fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() - @Postsubmit + @Presubmit @Test fun visibleWindowsShownMoreThanOneConsecutiveEntry() = testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry(listOf(IME_WINDOW_TITLE)) - @Postsubmit + @Presubmit @Test fun imeWindowBecomesInvisible() = testSpec.imeWindowBecomesInvisible() - @Postsubmit + @Presubmit @Test fun imeAppWindowBecomesInvisible() = testSpec.imeAppWindowBecomesInvisible(testApp) - @Postsubmit + @Presubmit @Test fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() - @Postsubmit + @Presubmit @Test fun statusBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() - @Postsubmit + @Presubmit @Test fun noUncoveredRegions() = testSpec.noUncoveredRegions(testSpec.config.startRotation, Surface.ROTATION_0) - @Postsubmit + @Presubmit @Test fun imeLayerBecomesInvisible() = testSpec.imeLayerBecomesInvisible() - @Postsubmit + @Presubmit @Test fun imeAppLayerBecomesInvisible() = testSpec.imeAppLayerBecomesInvisible(testApp) - @Postsubmit + @Presubmit @Test - fun navBarLayerRotatesAndScales() { - Assume.assumeFalse(testSpec.isRotated) - testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0) - } - - @FlakyTest - @Test - fun navBarLayerRotatesAndScales_Flaky() { - Assume.assumeTrue(testSpec.isRotated) + fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation, Surface.ROTATION_0) - } - @Postsubmit + @Presubmit @Test fun statusBarLayerRotatesScales() { Assume.assumeFalse(testSpec.isRotated) @@ -171,7 +162,8 @@ class CloseImeWindowToHomeTest(private val testSpec: FlickerTestParameter) { @JvmStatic fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(repetitions = 5) + .getConfigNonRotationTests(repetitions = 5, + supportedRotations = listOf(Surface.ROTATION_0)) } } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt index 212644c04505..3dfa31d64f8c 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt @@ -23,23 +23,23 @@ const val IME_WINDOW_TITLE = "InputMethod" fun FlickerTestParameter.imeLayerBecomesVisible() { assertLayers { - this.hidesLayer(IME_WINDOW_TITLE) + this.isInvisible(IME_WINDOW_TITLE) .then() - .showsLayer(IME_WINDOW_TITLE) + .isVisible(IME_WINDOW_TITLE) } } fun FlickerTestParameter.imeLayerBecomesInvisible() { assertLayers { - this.showsLayer(IME_WINDOW_TITLE) + this.isVisible(IME_WINDOW_TITLE) .then() - .hidesLayer(IME_WINDOW_TITLE) + .isInvisible(IME_WINDOW_TITLE) } } fun FlickerTestParameter.imeAppLayerIsAlwaysVisible(testApp: IAppHelper) { assertLayers { - this.showsLayer(testApp.getPackage()) + this.isVisible(testApp.getPackage()) } } @@ -83,9 +83,8 @@ fun FlickerTestParameter.imeAppWindowBecomesInvisible(testApp: IAppHelper) { fun FlickerTestParameter.imeAppLayerBecomesInvisible(testApp: IAppHelper) { assertLayers { - this.skipUntilFirstAssertion() - .showsLayer(testApp.getPackage()) + this.isVisible(testApp.getPackage()) .then() - .hidesLayer(testApp.getPackage()) + .isInvisible(testApp.getPackage()) } }
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt index c7a5178a6693..008ba2f72b6c 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt @@ -17,7 +17,7 @@ package com.android.server.wm.flicker.ime import android.app.Instrumentation -import android.platform.test.annotations.Postsubmit +import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice @@ -89,43 +89,43 @@ class OpenImeWindowTest(private val testSpec: FlickerTestParameter) { } } - @Postsubmit + @Presubmit @Test fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsAlwaysVisible() - @Postsubmit + @Presubmit @Test fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible() - @Postsubmit + @Presubmit @Test fun imeWindowBecomesVisible() = testSpec.imeWindowBecomesVisible() - @Postsubmit + @Presubmit @Test fun appWindowAlwaysVisibleOnTop() = testSpec.appWindowAlwaysVisibleOnTop(testApp.`package`) - @Postsubmit + @Presubmit @Test fun navBarLayerIsAlwaysVisible() = testSpec.navBarLayerIsAlwaysVisible() - @Postsubmit + @Presubmit @Test fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsAlwaysVisible() - @Postsubmit + @Presubmit @Test fun noUncoveredRegions() = testSpec.noUncoveredRegions(testSpec.config.startRotation) - @Postsubmit + @Presubmit @Test fun imeLayerBecomesVisible() = testSpec.imeLayerBecomesVisible() - @Postsubmit + @Presubmit @Test fun layerAlwaysVisible() = testSpec.layerAlwaysVisible(testApp.`package`) - @Postsubmit + @Presubmit @Test fun navBarLayerRotatesAndScales() { Assume.assumeFalse(testSpec.isRotated) @@ -139,7 +139,7 @@ class OpenImeWindowTest(private val testSpec: FlickerTestParameter) { testSpec.navBarLayerRotatesAndScales(testSpec.config.startRotation) } - @Postsubmit + @Presubmit @Test fun statusBarLayerRotatesScales() { Assume.assumeFalse(testSpec.isRotated) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt index e2a258aea239..18fac6a82de7 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt @@ -17,7 +17,6 @@ package com.android.server.wm.flicker.launch import android.app.Instrumentation -import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest @@ -148,17 +147,35 @@ class OpenAppFromOverviewTest(private val testSpec: FlickerTestParameter) { @Test fun focusChanges() = testSpec.focusChanges("NexusLauncherActivity", testApp.`package`) - @Postsubmit + @Presubmit @Test - fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + fun visibleWindowsShownMoreThanOneConsecutiveEntry() { + Assume.assumeFalse(testSpec.isRotated) testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry() + } + + @FlakyTest + @Test + fun visibleWindowsShownMoreThanOneConsecutiveEntry_Flaky() { + Assume.assumeTrue(testSpec.isRotated) + testSpec.visibleWindowsShownMoreThanOneConsecutiveEntry() + } + + @Presubmit + @Test + fun visibleLayersShownMoreThanOneConsecutiveEntry() { + Assume.assumeFalse(testSpec.isRotated) + testSpec.visibleLayersShownMoreThanOneConsecutiveEntry() + } @FlakyTest @Test - fun visibleLayersShownMoreThanOneConsecutiveEntry() = + fun visibleLayersShownMoreThanOneConsecutiveEntry_Flaky() { + Assume.assumeTrue(testSpec.isRotated) testSpec.visibleLayersShownMoreThanOneConsecutiveEntry() + } - @FlakyTest(bugId = 141361128) + @Presubmit @Test fun noUncoveredRegions() = testSpec.noUncoveredRegions(Surface.ROTATION_0, testSpec.config.endRotation) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt index 3cc509fe2b8e..20e4b88ff13b 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt @@ -16,13 +16,13 @@ package com.android.server.wm.flicker.rotation -import android.os.Bundle import android.platform.test.annotations.Presubmit import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory +import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.endRotation import com.android.server.wm.flicker.focusDoesNotChange import com.android.server.wm.flicker.helpers.SimpleAppHelper @@ -54,7 +54,15 @@ class ChangeAppRotationTest( testSpec: FlickerTestParameter ) : RotationTransition(testSpec) { override val testApp = SimpleAppHelper(instrumentation) - override fun getAppLaunchParams(configuration: Bundle): Map<String, String> = emptyMap() + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit + get() = { + super.transition(this, it) + setup { + test { + testApp.launchViaIntent(wmHelper) + } + } + } @Presubmit @Test @@ -78,11 +86,11 @@ class ChangeAppRotationTest( @Test fun screenshotLayerBecomesInvisible() { testSpec.assertLayers { - this.showsLayer(testApp.getPackage()) + this.isVisible(testApp.getPackage()) .then() - .showsLayer(SCREENSHOT_LAYER) + .isVisible(SCREENSHOT_LAYER) .then() - .showsLayer(testApp.getPackage()) + .isVisible(testApp.getPackage()) } } @@ -113,7 +121,7 @@ class ChangeAppRotationTest( @Test fun appLayerRotates_StartingPos() { testSpec.assertLayersStart { - this.hasVisibleRegion(testApp.getPackage(), startingPos) + this.coversExactly(startingPos, testApp.getPackage()) } } @@ -121,7 +129,7 @@ class ChangeAppRotationTest( @Test fun appLayerRotates_EndingPos() { testSpec.assertLayersEnd { - this.hasVisibleRegion(testApp.getPackage(), endingPos) + this.coversExactly(endingPos, testApp.getPackage()) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt index 04ab84dfcd8e..c391112a53ec 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt @@ -17,7 +17,6 @@ package com.android.server.wm.flicker.rotation import android.app.Instrumentation -import android.os.Bundle import androidx.test.platform.app.InstrumentationRegistry import com.android.server.wm.flicker.FlickerBuilderProvider import com.android.server.wm.flicker.FlickerTestParameter @@ -31,36 +30,37 @@ import com.android.server.wm.flicker.repetitions import com.android.server.wm.flicker.startRotation abstract class RotationTransition(protected val testSpec: FlickerTestParameter) { + protected abstract val testApp: StandardAppHelper + protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() protected val startingPos get() = WindowUtils.getDisplayBounds(testSpec.config.startRotation) protected val endingPos get() = WindowUtils.getDisplayBounds(testSpec.config.endRotation) - protected abstract val testApp: StandardAppHelper - protected abstract fun getAppLaunchParams(configuration: Bundle): Map<String, String> + protected open val transition: FlickerBuilder.(Map<String, Any?>) -> Unit = { + withTestName { testSpec.name } + repeat { testSpec.config.repetitions } + setup { + test { + device.wakeUpAndGoToHomeScreen() + } + eachRun { + this.setRotation(testSpec.config.startRotation) + } + } + teardown { + test { + testApp.exit() + } + } + transitions { + this.setRotation(testSpec.config.endRotation) + } + } @FlickerBuilderProvider fun buildFlicker(): FlickerBuilder { return FlickerBuilder(instrumentation).apply { - withTestName { testSpec.name } - repeat { testSpec.config.repetitions } - setup { - test { - device.wakeUpAndGoToHomeScreen() - val extras = getAppLaunchParams(testSpec.config) - testApp.launchViaIntent(wmHelper, stringExtras = extras) - } - eachRun { - this.setRotation(testSpec.config.startRotation) - } - } - teardown { - test { - testApp.exit() - } - } - transitions { - this.setRotation(testSpec.config.endRotation) - } + transition(testSpec.config) } } }
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt index ef1aeadb31bc..fc5bcc7eef1b 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt @@ -16,7 +16,6 @@ package com.android.server.wm.flicker.rotation -import android.os.Bundle import android.platform.test.annotations.Presubmit import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice @@ -24,6 +23,7 @@ import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.appWindowAlwaysVisibleOnTop +import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.endRotation import com.android.server.wm.flicker.focusDoesNotChange import com.android.server.wm.flicker.helpers.SeamlessRotationAppHelper @@ -58,9 +58,18 @@ class SeamlessAppRotationTest( ) : RotationTransition(testSpec) { override val testApp = SeamlessRotationAppHelper(instrumentation) - override fun getAppLaunchParams(configuration: Bundle): Map<String, String> = mapOf( - ActivityOptions.EXTRA_STARVE_UI_THREAD to configuration.starveUiThread.toString() - ) + override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit + get() = { + super.transition(this, it) + setup { + test { + testApp.launchViaIntent(wmHelper, + stringExtras = mapOf( + ActivityOptions.EXTRA_STARVE_UI_THREAD to it.starveUiThread.toString()) + ) + } + } + } @Presubmit @Test @@ -115,7 +124,7 @@ class SeamlessAppRotationTest( @Test fun appLayerRotates() { testSpec.assertLayers { - this.hasVisibleRegion(testApp.`package`, startingPos) + this.coversExactly(startingPos, testApp.`package`) } } @@ -126,12 +135,14 @@ class SeamlessAppRotationTest( companion object { private val testFactory = FlickerTestParameterFactory.getInstance() - private val Bundle.starveUiThread - get() = this.getBoolean(ActivityOptions.EXTRA_STARVE_UI_THREAD, false) + private val Map<String, Any?>.starveUiThread + get() = this.getOrDefault(ActivityOptions.EXTRA_STARVE_UI_THREAD, false) as Boolean - private fun FlickerTestParameter.createConfig(starveUiThread: Boolean): Bundle { - val config = this.config.deepCopy() - config.putBoolean(ActivityOptions.EXTRA_STARVE_UI_THREAD, starveUiThread) + private fun FlickerTestParameter.createConfig( + starveUiThread: Boolean + ): MutableMap<String, Any?> { + val config = this.config.toMutableMap() + config[ActivityOptions.EXTRA_STARVE_UI_THREAD] = starveUiThread return config } diff --git a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java index dc9e587332cb..e1da3d0ae2b3 100644 --- a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java +++ b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java @@ -17,6 +17,7 @@ package com.android.server; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET; @@ -84,6 +85,7 @@ public class NetworkAgentWrapper implements TestableNetworkCallback.HasNetwork { final String typeName = ConnectivityManager.getNetworkTypeName(type); mNetworkCapabilities = (ncTemplate != null) ? ncTemplate : new NetworkCapabilities(); mNetworkCapabilities.addCapability(NET_CAPABILITY_NOT_SUSPENDED); + mNetworkCapabilities.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED); mNetworkCapabilities.addTransportType(transport); switch (transport) { case TRANSPORT_ETHERNET: diff --git a/tests/net/java/android/net/util/MultinetworkPolicyTrackerTest.kt b/tests/net/java/android/net/util/MultinetworkPolicyTrackerTest.kt new file mode 100644 index 000000000000..9b0cfa9db30f --- /dev/null +++ b/tests/net/java/android/net/util/MultinetworkPolicyTrackerTest.kt @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.util + +import android.content.Context +import android.content.res.Resources +import android.net.ConnectivityManager.MULTIPATH_PREFERENCE_HANDOVER +import android.net.ConnectivityManager.MULTIPATH_PREFERENCE_PERFORMANCE +import android.net.ConnectivityManager.MULTIPATH_PREFERENCE_RELIABILITY +import android.net.util.MultinetworkPolicyTracker.ActiveDataSubscriptionIdChangedListener +import android.provider.Settings +import android.provider.Settings.Global.NETWORK_AVOID_BAD_WIFI +import android.provider.Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE +import android.telephony.SubscriptionInfo +import android.telephony.SubscriptionManager +import android.telephony.TelephonyManager +import android.test.mock.MockContentResolver +import androidx.test.filters.SmallTest +import androidx.test.runner.AndroidJUnit4 +import com.android.internal.R +import com.android.internal.util.test.FakeSettingsProvider +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.argThat +import org.mockito.Mockito.any +import org.mockito.Mockito.doReturn +import org.mockito.Mockito.mock +import org.mockito.Mockito.times +import org.mockito.Mockito.verify + +/** + * Tests for [MultinetworkPolicyTracker]. + * + * Build, install and run with: + * atest android.net.util.MultinetworkPolicyTrackerTest + */ +@RunWith(AndroidJUnit4::class) +@SmallTest +class MultinetworkPolicyTrackerTest { + private val resources = mock(Resources::class.java).also { + doReturn(0).`when`(it).getInteger(R.integer.config_networkAvoidBadWifi) + } + private val telephonyManager = mock(TelephonyManager::class.java) + private val subscriptionManager = mock(SubscriptionManager::class.java).also { + doReturn(null).`when`(it).getActiveSubscriptionInfo(anyInt()) + } + private val resolver = MockContentResolver().apply { + addProvider(Settings.AUTHORITY, FakeSettingsProvider()) } + private val context = mock(Context::class.java).also { + doReturn(Context.TELEPHONY_SERVICE).`when`(it) + .getSystemServiceName(TelephonyManager::class.java) + doReturn(telephonyManager).`when`(it).getSystemService(Context.TELEPHONY_SERVICE) + doReturn(subscriptionManager).`when`(it) + .getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE) + doReturn(resolver).`when`(it).contentResolver + doReturn(resources).`when`(it).resources + doReturn(it).`when`(it).createConfigurationContext(any()) + Settings.Global.putString(resolver, NETWORK_AVOID_BAD_WIFI, "1") + } + private val tracker = MultinetworkPolicyTracker(context, null /* handler */) + + private fun assertMultipathPreference(preference: Int) { + Settings.Global.putString(resolver, NETWORK_METERED_MULTIPATH_PREFERENCE, + preference.toString()) + tracker.updateMeteredMultipathPreference() + assertEquals(preference, tracker.meteredMultipathPreference) + } + + @Test + fun testUpdateMeteredMultipathPreference() { + assertMultipathPreference(MULTIPATH_PREFERENCE_HANDOVER) + assertMultipathPreference(MULTIPATH_PREFERENCE_RELIABILITY) + assertMultipathPreference(MULTIPATH_PREFERENCE_PERFORMANCE) + } + + @Test + fun testUpdateAvoidBadWifi() { + Settings.Global.putString(resolver, NETWORK_AVOID_BAD_WIFI, "0") + assertTrue(tracker.updateAvoidBadWifi()) + assertFalse(tracker.avoidBadWifi) + + doReturn(1).`when`(resources).getInteger(R.integer.config_networkAvoidBadWifi) + assertTrue(tracker.updateAvoidBadWifi()) + assertTrue(tracker.avoidBadWifi) + } + + @Test + fun testOnActiveDataSubscriptionIdChanged() { + val testSubId = 1000 + val subscriptionInfo = SubscriptionInfo(testSubId, ""/* iccId */, 1/* iccId */, + "TMO"/* displayName */, "TMO"/* carrierName */, 1/* nameSource */, 1/* iconTint */, + "123"/* number */, 1/* roaming */, null/* icon */, "310"/* mcc */, "210"/* mnc */, + ""/* countryIso */, false/* isEmbedded */, null/* nativeAccessRules */, + "1"/* cardString */) + doReturn(subscriptionInfo).`when`(subscriptionManager).getActiveSubscriptionInfo(testSubId) + + // Modify avoidBadWifi and meteredMultipathPreference settings value and local variables in + // MultinetworkPolicyTracker should be also updated after subId changed. + Settings.Global.putString(resolver, NETWORK_AVOID_BAD_WIFI, "0") + Settings.Global.putString(resolver, NETWORK_METERED_MULTIPATH_PREFERENCE, + MULTIPATH_PREFERENCE_PERFORMANCE.toString()) + + val listenerCaptor = ArgumentCaptor.forClass( + ActiveDataSubscriptionIdChangedListener::class.java) + verify(telephonyManager, times(1)) + .registerPhoneStateListener(any(), listenerCaptor.capture()) + val listener = listenerCaptor.value + listener.onActiveDataSubscriptionIdChanged(testSubId) + + // Check it get resource value with test sub id. + verify(subscriptionManager, times(1)).getActiveSubscriptionInfo(testSubId) + verify(context).createConfigurationContext(argThat { it.mcc == 310 && it.mnc == 210 }) + + // Check if avoidBadWifi and meteredMultipathPreference values have been updated. + assertFalse(tracker.avoidBadWifi) + assertEquals(MULTIPATH_PREFERENCE_PERFORMANCE, tracker.meteredMultipathPreference) + } +} diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 6de1075d519b..ad87567843c7 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -407,6 +407,8 @@ public class ConnectivityServiceTest { private QosCallbackMockHelper mQosCallbackMockHelper; private QosCallbackTracker mQosCallbackTracker; private VpnManagerService mVpnManagerService; + private TestNetworkCallback mDefaultNetworkCallback; + private TestNetworkCallback mSystemDefaultNetworkCallback; // State variables required to emulate NetworkPolicyManagerService behaviour. private int mUidRules = RULE_NONE; @@ -1547,6 +1549,7 @@ public class ConnectivityServiceTest { @After public void tearDown() throws Exception { + unregisterDefaultNetworkCallbacks(); setAlwaysOnNetworks(false); if (mCellNetworkAgent != null) { mCellNetworkAgent.disconnect(); @@ -2797,6 +2800,10 @@ public class ConnectivityServiceTest { NetworkCapabilities filter = new NetworkCapabilities(); filter.addCapability(capability); + // Add NOT_VCN_MANAGED capability into filter unconditionally since some request will add + // NOT_VCN_MANAGED automatically but not for NetworkCapabilities, + // see {@code NetworkCapabilities#deduceNotVcnManagedCapability} for more details. + filter.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED); final HandlerThread handlerThread = new HandlerThread("testNetworkFactoryRequests"); handlerThread.start(); final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(), @@ -4144,6 +4151,7 @@ public class ConnectivityServiceTest { handlerThread.start(); NetworkCapabilities filter = new NetworkCapabilities() .addTransportType(TRANSPORT_CELLULAR) + .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED) .addCapability(NET_CAPABILITY_INTERNET); final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(), mServiceContext, "testFactory", filter, mCsHandlerThread); @@ -6048,6 +6056,7 @@ public class ConnectivityServiceTest { .addTransportType(TRANSPORT_CELLULAR) .addCapability(NET_CAPABILITY_INTERNET) .addCapability(NET_CAPABILITY_NOT_CONGESTED) + .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED) .setLinkDownstreamBandwidthKbps(10); final NetworkCapabilities wifiNc = new NetworkCapabilities() .addTransportType(TRANSPORT_WIFI) @@ -6056,6 +6065,7 @@ public class ConnectivityServiceTest { .addCapability(NET_CAPABILITY_NOT_ROAMING) .addCapability(NET_CAPABILITY_NOT_CONGESTED) .addCapability(NET_CAPABILITY_NOT_SUSPENDED) + .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED) .setLinkUpstreamBandwidthKbps(20); mCellNetworkAgent.setNetworkCapabilities(cellNc, true /* sendToConnectivityService */); mWiFiNetworkAgent.setNetworkCapabilities(wifiNc, true /* sendToConnectivityService */); @@ -7748,19 +7758,13 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent.removeCapability(testCap); callbackWithCap.expectAvailableCallbacksValidated(mCellNetworkAgent); callbackWithoutCap.expectCapabilitiesWithout(testCap, mWiFiNetworkAgent); - // TODO: Test default network changes for NOT_VCN_MANAGED once the default request has - // it. - if (testCap == NET_CAPABILITY_TRUSTED) { - verify(mMockNetd).networkSetDefault(eq(mCellNetworkAgent.getNetwork().netId)); - reset(mMockNetd); - } + verify(mMockNetd).networkSetDefault(eq(mCellNetworkAgent.getNetwork().netId)); + reset(mMockNetd); mCellNetworkAgent.removeCapability(testCap); callbackWithCap.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); callbackWithoutCap.assertNoCallback(); - if (testCap == NET_CAPABILITY_TRUSTED) { - verify(mMockNetd).networkClearDefault(); - } + verify(mMockNetd).networkClearDefault(); mCm.unregisterNetworkCallback(callbackWithCap); mCm.unregisterNetworkCallback(callbackWithoutCap); @@ -9466,6 +9470,10 @@ public class ConnectivityServiceTest { fail("TOO_MANY_REQUESTS never thrown"); } + private UidRange createUidRange(int userId) { + return UidRange.createForUser(UserHandle.of(userId)); + } + private void mockGetApplicationInfo(@NonNull final String packageName, @NonNull final int uid) throws Exception { final ApplicationInfo applicationInfo = new ApplicationInfo(); @@ -9800,6 +9808,54 @@ public class ConnectivityServiceTest { assertEquals(expectedPerAppNetwork, defaultNetwork); assertEquals(expectedOemRequestsSize, defaultRequest.mRequests.size()); } + verifyMultipleDefaultCallbacks(expectedDefaultNetwork, expectedPerAppNetwork); + } + + /** + * Verify default callbacks for 'available' fire as expected. This will only run if + * registerDefaultNetworkCallbacks() was executed prior and will only be different if the + * setOemNetworkPreference() per-app API was used for the current process. + * @param expectedSystemDefault the expected network for the system default. + * @param expectedPerAppDefault the expected network for the current process's default. + */ + private void verifyMultipleDefaultCallbacks( + @NonNull final Network expectedSystemDefault, + @NonNull final Network expectedPerAppDefault) { + if (null != mSystemDefaultNetworkCallback && null != expectedSystemDefault + && mService.mNoServiceNetwork.network() != expectedSystemDefault) { + // getLastAvailableNetwork() is used as this method can be called successively with + // the same network to validate therefore expectAvailableThenValidatedCallbacks + // can't be used. + assertEquals(mSystemDefaultNetworkCallback.getLastAvailableNetwork(), + expectedSystemDefault); + } + if (null != mDefaultNetworkCallback && null != expectedPerAppDefault + && mService.mNoServiceNetwork.network() != expectedPerAppDefault) { + assertEquals(mDefaultNetworkCallback.getLastAvailableNetwork(), + expectedPerAppDefault); + } + } + + private void registerDefaultNetworkCallbacks() { + // Using Manifest.permission.NETWORK_SETTINGS for registerSystemDefaultNetworkCallback() + mServiceContext.setPermission( + Manifest.permission.NETWORK_SETTINGS, PERMISSION_GRANTED); + mSystemDefaultNetworkCallback = new TestNetworkCallback(); + mDefaultNetworkCallback = new TestNetworkCallback(); + mCm.registerSystemDefaultNetworkCallback(mSystemDefaultNetworkCallback, + new Handler(ConnectivityThread.getInstanceLooper())); + mCm.registerDefaultNetworkCallback(mDefaultNetworkCallback); + mServiceContext.setPermission( + Manifest.permission.NETWORK_SETTINGS, PERMISSION_DENIED); + } + + private void unregisterDefaultNetworkCallbacks() { + if (null != mDefaultNetworkCallback) { + mCm.unregisterNetworkCallback(mDefaultNetworkCallback); + } + if (null != mSystemDefaultNetworkCallback) { + mCm.unregisterNetworkCallback(mSystemDefaultNetworkCallback); + } } private void setupMultipleDefaultNetworksForOemNetworkPreferenceNotCurrentUidTest( @@ -9883,6 +9939,7 @@ public class ConnectivityServiceTest { @OemNetworkPreferences.OemNetworkPreference final int networkPref = OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY; final int expectedOemPrefRequestSize = 1; + registerDefaultNetworkCallbacks(); // Setup the test process to use networkPref for their default network. setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref); @@ -9897,6 +9954,7 @@ public class ConnectivityServiceTest { // Verify that the active network is correct verifyActiveNetwork(TRANSPORT_ETHERNET); + // default NCs will be unregistered in tearDown } @Test @@ -9904,6 +9962,7 @@ public class ConnectivityServiceTest { @OemNetworkPreferences.OemNetworkPreference final int networkPref = OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY; final int expectedOemPrefRequestSize = 1; + registerDefaultNetworkCallbacks(); // Setup the test process to use networkPref for their default network. setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref); @@ -9924,6 +9983,7 @@ public class ConnectivityServiceTest { mEthernetNetworkAgent.getNetwork()); assertFalse(mCm.isActiveNetworkMetered()); + // default NCs will be unregistered in tearDown } @Test @@ -10080,7 +10140,6 @@ public class ConnectivityServiceTest { /** * Test the tracked default requests clear previous OEM requests on setOemNetworkPreference(). - * @throws Exception */ @Test public void testSetOemNetworkPreferenceClearPreviousOemValues() throws Exception { @@ -10108,9 +10167,8 @@ public class ConnectivityServiceTest { } /** - * Test network priority for preference OEM_NETWORK_PREFERENCE_OEM_PAID following in order: + * Test network priority for preference OEM_NETWORK_PREFERENCE_OEM_PAID in the following order: * NET_CAPABILITY_NOT_METERED -> NET_CAPABILITY_OEM_PAID -> fallback - * @throws Exception */ @Test public void testMultilayerForPreferenceOemPaidEvaluatesCorrectly() @@ -10176,9 +10234,8 @@ public class ConnectivityServiceTest { } /** - * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK following in order: + * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK in the following order: * NET_CAPABILITY_NOT_METERED -> NET_CAPABILITY_OEM_PAID - * @throws Exception */ @Test public void testMultilayerForPreferenceOemPaidNoFallbackEvaluatesCorrectly() @@ -10239,10 +10296,9 @@ public class ConnectivityServiceTest { } /** - * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY following in order: + * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY in the following order: * NET_CAPABILITY_OEM_PAID * This preference should only apply to OEM_PAID networks. - * @throws Exception */ @Test public void testMultilayerForPreferenceOemPaidOnlyEvaluatesCorrectly() @@ -10293,10 +10349,9 @@ public class ConnectivityServiceTest { } /** - * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY following in order: + * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY in the following order: * NET_CAPABILITY_OEM_PRIVATE * This preference should only apply to OEM_PRIVATE networks. - * @throws Exception */ @Test public void testMultilayerForPreferenceOemPrivateOnlyEvaluatesCorrectly() @@ -10346,7 +10401,235 @@ public class ConnectivityServiceTest { true /* shouldDestroyNetwork */); } - private UidRange createUidRange(int userId) { - return UidRange.createForUser(UserHandle.of(userId)); + /** + * Test network priority for preference OEM_NETWORK_PREFERENCE_OEM_PAID in the following order: + * NET_CAPABILITY_NOT_METERED -> NET_CAPABILITY_OEM_PAID -> fallback + */ + @Test + public void testMultipleDefaultNetworksTracksOemNetworkPreferenceOemPaidCorrectly() + throws Exception { + @OemNetworkPreferences.OemNetworkPreference final int networkPref = + OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID; + setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref); + final int expectedDefaultRequestSize = 2; + final int expectedOemPrefRequestSize = 3; + registerDefaultNetworkCallbacks(); + + // The fallback as well as the OEM preference should now be tracked. + assertEquals(expectedDefaultRequestSize, mService.mDefaultNetworkRequests.size()); + + // Test lowest to highest priority requests. + // Bring up metered cellular. This will satisfy the fallback network. + setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mCellNetworkAgent.getNetwork()); + + // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID. + setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mEthernetNetworkAgent.getNetwork()); + + // Bring up unmetered Wi-Fi. This will satisfy NET_CAPABILITY_NOT_METERED. + setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, true); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mWiFiNetworkAgent.getNetwork(), + mWiFiNetworkAgent.getNetwork()); + + // Disconnecting unmetered Wi-Fi will put the pref on OEM_PAID and fallback on cellular. + setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, false); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mEthernetNetworkAgent.getNetwork()); + + // Disconnecting cellular should keep OEM network on OEM_PAID and fallback will be null. + setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, false); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + null, + mEthernetNetworkAgent.getNetwork()); + + // Disconnecting OEM_PAID will put both on null as it is the last network. + setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + null, + null); + + // default NCs will be unregistered in tearDown + } + + /** + * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK in the following order: + * NET_CAPABILITY_NOT_METERED -> NET_CAPABILITY_OEM_PAID + */ + @Test + public void testMultipleDefaultNetworksTracksOemNetworkPreferenceOemPaidNoFallbackCorrectly() + throws Exception { + @OemNetworkPreferences.OemNetworkPreference final int networkPref = + OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK; + setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref); + final int expectedDefaultRequestSize = 2; + final int expectedOemPrefRequestSize = 2; + registerDefaultNetworkCallbacks(); + + // The fallback as well as the OEM preference should now be tracked. + assertEquals(expectedDefaultRequestSize, mService.mDefaultNetworkRequests.size()); + + // Test lowest to highest priority requests. + // Bring up metered cellular. This will satisfy the fallback network but not the pref. + setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mService.mNoServiceNetwork.network()); + + // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID. + setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mEthernetNetworkAgent.getNetwork()); + + // Bring up unmetered Wi-Fi. This will satisfy NET_CAPABILITY_NOT_METERED. + setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, true); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mWiFiNetworkAgent.getNetwork(), + mWiFiNetworkAgent.getNetwork()); + + // Disconnecting unmetered Wi-Fi will put the OEM pref on OEM_PAID and fallback on cellular. + setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, false); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mEthernetNetworkAgent.getNetwork()); + + // Disconnecting cellular should keep OEM network on OEM_PAID and fallback will be null. + setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, false); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + null, + mEthernetNetworkAgent.getNetwork()); + + // Disconnecting OEM_PAID puts the fallback on null and the pref on the disconnected net. + setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + null, + mService.mNoServiceNetwork.network()); + + // default NCs will be unregistered in tearDown + } + + /** + * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY in the following order: + * NET_CAPABILITY_OEM_PAID + * This preference should only apply to OEM_PAID networks. + */ + @Test + public void testMultipleDefaultNetworksTracksOemNetworkPreferenceOemPaidOnlyCorrectly() + throws Exception { + @OemNetworkPreferences.OemNetworkPreference final int networkPref = + OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY; + setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref); + final int expectedDefaultRequestSize = 2; + final int expectedOemPrefRequestSize = 1; + registerDefaultNetworkCallbacks(); + + // The fallback as well as the OEM preference should now be tracked. + assertEquals(expectedDefaultRequestSize, mService.mDefaultNetworkRequests.size()); + + // Test lowest to highest priority requests. + // Bring up metered cellular. This will satisfy the fallback network. + setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mService.mNoServiceNetwork.network()); + + // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID. + setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mEthernetNetworkAgent.getNetwork()); + + // Bring up unmetered Wi-Fi. The OEM network shouldn't change, the fallback will take Wi-Fi. + setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, true); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mWiFiNetworkAgent.getNetwork(), + mEthernetNetworkAgent.getNetwork()); + + // Disconnecting unmetered Wi-Fi shouldn't change the OEM network with fallback on cellular. + setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, false); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mEthernetNetworkAgent.getNetwork()); + + // Disconnecting OEM_PAID will keep the fallback on cellular and nothing for OEM_PAID. + // OEM_PAID_ONLY not supporting a fallback now uses the disconnected network. + setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mService.mNoServiceNetwork.network()); + + // Disconnecting cellular will put the fallback on null and the pref on disconnected. + setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, false); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + null, + mService.mNoServiceNetwork.network()); + + // default NCs will be unregistered in tearDown + } + + /** + * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY in the following order: + * NET_CAPABILITY_OEM_PRIVATE + * This preference should only apply to OEM_PRIVATE networks. + */ + @Test + public void testMultipleDefaultNetworksTracksOemNetworkPreferenceOemPrivateOnlyCorrectly() + throws Exception { + @OemNetworkPreferences.OemNetworkPreference final int networkPref = + OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY; + setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref); + final int expectedDefaultRequestSize = 2; + final int expectedOemPrefRequestSize = 1; + registerDefaultNetworkCallbacks(); + + // The fallback as well as the OEM preference should now be tracked. + assertEquals(expectedDefaultRequestSize, mService.mDefaultNetworkRequests.size()); + + // Test lowest to highest priority requests. + // Bring up metered cellular. This will satisfy the fallback network. + setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mService.mNoServiceNetwork.network()); + + // Bring up ethernet with OEM_PRIVATE. This will satisfy NET_CAPABILITY_OEM_PRIVATE. + startOemManagedNetwork(false); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mEthernetNetworkAgent.getNetwork()); + + // Bring up unmetered Wi-Fi. The OEM network shouldn't change, the fallback will take Wi-Fi. + setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, true); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mWiFiNetworkAgent.getNetwork(), + mEthernetNetworkAgent.getNetwork()); + + // Disconnecting unmetered Wi-Fi shouldn't change the OEM network with fallback on cellular. + setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, false); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mEthernetNetworkAgent.getNetwork()); + + // Disconnecting OEM_PRIVATE will keep the fallback on cellular. + // OEM_PRIVATE_ONLY not supporting a fallback now uses to the disconnected network. + stopOemManagedNetwork(); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + mCellNetworkAgent.getNetwork(), + mService.mNoServiceNetwork.network()); + + // Disconnecting cellular will put the fallback on null and pref on disconnected. + setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, false); + verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, + null, + mService.mNoServiceNetwork.network()); + + // default NCs will be unregistered in tearDown } } |